【C++:异常】C++ 异常处理完全指南:从理论到实践,深入理解栈展开与最佳实践

【C++:异常】C++ 异常处理完全指南:从理论到实践,深入理解栈展开与最佳实践

在这里插入图片描述


🎬 个人主页艾莉丝努力练剑
专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录
Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬 艾莉丝的简介:

在这里插入图片描述

🎬 艾莉丝的C++专栏简介:

在这里插入图片描述

文章目录


C++学习阶段的三个参考文档

看库文件(非官方文档):Cplusplus.com

在这里插入图片描述

这个文档在C++98、C++11时候还行,之后就完全没法用了……

准官方文档(同步更新)——还 可以看语法C++准官方参考文档

在这里插入图片描述


这个行,包括C++26都同步了,我们以后主要会看这个。

官方文档(类似论坛):Standard C++

在这里插入图片描述


这个网站上面会有很多大佬,类似于论坛。


在这里插入图片描述

1 ~> 异常的概念

在C语言里面,异常的处理机制——通过错误码的形式处理错误,比较麻烦,如下图所示——

在这里插入图片描述

异常处理机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并做出相应的处理,异常使得我们能够将问题的检测与解决问题的过程分开,程序的一部分负责检测问题的出现,然后解决问题的任务传递给程序的另一部分,检测环节无须知道问题的处理模块的所有细节。

C语言主要通过错误码的形式处理错误,错误码本质就是对错误信息进行分类编号,拿到错误码以后还要去查询错误信息,比较麻烦。异常时抛出一个对象(可以抛出任何类型的异常),这个对象可以函数更全面的各种信息(包含各种各样的信息)。


2 ~> 异常的使用层

2.1 异常的抛出和捕获

程序出现问题时,我们通过抛出(throw)一个对象来引发一个异常,该对象的类型以及当前的调用链决定了应该由哪个catch的处理代码来处理该异常。

被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。根据抛出对象的类型和内容,程序的抛出异常部分告知异常处理部分到底发生了什么错误。

当throw执行时,throw后面的语句将不再被执行。程序的执行从throw位置跳到与之匹配的catch模块,catch可能是同一函数中的一个局部的catch,也可能是调用链中另一个函数中的catch,控制权从throw位置转移到了catch位置。

在这里插入图片描述

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


2.2 栈展开

2.2.1 理论

抛出异常后,程序暂停当前函数的执行,开始寻找与之匹配的catch子句,首先检查throw本身是否在try块内部,如果在则查找匹配的catch语句,如果有匹配的,则跳到catch的地方进行处理。

如果当前函数中没有try/catch子句,或者有try/catch子句但是类型不匹配,则退出当前函数,继续在外层调用函数链中查找,上述查找的catch过程被称为栈展开。

如果到达main函数,依旧没有找到匹配的catch子句,程序会调用标准库的terminate函数终止程序。

如果找到匹配的catch子句处理后,catch子句后面的代码会继续执行。

在这里插入图片描述

2.2.2 最佳实践

注意: e.what(); 返回包含异常的字符串。

在这里插入图片描述

上图中,[ LINE ] 的作用:获取你是哪个位置的宏

在这里插入图片描述

2.3 查找匹配的处理代码

2.3.1 抛出对象和catch一般是类型完全匹配的

一般情况下抛出对象和catch是类型完全匹配的,如果有多个类型匹配的,就选择离它位置更近的那个(类型匹配时遵循“就近原则”)。

但是也有一些例外,允许从非常量向常量的类型转换,也就是权限缩小;允许数组转换成指向数组元素类型的指针,函数被转换成指向函数的指针;允许从派生类向基类类型的转换,这个点非常实用,实际中继承体系基本都是用这个方式设计的。

如果到main函数,异常仍旧没有被匹配就会终止程序,不是发生严重错误的情况下,我们是不期望程序终止的,所以一般main函数中最后都会使用catch(…),它可以捕获任意类型的异常,但是我们不知道异常错误是什么。

2.3.2 最佳实践

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.4 异常重新抛出

2.4.1 概念

有时catch到一个异常对象后,需要对错误进行分类,其中的某种异常错误需要进行特殊的处理,其他错误则重新抛出异常给外层调用链处理。捕获异常后需要重新抛出,直接throw;就可以把捕获的对象直接拋出。

