C++之异常

目录

前言

一、什么是异常

二、C++中的异常

2.1 C语言中的异常处理

2.2 C++中的异常处理

2.3 异常的抛出与捕获

2.4 栈展开

2.5 查找匹配的处理代码

2.6 异常重新抛出

2.7 异常安全问题

2.8 异常规范

2.9 标准库的异常


前言

在之前我们已经学习了C++中不少知识了,但是其中的一些异常,我们当时可能就随便糊弄过去了,我们不清楚C++中处理异常的机制,而今天我们就来学习一下C++中如何处理异常。


一、什么是异常

异常指程序、系统或事物运行中出现的不符合预期或正常逻辑的状态或事件,可能导致流程中断、错误结果或功能失效,这是我们在百度上的对异常这个名词的权威解释。那么,编程语言中的异常是指程序在运行过程中遇到的一些不正常的情况,如除以零、访问不存在的文件、内存不足等。这些异常如果不处理,可能导致程序崩溃或产生不可预期的结果。异常处理机制允许程序检测到这些异常,并采取相应的措施,如记录错误信息、提示用户或尝试恢复程序,从而提高程序的健壮性和用户体验。常见的异常处理机制包括try-catch块,用于包围可能抛出异常的代码,并在catch块中处理异常。有效的异常处理可以帮助开发者写出更加可靠和稳定的代码。

简单来说,我们在编程中所遇到的那些异常就是因为我们的某些操作,导致出现了一些可能会让计算机程序崩溃的情况,我们把这些情况都叫做异常。

二、C++中的异常

2.1 C语言中的异常处理

我们在C语言阶段,当时我们学习时是没有学习到异常的,因为在C语言中并没有内置的处理异常的机制。在C语言中处理异常通常是使用返回值或者全局变量。例如,当我们的一个函数出现异常时,它会返回一个错误码信息,从而使得编译器发生报错。这样做看起来,十分地麻烦,因为每个函数都返回一个错误码的话,就会导致代码冗长复杂。

2.2 C++中的异常处理

C++中的异常处理机制相较于C语言更为强大和灵活,提供了try、catch、throw等关键字,支持类型化异常和栈展开,从而更优雅地处理错误,减少资源泄漏的风险。这样一来,即使出现了异常,我们也有办法去处理。接下来我们就来学习学习C++中处理异常的方式。

2.3 异常的抛出与捕获

  • 当程序出现问题时,我们通过抛出(throw)一个对象来引发一个异常,该对象的数据类型以及当前的调用链决定了应该由哪个catch的处理代码来处理异常。(这里我们需要注意的是,我们的try块语句不一定就和下一个catch进行匹配,它们是根据数据类型进行匹配的)
  • 被选中的处理代码是调用链中与改对象类型匹配且与抛出异常位置最近的那一个。根据抛出对象的类型和内容,程序的抛出异常部分会告知异常处理部分到底发生了什么错误。
  • 当throw执行时,throw后面的语句就不再被执行。程序的执行从throw位置跳到与之匹配的catch模块,catch可能是同一函数中的一个局部的catch,也可能时调用链中另一个函数中的catch,控制权从throw转移到了catch位置。这里还有两个重要的含义:1.沿用调用链的函数可能提早退出;2.一旦程序开始执行异常处理程序,沿用调用链创建的对象都将被销毁。
  • 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一些局部对象,我们在离开那个函数后,那些局部对象就销毁,所以会生成一个拷贝对象,这个拷贝的对象会在catch子句后销毁。(这里类似我们之前的函数传值返回,我们只是传递的是函数的值,对于形参的操作并不会影响对实参的内容。我们这里对原来那个异常对象的拷贝对象进行处理不会影响原来的那个异常对象)

