【C++进阶】C++异常详解

C++异常

  • 一,传统处理错误方式
  • 二,C++处理的方式
  • 三,异常的概念
  • 四,异常的使用
    • 4.1 异常和捕获的匹配原则
    • 4.2 函数调用链中异常栈展开匹配原则
    • 4.3 异常的重新抛出(异常安全问题)
    • 4.4 RAII思想在异常中的作用
  • 五,C++标准库的异常体系
  • 六,自定义异常体系
  • 七,异常规范
  • 八,总结

一,传统处理错误方式

在讲处理错误之前,我们先来看一个例子:
当我们在做除法运算时,如果被除数为0,那么就会报错,所以我们加上assert断言,如果time为0则程序退出。

double Division(int len, int time)
{assert(time != 0);return len / time;
}int main(){Division(1,0);return 0;
}

在C语言中,我们会加上assert断言,如果time为0则程序退出。
或者返回错误码,也就是函数运行完后查看返回码,但是需要程序员自己去查找对应的错误。如系统的很多库的接口函数都是通过把错误码放到errno中表示错误的。

这些处理错误的方式是比较暴力的,因为会直接终止掉程序,如果一个程序部署在服务器上,一旦出现错误就退出的话,那么这个错误造成的损失是非常的大。

所以C++引入了新的处理错误的方式,让程序可以不用再退出。

二,C++处理的方式

C++的解决办法是捕获异常。这里如果time如果为0的话,就抛出了一个字符串。在下面的main函数中捕获抛出的字符串。如果time为0时,main函数就会执行到catch里的语句。这样就不会出现程序直接异常退出了。

double Division(int len, int time)
{if (time == 0){throw "除0错误";}else{return (double)len / (double)time;}
}int main() {try {Division(1, 0);}catch (const char* s) {cout << s << endl;}return 0;
}

三,异常的概念

异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者去处理这个错误

1. 这里的有个新的关键字throw,意思是抛出错误。抛出的错误会被catch捕获。
2. catch是在想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常,可以有多个catch进行捕获。

try
{// 保护的标识代码
}catch( ExceptionName e1 )
{// catch 块
}catch( ExceptionName e2 )
{// catch 块
}catch( ExceptionName eN )
{// catch 块
}

3. try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块

四,异常的使用

4.1 异常和捕获的匹配原则

捕获异常不是随便捕获的,有下面几个原则:
1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码