2.4.2 最佳实践

在这里插入图片描述
在这里插入图片描述

2.5 异常安全问题

2.5.1 注意事项

1、异常抛出后,后面的代码就不再执行,前面申请了资源(内存、锁等),后面进行释放,但是中间可能会抛异常就会导致资源没有释放,这里由于异常就引发了资源泄漏,产生安全性的问题。中间我们需要捕获异常,释放资源后面再重新抛出,当然后面智能指针章节讲的RAII方式解决这种问题是更好的。

在这里插入图片描述

2、其次析构函数中,如果抛出异常也要谨慎处理,比如析构函数要释放10个资源,释放到第5个时抛出异常,则也需要捕获处理,否则后面的5个资源就没释放,也资源泄漏了。《EffctiveC++》中的第8个条款也专门讲了这个问题,别让异常逃离析构函数。

2.5.2 最佳实践

在这里插入图片描述

2.6 异常规范

2.6.1 理论

对于用户和编译器而言,预先知道某个程序会不会抛出异常大有裨益,知道某个函数是否会抛出异常有助于简化调用函数的代码。

C++98中函数参数列表的后面接throw),表示函数不抛异常,函数参数列表的后面接throw(类型1,类型2…)表示可能会抛出多种类型的异常,可能会抛出的类型用逗号分割

C++98的方式这种方式过于复杂,实践中并不好用,C++11中进行了简化,函数参数列表后面加noexcept表示不会抛出异常,啥都不加表示可能会抛出异常。

编译器并不会在编译时检查noekcept,也就是说如果一个函数用noexcept修饰了,但是同时又包含了throw语句或者调用的函数可能会抛出异常,编译器还是会顺利编译通过的(有些编译器可能会报个警告)。但是一个声明了noexcept的函数抛出了异常,程序会调用terminate终止程序。

noexcept(expression)还可以作为一个运算符去检测一个表达式是否会抛出异常,可能会则返回false,不会就返回true。

2.6.2 最佳实践

在这里插入图片描述
在这里插入图片描述

3 ~> 标准库的异常

标准库链接:std::exception

在这里插入图片描述


C++标准库也定义了一套自己的一套异常继承体系库,基类是exception,其它的都是它的派生类,所以我们日常写程序,需要在主函数捕获exception即可,要获取异常信息,调用what函数,what是一个虚函数,派生类可以重写。

在这里插入图片描述

C++11完整代码示例与实践演示

Test.cpp:

