目录一、为什么需要异常二、基本语法try / throw / catch三、throw 抛出什么四、catch 捕获按值 vs 按引用 vs 按指针按值捕获按引用捕获推荐按指针捕获结论永远用 const 异常类型 捕获异常。五、多个 catch 块六、标准异常类体系使用示例自定义异常类七、栈展开与资源管理八、异常安全级别九、常见错误1. 按值捕获导致对象切片2. 在构造函数中抛出异常但未处理好已分配的资源3. 析构函数中抛出异常4. 捕获所有异常但不重新抛出十、这一篇的收获一、为什么需要异常C 语言的错误处理方式错误码。cFILE* f fopen(test.txt, r); if (f NULL) { return -1; // 调用者需要检查 } // ... 每一层都要传递错误码问题错误码容易被忽略业务逻辑和错误处理混在一起构造函数无法返回错误码C 的异常机制错误在发生处抛出throw在合适的层级捕获catch中间的调用栈自动展开局部对象被正确析构二、基本语法try / throw / catchcpp#include iostream #include stdexcept using namespace std; double divide(double a, double b) { if (b 0) { throw runtime_error(除数不能为0); // 抛出异常 } return a / b; } int main() { double x, y; cout 请输入两个数: ; cin x y; try { double result divide(x, y); cout 结果: result endl; } catch (const runtime_error e) { // 捕获异常 cout 错误: e.what() endl; } cout 程序继续运行 endl; return 0; }流程divide中b 0→throw抛出runtime_error对象函数立即返回不再执行后续代码调用栈展开stack unwinding沿途局部对象被析构在main的catch块中被捕获捕获后继续执行catch后面的代码三、throw 抛出什么throw后面可以是任何类型的对象。cppthrow 42; // 抛出 int throw error; // 抛出 const char* throw string(error); // 抛出 string throw runtime_error(error); // 抛出标准异常对象 class MyError {}; throw MyError(); // 抛出自定义类对象最佳实践抛出标准异常类或自定义异常类继承自std::exception而不是内置类型。四、catch 捕获按值 vs 按引用 vs 按指针按值捕获cppcatch (runtime_error e) { // 拷贝一份异常对象 cout e.what() endl; }缺点发生拷贝如果异常对象很大有开销且可能发生对象切片如果捕获的是派生类对象。按引用捕获推荐cppcatch (const runtime_error e) { // 不拷贝保留多态 cout e.what() endl; }优点无拷贝保留派生类信息效率高。按指针捕获cppcatch (runtime_error* e) { delete e; // 需要手动管理内存 }问题谁new谁delete容易出错。不推荐。结论永远用const 异常类型捕获异常。五、多个 catch 块cpptry { // 可能抛出多种异常的代码 } catch (const runtime_error e) { cout 运行时错误: e.what() endl; } catch (const logic_error e) { cout 逻辑错误: e.what() endl; } catch (const exception e) { cout 标准异常: e.what() endl; } catch (...) { // 捕获任何异常万能捕获 cout 未知异常 endl; }匹配规则catch块按顺序匹配找到第一个能处理的类型后执行因此派生类在前基类在后否则派生类永远不会被匹配catch(...)必须放在最后cpp// ❌ 错误顺序基类在前 catch (const exception e) { ... } catch (const runtime_error e) { ... } // 永远不会进入 // ✅ 正确顺序派生类在前 catch (const runtime_error e) { ... } catch (const exception e) { ... }六、标准异常类体系cpp#include stdexcept // 标准异常类头文件 std::exception // 所有标准异常的基类 ├── std::logic_error // 逻辑错误程序可避免 │ ├── std::invalid_argument // 无效参数 │ ├── std::domain_error // 定义域错误 │ ├── std::out_of_range // 越界访问 │ └── std::length_error // 长度错误 └── std::runtime_error // 运行时错误不可预知 ├── std::range_error // 范围错误 ├── std::overflow_error // 上溢 └── std::underflow_error // 下溢使用示例cpp#include iostream #include stdexcept #include vector using namespace std; int getElement(const vectorint vec, size_t index) { if (index vec.size()) { throw out_of_range(索引越界: to_string(index)); } return vec[index]; } int main() { vectorint v {1, 2, 3}; try { cout getElement(v, 5) endl; } catch (const out_of_range e) { cout 越界异常: e.what() endl; } catch (const exception e) { cout 其他异常: e.what() endl; } return 0; }自定义异常类cppclass MyException : public exception { private: string msg; public: MyException(const string m) : msg(m) {} const char* what() const noexcept override { return msg.c_str(); } }; throw MyException(自定义错误信息);七、栈展开与资源管理当异常抛出时从throw到catch之间的所有局部对象会被自动析构栈展开。cppclass Resource { public: Resource() { cout 获取资源 endl; } ~Resource() { cout 释放资源 endl; } }; void riskyFunction() { Resource r; // 栈上对象 throw runtime_error(出错啦); // 抛出后r 会被析构 } int main() { try { riskyFunction(); } catch (const exception e) { cout 捕获: e.what() endl; } }输出text获取资源 释放资源 捕获: 出错啦关键这正是 RAII 的基础——资源由对象管理栈展开时会自动释放。八、异常安全级别级别含义不抛出保证函数保证不会抛出异常如析构函数强保证操作要么成功要么回滚到操作前状态无变化基本保证操作失败后程序处于有效状态无资源泄漏无保证操作失败后可能泄露资源或处于无效状态cpp// 强保证示例先构造临时对象再交换 void push_back(const T value) { Node* new_node new Node(value); // 可能抛异常 // 成功后才修改数据 new_node-next head; head new_node; }九、常见错误1. 按值捕获导致对象切片cppclass BaseError : public exception { virtual const char* what() const { return Base; } }; class DerivedError : public BaseError { const char* what() const override { return Derived; } }; try { throw DerivedError(); } catch (BaseError e) { // ❌ 切片丢失了派生类信息 cout e.what() endl; // 输出 Base不是 Derived }2. 在构造函数中抛出异常但未处理好已分配的资源cppclass MyClass { int* p; int* q; public: MyClass() : p(new int[100]), q(new int[100]) { // 如果 new q 抛出异常p 不会自动释放 → 内存泄漏 } };解决方案用智能指针或 try-catch 处理。3. 析构函数中抛出异常cpp~MyClass() { throw runtime_error(); // ❌ 危险如果栈展开时已有异常程序崩溃 }析构函数必须保证不抛出异常用noexcept或 catch 所有异常。4. 捕获所有异常但不重新抛出cppcatch (...) { // 吞掉所有异常不处理也不重新抛出 // 导致上层不知道发生了错误 }十、这一篇的收获你现在应该理解基本流程try块中throw异常catch捕获处理throw任何类型但推荐从std::exception派生捕获方式用const ExceptionType按引用避免按值和按指针多个 catch派生类在前基类在后catch(...)放最后栈展开异常抛出后局部对象自动析构RAII 的关键标准异常类std::exception基类logic_error、runtime_error两个分支 小作业写一个BankAccount类withdraw函数在余额不足时抛出insufficient_funds异常自定义。写测试代码捕获并处理这个异常。下一篇预告第34篇《栈展开Stack Unwinding与异常安全级别》——深入栈展开的细节析构函数抛异常的后果异常安全的三种保证级别。理解这些才能写出真正健壮的异常处理代码。