异常--C++

异常--C++

文章目录

一、异常的概念及使用

1、异常的概念

  1. 异常处理机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并做出相应的处理,异常使得我们能够将问题的检测与解决问题的过程分开,程序的一部分负责检测问题的出现,然后解决问题的任务传递给程序的另一部分,检测环节无须知道问题的处理模块的所有细节。
  2. C语言主要通过错误码的形式处理错误,错误码本质就是对错误信息进行分类编号,拿到错误码以后还要去查询错误信息,比较麻烦。异常时抛出一个对象,这个对象可以含更全面的各种信息。

2、异常的抛出和捕获

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

3、栈展开

  1. 抛出异常后,程序暂停当前函数的执行,开始寻找与之匹配的catch子句,⾸先检查throw本身是否在try块内部,如果在则查找匹配的catch语句,如果有匹配的,则跳到catch的地方进行处理。
  2. 如果当前函数中没有try/catch子句,或者有try/catch子句但是类型不匹配,则退出当前函数,继续在外层调用函数链中查找,上述查找的catch过程被称为栈展开。
  3. 如果到达main函数,依旧没有找到匹配的catch子句,程序会调用标准库的 terminate 函数终止程序。

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

在这里插入图片描述
doubleDivide(int a,int b){try{// 当b == 0时抛出异常if(b ==0){ string s("Divide by zero conditon!");throw s;}else{return((double)a /(double)b);}}catch(int errid){ cout << errid << endl;}return0;}voidFunc(){int len, time; cin >> len >> time;try{ cout <<Divide(len, time)<< endl;}catch(constchar* errmsg){ cout << errmsg << endl;} cout << __FUNCTION__ <<":"<<__LINE__<<"行执行"<< endl;}intmain(){while(1){try{Func();}catch(const string& errmsg){ cout << errmsg << endl;}}return0;}

4、查找匹配的处理代码

  1. 一般情况下抛出对象和catch是类型完全匹配的,如果有多个类型匹配的,就选择离他位置更近的那个。
  2. 但是也有一些例外,允许从非常量向常量的类型转换,也就是权限缩小;允许数组转换成指向数组元素类型的指针,函数被转换成指向函数的指针;允许从派生类向基类类型的转换,这个点非常实用,实际中继承体系基本都是用这个方式设计的。
  3. 如果到main函数,异常仍旧没有被匹配就会终止程序,不是发生严重错误的情况下,我们是不期望程序终止的,所以一般main函数中最后都会使用catch(…),它可以捕获任意类型的异常,但是是不知道异常错误是什么。
#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();}voidHttpServer(){if(rand()%3==0){throwHttpException("请求资源不存在",100,"get");}elseif(rand()%4==0){throwHttpException("权限不足",101,"post");}else{ cout <<"HttpServer调用成功"<< endl;}CacheMgr();}intmain(){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;}}return0;}

5、异常重新抛出

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

// 下⾯程序模拟展⽰了聊天时发送消息,发送失败补货异常,但是可能在// 电梯地下室等场景⼿机信号不好,则需要多次尝试,如果多次尝试都发// 送不出去,则就需要捕获异常再重新抛出,其次如果不是⽹络差导致的// 错误,捕获后也要重新抛出。void_SeedMsg(const string& s){if(rand()%2==0){throwHttpException("⽹络不稳定,发送失败",102,"put");}elseif(rand()%7==0){throwHttpException("你已经不是对象的好友,发送失败",103,"put");}else{ cout <<"发送成功"<< endl;}}voidSendMsg(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;}}}}intmain(){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;}}return0;}

6、异常安全问题

  1. 异常抛出后,后面的代码就不再执行,前面申请了资源(内存、锁等),后面进行释放,但是中间可能会抛异常就会导致资源没有释放,这里由于异常就引发了资源泄漏,产生安全性的问题。中间我们需要捕获异常,释放资源后面再重新抛出,当然后面智能指针章节讲的RAII方式解决这种问题是更好的。
  2. 其次析构函数中,如果抛出异常也要谨慎处理,比如析构函数要释放10个资源,释放到第5个时抛出异常,则也需要捕获处理,否则后面的5个资源就没释放,也资源泄漏了。《Effctive C++》第8个条款也专门讲了这个问题,别让异常逃离析构函数。
doubleDivide(int a,int b){// 当b == 0时抛出异常if(b ==0){throw"Divide by zero conditon!";}return((double)a /(double)b);}voidFunc(){// 这⾥可以看到如果发⽣除0错误抛出异常,另外下⾯的array没有得到释放。// 所以这里捕获异常后并不处理异常,异常还是交给外层处理,这里捕获了再// 重新抛出去。int* array =newint[10];try{int len, time; cin >> len >> time; cout <<Divide(len, time)<< endl;}catch(...){// 捕获异常释放内存 cout <<"delete []"<< array << endl;delete[] array;throw;} cout <<"delete []"<< array << endl;delete[] array;}intmain(){try{Func();}catch(constchar* errmsg){ cout << errmsg << endl;}catch(const exception& e){ cout << e.what()<< endl;}catch(...){ cout <<"Unkown Exception"<< endl;}return0;}

7、异常规范

  1. 对于用户和编译器而言,预先知道某个程序会不会抛出异常大有裨益,知道某个函数是否会抛出异常有助于简化调用函数的代码。
  2. C++98中函数参数列表的后面接throw(),表示函数不抛异常,函数参数列表的后面接throw(类型1,类型2…)表示可能会抛出多种类型的异常,可能会抛出的类型用逗号分割。
  3. C++98的方式这种方式过于复杂,实践中并不好用,C++11中进行了简化,函数参数列表后面加noexcept表示不会抛出异常,啥都不加表示可能会抛出异常。
  4. 编译器并不会在编译时检查noexcept,也就是说如果一个函数用noexcept修饰了,但是同时又包含了throw语句或者调用的函数可能会抛出异常,编译器还是会顺利编译通过的(有些编译器可能会报个警告)。但是一个声明了noexcept的函数抛出了异常,程序会调用 terminate 终止程序。
  5. noexcept(expression)还可以作为一个运算符去检测一个表达式是否会抛出异常,可能会则返回false,不会就返回true。
// C++98// 这⾥表⽰这个函数只会抛出bad_alloc的异常void*operatornew(std::size_t size)throw(std::bad_alloc);// 这⾥表⽰这个函数不会抛出异常void*operatordelete(std::size_t size,void* ptr)throw();// C++11 size_type size()constnoexcept; iterator begin()noexcept; const_iterator begin()constnoexcept;doubleDivide(int a,int b)//noexcept{// 当b == 0时抛出异常if(b ==0){throw"Divide by zero conditon!";}return((double)a /(double)b);}intmain(){try{int len, time; cin >> len >> time; cout <<Divide(len, time)<< endl;}catch(constchar* 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;return0;}

二、标准库的异常

  1. https://legacy.cplusplus.com/reference/exception/exception/

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

在这里插入图片描述

Read more

今日AI榜单速览(GitHub Trending AI Top3)

今日AI榜单速览(GitHub Trending AI Top3)

🔥 个人主页:杨利杰YJlio❄️ 个人专栏:《Sysinternals实战教程》《Windows PowerShell 实战》《WINDOWS教程》《IOS教程》《微信助手》《锤子助手》《Python》《Kali Linux》《那些年未解决的Windows疑难杂症》🌟 让复杂的事情更简单,让重复的工作自动化 今日AI热榜 * 1 1 今日榜单速览(GitHub Trending AI Top3) * 2 2 ruvnet / RuView:WiFi DensePose 的“无线透视”路线 * 2 我的一句话总结 * 2 为什么今天它能冲到第一? * 2 图:它的可视化界面长这样(很直观) * 2 我如何最快验证(不折腾工具链) * 3 3 K-Dense-AI / claude-scientific-skills:给

By Ne0inhk
告别996:GitHub Copilot将我的开发效率提升300%的实战记录

告别996:GitHub Copilot将我的开发效率提升300%的实战记录

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕AI这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * 告别996:GitHub Copilot将我的开发效率提升300%的实战记录 * 引言:从疲惫到高效 * 什么是GitHub Copilot?🤖 * 效率提升300%的核心场景 * 1. 快速生成样板代码 * 2. 自动编写单元测试 * 3. 智能调试与注释 * 集成Copilot到工作流 * 步骤1:设置合理的期望 * 步骤2:结合IDE使用 * 步骤3:代码审查与调整 * 高级用法:超越代码生成 * 数据库查询优化 * API接口设计 * 正则表达式助手 * 数据支撑:效率提升分析 * 避坑指南:常见问题与解决 * 1. 可能生成过时或不安全代码

By Ne0inhk

睡前定方向,醒来收初稿:全自动跑实验改论文的工作流开源了

与其在实验室通宵,不如让 Claude 替你卷。 如果你还在熬夜手搓代码、调参跑实验,那这个刚刚开源的科研工作流绝对会让你眼前一亮。 它就是 ARIS(Auto-Research-In-Sleep),一款真正帮你实现“睡后科研”的全自动神器。 这个项目的核心理念很直接,让 Claude Code 在你睡觉时做科研。 睡前丢给 AI 一篇论文初稿,醒来就能发现,站不住脚的 claim 已被剔除,20 多组 GPU 实验默默跑完,整篇论文的叙事框架焕然一新,分数也从 5.0 稳步提升到了可投稿的 7.5 分——而且全流程零人工干预。 作为一套专为机器学习科研定制的 Claude Code Skills,ARIS 既吸收了 FARS 的经验,也呼应了 Karpathy 提出的 autoresearch

By Ne0inhk
内置AI与浏览器的开源终端Wave Terminal安装与远程连接内网服务器教程

内置AI与浏览器的开源终端Wave Terminal安装与远程连接内网服务器教程

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老导航檀越剑指大厂系列:全面总结 java 核心技术,jvm,并发编程 redis,kafka,Spring,微服务等常用开发工具系列:常用的开发工具,IDEA,Mac,Alfred,Git,typora 等数据库系列:详细总结了常用数据库 mysql 技术点,以及工作中遇到的 mysql 问题等新空间代码工作室:提供各种软件服务,承接各种毕业设计,毕业论文等懒人运维系列:总结好用的命令,解放双手不香吗?能用一个命令完成绝不用两个操作数据结构与算法系列:总结数据结构和算法,不同类型针对性训练,提升编程思维,剑指大厂 非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。💝💝💝 ✨✨ 欢迎订阅本专栏 ✨✨ 博客目录 * 前言

By Ne0inhk