【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

2026最新 Python+AI 入门指南:0基础也能快速上手,避开90%新手坑

2026最新 Python+AI 入门指南:0基础也能快速上手,避开90%新手坑

🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 文章目录: * 【前言】 * 一、为什么2026年入门AI,首选Python?(新颖热点解读) * 二、Python+AI入门必备:前提+环境搭建(10分钟搞定) * 2.1 核心前提(不用啃硬骨头) * 2.2 环境搭建(Windows/Mac通用,避版本冲突) * 三、Python+AI入门实战:3个热门案例(附完整代码) * 案例1:数据处理(AI入门必备,80%AI开发第一步) * 案例2:机器学习入门(线性回归,房价预测) * 案例3:2026热门·大模型对接(LangChain快速调用) * 四、

By Ne0inhk
AI Agent 面试八股文100问:大模型智能体高频考点全解析(附分类指南和简历模板)

AI Agent 面试八股文100问:大模型智能体高频考点全解析(附分类指南和简历模板)

AI Agent 面试八股文100问:大模型智能体高频考点全解析(附分类指南和简历模板) 如果你对学成归来的简历没有概念,可以看看以下的模板先,毕竟先看清眼前的路,比奔跑更重要: 最终的AI Agent简历模板,点我跳转! 适用人群:LLM Agent、RAG、AutoGPT、LangChain、Function Calling 等方向的求职者与开发者 随着大模型技术的飞速演进,AI Agent(智能体) 已成为工业界和学术界共同关注的焦点。无论是 AutoGPT、LangChain 还是 LlamaIndex,背后都离不开对 Agent 架构、推理机制、工具调用等核心能力的深入理解。 本文系统整理了 AI Agent 方向的 100 道高频面试问题,覆盖 基础概念、架构设计、推理决策、工具调用、记忆管理、评估方法、安全对齐、

By Ne0inhk
人工智能、机器学习和深度学习,其实不是一回事

人工智能、机器学习和深度学习,其实不是一回事

一、人工智能、机器学习与深度学习的真正区别 在当今科技领域,我们经常听到人工智能、机器学习和深度学习这三个词。它们虽然相关,但含义不同。 1.1 人工智能 人工智能是计算机科学的一个分支,旨在研究如何合成与分析能够像人一样行动的计算主体。简单来说,AI 的目标是利用计算机来模拟甚至替代人类大脑的功能。 一个理想的 AI 系统通常具备以下特征:像人一样思考、像人一样行动、理性地思考与行动。 1.2 机器学习 机器学习是实现人工智能的一种途径。它的核心定义是:赋予计算机在没有被显式编程的情况下进行学习的能力。 与传统的基于规则的编程不同,机器学习不依赖程序员手写每一条逻辑指令,而是通过算法让机器从大量数据中寻找规律,从而对新的数据产生预测或判断。 1.3 深度学习 深度学习是机器学习的一种特殊方法,也称为深度神经网络。它受人类大脑结构的启发,通过设计多层的神经元网络结构,来模拟万事万物的特征表示。 1.4 三者之间的层级关系 厘清这三者的关系对于初学者至关重要。人工智能 AI是最宏大的概念,包含了所有让机器变聪明的技术。机器学习 ML是 AI

By Ne0inhk
AI 基建:我拿到邀请码了,evomap 初体验,说说感受!——最后面有我本地节点上传的执行全指引。

AI 基建:我拿到邀请码了,evomap 初体验,说说感受!——最后面有我本地节点上传的执行全指引。

task: 我拿到邀请码了,evomap 初体验,说说感受!——最后面有我本地节点上传的执行全指引。 一天在 evomap 赚了几千积分,详细看我后续文章! 背景 昨天吃了口热饭,抢到了邀请码。并且我本地已经构建了evomap 的全套,今天就想简单看看具体有哪些功能! 航海日志-实操 “一个 Agent 学会,百万 Agent 继承。”从字面意思理解,应该有以下几种方向: 1、我的 agent,可以流动给别人。 2、我的 agent,可以自己迭代。 3、我的 agent,可以被另外一个 agent 学习。 4、我的 agent,可以从 A 环境无损迁移到 B 环境。 ...... 按照首页的指引,我应该是完成了接入我本地。 接入你的

By Ne0inhk