#define_CRT_SECURE_NO_WARNINGS1#include<iostream>usingnamespace std;#include<exception>//double Divide(int a, int b)//{// // 当b == 0时抛出异常 // if (b == 0)// {// //string s("Divide by zero condition!");// //throw s;//// throw exception("Divide by zero condition!");// }// else// {// return ((double)a / (double)b);// }//}////void Func()//{// try// {// int len, time;// cin >> len >> time;// cout << Divide(len, time) << endl;// }// catch (const exception& e)// {// cout << e.what() << endl;// }//// cout << "Func():" << __LINE__ << endl;//}////int main()//{// while (1)// {// try// {// Func();// }// catch (const string& s)// {// cout << s << endl;// }//// catch (const exception& e)// {// cout << e.what() << endl;// }//// catch (...) // 任意类型的对象// {// cout << "未知异常" << endl;// }//// cout << "main():" << __LINE__ << endl;// }//// return 0;//}#include<thread>// 延时功能// 一般大型项目才会使用异常,下面我们模拟设计一个服务的几个模块// 每个模块的继承都是Exception的派生类,每个模块可以添加自己的数据// 最后捕获时,我们捕获基类即可classException{public:Exception(const string& errmsg,int id):_errmsg(errmsg),_id(id){}virtual string what()const{return _errmsg;}intgetid()const{return _id;}protected: string _errmsg;int _id;};classSqlException:publicException{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;};classCacheException:publicException{public:CacheException(const string& errmsg,int id):Exception(errmsg, id){}virtual string what()const{ string str ="CacheException:"; str += _errmsg;return str;}};classHttpException:publicException{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;};voidSQLMgr(){if(rand()%7==0){throwSqlException("权限不足",100,"select * from name = 张三");}else{ cout <<"SQLMgr 调用成功"<< endl;}}voidCacheMgr(){if(rand()%5==0){throwCacheException("权限不足",100);}elseif(rand()%6==0){throwCacheException("数据不存在",101);}else{ cout <<"CacheMgr 调用成功"<< endl;}SQLMgr();}voidHttpSever(){if(rand()%3==0){throwHttpException("申请资源不存在",100,"get");}elseif(rand()%4==0){throwHttpException("权限不足",101,"post");}else{ cout <<"HttpSever 调用成功"<< endl;}CacheMgr();}//int main()//{// srand(time(0));// while (1)// {// this_thread::sleep_for(chrono::seconds(1)); // 延迟1秒//// try// {// HttpSever();// }// catch (const Exception& e) // 这里捕获基类,基类对象和派生类对象都可以被捕获// {// // 多态调用// cout << e.what() << endl;// }//// catch (...) // 任意类型的对象// {// cout << "Unknown Exception" << endl;// }// }//// return 0;//}// 输出结果:// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpSever 调用成功// CacheException:权限不足// HttpSever 调用成功// CacheException:数据不存在// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpException:get : 申请资源不存在// HttpException:post : 权限不足// HttpException:get : 申请资源不存在// HttpException:post : 权限不足// HttpSever 调用成功// CacheException:权限不足// HttpSever 调用成功// CacheException:数据不存在// HttpException:get : 申请资源不存在// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpException:get : 申请资源不存在// HttpException:get : 申请资源不存在// HttpException:get : 申请资源不存在// HttpException:get : 申请资源不存在// HttpException:post : 权限不足// HttpException:post : 权限不足// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpSever 调用成功// CacheException:权限不足// HttpException:get : 申请资源不存在// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpException:post : 权限不足// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// HttpSever 调用成功// CacheMgr 调用成功// SqlException:权限不足->select* from name = 张三// HttpException:post : 权限不足// HttpSever 调用成功// CacheMgr 调用成功// SQLMgr 调用成功// . . . . . . .void_Sending(const string& s){if(rand()%2==0){throwHttpException("网络不稳定,发送失败",102,"put");}elseif(rand()%4==0){throwHttpException("你已经不是对象的好友,发送失败",103,"put");}else{ cout <<"发送成功"<< endl;}}// 网络不稳定,要求重试3次,均失败voidSendMsg(const string& s){for(size_t i =0; i <4; i++){try{_Sending(s);// 走到这里代表成功了,跳出循环break;}catch(const Exception& e){if(e.getid()==102){if(i ==3)throw; cout <<"开始第"<< i +1<<"重试"<< endl;}else{// 重新抛出异常//throw e;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 << "Unknown Exception" << endl;// }// }//// return 0;//}//double Divide(int a, int b) noexcept;//double Divide(int a, int b) throw(const char*);doubleDivide(int a,int b){// 当b == 0时抛出异常 if(b ==0){throw"Division by zero condition!";}return(double)a /(double)b;}voidFunc(){// 这里可以看出如果发生除0错误抛出异常,另外下面的array没有得到释放// 所以这里捕获异常后并不处理异常,异常还是交给外层处理,这里捕获了再重新抛出去int* array =newint[10];int len, time;//try//{// cout << Divide(len, time) << endl;//}//catch(...)//{// cout << "delete []" << array << endl;// delete[] array;// // 重新抛出,捕获到什么就抛出什么// throw;//} cout <<"delete[] "<< array << endl;delete[] array;}intmain(){//try//{// Func();//}//catch (const char* errmsg)//{// cout << errmsg << endl;//}//catch (...)//{// cout << "Unknown Exception" << endl;//}int i =0; cout <<noexcept(Divide(1,2))<< endl; cout<<noexcept(Divide(1,0))<< endl; cout <<noexcept(Func())<< endl; cout <<noexcept(++i)<< endl;return0;}// 输出结果:// 0// 0// 0// 1

结尾

uu们,本文的内容到这里就全部结束了,艾莉丝再次感谢您的阅读!

结语:希望对学习C++相关内容的uu有所帮助,不要忘记给博主“一键四连”哦!

往期回顾:

