try { } catch(…) { }
C++异常处理完全指南
编程小白也能理解的异常处理知识汇总,包含详细解释、代码示例和最佳实践
❗ 什么是异常处理?
异常处理是C++中处理程序运行时错误的一种机制。想象一下你正在开车,突然前方出现障碍物 – 异常处理就像是你的应急系统,让你能够安全地处理突发情况,而不是直接让程序”撞车”崩溃。
在程序中,异常可能由多种原因引起:内存不足、文件不存在、除以零、无效输入等。异常处理机制允许我们检测这些错误并优雅地处理它们。
为什么需要异常处理?
- 防止程序意外崩溃
- 将错误处理代码与正常逻辑分离
- 提供统一的错误处理机制
- 使代码更清晰、更易维护
🔑 异常处理关键字
C++异常处理基于三个核心关键字:
try {
// 尝试执行可能出错的代码
}
catch (ExceptionType e) {
// 处理特定类型的异常
}
throw exception_object; // 抛出异常
关键字解释:
try:包裹可能抛出异常的代码块,相当于”尝试执行这段代码”。
catch:捕获并处理特定类型的异常,相当于”如果发生这种错误,就这样处理”。
throw:主动抛出一个异常,相当于”报告这里出错了”。
注意: 每个try块后面必须跟着一个或多个catch块,否则编译器会报错。
🚀 基本使用流程
让我们通过一个简单的例子理解异常处理的基本流程:
#include <iostream>
using namespace std;
int main() {
try {
int age = -5;
// 如果年龄无效,抛出异常
if (age < 0) {
throw “年龄不能为负数!”;
}
cout << “年龄是:” << age << endl;
}
catch (const char* errorMsg) {
// 捕获字符串类型的异常
cerr << “错误:” << errorMsg << endl;
}
return 0;
}
在这个例子中:
- 我们在try块中检查年龄是否小于0
- 如果小于0,使用throw抛出一个字符串异常
- catch块捕获这个异常并打印错误信息
🎯 多个catch块
一个try块后面可以跟多个catch块,用于捕获不同类型的异常:
try {
// 可能抛出多种异常的代码
throw “发生了一个错误”; // 字符串异常
// 或者:throw 404; // 整数异常
// 或者:throw runtime_error(“运行时错误”);
}
catch (const char* msg) {
cout << “字符串异常:” << msg << endl;
}
catch (int code) {
cout << “错误代码:” << code << endl;
}
catch (const exception& e) {
cout << “标准异常:” << e.what() << endl;
}
catch (…) {
cout << “未知类型的异常” << endl;
}
重要提示:
- catch块的顺序很重要 – 异常会被第一个匹配类型的catch块捕获
- catch(…) 是”通配符”,可以捕获所有类型的异常,通常放在最后
- 尽量从具体到一般排列catch块
📦 标准异常类
C++标准库提供了许多预定义的异常类,都继承自std::exception:
#include <stdexcept> // 包含标准异常
try {
// 尝试打开文件
ifstream file(“nonexistent.txt”);
if (!file) {
throw runtime_error(“文件打开失败!”);
}
// 尝试访问无效索引
vector<int> nums = {1, 2, 3};
if (nums.size() <= 5) {
throw out_of_range(“索引超出范围!”);
}
}
catch (const runtime_error& e) {
cerr << “运行时错误:” << e.what() << endl;
}
catch (const out_of_range& e) {
cerr << “范围错误:” << e.what() << endl;
}
catch (const exception& e) {
cerr << “标准异常:” << e.what() << endl;
}
常用标准异常:
- logic_error:程序逻辑错误
- runtime_error:运行时错误
- invalid_argument:无效参数
- out_of_range:超出有效范围
- bad_alloc:内存分配失败
✨ 自定义异常
当标准异常不能满足需求时,可以创建自己的异常类:
// 自定义异常类
class MyException : public exception {
private:
string message;
public:
MyException(const string& msg) : message(msg) {}
virtual const char* what() const noexcept override {
return message.c_str();
}
};
void processPayment(double amount) {
if (amount <= 0) {
throw MyException(“支付金额必须大于0”);
}
// 处理支付…
}
int main() {
try {
processPayment(-50.0);
}
catch (const MyException& e) {
cerr << “自定义异常: “ << e.what() << endl;
}
return 0;
}
创建自定义异常的好处:
- 可以包含特定于应用的错误信息
- 可以添加额外的错误处理逻辑
- 使错误分类更清晰
- 便于在大型项目中管理错误
🏆 异常处理最佳实践
✅ 应该这样做
- 在构造函数和析构函数中谨慎使用异常
- 按引用捕获异常(catch (const exception& e))
- 从std::exception派生自定义异常
- 在catch块中清理资源或使用RAII
- 为可能失败的操作提供异常安全保证
- 在文档中记录函数可能抛出的异常
❌ 避免这样做
- 不要用异常处理正常的控制流程
- 避免抛出原始指针或容易导致内存泄漏的类型
- 不要在析构函数中抛出异常
- 不要忽略捕获的异常(空的catch块)
- 避免过度使用异常影响性能
- 不要用异常替代参数检查
RAII原则: 资源获取即初始化(Resource Acquisition Is Initialization)是C++管理资源的关键原则。通过对象生命周期管理资源,确保即使发生异常,资源也能被正确释放。
⚠️ 常见错误
1. 未捕获的异常
如果抛出的异常没有被任何catch块捕获,程序会调用std::terminate()终止。
2. 异常屏蔽
当在catch块中抛出新的异常时,原始异常会被丢弃:
try {
throw “原始错误”;
}
catch (…) {
throw “新错误”; // 原始异常丢失
}
3. 切片问题
按值捕获派生类异常会导致对象切片:
class MyException : public exception { /*…*/ };
try {
throw MyException();
}
catch (exception e) { // 按值捕获 – 错误!
// 这里会发生对象切片,丢失派生类信息
}
// 正确做法:catch (const MyException& e)
🎓 总结
异常处理是C++中强大的错误处理机制,核心要点总结:
- try-catch-throw 是异常处理的基础结构
- 优先使用标准异常,必要时创建自定义异常
- 按引用捕获异常,避免对象切片
- 遵循RAII原则管理资源
- 为代码提供异常安全保证
- 避免在析构函数中抛出异常
- 不要滥用异常 – 它们用于处理异常情况,而非控制流程
合理使用异常处理可以使你的程序:
- 更健壮 – 能够从错误中恢复或优雅退出
- 更清晰 – 分离正常逻辑和错误处理
- 更安全 – 避免资源泄漏和未定义行为
“好的异常处理不是避免错误,而是优雅地处理错误。”