【C++】C++异常

【C++】C++异常



🎬 个人主页MSTcheng · ZEEKLOG
🌱 代码仓库MSTcheng · Gitee
🔥 精选专栏: 《C语言
数据结构
《算法学习》
C++由浅入深

💬座右铭:路虽远行则将至,事虽难做则必成!


在前面的文章中,我们已经介绍了C++11的一些新特性。本文将和下一篇一起为大家讲解C++的最后两个重要主题:异常处理和智能指针。

文章目录

一、异常的概念及使用

1.1异常的概念

异常(Exception)是指在程序执行过程中发生的意外或错误情况,这些情况可能导致程序无法继续正常执行。异常处理是编程中用于管理这些意外情况的机制,旨在提高程序的健壮性和用户体验。
相比于C语言,C语言主要通过错误码的方式处理错误,而错误码的本质就是对错误的信息进行分类编写。拿到错误码以后还要去查询错误信息,是比较麻烦的。而异常时直接抛出一个对象,这个对象可以涵盖更全面的错误信息。

1.2异常的分类

1、编译时异常Checked Exception

  • 这类异常在编译阶段就会被检查,必须显式处理(捕获或声明抛出)。
    常见于外部资源操作,如文件不存在(FileNotFoundException)、数据库连接 失败等。

2、运行时异常Runtime Exception

  • 编译时不会被强制检查,通常由逻辑错误引发,如空指针访问NullPointerException)、数组越界(ArrayIndexOutOfBoundsException)等。

3、错误Error

  • 指严重问题(如内存耗尽OutOfMemoryError),通常无法通过程序处理,需从系统层面解决。

1.3异常的抛出与捕获

异常的抛出:

  • 当程序出现问题的时候,首先通过抛出(throw)一个对象来引发一个异常,该对象的类型以及当前调用链来决定匹配哪个catch,然后再被这个catch接收并处理异常。

异常的捕获:

  • 异常的捕获首先通过一个try/catch语句来捕获,并且该catch要与throw对象的类型匹配且为距离抛出异常位置最近的那一个catch,然后根据抛出的对象的类型和内容告知异常部分到底发生了什么错误。

下面我们就来看看C++的异常抛出与捕获机制:

#include<iostream>#include<string>usingnamespace std;//=================================//Divide这个函数是用来计算两个数相除的函数//对于一个除数来说被除数是不能为0的 也就是分子可以为0 但分母不能//所以我们针对被除数是否为0设计了一个异常机制//假设a为除数b为被除数 如果检测到b为0那么就抛出异常//=================================doubleDivide(int a,int b){try{// 当b == 0时抛出异常 引发除零错误if(b ==0){//===============================//抛出异常对象后,会⽣成⼀个异常对象的拷⻉,//因为抛出的异常对象可能是⼀个局部对象,//所以会⽣成⼀个拷⻉对象,//这个拷⻉的对象会在catch⼦句后销毁。//(这⾥的处理类似于函数的传值返回)//=============================== string s("Divide by zero condition!");throw s;//抛出的是一个string对象 catch的时候要用string类型接收}else{return((double)a /(double)b);}//... fxx()}catch(constint& s){ cout << s << endl;}//第一个catch 这个catch的类型与抛出异常的string对象类型匹配 且离抛出位置最近/*catch (const string& errmsg) { cout << errmsg << endl; }*/ cout << __FUNCTION__ <<":"<<__LINE__<<"行执行"<< endl;return0;}voidFunc(){int len, time; cin >> len >> time; cout <<Divide(len, time)<< endl; cout << __FUNCTION__ <<":"<<__LINE__<<"行执行"<< endl;}intmain(){try{Func();}//第二个catch 虽然也是string类型 与抛出异常的string对象类型配但是离抛出位置较远//所以第一个catch存在的情况下程序优先会跳到第一个catch中catch(const string& errmsg){ cout << errmsg << endl;}catch(int errid){ cout << errid << endl;}}
1、当被第一个catch捕获时

2、注释掉第一个catch,当被第二个catch捕获时

注意事项:

  1. throw执行时,throw后面的语句将不再执行,直接跳转到对应的catch中去执行catch可能是同一个函数中的局部catch,也可能是调用链中另一个函数中的catch,制空权从throw位置转移到了catch位置之后还有两个重要含义:
    1、沿着调用链的函数可能提早退出
    2、一旦程序开始执行异常处理程序,沿着调用链创建的对象都将销毁。
对于第一点含义我们对比两次catch执行的结果就会发现,当第一个catch捕获时,由于第一个catch与抛出异常的对象位于同一个域,所以当异常对象s被第一个catch捕获时并没有跳过func函数所以程序运行时会执行func函数中的内容而第二次catch就不同了,第二个catchmain函数中,throw之后直接跳过func函数,到main函数中执行catch之后的内容,跳过了func函数所以func函数中的内容不被执行。

对于第二点,我们就要来看看栈的展开了

1.4栈展开