【C++:C++11收尾】解构C++可调用对象:从入门到精通,掌握function包装器与bind适配器包装器详解

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡૮₍ ˶ ˊ ᴥ ˋ˶₎ა

Read more

【C++】哈希扩展——位图和布隆过滤器的介绍与实现

【C++】哈希扩展——位图和布隆过滤器的介绍与实现

各位读者大佬好,我是落羽!一个坚持不断学习进步的学生。 如果您觉得我的文章还不错,欢迎多多互三分享交流,一起学习进步! 也欢迎关注我的blog主页:落羽的落羽 文章目录 * 一、位图 * 1. 概念与实现 * 2. std::bitset * 二、布隆过滤器 * 1. 概念 * 2. 布隆过滤器误判率数学推导 * 3. 实现 一、位图 1. 概念与实现 在许多公司的面试题中会考到这样的场景:给40亿个不重复无符号整数,如何快速判断一个数是否在这40亿数中。 如果使用常规思路,每次查询暴力遍历O(N)太慢,排序+二分查找O(NlogN)+O(logN),内存不足以放下这些数据。 数据是否在给定的整型数据中,结果是在或不在,正好是两种状态,那么可以用一个二进制比特位来代表数据是否存在的信息,比特位为1代表存在,比特位为0代表不在。那么,我们可以设计一个用比特位表示数据是否存在的数据结构——位图!

By Ne0inhk
排序(数据结构)

排序(数据结构)

一. 排序概念及运用 排序在数据结构中是非常重要的一部分,所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 在生活中也有很多的应用,比如当我们搜索一款产品时候,我们可以选择按销量多少的顺序来给我们推荐产品,也可以按照价格高低来给我们推荐产品,所以排序在生活中也是很常见的。 1.1插入排序 (1)直接插入排序 上面就是一些常见的排序算法,首先我们来认识一下插入排序,插入排序又分为直接插入排序和希尔排序,直接插入排序是比较好理解的,比如我们日常生活中的扑克牌游戏,当我们拿到牌的时候我们会习惯性的直接将牌按我们想要的顺序排列,如下:   那么希尔排序又是怎么回事呢? 我还是用一张清晰的思路图来向大家展示: void InitSort(int* arr, int n) { for (int i = 0; i < n-1; i++) { int end = i; int tmp = arr[end + 1]; while (end >

By Ne0inhk
2026 前端 / 后端 / 算法岗 AI 技能清单,直接对标大厂

2026 前端 / 后端 / 算法岗 AI 技能清单,直接对标大厂

2026 大厂前端岗 AI 技能清单 核心基础技能 * 大模型前端适配能力:掌握大模型上下文管理,实现对话历史的高效存储与加载,适配流式输出的前端渲染逻辑。 * AI 组件开发:熟练开发基于大模型的智能组件,如代码补全、智能问答、内容生成类组件,支持参数化配置与多模型切换。 * 向量数据库集成:掌握 Pinecone、Weaviate 等向量数据库的前端调用方法,实现语义搜索、相似内容推荐等功能。 进阶实践技能 * 大模型微调适配:理解大模型微调原理,能够基于前端业务场景,将微调后的模型部署至前端环境,实现模型轻量化调用。 * 多模态交互开发:支持文本、图像、音频等多模态输入的前端处理,对接多模态大模型 API 实现智能交互。 * AI 性能优化:实现大模型请求的批量处理、缓存复用与增量更新,降低前端请求延迟与资源消耗。 实战代码示例 以下为基于 OpenAI API 实现的流式对话前端组件,使用 React 18 开发:

By Ne0inhk
【数据结构-初阶】详解线性表(3)---双链表

【数据结构-初阶】详解线性表(3)---双链表

🎈主页传送门:良木生香 🔥个人专栏:《C语言》 《数据结构-初阶》 《程序设计》 🌟人为善,福随未至,祸已远行;人为恶,祸虽未至,福已远离 目录 1、双链表的概念 2、双链表的基本实现 2.1、双向链表节点的创建 2.2、双向链表的初始化 2.3、双向链表长度的计算 2.4、双向链表的插入操作: 2.4.1、头部插入: 2.4.2、尾部插入: 2.4.3、查找pos位置的元素: 2.4.4、pos位置插入: 2.4.4.1、pos位置之前插入:

By Ne0inhk