【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

TRAE vs Qoder vs Cursor vs GitHub Copilot:谁才是真正的“AI 工程师”?

引言:工具选择 = 成本 + 效率 + 风险 的综合权衡 2026 年,AI 编程工具已从“玩具”走向“生产主力”。但面对 TRAE、Qoder、Cursor、GitHub Copilot 等选项,开发者不仅要问: * 它能写 Rust 吗?支持中文需求吗? * 更要问:一个月多少钱?团队用得起吗?代码安全有保障吗? 本文将从 五大核心维度 深度剖析四大主流 AI IDE: 1. 核心理念与自主性 2. 多语言与跨生态支持能力 3. 工程化与交付闭环能力 4. 中文本地化与业务适配 5. 收费模式、定价策略与企业成本 帮你做出技术可行、经济合理、风险可控的决策。 一、核心理念:

By Ne0inhk

Cogito-v1-preview-llama-3B应用场景:工业设备故障日志分析与维修建议生成

Cogito-v1-preview-llama-3B应用场景:工业设备故障日志分析与维修建议生成 1. 工业设备运维的痛点与机遇 在工业制造领域,设备故障是每个工厂管理者最头疼的问题。一台关键设备的突然停机,可能导致整条生产线瘫痪,每小时损失数以万计。传统的故障排查方式往往依赖经验丰富的老师傅,通过查看设备日志、听声音、摸温度来判断问题所在。 但这种传统方式面临三大挑战:人才断层(老师傅越来越少)、响应延迟(排查需要时间)、经验局限(个人经验有限)。现在,借助Cogito-v1-preview-llama-3B这样的智能模型,我们可以让设备运维进入智能化时代。 Cogito v1预览版是Deep Cogito推出的混合推理模型,在标准基准测试中超越了同等规模的其他开源模型。它不仅能直接回答问题,还能进行自我反思和推理,特别适合处理工业设备故障日志这类复杂的技术问题。 2. Cogito模型的独特优势 2.1 混合推理能力 Cogito模型最大的特点是混合推理能力。当面对设备故障日志时,它不会简单地匹配关键词,而是会: * 先理解:分析日志中的技术术语和上下文关系

By Ne0inhk
GitHub 热榜项目 - 日榜(2026-1-10)

GitHub 热榜项目 - 日榜(2026-1-10)

GitHub 热榜项目 - 日榜(2026-1-10) 生成于:2026-1-10 统计摘要 共发现热门项目: 12 个 榜单类型:日榜 本期热点趋势总结 本期GitHub热榜显示AI智能体开发工具正席卷开发者社区,Claude Code、opencode等项目通过自然语言交互极大提升编码效率,Chrome DevTools MCP和UI-TARS-desktop则推动多模态智能体与开发工具深度集成,同时TailwindCSS持续领跑前端工具链,NetBird提供现代化安全网络方案,反映出开发者正积极采用AI助手优化工作流,并重点关注智能体工具链集成、实用型开发工具及基础设施安全三大趋势,这些高质量开源方案切实提升了开发体验与工程效率。 1. ChromeDevTools/chrome-devtools-mcp * 🏷️ 项目名称:ChromeDevTools/chrome-devtools-mcp * 🔗 项目地址: https://github.com/ChromeDevTools/chrome-devtools-mcp * ⭐ 当前 Star 数:

By Ne0inhk
AtomGit首发模型深度评测:多模态能力与场景适配性实战分析

AtomGit首发模型深度评测:多模态能力与场景适配性实战分析

文章目录 * 每日一句正能量 * 前言 * 一、评测背景与方法论 * 1.1 评测动机 * 1.2 评测环境 * 1.3 评测框架 * 二、核心能力深度测试 * 2.1 文本生成质量评测 * 2.2 代码能力实测 * 2.3 逻辑推理能力 * 三、性能表现实测数据 * 3.1 响应延迟测试 * 3.2 长上下文处理能力 * 3.3 输出稳定性 * 四、场景适配性分析 * 4.1 中文场景优化 * 4.2 垂直领域表现 * 4.3 API易用性 * 五、综合评估与优化建议 * 5.

By Ne0inhk