如果一直到main函数都没有找到了与之类型匹配的catch子句,则程序会调用标准库的terminate函数终止程序,如下图:

在这里插入图片描述

当抛出异常后,程序暂停当前函数的执行,开始寻找与之匹配的catch子句首先检查throw本⾝是否在try块内部,如果在则查找匹配的catch语句:如果有匹配的,则跳到catch的地方进行处理;如果当前函数中没有try/catch子句,或者try/catch子句但是类型不匹配,则退出当前函数,继续在外层调用函数链中查找,上述查找的catch过程被称为栈展开。 下面来看看栈展开图

在这里插入图片描述

1.5 查找匹配的处理代码

⼀般情况下抛出对象和catch是类型完全匹配的,如果有多个类型匹配的,就选择离他位置更近的那个。
但是也有⼀些例外,允许从非常量向常量的类型转换,也就是权限缩小允许数组转换成指向数组元素类型的指针,函数被转换成指向函数的指针允许从派⽣类向基类类型的转换,这个点非常实⽤,实际中继承体系基本都是用这个方式式设计的。下面来看一些例子:
//=============第一种==============//精确匹配try{throw42;// 抛出int类型}catch(int e){// 精确匹配int std::cout <<"Caught int: "<< e;}//=============第二种===============//权限缩小转换(非常量->常量)try{char* ptr =newchar[10];throw ptr;// 抛出char*}catch(constchar* e){// 允许非常量转常量 std::cout <<"Caught const pointer";delete[] e;}//=============第三种==============//派生类->基类转换classBase{virtualvoidfoo(){}};classDerived:publicBase{};try{throwDerived();// 抛出派生类对象}catch(Base& e){// 捕获基类引用(多态处理) std::cout <<"Caught Base reference";}//==============第四种=============//数组->指针转换try{int arr[5]{1,2,3};throw arr;// 抛出int[5]}catch(int* e){// 自动转为指针 std::cout <<"Caught pointer to first element: "<< e[0];}//==============第五种=============//就近原则匹配catchtry{throw std::string("error");}catch(const std::string& e){// 优先匹配更近的 std::cout <<"Caught by string ref";}catch(const std::exception& e){ std::cout <<"Caught by exception ref";}

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

#include<iostream>#include<stdexcept>intmain(){try{// 可能抛出异常的代码throw std::runtime_error("An error occurred");}catch(const std::exception& e){ std::cerr <<"Caught exception: "<< e.what()<<std::endl;}catch(...)//使用三个点来接收异常{ std::cerr <<"Caught an unknown exception"<< std::endl;}return0;}

1.6异常重新抛出

