[C++] 异常详解

news2024/9/19 11:03:47

标题:[C++] 异常详解

@水墨不写bug



目录

一、错误处理方式

C语言

Java语言

二、异常的概念

三、异常的使用

1.异常的抛出和捕获(基本用法)

 2.异常的重新抛出(特殊情况)

3.异常的规范和常见坑点

四、标准库的异常体系

五、 C++异常小结


正文开始:

一、错误处理方式


        在程序运行中,不乏会出现一些错误,这些错误或许在我们的意料之中,也可能在意料之外。有错误就要处理,关于错误处理,不同语言有不同的错误处理方法;简单举几个例子:


C语言

错误处理方式

  • 返回值:C语言通常通过函数的返回值来指示错误。例如,标准I/O库中的fopen函数在成功时返回一个指向FILE对象的指针,在失败时返回NULL
  • 全局变量:C语言还使用全局变量(如errno)来记录最近一次系统调用的错误码。通过检查errno的值,程序可以获取到更多关于错误的信息。
  • assert断言:可直接终止程序,一般对程序的影响较大。

Java语言

错误处理方式

  • 异常处理:Java采用面向对象的异常处理机制,通过try-catch-finally-throw块来捕获和处理异常。Java中的异常分为检查型异常(checked exceptions)和非检查型异常(unchecked exceptions,如RuntimeException及其子类)。
  • 资源自动管理:Java 7引入了try-with-resources语句,自动管理实现了AutoCloseable接口的资源,如文件、数据库连接等,在try块执行完毕后自动关闭资源。

         而C++的错误处理方式虽然与Java类似,都是异常,但是C++与Java仍是有一些区别的。


二、异常的概念

        异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数直接或间接的调用者处理这个错误(准确来说就是通过返回栈帧的方式来返回异常,从而让上一级处理这个异常)

C++的异常处理机制具体是通过下面三个关键字实现的: 

        throw: 当问题出现时,程序会抛出一个异常。
        catch: 设置在想要处理问题的地方,通过异常处理程序捕获异常.catch 关键字用于捕获异常,可以有多个catch进行捕获。
        try: try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。

 
        如果有一个块抛出一个异常,捕获异常的方法会使用 trycatch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码catch 块中写某种异常发生后需要做的后续处理,catch 块进入的原则是 抛出的异常的类型与catch 后()内变量的类型完全一致。

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

 

三、异常的使用


1.异常的抛出和捕获(基本用法)

        抛出异常是通过关键字 “throw” 实现的,假设throw 关键字会抛出一个A类型的对象,则立刻终止当前逻辑,一层一层向上返回栈桢,最终会有两个结果:1.找到匹配的catch ,进入catch;2.没有匹配的catch,终止进程。


