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

C++中的异常处理机制(try、catch、throw)

异常处理,它解决的是「代码出错了怎么办」的问题。

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

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

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

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

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

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

  • try:包裹可能出错的代码
  • throw:当检测到错误时,「抛出」一个异常
  • catch:「捕获」异常并处理
2.1 最简单的例子
#include <iostream> using namespace std; int divide(int a, int b) { if (b == 0) { throw "除数不能为零!"; // 抛出异常 } return a / b; } int main() { try { cout << divide(10, 2) << endl; // 正常执行 cout << divide(10, 0) << endl; // 会触发异常 cout << "这行不会被执行" << endl; // 异常后跳过 } catch (const char* msg) { cout << "捕获到异常:" << msg << endl; } cout << "程序继续正常运行" << endl; return 0; }

输出:

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

几个关键点:

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

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

#include <iostream> using namespace std; void process(int type) { if (type == 1) throw 42; if (type == 2) throw "出错了"; if (type == 3) throw 3.14; } int main() { for (int i = 1; i <= 3; i++) { try { process(i); } catch (int e) { cout << "整数异常:" << e << endl; } catch (const char* e) { cout << "字符串异常:" << e << endl; } catch (double e) { cout << "浮点异常:" << e << endl; } } return 0; }

输出:

整数异常: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> using namespace std; int getElement(const vector<int>& vec, int index) { if (index < 0 || index >= vec.size()) { throw out_of_range("下标 " + to_string(index) + " 越界!"); } return vec[index]; } int main() { vector<int> nums = {10, 20, 30}; try { cout << getElement(nums, 1) << endl; // 20 cout << getElement(nums, 5) << endl; // 越界! } catch (const out_of_range& e) { cout << "异常:" << e.what() << endl; } return 0; }

输出:

20 异常:下标 5 越界!

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

3.3 统一捕获所有异常

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

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

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

4. 自定义异常类

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

#include <iostream> #include <exception> #include <string> using namespace std; class MyException : public exception { private: string message; public: MyException(const string& msg) : message(msg) {} const char* what() const noexcept override { return message.c_str(); } }; void riskyOperation() { throw MyException("自定义异常:操作失败!"); } int main() { try { riskyOperation(); } catch (const MyException& e) { cout << e.what() << endl; } return 0; }

输出:

自定义异常:操作失败!

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

5. 异常的传播机制

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

5.1 调用链中的异常传播
#include <iostream> #include <stdexcept> using namespace std; void funcC() { throw runtime_error("funcC 中出错了"); } void funcB() { funcC(); // 不处理,继续向上传 } void funcA() { try { funcB(); // 不处理,继续向上传 } catch (const runtime_error& e) { cout << "funcA 捕获:" << e.what() << endl; } } int main() { funcA(); return 0; }

输出:

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> using namespace std; void processFile(const string& filename) { ifstream file(filename); // RAII:文件在构造时打开 if (!file.is_open()) { throw runtime_error("无法打开文件:" + filename); } string line; while (getline(file, line)) { cout << line << endl; } // 函数结束时,file 的析构函数自动关闭文件 // 即使中途抛出异常,析构函数也会被调用 } int main() { try { processFile("test.txt"); } catch (const runtime_error& e) { cout << "错误:" << e.what() << endl; } return 0; }

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

7. 使用异常的最佳实践

7.1 什么时候该用异常
  • 真正的异常情况:文件不存在、网络断开、内存不足等不可预期的错误
  • 不适合正常流程控制:不要用异常来代替if-else判断
7.2 异常安全的三个级别
级别保证说明
基本保证程序不会泄漏资源最低要求
强保证操作要么完全成功,要么回到操作前的状态事务性
不抛出保证函数保证不抛出异常noexcept声明
7.3 使用noexcept标记不抛出异常的函数

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

int add(int a, int b) noexcept { return a + b; }

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

8. 总结

C++ 的异常处理机制:

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

异常处理是编写健壮程序的重要保障。

http://www.cnnetsun.cn/news/3203084.html

相关文章:

  • RimWorld终极性能优化指南:Performance Fish完整配置教程
  • 图片处理神器,太牛批了
  • 如何快速掌握微信小程序图表开发:wx-charts完整实战指南
  • Performance Fish:RimWorld终极性能优化模组完全指南
  • StreamCap:一站式自动化直播录制解决方案,高效捕获40+平台精彩内容
  • iOS越狱终极指南:5步解锁iPhone隐藏功能的完整教程
  • 代谢组学分析环境搭建指南:掌握MetaboAnalystR的核心配置策略
  • 近期这4个技术小动态,可能悄悄影响你的企业数字化
  • STM32嵌入式开发实战:从零到机器人控制的20个例程完整指南
  • MediaPipe TouchDesigner插件实战:SpoutCam与Syphon虚拟摄像头深度集成
  • U盘数据恢复完全指南
  • AI工作流自动化的触发与回退设计:人机协作的分级策略、审核节点与状态机建模
  • 突破游戏修改器限制:Wand-Enhancer让你的WeMod体验全面升级
  • Python基础知识 - 费曼学习法讲解
  • 价值工程 VE 3 方案比选:功能系数法在 75k 预算设备采购中的决策应用
  • AI 性能建议评估:能优化,不代表值得改
  • Awoo Installer:破解Nintendo Switch游戏安装瓶颈的4种部署策略
  • XOutput:让老旧游戏手柄重获新生的免费兼容神器
  • 买银行理财别只盯收益率!90%的人都算错了,真实到手收益这么算
  • PyTorch 实现 U-Net 上采样:转置卷积层参数详解与3个实战调优技巧
  • 2026年国家高新技术企业申报筹划建议
  • 暗黑3按键助手D3KeyHelper:解放双手的智能游戏伴侣
  • Python 设备健康度评估算法:3种权重计算与5级健康状态划分实战
  • 127.TwinCAT/CODESYS 通用!四相位交通灯控制系统标准化源码
  • 2026年,AI如何重构数据科学工作流
  • phpStudy供应链攻击深度复盘:从后门分析到环境安全加固实战
  • 如何安全备份QQ空间历史数据:GetQzonehistory完整指南
  • 为什么 AI 会一本正经地胡说八道?
  • Claude Code 用户狂喜:不用换工具,直接白嫖OpenAI最强代码模型?这波羊毛必须薅
  • 3分钟彻底清理Windows右键菜单:ContextMenuManager完全使用指南