有时catch到⼀个异常对象后,需要对错误进行分类,其中的某种异常错误需要进行特殊的处理,其他错误则重新抛出异常给外层调用链处理。捕获异常后需要重新抛出,直接 throw; 就可以把捕获的对象直接抛出
try{// 可能抛出异常的代码someRiskyOperation();}catch(const std::exception& e){if(isSpecialError(e)){// 特殊错误处理handleSpecialCase();}else{// 其他错误重新抛出throw;// 注意没有参数}}

注意throwthrow e的区别:

  1. throw重新抛出当前异常对象,不进行拷贝构造。
  2. throw e会通过拷贝构造函数创建一个新的异常对象。

1.7异常的安全问题

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

1.8异常规范

相比于传统C++98时的异常规范C++11中进行了简化:函数参数列表后面加
noexcept表示不会抛出异常,什么都不加表示可能会抛出异常。
  • 编译器并不会在编译时检查noexcept,也就是说如果⼀个函数用noexcept修饰了,但是同时又包含了throw语句或者调用的函数可能会抛出异常,编译器还是会顺利编译通过的(有些编译器可能会报个警告)。但是⼀个声明了noexcept的函数抛出了异常,程序会调用terminate函数来终止程序。
  • 另外noexcept还可以作为一个运算符去检测一个表达式是否会抛出异常,如果会抛出异常就返回false,如果不会就返回true
doubleDivide(int a,int b)noexcept{// 当b == 0时抛出异常if(b ==0){throw"Division by zero condition!";}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;}
在这里插入图片描述

二、总结

C++的异常处理机制是管理运行时错误的重要工具,它通过try、catchthrow三个关键字的配合使用,能够有效应对程序执行中的意外情况。这套机制不仅确保了程序的健壮性,还能实现错误的优雅处理。因此,在代码编写时应当重视异常处理的应用。

MSTcheng 始终坚持用直观图解 + 实战代码,把复杂技术拆解得明明白白! 👁️ 【关注】 看普通程序员如何用实用派思路搞定复杂需求 👍 【点赞】 给 “不搞虚的” 技术分享多份认可 🔖 【收藏】 把这些 “好用又好懂” 的干货技巧存进你的知识库 💬 【评论】 来唠唠 —— 你踩过最 “离谱” 的技术坑是啥? 🔄 【转发】把实用技术干货分享给身边有需要的程序员伙伴 技术从无唯一解,让我们一起用最接地气的方式,写出最扎实的代码! 🚀💻 
感谢能够看到这里的小伙伴,如果这篇文章有帮到您,还请给个三连!你们的持续支持是我更新最大的动力!谢谢!

Read more

内存暴涨700%背后的惊天真相:AI正在吞噬一切!能源·隐私·绿色三大维度深度拆解

内存暴涨700%背后的惊天真相:AI正在吞噬一切!能源·隐私·绿色三大维度深度拆解

🔥作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生,研究方向无线联邦学习 🎬擅长领域:驱动开发,嵌入式软件开发,BSP开发 ❄️作者主页:一个平凡而乐于分享的小比特的个人主页 ✨收录专栏:未来思考,本专栏结合当前国家战略和实时政治,对未来行业发展的思考 欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖 🔥内存暴涨700%背后的惊天真相:AI正在吞噬一切!能源·隐私·绿色三大维度深度拆解 |前言| 最近装机的小伙伴们欲哭无泪:DDR5内存价格一路狂飙,部分DRAM现货价格在过去一年暴涨近700% 。大家习惯性吐槽“厂商放火”、“产能不足”,但很少有人看到,这场涨价风暴的真正推手,是那只名为“AI”的巨兽。 当你还在为多花几百块钱买内存心疼时,国家正在西部荒漠建起一座座数据中心,科技巨头正在为“吃电怪兽”抢购每一颗芯片。2026年,大型科技公司的AI相关投资预计将达到6500亿美元,较去年增长约80% 。 今天,我们从能源供应、隐私安全、绿色AI 三个维度,结合东数西算、算电协同、

By Ne0inhk
AI实践(8)Skills技能

AI实践(8)Skills技能

AI实践(10)Skills技能 Author: Once Day Date: 2026年3月18日 一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦… 漫漫长路,有人对你微笑过嘛… 全系列文章可参考专栏: AI实践成长_Once-Day的博客-ZEEKLOG博客 参考文章:Prompt Engineering Guide提示词技巧 – Claude 中文 - Claude AI 开发技术社区Documentation - Claude API DocsOpenAI for developersSkills(技能) – Claude 中文 - Claude AI 开发技术社区模式库:把工程经验沉淀为 Skills – Claude 中文 - Claude AI 开发技术社区持续学习:把会话复盘沉淀成 Skills – Claude

By Ne0inhk
医疗AI场景下算法编程的深度解析(2026新生培训讲稿)(八)

医疗AI场景下算法编程的深度解析(2026新生培训讲稿)(八)

第15章 模型融合与集成策略 在机器学习竞赛和实际应用中,模型融合(Model Ensemble)是提升预测性能的利器。通过组合多个不同的基模型,集成策略能够综合各个模型的优势,抵消单个模型的偏差和方差,从而获得比任何单一模型更稳定、更准确的预测结果。在医疗AI领域,模型融合同样具有重要价值——面对复杂多模态的医疗数据,单一模型往往难以全面捕捉所有信息,而融合多个异质模型可以提升诊断的鲁棒性和准确性。本章将从集成学习的基本思想出发,系统介绍常见的模型融合方法,包括投票法、平均法、Stacking、Blending等,并通过实战案例展示如何构建融合模型来提升疾病预测性能。 15.1 集成学习的基本思想 集成学习(Ensemble Learning)的核心思想是“三个臭皮匠,顶个诸葛亮”——通过结合多个学习器来完成学习任务,通常可以获得比单一学习器更优越的泛化性能。根据个体学习器的生成方式,集成学习主要分为两大类: * Bagging:并行训练多个独立的基学习器,然后通过平均或投票进行结合。典型代表是随机森林。Bagging主要降低方差。 * Boosting:串行训练基学习

By Ne0inhk
人工智能:自然语言处理在教育领域的应用与实战

人工智能:自然语言处理在教育领域的应用与实战

人工智能:自然语言处理在教育领域的应用与实战 学习目标 💡 理解自然语言处理(NLP)在教育领域的应用场景和重要性 💡 掌握教育领域NLP应用的核心技术(如智能问答、作业批改、个性化学习) 💡 学会使用前沿模型(如BERT、GPT-3)进行教育文本分析 💡 理解教育领域的特殊挑战(如多学科知识、学生认知差异、数据隐私) 💡 通过实战项目,开发一个智能问答系统应用 重点内容 * 教育领域NLP应用的主要场景 * 核心技术(智能问答、作业批改、个性化学习) * 前沿模型(BERT、GPT-3)在教育领域的使用 * 教育领域的特殊挑战 * 实战项目:智能问答系统应用开发 一、教育领域NLP应用的主要场景 1.1 智能问答 1.1.1 智能问答的基本概念 智能问答是通过自然语言与用户进行交互,回答用户问题的程序。在教育领域,智能问答的主要应用场景包括: * 课程问答:回答课程相关的问题(如“什么是机器学习”

By Ne0inhk