double Division(int len, int time)
{if (time == 0){string s("除0错误");//这里抛出一个对象throw s;//}else{return (double)len / (double)time;}
}int main() {try {Division(1, 0);}catch (const char* s) {cout << s << endl;}catch (const string& s) {cout << s << endl;}return 0;
}

以上面代码为例,当出现异常后,这里的异常会激活第二个catch,而不是第一个,因为抛出的是一个string对象,第一个catch捕获的是字符串,和抛出的对象类型不匹配。

2. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回

还是以上面的代码为例,

double Division(int len, int time)
{if (time == 0){string s("除0错误");//这里抛出一个对象throw s;//}else{return (double)len / (double)time;}
}

就是说抛出的string这里其实抛出的是s的临时拷贝(移动拷贝),因为s出了这个作用域会销毁

3. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个

double Division(int len, int time)
{if (time == 0){throw "除0错误";}else{return (double)len / (double)time;}
}void func() {try {int len, time;cin >> len >> time;Division(len, time);}catch (const char* s) {cout << s << endl;}cout << "aaaaaaaaaaaaaa" << endl;
}int main() {try {func();}catch (const char* s) {cout << s << endl;}return 0;
}

这里分别在main函数中和func中捕获了异常,会执行哪里的catch的语句呢?
如果执行的是main中的,那么输出字符串后程序就会正常退出。
如果执行的是func中的,那么就会向下再执行那句打印"aaaaaaaaaa…"

我们来看一下结果:

在这里插入图片描述
这里也就验证了当有多个try catch时会匹配离异常近的。

4. catch(…)可以捕获任意类型的异常,问题是不知道异常错误是什么

int main() {try {func();//f1();}catch (const char* s) {cout << s << endl;}catch (const string &s) {cout << s << endl;}catch (...) {//cout << "位未知异常" << endl;}return 0;
}

如果捕获到未知异常 ,也就说明程序走到这里时有人没按规定抛异常。因为在一个项目组里都会统一规定如何抛异常,如果没有按照规定抛,则就会被最后的catch捕获。


4.2 函数调用链中异常栈展开匹配原则

函数在使用的时候会建立栈帧,所以调用函数就会产生调用链,如果出现了异常,那么这个调用链就会发生跳转
没有捕获异常时的调用栈:
在这里插入图片描述
有捕获异常的调用栈:
找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行
在这里插入图片描述

4.3 异常的重新抛出(异常安全问题)

异常的重新抛出就是在捕获到异常后,再将这个异常抛出去。有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理

看下面的例子:
在Func函数捕获异常之前,new了一个array数组。当main函数捕获到异常后,执行语句就会跳转到catch的地方,执行完后会退出程序。

double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw "Division by zero condition!";}return (double)a / (double)b;
}
void Func()
{int* array = new int[10];int len, time;cin >> len >> time;cout << Division(len, time) << endl;cout << "delete []" << array << endl;delete[] array;}int main()
{try{Func();}catch (const char* errmsg){cout << errmsg << endl;}return 0;
}

没有异常时的执行:
在这里插入图片描述
捕获到异常后的执行结果:
在这里插入图片描述

这里就出现了一个比较坑的问题就是出现了内存泄漏,捕获到异常后,没有执行Func中的delete,没有将数组释放掉。

所以就需要将捕获到的异常重新抛出,交给外层去处理,这里捕获到后将数组释放掉

void Func()
{int* array = new int[10];try {int len, time;cin >> len >> time;cout << Division(len, time) << endl;}catch (...){cout << "delete []" << array << endl;delete[] array;throw;//捕到什么抛什么}// ...cout << "delete []" << array << endl;delete[] array;
}

这里在Func中捕获异常是捕获任意类型的异常,然后可以不到什么抛什么,直接交给外层去处理就行。

这里捕获到后,释放了new的空间,然后又在main函数中捕获到异常,处理完后正常退出。


但是又有一个更坑的问题,如果Func中的函数中new了好多个数组呢,难道要一个个去释放吗?
这显然是不合理的。
这就需要智能指针来解决了。


这里说一下还要注意的地方:

  1. 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化

  2. 析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等)

4.4 RAII思想在异常中的作用

RAII思想就是借助对象的生命周期来控制资源(解决异常安全问题)

接着上面的例子,借助智能指针来解决,这个智能指针我们在下一节进行讲解。
智能指针其实就是RAII思想的一种实现。这些我们统一在下一节讲解。

五,C++标准库的异常体系

C++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:

在这里插入图片描述
在这里插入图片描述
感兴趣大家可以自己去学习了解一下:C++异常

六,自定义异常体系

但是因为一些原因,C++的异常体系设计的不是那么的好用,所以大多数都是我们自己去
用第三方的库或者自己实现一个异常体系。

实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家随意抛异常,那么外层的调用者基本就没办法玩了,所以实际中都会定义一套继承的规范体系。这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了。

在这里插入图片描述

七,异常规范

这里再说一下程序中抛异常的规范。
因为在实践中,往往是一个项目组共同进行开发,大家都会去抛异常,这样就会导致物化八门,那么如何知道这个函数有没有抛出异常呢?

异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。

这里可以在函数的后面接throw(类型),列出这个函数可能抛掷的所有异常类型
函数的后面接throw(),表示函数不抛异常


// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw()

函数后面加上这些throw(类型),其实就相当于提醒作用,编译器并不会去强制的不捕获异常。不加也是可以的。


C++11中新增的noexcept,表示不会出现异常

void* operator delete (std::size_t size, void* ptr) noexcept;

但是和throw()不同的是,这里加上后如果出现异常,程序就不会捕获这里的异常。那么程序就会异常退出

八,总结

这里C++ 异常的讲解就到这里了,我们引出了智能指针的概念和RAII的概念,还是比较重要的,我会在下一节重点讲解。希望大家可以对异常能有个很好的理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/305889.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

标定系列——Ubuntu18.04下opencv-4.5.3与opencv_contrib-4.5.3源码编译(二十)

Ubuntu18.04下opencv-4.5.3与opencv_contrib-4.5.3源码编译 说明下载安装步骤1.更新2.安装必要的依赖包3.下载源码包并解压4.终端运行如下命令5.添加配置路径6.验证安装是否成功 说明 Ubuntu18.04下对opencv-4.5.3与opencv_contrib-4.5.3源码编译 下载 CSDN下载 安装步骤 …

手写ArrrayList

需求 自定义的MyArrayList import java.util.Arrays; import java.util.Objects;public class MyArrayList<E> {private Object[] elementData ; // 存储元素的数组private int size; // 记录 的元素个数private static final int DEFAULT_CAPACITY 10; // 默认容量// …

提升编程效率的秘密武器:IntelliJ IDEA

IntelliJ IDEA的基本介绍 正如一个故事的开头&#xff0c;我们从一个名字开始 - IntelliJ IDEA。这是一个在程序员中广受欢迎的集成开发环境&#xff08;IDE&#xff09;&#xff0c;由捷克公司JetBrains开发。它的名字听起来有些复杂&#xff0c;但实际上&#xff0c;它的功能…

蓝桥杯备考day4

1.1 二分查找模板 bool check(int x) {// 进行某些操作 } // 二分查找函数 int binarySearch() {int l 1, r n; // 初始化左右边界while (r - l > 1) // 当右边界与左边界相差大于1时{int mid (l r) >> 1; // 取中间位置if (check(mid)) // 如果满足条件r mid; …

第十一届蓝桥杯大赛第二场省赛试题 CC++ 研究生组-七段码

#include<iostream> using namespace std; const int N 10, M 7; int e[N][N] {0}, f[N], open[N];//e[i][j]表示i和j之间是否连通&#xff1b;f[i]表示结点i的父节点&#xff1b;open[i] 1表示结点i打开&#xff0c;0表示关闭 long long ans 0;int find(int x){if(…

PP-LCNet:一种轻量级CPU卷积神经网络

PP-LCNet: A Lightweight CPU Convolutional Neural Network 最近看了一个新的分享&#xff0c;在图像分类的任务上表现良好&#xff0c;具有很高的实践意义。 论文&#xff1a; https://arxiv.org/pdf/2109.15099.pdf项目&#xff1a; https://github.com/PaddlePaddle/Padd…

AMD Tensile 简介与示例

按照知其然&#xff0c;再知其所以然的认知次序进行 1&#xff0c;下载代码 git clone --recursive https://github.com/ROCm/Tensile.git 2&#xff0c;安装 Tensile cd Tensile mkdir build cd build ../Tensile/bin/Tensile ../Tensile/Configs/rocblas_dgemm_nn_asm_full…

微信小程序(六)定位搜索

一、引言 作者上一章讲了微信小程序的地图实现微信小程序&#xff08;五&#xff09;地图-CSDN博客&#xff0c;但是还有一个功能是和地图紧密结合的&#xff0c;那就是位置搜索定位&#xff0c;这里作者讲讲实现和原理&#xff0c;包括城市筛选。 二、定位搜索实现 1、位置搜…

mysql查看数据库表容量大小

【推荐】单表行数超过 500 万行或者单表容量超过 2GB&#xff0c;才推荐进行分库分表。 说明&#xff1a;如果预计三年后的数据量根本达不到这个级别&#xff0c;请不要在创建表时就分库分表。 1. 查询所有数据库记录数和容量 SELECTtable_schema AS 数据库,SUM(table_rows) …

用vue.js写案例——ToDoList待办事项 (步骤和全码解析)

目录 一.准备工作 二.编写各个组件的页面结构 三.实现初始任务列表的渲染 四.新增任务 五.删除任务 六.展示未完成条数 七.切换状态-筛选数据 八.待办事项&#xff08;全&#xff09;代码 一.准备工作 在开发“ToDoList”案例之前&#xff0c;需要先完成一些准备工作&a…

图像处理与视觉感知---期末复习重点(7)

文章目录 一、图像压缩1.1 三种冗余1.2 模型1.3 信息测量 二、无误差压缩2.1 哈夫曼编码2.1.1 步骤2.1.2 例题 2.2 算术编码 三、变换编码 一、图像压缩 1.1 三种冗余 1. 三种基本的是数据冗余为&#xff1a;编码冗余、像素间冗余、心理视觉冗余。 2. 编码冗余&#xff1a;如果…

蓝桥杯——玩具蛇

题目 小蓝有—条玩具蛇&#xff0c;一共有16节&#xff0c;上面标着数字1至16。每—节都是一个正方形的形状。相邻的两节可以成直线或者成90度角。 小蓝还有一个44的方格盒子&#xff0c;用于存放玩具蛇&#xff0c;盒子的方格上依次标着字母A到Р共16个字母。 小蓝可以折叠自…

浙大恩特客户资源管理系统 i0004_openFileByStream.jsp 任意文件读取漏洞复现

0x01 产品简介 浙大恩特客户资源管理系统是一款针对企业客户资源管理的软件产品。该系统旨在帮助企业高效地管理和利用客户资源,提升销售和市场营销的效果。 0x02 漏洞概述 浙大恩特客户资源管理系统 i0004_openFileByStream.jsp接口处存在任意文件读取漏洞,未经身份验证攻…

快速开始vue3

版本 node (20.11.1)vue3 (3.4.21) 脚手架创建项目并运行 安装脚手架并创建项目 npm create vuelatest这一指令将会安装并执行 create-vue&#xff0c;它是 Vue 官方的项目脚手架工具 2&#xff09; 安装以下进行选择 ## 配置项目名称 √ Project name: vue3_test ## 是否…

网络编程基础

目录 【1】网络编程&#xff1a; ​【2】通信两个重要的要素&#xff1a;IPPORT 【3】设备之间进行传输的时候&#xff0c;必须遵照一定的规则 ---》通信协议&#xff1a; 【4】TCP协议&#xff1a;可靠的 建立连接&#xff1a; 三次握手 ​编辑释放连接&#xff1a;四次挥…

Python生成图片和音频验证码

captcha是pyhton的一个模块&#xff0c;用来生成图片和音频验证码。 安装 pip install captcha使用 from captcha.audio import AudioCaptcha from captcha.image import ImageCaptcha# 加载声音和字体 audio AudioCaptcha(voicedir/path/to/voices) image ImageCaptcha(…

【洛谷 P4017】最大食物链计数 题解(深度优先搜索+动态规划+邻接表+记忆化搜索+剪枝)

最大食物链计数 题目背景 你知道食物链吗&#xff1f;Delia 生物考试的时候&#xff0c;数食物链条数的题目全都错了&#xff0c;因为她总是重复数了几条或漏掉了几条。于是她来就来求助你&#xff0c;然而你也不会啊&#xff01;写一个程序来帮帮她吧。 题目描述 给你一个…

蓝牙耳机哪个品牌质量最好最耐用?五大口碑最佳机型,硬核推荐

​在快节奏的都市生活中&#xff0c;无线蓝牙耳机成为了我们摆脱线缆束缚、随时随地享受音乐的完美伴侣。面对市场上琳琅满目的品牌和型号&#xff0c;挑选一款合适的耳机似乎是一项挑战。因此&#xff0c;我精心挑选了几款性能卓越的蓝牙耳机&#xff0c;希望我的分享能为你提…

Vue学习笔记(一)

1. 绑定事件按按键修饰符 <!DOCTYPE html> <html lang"en"><head><meta charset"utf-8"><title>绑定事件和按键修饰符</title> </head><body> <div id"app">{{ person }}<hr/><…

【Linux】开始了解重定向

送给大家一句话&#xff1a; 人真正的名字是&#xff1a;欲望。所以你得知道&#xff0c;消灭恐惧最有效的办法&#xff0c;就是消灭欲望。 – 史铁生 《我与地坛》 开始了解重定向 1 前言2 重定向与缓冲区2.1 文件描述符分配规则2.2 重定向的现象2.3 重定向的理解2.4 缓冲区…