当前位置: 首页 > news >正文

【C++】零基础入门 · 第 13 节:异常处理(try、catch、throw)

在前面 12 节中,我们学习了变量、函数、类、指针、文件操作、模板和 STL。这些都是「怎么写代码」的知识。今天,我们来学习一个同样重要但经常被初学者忽略的主题——异常处理。它解决的是「代码出错了怎么办」的问题。

1. 为什么需要异常处理?

程序运行时,总会遇到各种意外情况:文件找不到、内存不够用、数组越界、除以零……这些情况如果不处理,程序就会直接崩溃。

你可能会说:用if判断一下不就行了?确实可以,但if有一个局限——它只能处理「当前函数」里的错误。如果错误发生在深层嵌套的函数调用中,你需要一层一层地把错误信息往上传,非常麻烦。

异常处理提供了一种更优雅的方式:在出错的地方「抛出」异常,在合适的地方「捕获」它。错误信息会自动沿着调用链向上传递,直到被处理。

2. 基本语法:try、catch、throw

C++ 的异常处理由三个关键字组成:

  • try:包裹可能出错的代码
  • throw:当检测到错误时,「抛出」一个异常
  • catch:「捕获」异常并处理

2.1 最简单的例子

#include<iostream>usingnamespacestd;intdivide(inta,intb){if(b==0){throw"除数不能为零!";// 抛出异常}returna/b;}intmain(){try{cout<<divide(10,2)<<endl;// 正常执行cout<<divide(10,0)<<endl;// 会触发异常cout<<"这行不会被执行"<<endl;// 异常后跳过}catch(constchar*msg){cout<<"捕获到异常:"<<msg<<endl;}cout<<"程序继续正常运行"<<endl;return0;}

输出:

5 捕获到异常:除数不能为零! 程序继续正常运行

几个关键点:

  • throw抛出异常后,try块中剩余的代码不会被执行,程序直接跳转到对应的catch块。
  • catch处理完异常后,程序继续往下执行,不会崩溃。
  • throw后面可以跟任何类型的值:字符串、整数、自定义对象等。

2.2 多个 catch 块

你可以针对不同类型的异常写不同的处理逻辑:

#include<iostream>usingnamespacestd;voidprocess(inttype){if(type==1)throw42;if(type==2)throw"出错了";if(type==3)throw3.14;}intmain(){for(inti=1;i<=3;i++){try{process(i);}catch(inte){cout<<"整数异常:"<<e<<endl;}catch(constchar*e){cout<<"字符串异常:"<<e<<endl;}catch(doublee){cout<<"浮点异常:"<<e<<endl;}}return0;}

输出:

整数异常:42 字符串异常:出错了 浮点异常:3.14

catch块会按照书写的顺序匹配异常类型,只会执行第一个匹配的catch

3. 标准异常类

在实际开发中,我们通常不会throw裸字符串或整数,而是使用 C++ 标准库提供的异常类。它们都在<stdexcept>头文件中。

3.1 常见的标准异常

异常类用途
std::runtime_error运行时错误(如文件不存在)
std::invalid_argument无效参数
std::out_of_range越界访问
std::overflow_error算术溢出
std::logic_error逻辑错误

3.2 使用标准异常

#include<iostream>#include<stdexcept>#include<vector>usingnamespacestd;intgetElement(constvector<int>&vec,intindex){if(index<0||index>=vec.size()){throwout_of_range("下标 "+to_string(index)+" 越界!");}returnvec[index];}intmain(){vector<int>nums={10,20,30};try{cout<<getElement(nums,1)<<endl;// 20cout<<getElement(nums,5)<<endl;// 越界!}catch(constout_of_range&e){cout<<"异常:"<<e.what()<<endl;}return0;}

输出:

20 异常:下标 5 越界!

标准异常类都有一个what()方法,返回错误描述信息。用const 引用const exception&)来捕获是一个好习惯,可以避免不必要的拷贝。

3.3 统一捕获所有异常

如果不确定会抛出什么类型的异常,可以用catch (...)捕获所有异常:

try{// 可能出错的代码}catch(constexception&e){cout<<"标准异常:"<<e.what()<<endl;}catch(...){cout<<"未知异常"<<endl;}

建议把catch (const exception&)放在前面,catch (...)放在最后作为兜底。

4. 自定义异常类

当标准异常类不能满足需求时,你可以定义自己的异常类。通常的做法是继承std::exception

#include<iostream>#include<exception>#include<string>usingnamespacestd;classMyException:publicexception{private:string message;public:MyException(conststring&msg):message(msg){}constchar*what()constnoexceptoverride{returnmessage.c_str();}};voidriskyOperation(){throwMyException("自定义异常:操作失败!");}intmain(){try{riskyOperation();}catch(constMyException&e){cout<<e.what()<<endl;}return0;}

输出:

自定义异常:操作失败!

自定义异常的好处是你可以携带更多的上下文信息(比如错误代码、发生位置等),方便调试和日志记录。

5. 异常的传播机制

理解异常是怎么「传递」的,对于正确使用异常处理至关重要。

5.1 调用链中的异常传播

#include<iostream>#include<stdexcept>usingnamespacestd;voidfuncC(){throwruntime_error("funcC 中出错了");}voidfuncB(){funcC();// 不处理,继续向上传}voidfuncA(){try{funcB();// 不处理,继续向上传}catch(construntime_error&e){cout<<"funcA 捕获:"<<e.what()<<endl;}}intmain(){funcA();return0;}

输出:

funcA 捕获:funcC 中出错了

异常从funcC抛出,经过funcB(没有catch),最终在funcA中被捕获。异常会沿着调用链自动向上传递,直到找到匹配的catch块。

5.2 没有被捕获的异常

如果异常一路传到main函数都没有被捕获,程序会调用std::terminate()直接终止,并可能弹出系统级的错误提示。所以一定要确保所有可能抛出异常的地方都有对应的catch处理。

6. RAII:C++ 资源管理的黄金法则

异常处理有一个容易被忽视的问题:资源泄漏。如果在try块中申请了内存或打开了文件,异常发生后这些资源可能不会被释放。

C++ 的解决方案是RAII(Resource Acquisition Is Initialization)——把资源的生命周期绑定到对象的生命周期上。当对象离开作用域时,析构函数会自动释放资源,即使是因为异常导致的离开。

#include<iostream>#include<fstream>#include<stdexcept>usingnamespacestd;voidprocessFile(conststring&filename){ifstreamfile(filename);// RAII:文件在构造时打开if(!file.is_open()){throwruntime_error("无法打开文件:"+filename);}string line;while(getline(file,line)){cout<<line<<endl;}// 函数结束时,file 的析构函数自动关闭文件// 即使中途抛出异常,析构函数也会被调用}intmain(){try{processFile("test.txt");}catch(construntime_error&e){cout<<"错误:"<<e.what()<<endl;}return0;}

STL 容器(vectorstring等)和智能指针(unique_ptrshared_ptr)都遵循 RAII 原则。在 C++ 中,优先使用 RAII 管理资源,而不是手动new/deleteopen/close

7. 使用异常的最佳实践

7.1 什么时候该用异常

  • 真正的异常情况:文件不存在、网络断开、内存不足等不可预期的错误
  • 不适合正常流程控制:不要用异常来代替if-else判断

7.2 异常安全的三个级别

级别保证说明
基本保证程序不会泄漏资源最低要求
强保证操作要么完全成功,要么回到操作前的状态事务性
不抛出保证函数保证不抛出异常noexcept声明

7.3 使用noexcept标记不抛出异常的函数

如果你确定某个函数不会抛出异常,可以用noexcept声明,帮助编译器做更好的优化:

intadd(inta,intb)noexcept{returna+b;}

析构函数默认就是noexcept的,你不应该在析构函数中抛出异常。

8. 总结

这一节我们学习了 C++ 的异常处理机制:

  • try包裹可能出错的代码,throw抛出异常,catch捕获并处理。
  • 标准异常类(runtime_errorout_of_range等)提供了统一的错误描述接口。
  • 异常沿着调用链自动向上传递,直到被catch捕获。
  • RAII 是 C++ 资源管理的核心原则,确保异常发生时资源不会泄漏。
  • 异常只用于处理真正的异常情况,不要用来做流程控制。

异常处理是编写健壮程序的重要保障。掌握了它,你的代码就能在面对意外情况时「优雅地失败」,而不是直接崩溃。下一节我们将继续探索 C++ 的更多高级特性。加油!

http://www.rkmt.cn/news/1436351.html

相关文章:

  • 加油
  • Blender建筑建模革命:用building_tools插件告别繁琐手动建模
  • 5分钟快速上手:跨平台资源下载工具res-downloader终极指南
  • 2026芜湖奢侈品名包名表回收靠谱商家测评:口碑老店 - 鸿运名品
  • TypeError: Autotuner.__init__() takes from 6 to 9 positional arguments but 14 were given
  • Windows端口被占?除了netstat,你还可以试试这些更强大的工具(附PowerShell终极方案)
  • 基于Arduino与NRF24L01的乐高坦克遥控系统全解析
  • 2026西安黄金回收上门服务榜单丨告别出门排队 当面验金秒到账全指南 - 西安闲转记
  • 6款主流降AIGC网站 降痕效果拉满
  • AI Agent Harness Engineering 在制造:巡检、质检与工艺优化
  • 个人助手Agent:全场景任务自动执行
  • 告别卡顿!5分钟用GHelper释放华硕笔记本全部潜力
  • 微信聊天记录永久保存与智能分析的终极指南:WeChatMsg完整解决方案
  • 如何构建企业级游戏串流服务器:Sunshine高级部署完全指南
  • 抖音下载器终极指南:3分钟掌握批量下载无水印视频的完整方法
  • PKSM终极指南:一站式管理所有世代宝可梦存档的免费方案
  • Arduino定时控制实战:从继电器驱动到220V设备安全控制
  • 郑州市 高新区 上门安装、维修维保|维小达 开关插座/灯具/门窗/柜体/锁具/卫浴/龙头/洗菜盆/踢脚线一站式家装安装服务 - 维小达科技
  • Julia深度学习实战:从图像分类到GAN生成的五大案例解析
  • 终极QQ音乐解密指南:qmcdump让加密音频自由播放
  • 3步轻松获取国家中小学智慧教育平台电子课本:智能解析工具全攻略
  • 别再乱用-divide_by和-multiply_by了!手把手教你用create_generated_clock的-edge_shift和-duty_cycle调出任意波形
  • Gemini 2.5安全增强模块首次曝光:零日提示注入防御机制如何通过NIST AI RMF三级认证?
  • Arduino星形投影夜灯制作:从PWM调光到电位器控制的完整实践
  • 3天掌握ODrive:开源电机控制器的高性能控制算法实战
  • RimSort终极指南:如何用智能模组管理器告别《RimWorld》加载冲突
  • 解决Keil MDK中RTX5调试信息丢失问题
  • Obsidian PDF++:3个革命性功能重新定义你的PDF标注工具
  • DsHidMini深度探索:Windows平台PS3手柄虚拟HID驱动实战解析
  • 深度学习表征学习(一)—— 对比学习与 CLIP(五十四)