2.4 栈展开

  • 当我们抛出异常时,程序会暂停当前函数的执行,开始寻找与之匹配的catch子句,首先检查throw本身是否在try块内部,如果在则查找匹配的catch子句,如果有匹配的,则跳到对应的catch的地方进行处理。(这一系列操作在运行时动态进行的)
  • 如果当前函数中没有try/catch子句,或者有try/catch子句但是类型不匹配,则退出当前函数,继续在外层调用函数链中进行查找,上述的查找catch过程被称作为栈展开。
  • 如果到达main函数,依旧没有找到匹配的catch子句,程序会调用标准库中的 terminate 函数终止程序。
  • 如果找到匹配的catch子句处理后,catch子句代码会继续执行。

如下图是我们抛出异常后栈展开的示意图:

接下来我们用如下示例代码来给大家展示一下,异常是用哪个catch来进行捕获的(我们写了两个函数一个除法函数,我们在函数体中使用try...catch语句来捕获异常;还有一个函数则是在try块语句中调用了除法函数同时也抛出异常了,最后我们在main函数中try块语句中调用我们的Func函数,这样就形成了一个层层调用的形式,我们来通过运行结果来看看最后匹配的catch是哪个

double Divide(int a, int b)
{try{// 当b == 0时抛出异常if (b == 0){string s("Divide by zero condition!");throw s;}else{return ((double)a / (double)b);}}catch (int errid){cout << errid << endl;}return 0;
}void Func()
{int len, time;cin >> len >> time;try {cout << Divide(len, time) << endl;}catch (const char* errmsg){cout << errmsg << endl;cout << "Func" << endl;}}int main()
{while (1){try{Func();}catch (const string& errmsg){cout << errmsg << endl;cout << "main" << endl;}}return 0;
}

从上面的运行代码结果,我们可以看出来它最终匹配的catch是我们main函数中的catch,因为我们在Divide函数中抛出的异常是一个string类型的语句,由于我们的匹配原则是根据数据类型来进行匹配的,因此我们匹配的是main函数中的catch语句,它的catch参数是string类型的。我们通过上面的例子,我们更加清晰地认识到了我们try中的异常来匹配catch,是通过抛出的异常数据类型与catch参数类型是否匹配来进行匹配选择的。

2.5 查找匹配的处理代码

  • 一般情况下抛出对象和catch类型是完全匹配的,如果有多个类型匹配的,就选择离它位置更近的那个。
  • 但是有一些例外,它允许从非常量向常量类型进行转换(即将普通变量向有const修饰的变量进行转换)即我们之前所说的权限缩小;它还允许数组转换成指向数组元素类型的指针,函数转换成函数的指针;允许从派生类向基类类型进行转换,这点非常常用,实际中继承体系基本都是用这个方式来进行设计的。
  • 如果到main函数,异常仍没有被匹配的话,就终止程序。不是在发生严重的错误情况下,我们是不期望程序终止的,所以一般main函数中最后都会使用catch(...),它可以捕获任意类型的异常,这样我们就不用写好几个不同类型的catch语句来进行捕获异常了,但是这种catch语句我们是不知道异常错误是什么。

如下代码,我们通过一个异常基类来获取一些基本的信息:错误码,id等,然后我们再将几个派生类来继承那个基类,除此之外加上自己的一些异常信息。我们是为了测试查找匹配的异常处理代码,于是我们在main函数中设置一系列的随机数,并定时出现结果。

#include<thread>class Exception
{
public:Exception(const string& errmsg, int id):_errmsg(errmsg), _id(id){}virtual string what() const{return _errmsg;}int getid() const{return _id;}
protected:string _errmsg;int _id;
};class SqlException : public Exception
{
public:SqlException(const string& errmsg, int id, const string& sql):Exception(errmsg, id), _sql(sql){}virtual string what() const{string str = "SqlException:";str += _errmsg;str += "->";str += _sql;return str;}
private:const string _sql;
};class CacheException : public Exception
{
public:CacheException(const string& errmsg, int id):Exception(errmsg, id){}virtual string what() const{string str = "CacheException:";str += _errmsg;return str;}
};class HttpException : public Exception
{
public:HttpException(const string& errmsg, int id, const string& type):Exception(errmsg, id), _type(type){}virtual string what() const{string str = "HttpException:";str += _type;str += ":";str += _errmsg;return str;}
private:const string _type;
};void SQLMgr()
{if (rand() % 7 == 0){throw SqlException("权限不足", 100, "select * from name = '张三'");}else{cout << "SQLMgr 调用成功" << endl;}
}void CacheMgr()
{if (rand() % 5 == 0){throw CacheException("权限不足", 100);}else if (rand() % 6 == 0){throw CacheException("数据不存在", 101);}else{cout << "CacheMgr 调用成功" << endl;}SQLMgr();
}void HttpServer()
{if (rand() % 3 == 0){throw HttpException("请求资源不存在", 100, "get");}else if (rand() % 4 == 0){throw HttpException("权限不足", 101, "post");}else{cout << "HttpServer调用成功" << endl;}CacheMgr();
}int main()
{srand(time(0));while (1){this_thread::sleep_for(chrono::seconds(1));try{HttpServer();}catch (const Exception& e) // 这里捕获基类,基类对象和派生类对象都可以被捕获{// 多态cout << e.what() << endl;}catch (...){cout << "Unkown Exception" << endl;}}return 0;
}

我们可以看到,由于我们输入的是随机信息,因此最后的结果也是随机的。我们从上面的结果可以看出,我们匹配catch语句,它的数据类型是可以通过派生类继承基类来实现的,而这也是我们以后主要使用到的。

2.6 异常重新抛出

有时catch到一个异常对象后,需要对我们所查找的异常对象所造成的错误进行分类,其中的某种异常错误需要进行特殊的处理,其他错误则重新抛出异常给外层调用链来进行处理。捕获异常后需要重新抛出,我们直接使用throw这个关键字就可以把捕获的对象直接抛出。

其实对异常重新抛出就是对我们所抛出的那个异常在放到一个循环中,我们在这个循环中来进行对异常的处理,达到条件我们就将其抛出。如下代码是我们模拟我们平时信息对话,信号不好尝试多次才发送出去的情况。

void _SeedMsg(const string& s)
{if (rand() % 2 == 0){throw HttpException("网络不稳定,发送失败", 102, "put");}else if (rand() % 7 == 0){throw HttpException("你已经不是对象的好友,发送失败", 103, "put");}else{cout << "发送成功" << endl;}
}void SendMsg(const string& s)
{// 发送消息失败,则再重试3次for (size_t i = 0; i < 4; i++){try{_SeedMsg(s);break;}catch (const Exception& e){// 捕获异常,if中是102号错误,网络不稳定,则重新发送// 捕获异常,else中不是102号错误,则将异常重新抛出if (e.getid() == 102){// 重试三次以后否失败了,则说明网络太差了,重新抛出异常if (i == 3)throw;cout << "开始第" << i + 1 << "重试" << endl;}else{throw;}}}
}
int main()
{srand(time(0));string str;while (cin >> str){try{SendMsg(str);}catch (const Exception& e){cout << e.what() << endl << endl;}catch (...){cout << "Unkown Exception" << endl;}}return 0;
}

我们从上面的代码可以看出,异常的重新抛出,我们就是将我们的主要异常封装成一个函数,然后我们在另一个函数体中使用循环多次调用那个函数,同时使用throw来抛出异常,这里由于我们抛出的异常我们在第一个函数中就已经展示出来了,这里我们的throw就不用接其他内容了。

2.7 异常安全问题

  • 异常抛出后,后面的代码就不再执行,前面申请了资源(内存,锁等),后面进行释放,但是中间可能会抛出异常就会导致资源没有释放,这里由于异常就引发了资源泄露,产生安全性问题。中间我们需要捕获异常,释放资源后再重新抛出。
  • 其次析构函数中,如果抛出异常后也要谨慎处理,比如析构函数需要释放10个资源,释放到第5个时抛出异常的话,我们也是需要进行捕获处理的,否则后面的5个资源就没法释放,也就造成了资源泄露了。

下面的代码是有资源申请的,我们在捕获异常时需要对资源进行释放,另外我们在捕获异常的后面对两个指针进行释放,再输出相应的地址。

double Divide(int a, int b)
{try{// 当b == 0时抛出异常if (b == 0){string s("Divide by zero condition!");throw s;}else{return ((double)a / (double)b);}}catch (int errid){cout << errid << endl;}return 0;
}void Func()
{int* ptr1 = new int[10];int* ptr2 = new int[10];int len, time;cin >> len >> time;try {cout << Divide(len, time) << endl;}catch (...){delete[] ptr1;cout << "delete:" << ptr1 << endl;delete[] ptr2;cout << "delete:" << ptr2 << endl;// 重新抛出throw;}delete[] ptr1;cout << "delete:" << ptr1 << endl;delete[] ptr2;cout << "delete:" << ptr2 << endl;
}int main()
{while (1){try{Func();}catch (const string& errmsg){cout << errmsg << endl;}catch (...){cout << "未知异常" << endl;}}return 0;
}

我们从上面的运行结果,我们可以看出来地址都是相同的,说明最后都是被释放的。(我们在catch语句块中选择了对指针的资源释放)

2.8 异常规范

  • 对于用户和编译器而言,预先知道某个程序会不会抛出异常会大有裨益,知道某个函数是否会抛出异常有助于简化调用函数的代码。
  • C++98中函数参数列表的后面接throw(),表示函数不抛出异常,函数参数列表的后面接throw(类型1,类型2...)表示可能会抛出多种类型的异常,可能会抛出的异常类型用逗号来进行分割。
  • C++98的方式过于复杂,实践中并不好用,在C++11中进行了简化,在函数参数列表后面加上noexcept则表示不会抛出异常,啥都不加的话就表示可能会抛出异常。
  • 编译器并不会在编译时检查noexcept,也就是说如果一个函数用noexcept修饰了,但是同时又包含了throw语句或者调用的函数可能会抛出异常,编译器还是会顺利编译通过的(有些编译器可能会报个警告)但是一个声明了noexcept的函数抛出了异常,程序会调用 terminate 来终止程序。
  • noexcept(expression)还可以作为一个运算符去检测一个表达式是否会抛出异常,可能会抛出异常的话就返回false,不会抛出异常的话就返回true。

有时候,我们传递一个函数(会抛出异常的函数)的话,它是根据那个函数是否会抛出异常来返回返回值的,我们不是通过看传递的参数来进行判断的,即使我们传递的参数不会造成异常,但是这个函数是可能会抛出异常的,因此它还是会返回一个false的。

// C++98
// 这⾥表⽰这个函数只会抛出bad_alloc的异常 
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这⾥表⽰这个函数不会抛出异常 
void* operator delete (std::size_t size, void* ptr) throw();
// C++11
size_type size() const noexcept;
iterator begin() noexcept;
const_iterator begin() const noexcept;
double Divide(int a, int b) noexcept
{// 当b == 0时抛出异常 if (b == 0){throw "Division by zero condition!";}return (double)a / (double)b;
}
int main()
{try{int len, time;cin >> len >> time;cout << Divide(len, time) << endl;}catch (const char* errmsg){cout << errmsg << endl;}catch (...){cout << "Unkown Exception" << endl;}int i = 0;cout << noexcept(Divide(1,2)) << endl;cout << noexcept(Divide(1,0)) << endl;cout << noexcept(++i) << endl;return 0;
}

2.9 标准库的异常

在C++标准库中也定义了一套自己的异常继承体系库,基类是exception,所以我们日常写程序时,需要在主函数捕获exception即可,要获取异常信息,调用what函数,what函数是一个虚函数,因为我们在前面就说过了我们可以使用派生类来继承一个异常的基类,我们可以在派生类中重写那些what虚函数。如下图是一些标准库中的异常

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

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

相关文章

$在R语言中的作用

在 R 语言中&#xff0c;$ 是一个非常重要的操作符&#xff0c;主要用于访问对象的成员或组件。它的用途非常广泛&#xff0c;不仅限于数据框&#xff08;data frame&#xff09;&#xff0c;还可以用于列表&#xff08;list&#xff09;、环境&#xff08;environment&#xf…

设计一个分布式系统:要求全局消息顺序,如何使用Kafka实现?

一、高吞吐低延迟 Kafka 集群设计要点 1. 分区策略优化 // 计算合理分区数公式&#xff08;动态调整&#xff09; int numPartitions max(Tp, Tc) / min(Tp, Tc) // Tp生产者吞吐量 Tc消费者吞吐量建议初始按业务键&#xff08;如订单ID&#xff09;哈希分区单分区吞吐建议…

[dify]官方模板DeepResearch工作流学习笔记

一、功能 根据用户输入的主题进行多轮搜索并生成综合报告 1、流程分析 1.1 初始阶段 Start节点&#xff1a;接收用户输入的"depth"参数&#xff0c;决定搜索的深度/轮数 参数可以不填&#xff0c;不填的时候取默认值3 Create Array节点&#xff1a;根据depth参数…

hadoop中的序列化和反序列化(3)

3. Java的序列化 Java提供了内置的序列化机制&#xff0c;通过java.io.Serializable接口实现。 3.1 如何实现Java序列化 让类实现Serializable接口。 使用ObjectOutputStream进行序列化。 使用ObjectInputStream进行反序列化。 示例代码 序列化 java 复制 import jav…

6、CMake基础:流程控制

流程控制 1. 条件判断1.1 基本表达式1.2 逻辑判断1.3 比较基于数值的比较基于字符串的比较 1.4 文件操作1.5 其他 2. 循环2.1 foreach方法1方法2方法3方法4 2.2 while 在 CMake 的 CMakeLists.txt 中也可以进行流程控制&#xff0c;也就是说可以像写 shell 脚本那样进行条件判断…

【网络编程】二、UDP网络套接字编程详解

文章目录 前言Ⅰ. UDP服务端一、服务器创建流程二、创建套接字 -- socketsocket 属于什么类型的接口❓❓❓socket 是被谁调用的❓❓❓socket 底层做了什么❓❓❓和其函数返回值有没有什么关系❓❓❓ 三、绑定对应端口号、IP地址到套接字 -- bind四、数据的发送和接收 -- sendto…

准确--Notepad++ 实用的插件介绍

Notepad 提供了很多实用的插件&#xff0c;可以极大地提升编程和文本编辑的效率。以下是一些常用且有用的插件介绍&#xff1a; 1. NPP Export 功能&#xff1a;可以将打开的文件导出为 HTML 或 RTF 格式&#xff0c;方便生成漂亮的代码文档。用途&#xff1a;适合需要将代码…

[20250507] AI边缘计算开发板行业调研报告 ​​(2024年最新版)​

[20250507] AI边缘计算开发板行业调研报告 ​​(2024年最新版&#xff09;​ 一、行业背景​​ 随着物联网设备激增与AI模型轻量化&#xff0c;边缘计算成为AI落地核心场景。AI边缘计算开发板&#xff08;Edge AI Board&#xff09;作为硬件载体&#xff0c;需满足​​低延迟…

传输层协议 1.TCP 2.UDP

传输层协议 1.TCP 2.UDP TCP协议 回顾内容 传输层功能&#xff1a;定义应用层协议数据报文的端口号&#xff0c;流量控制对原始数据进行分段处理 传输层所提供服务 传输连接服务数据传输服务&#xff1a;流量控制、差错控制、序列控制 一、传输层的TCP协议 1.面向连接的…

LVGL -meter的应用

1 meter介绍 lv_meter 是 LVGL v8 引入的一种图形控件&#xff0c;用于创建仪表盘样式的用户界面元素&#xff0c;它可以模拟像速度表、电压表、温度表这类模拟表盘。它通过可视化刻度、指针、颜色弧线等来展示数值信息&#xff0c;是一种非常直观的数据展示控件。 1.1 核心特…

GoFly企业版框架升级2.6.6版本说明(框架在2025-05-06发布了)

前端框架升级说明&#xff1a; 1.vue版本升级到^3.5.4 把"vue": "^3.2.40",升级到"vue": "^3.5.4"&#xff0c;新版插件需要时useTemplateRef,所以框架就对齐进行升级。 2.ArcoDesign升级到2.57.0&#xff08;目前最新2025-02-10&a…

阿里联合北大开源数字人项目FantasyTalking,输出内容更加动态化~

简介 FantasyTalking 的核心目标是从单一静态图像、音频&#xff08;以及可选的文本提示&#xff09;生成高保真、连贯一致的说话肖像。研究表明&#xff0c;现有方法在生成可动画化头像时面临多重挑战&#xff0c;包括难以捕捉细微的面部表情、整体身体动作以及动态背景的协调…

基于nnom的多选择器

核心组件 元件类型目的接口STM32F103CB微控制器主处理单元-MPU60506 轴 IMU移动侦测I2C 接口W25Q64 系列闪存信号和配置存储SPI 系列按钮用户输入模式选择和激活GPIO &#xff08;通用输出&#xff09;搭载了LED用户反馈系统状态指示GPIO &#xff08;通用输出&#xff09;RT6…

Redis中6种缓存更新策略

Redis作为一款高性能的内存数据库&#xff0c;已经成为缓存层的首选解决方案。然而&#xff0c;使用缓存时最大的挑战在于保证缓存数据与底层数据源的一致性。缓存更新策略直接影响系统的性能、可靠性和数据一致性&#xff0c;选择合适的策略至关重要。 本文将介绍Redis中6种缓…

项目优先级频繁变动,如何应对?

项目优先级频繁变动是许多公司和团队在工作中常遇到的挑战。 这种情况通常由业务需求变化、市场压力或高层决策调整等因素引起&#xff0c;常常让团队成员感到困惑和不安。首先&#xff0c;制定明确的优先级管理框架是应对项目优先级变动的基础&#xff0c; 通过清晰的优先级排…

屏蔽力 | 在复杂世界中从内耗到成长的转变之道

注&#xff1a;本文为“屏蔽力”相关文章合辑。 略作重排&#xff0c;未全整理。 世上的事再复杂&#xff0c;不外乎这三种 原创 小鹿 读者 2022 年 12 月 02 日 18 : 27 甘肃 文 / 小鹿 在这世上&#xff0c;每天都有大事小事、琐事烦事。我们总为世事奔波忙碌&#xff0c;…

[数据处理] 3. 数据集读取

&#x1f44b; 你好&#xff01;这里有实用干货与深度分享✨✨ 若有帮助&#xff0c;欢迎&#xff1a;​ &#x1f44d; 点赞 | ⭐ 收藏 | &#x1f4ac; 评论 | ➕ 关注 &#xff0c;解锁更多精彩&#xff01;​ &#x1f4c1; 收藏专栏即可第一时间获取最新推送&#x1f514;…

IIS配置SSL

打开iis 如果搜不到iis&#xff0c;要先开 再搜就打得开了 cmd中找到本机ip 用http访问本机ip 把原本的http绑定删了 再用http访问本机ip就不行了 只能用https访问了

RabbitMQ的交换机

一、三种交换机模式 核心区别对比​​ ​​特性​​​​广播模式&#xff08;Fanout&#xff09;​​​​路由模式&#xff08;Direct&#xff09;​​​​主题模式&#xff08;Topic&#xff09;​​​​路由规则​​无条件复制到所有绑定队列精确匹配 Routing Key通配符匹配…

(2025,AR,NAR,GAN,Diffusion,模型对比,数据集,评估指标,性能对比)文本到图像的生成和编辑:综述

【本文为我在去年完成的综述&#xff0c;因某些原因未能及时投稿&#xff0c;但本文仍能为想要全面了解文本到图像的生成和编辑的学习者提供可靠的参考。目前本文已投稿 ACM Computing Surveys。 完整内容可在如下链接获取&#xff0c;或在 Q 群群文件获取。 中文版为论文初稿&…