异常的抛出和匹配原则


        1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。(注意:catch时不会发生隐式类型转换,比如:抛出int,用size_t就无法匹配;抛出const char* ,用string就无法匹配


实例一:

double Div(double a, double b)
{
	if (b == 0)
		throw "div by 0";//抛出异常的类型为常量字符串类型
	else
		return a / b;
}
void func()
{
	double a,b;
	cin >> a >> b;
	cout << Div(a, b) << endl;
}
int main()
{
	try {
		func();
	}
	catch (const char* my_exception)//通过const char* 类型变量catch,可以匹配
	{
		cout << my_exception << endl;
	}
	return 0;
}
double Div(double a, double b)
{
	if (b == 0)
		throw "div by 0";//抛出异常的类型为常量字符串类型
	else
		return a / b;
}
void func()
{
	double a,b;
	cin >> a >> b;
	cout << Div(a, b) << endl;
}
int main()
{
	try {
		func();
	}
	catch (string my_exception)//通过string来catch
		//尽管可以通过string构造函数进行隐式类型转换,但是仍然不能匹配
	{
		cout << my_exception << endl;
	}
	return 0;
}


        2. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。(如果有多个catch可以与抛出的异常匹配时,只有最先匹配到的catch 会起作用


        3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象(比如实例一的Div抛出的const char*类型),所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch 匹配以后销毁。(这里的处理类似于函数的传值返回)


        4. catch(...)可以捕获任意类型的异常,问题是不知道异常错误是什么。(这也可以用来兜底:在我们自己设计的所有catch块之后,手动添加一个catch(...),这样不至于因为异常没有被捕捉导致进程直接结束)


        5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对、象,使用基类捕获。(在实际项目中十分好用

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

在抛出异常后,会终止当前代码逻辑,然后:


        1. 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则调到catch的地方进行处理。


        2. 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。


        3. 如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch子句的过程称为栈展开

        所以实际中我们最后都要加一个catch(...)捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。


        4. 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。(特别注意)
 

 2.异常的重新抛出(特殊情况)

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

实例二:


double Div(double a, double b)
{
	if (b == 0)
		throw "div by 0";//抛出异常的类型为常量字符串类型
	else
		return a / b;
}
void func()
{
	//在func中开辟堆区空间,需要手动释放
	int* arr = new int[12];
	double a,b;
	cin >> a >> b;

    //调用Div,首先其内部可能会抛出异常
    //其次,如果抛出异常,则会直接终止当前逻辑,转而去寻找匹配的catch
    //最终,会直接跳转到main函数的catch (const char* my_exception)中
    //在一系列操作中国,忽视了delete[] arr, 导致内存泄漏
	cout << Div(a, b) << endl;

	delete[] arr;
}
int main()
{
	try {
		func();
	}
	catch (const char* my_exception)
	{
		cout << my_exception << endl;
	}
	return 0;
}

        如何改呢?如果没有发生异常,走正常逻辑;如果发生异常,则我们需要在func栈桢层中,捕获异常,然后delete[] arr 后,重新抛出异常,再返回main 处理:

double Div(double a, double b)
{
	if (b == 0)
		throw "div by 0";//抛出异常的类型为常量字符串类型
	else
		return a / b;
}
void func()
{
	//在func中开辟堆区空间,需要手动释放
	int* arr = new int[12];
	double a,b;
	cin >> a >> b;
	try {
		cout << Div(a, b) << endl;
	}
	catch (...)
	{
		//捕获任意类型异常,在delete arr 之后,再重新抛出任意类型的异常,交给main的逻辑捕获
		delete[] arr;
		throw;
	}
	delete[] arr;
}
int main()
{
	try {
		func();
	}
	catch (const char* my_exception)
	{
		cout << my_exception << endl;
	}
	return 0;
}

3.异常的规范和常见坑点

在《Effective C++》这本书中,强调了一下几点: 

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

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

        C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题。

         


关键字noexcept 

        在C++11之前,如C++98,规定在函数头后面加上声明 throw(),throw括号内部写 可能会抛出异常的类型,但是由于这个语法的不规范使用,加上throw可能会导致一些意想不到的错误,所以在C++11,新增了关键字:noexcept;表示保证这个函数不会抛出异常。

        其次throw()括号内什么都不写,也表示一样的效果。

 

thread() noexcept;
thread() throw();

四、标准库的异常体系

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

        根据规则:

        5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对、象,使用基类捕获。(在实际项目中十分好用

        我们可以得出启示:实际项目组中,我们实际会设计基类异常,在基类的基础上封装自己项目组的异常,这样一来不仅容易区分,也因为 可以抛出的派生类对、象,使用基类捕获,异常就不容易被忽略。


五、 C++异常小结

优点:
        1. 异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的bug。

缺点:

        1. 异常会导致程序的执行流程乱跳,导致非常的混乱,这会导致我们跟踪调试时以及分析程序时,比较困难。


完~

未经作者同意禁止转载 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式UI开发-lvgl+wsl2+vscode系列:11、SSD202移植运行评估demo程序

一、前言 接下来我们根据开发板的LVGL指南移植lvgl的demo程序到开发板上&#xff0c;以及将一个评估的项目移植到开发板上&#xff0c;你将会发现移植lvgl到ssd2xx的板子上似乎很简单&#xff0c;但通过评估程序你将更加方便了解lvgl是否可以满足你的开发需求&#xff0c;除了…

JS 遍历请求数据,实现分页

JS 遍历请求分页 文章目录 JS 遍历请求分页效果图递归方式for循环 效果图 递归方式 const pageSize 10; // 假设每页10项/*** 接收页码和每页项数&#xff0c;返回Promise对象*/ function paginateData(page, pageSize) {return new Promise((resolve, reject) > {setTime…

用甜羊浏览器一键登录30个微店店铺的便捷之道

导语&#xff1a;微店是目前越来越受欢迎的电商平台&#xff0c;对于经营者来说&#xff0c;登录管理多个店铺可能是一项繁琐的任务。而甜羊浏览器的强大功能和智能体验&#xff0c;能够帮助用户轻松实现一键登录30个微店店铺&#xff0c;极大地提高了效率。本文将为大家介绍如…

3D打印随形透气钢:模具排气创新解决方案

在飞速发展的制造业中&#xff0c;技术的每一次飞跃都深刻影响着行业的进步。其中&#xff0c;3D打印随形透气钢技术的诞生&#xff0c;如同一股清新的风&#xff0c;为模具制造中的困气问题带来了革命性的解决方案。 简单来说&#xff0c;3D打印随形透气钢就是一项能够根据模具…

【Vue3教程】组件通信

组件通信 一、props二、自定义事件三、mitt四、v-model五、$attrs六、$refs 和 $parent七、provide&#xff0c;inject八、pinia九、插槽默认插槽具名插槽作用域插槽 总结 一、props 概述&#xff1a;props是使用频率最高的一种通信方式&#xff0c;常用与&#xff1a;父<—…

vue3 element-plus el-table 多层级表头动态渲染。

效果图: html: <el-table :data"arrlist" border style"width: 100%"><template v-for"(i, index) in currentFieldData" :key"index"><el-table-column :label"i.label" :header-D"i.headerAlign&q…

北京理工大学“源源不断”团队在全国大学生物联网竞赛中获得一等奖

在2024年8月24日结束的全国大学生物联网设计竞赛&#xff08;华为杯&#xff09;全国总决赛中的&#xff0c;北京理工大学的“源源不断”团队获得一等奖。 该团队的两名创始成员&#xff0c;张卓玉和杜智聪同学&#xff0c;曾在信息与电子学院李海老师的《智能物联网应用设计》…

vTable实现多维表格

介绍 vTable是字节开发的一款能用来渲染表格的库&#xff0c;是用canvas渲染&#xff0c;避免了传统用dom组件表格的一些问题&#xff0c;能很快的渲染出上万格子的表格。 接下来我将使用vTable构建类似下面的多维表格&#xff0c;其中quantity、sales等是指标。 使用 官网地址…

TensorRT部署模型入门(pythonC++)

文章目录 1. TensorRT安装1.1 cuda/cudnn以及虚拟环境的创建1.2 根据cuda版本安装相对应版本的tensorRT 2. 模型转换2.1 pth转onnx2.2 onnx转engine 3. TensorRT部署TensorRT推理&#xff08;python API&#xff09;TensorRT推理&#xff08;C API&#xff09; 可能遇到的问题参…

洛谷 P2254 [NOI2005] 瑰丽华尔兹

题目来源于&#xff1a;洛谷 题目本质&#xff1a;动态规划&#xff0c;单调队列 解题思路&#xff1a; f[i][x][y] max(f[i - 1][x’][y]) dist(x,y,x,y); i表示的是第i个时间段结束后&#xff0c;(x,y)这个位置最长的滑行距离。 注意(x,y)与(x,y)必定是在同一列或同一行…

数据结构之排序(一)

目录 一.排序的概念及其运用 1.1排序的概念 1.2 常见的排序算法 1.3排序的用途 二、排序的原理及实现 2.1插入排序 2.1.1基本思想 &#xff1a; 2.1.2排序过程&#xff1a; ​编辑2.1.3代码实现 2.1.4直接插入排序的特性总结&#xff1a; 2.2希尔排序&#xff08;希尔…

【TB作品】PIC16F1719单片机,EEPROM,PFM,读写,PIC16F1718/19

对于PIC16F1719单片机&#xff0c;没有直接的EEPROM&#xff0c;而是使用高耐久度的程序闪存&#xff08;PFM&#xff09;作为非易失性数据存储区域。这个区域特别适合存储那些需要频繁更新的数据。读写这个内存区域需要操作一些特殊功能寄存器&#xff0c;比如用于地址的PMADR…

Python - sqlparse 解析库的基础使用

安装 首先打开命令行&#xff0c;输入&#xff1a; pip install sqlparse这样就显示已经安装好了 使用 创建一个 Python 项目&#xff0c;导入 sqlparse 包&#xff1a; 1. parse sql "select * from table1 where id 1;"# 1. parse # parse方法将 SQL语句 解析…

全网最适合入门的面向对象编程教程:38 Python常用复合数据类型-使用列表实现堆栈、队列和双端队列

全网最适合入门的面向对象编程教程&#xff1a;38 Python 常用复合数据类型-使用列表实现堆栈、队列和双端队列 摘要&#xff1a; 在 Python 中&#xff0c;列表&#xff08;list&#xff09;是一种非常灵活的数据结构&#xff0c;可以用来实现堆栈&#xff08;stack&#xff…

如何使用ssm实现国学文化网站的设计与制作

TOC ssm187国学文化网站的设计与制作jsp 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0c;规范…

【Kaggle】练习赛《有毒蘑菇的二分类预测》(上)

前言 本篇文章介绍的是Kaggle月赛《Binary Prediction of Poisonous Mushrooms》&#xff0c;即《有毒蘑菇的二分类预测》。与之前练习赛一样&#xff0c;这声比赛也同样适合初学者&#xff0c;但与之前不同的是&#xff0c;本次比赛的数据集有大量的缺失值&#xff0c;如何处…

没有找到c:\windows\system32\msrd3x43.dll。

打开鸭子串口工具&#xff0c;总会出现这个弹窗&#xff1b; 原因&#xff1a;没有以管理员身份运行 解决办法&#xff1a; 1.不用理会它&#xff0c;对串口工具运行没有任何影响。就算你下载了也没用&#xff0c;依然会有提示。 2.或者鼠标右键&#xff0c;以管理员身份运…

go国内源设置

一、背景 部分网络环境不稳定、丢包或无法连外网&#xff0c;在编译go代码时&#xff0c;需要更新相关依赖&#xff0c;可通过设置go国内源地址来更新。 二、国内可用镜像源 2.1 镜像源一 https://goproxy.cn 2.2 镜像源二 https://goproxy.io 2.3 镜像源三 https://gop…

零基础学习Redis(6) -- string类型命令使用

redis中&#xff0c;不同的数据结构有不同的操作命令。 redis中的string是按照二进制存储的&#xff0c;不会对数据做任何编码转换。 1. set / get 命令 为了方便使用&#xff0c;redis提供了多个版本的get / set命令来操作字符串 1. set set key value [expiration EX sec…

NVIDIA将在Hot Chips 2024会议上展示Blackwell服务器装置

NVIDIA 将在 Hot Chips 2024 上展示其 Blackwell 技术堆栈&#xff0c;并在本周末和下周的主要活动中进行会前演示。对于 NVIDIA 发烧友来说&#xff0c;这是一个激动人心的时刻&#xff0c;他们将深入了解NVIDIA的一些最新技术。然而&#xff0c;Blackwell GPU 的潜在延迟可能…