在前面 12 节中我们学习了变量、函数、类、指针、文件操作、模板和 STL。这些都是「怎么写代码」的知识。今天我们来学习一个同样重要但经常被初学者忽略的主题——异常处理。它解决的是「代码出错了怎么办」的问题。1. 为什么需要异常处理程序运行时总会遇到各种意外情况文件找不到、内存不够用、数组越界、除以零……这些情况如果不处理程序就会直接崩溃。你可能会说用if判断一下不就行了确实可以但if有一个局限——它只能处理「当前函数」里的错误。如果错误发生在深层嵌套的函数调用中你需要一层一层地把错误信息往上传非常麻烦。异常处理提供了一种更优雅的方式在出错的地方「抛出」异常在合适的地方「捕获」它。错误信息会自动沿着调用链向上传递直到被处理。2. 基本语法try、catch、throwC 的异常处理由三个关键字组成try包裹可能出错的代码throw当检测到错误时「抛出」一个异常catch「捕获」异常并处理2.1 最简单的例子#includeiostreamusingnamespacestd;intdivide(inta,intb){if(b0){throw除数不能为零;// 抛出异常}returna/b;}intmain(){try{coutdivide(10,2)endl;// 正常执行coutdivide(10,0)endl;// 会触发异常cout这行不会被执行endl;// 异常后跳过}catch(constchar*msg){cout捕获到异常msgendl;}cout程序继续正常运行endl;return0;}输出5 捕获到异常除数不能为零 程序继续正常运行几个关键点throw抛出异常后try块中剩余的代码不会被执行程序直接跳转到对应的catch块。catch处理完异常后程序继续往下执行不会崩溃。throw后面可以跟任何类型的值字符串、整数、自定义对象等。2.2 多个 catch 块你可以针对不同类型的异常写不同的处理逻辑#includeiostreamusingnamespacestd;voidprocess(inttype){if(type1)throw42;if(type2)throw出错了;if(type3)throw3.14;}intmain(){for(inti1;i3;i){try{process(i);}catch(inte){cout整数异常eendl;}catch(constchar*e){cout字符串异常eendl;}catch(doublee){cout浮点异常eendl;}}return0;}输出整数异常42 字符串异常出错了 浮点异常3.14catch块会按照书写的顺序匹配异常类型只会执行第一个匹配的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 使用标准异常#includeiostream#includestdexcept#includevectorusingnamespacestd;intgetElement(constvectorintvec,intindex){if(index0||indexvec.size()){throwout_of_range(下标 to_string(index) 越界);}returnvec[index];}intmain(){vectorintnums{10,20,30};try{coutgetElement(nums,1)endl;// 20coutgetElement(nums,5)endl;// 越界}catch(constout_of_rangee){cout异常e.what()endl;}return0;}输出20 异常下标 5 越界标准异常类都有一个what()方法返回错误描述信息。用const 引用const exception来捕获是一个好习惯可以避免不必要的拷贝。3.3 统一捕获所有异常如果不确定会抛出什么类型的异常可以用catch (...)捕获所有异常try{// 可能出错的代码}catch(constexceptione){cout标准异常e.what()endl;}catch(...){cout未知异常endl;}建议把catch (const exception)放在前面catch (...)放在最后作为兜底。4. 自定义异常类当标准异常类不能满足需求时你可以定义自己的异常类。通常的做法是继承std::exception#includeiostream#includeexception#includestringusingnamespacestd;classMyException:publicexception{private:string message;public:MyException(conststringmsg):message(msg){}constchar*what()constnoexceptoverride{returnmessage.c_str();}};voidriskyOperation(){throwMyException(自定义异常操作失败);}intmain(){try{riskyOperation();}catch(constMyExceptione){coute.what()endl;}return0;}输出自定义异常操作失败自定义异常的好处是你可以携带更多的上下文信息比如错误代码、发生位置等方便调试和日志记录。5. 异常的传播机制理解异常是怎么「传递」的对于正确使用异常处理至关重要。5.1 调用链中的异常传播#includeiostream#includestdexceptusingnamespacestd;voidfuncC(){throwruntime_error(funcC 中出错了);}voidfuncB(){funcC();// 不处理继续向上传}voidfuncA(){try{funcB();// 不处理继续向上传}catch(construntime_errore){coutfuncA 捕获e.what()endl;}}intmain(){funcA();return0;}输出funcA 捕获funcC 中出错了异常从funcC抛出经过funcB没有catch最终在funcA中被捕获。异常会沿着调用链自动向上传递直到找到匹配的catch块。5.2 没有被捕获的异常如果异常一路传到main函数都没有被捕获程序会调用std::terminate()直接终止并可能弹出系统级的错误提示。所以一定要确保所有可能抛出异常的地方都有对应的catch处理。6. RAIIC 资源管理的黄金法则异常处理有一个容易被忽视的问题资源泄漏。如果在try块中申请了内存或打开了文件异常发生后这些资源可能不会被释放。C 的解决方案是RAIIResource Acquisition Is Initialization——把资源的生命周期绑定到对象的生命周期上。当对象离开作用域时析构函数会自动释放资源即使是因为异常导致的离开。#includeiostream#includefstream#includestdexceptusingnamespacestd;voidprocessFile(conststringfilename){ifstreamfile(filename);// RAII文件在构造时打开if(!file.is_open()){throwruntime_error(无法打开文件filename);}string line;while(getline(file,line)){coutlineendl;}// 函数结束时file 的析构函数自动关闭文件// 即使中途抛出异常析构函数也会被调用}intmain(){try{processFile(test.txt);}catch(construntime_errore){cout错误e.what()endl;}return0;}STL 容器vector、string等和智能指针unique_ptr、shared_ptr都遵循 RAII 原则。在 C 中优先使用 RAII 管理资源而不是手动new/delete或open/close。7. 使用异常的最佳实践7.1 什么时候该用异常真正的异常情况文件不存在、网络断开、内存不足等不可预期的错误不适合正常流程控制不要用异常来代替if-else判断7.2 异常安全的三个级别级别保证说明基本保证程序不会泄漏资源最低要求强保证操作要么完全成功要么回到操作前的状态事务性不抛出保证函数保证不抛出异常用noexcept声明7.3 使用noexcept标记不抛出异常的函数如果你确定某个函数不会抛出异常可以用noexcept声明帮助编译器做更好的优化intadd(inta,intb)noexcept{returnab;}析构函数默认就是noexcept的你不应该在析构函数中抛出异常。8. 总结这一节我们学习了 C 的异常处理机制try包裹可能出错的代码throw抛出异常catch捕获并处理。标准异常类runtime_error、out_of_range等提供了统一的错误描述接口。异常沿着调用链自动向上传递直到被catch捕获。RAII 是 C 资源管理的核心原则确保异常发生时资源不会泄漏。异常只用于处理真正的异常情况不要用来做流程控制。异常处理是编写健壮程序的重要保障。掌握了它你的代码就能在面对意外情况时「优雅地失败」而不是直接崩溃。下一节我们将继续探索 C 的更多高级特性。加油
【C++】零基础入门 · 第 13 节:异常处理(try、catch、throw)
发布时间:2026/5/31 20:43:02
在前面 12 节中我们学习了变量、函数、类、指针、文件操作、模板和 STL。这些都是「怎么写代码」的知识。今天我们来学习一个同样重要但经常被初学者忽略的主题——异常处理。它解决的是「代码出错了怎么办」的问题。1. 为什么需要异常处理程序运行时总会遇到各种意外情况文件找不到、内存不够用、数组越界、除以零……这些情况如果不处理程序就会直接崩溃。你可能会说用if判断一下不就行了确实可以但if有一个局限——它只能处理「当前函数」里的错误。如果错误发生在深层嵌套的函数调用中你需要一层一层地把错误信息往上传非常麻烦。异常处理提供了一种更优雅的方式在出错的地方「抛出」异常在合适的地方「捕获」它。错误信息会自动沿着调用链向上传递直到被处理。2. 基本语法try、catch、throwC 的异常处理由三个关键字组成try包裹可能出错的代码throw当检测到错误时「抛出」一个异常catch「捕获」异常并处理2.1 最简单的例子#includeiostreamusingnamespacestd;intdivide(inta,intb){if(b0){throw除数不能为零;// 抛出异常}returna/b;}intmain(){try{coutdivide(10,2)endl;// 正常执行coutdivide(10,0)endl;// 会触发异常cout这行不会被执行endl;// 异常后跳过}catch(constchar*msg){cout捕获到异常msgendl;}cout程序继续正常运行endl;return0;}输出5 捕获到异常除数不能为零 程序继续正常运行几个关键点throw抛出异常后try块中剩余的代码不会被执行程序直接跳转到对应的catch块。catch处理完异常后程序继续往下执行不会崩溃。throw后面可以跟任何类型的值字符串、整数、自定义对象等。2.2 多个 catch 块你可以针对不同类型的异常写不同的处理逻辑#includeiostreamusingnamespacestd;voidprocess(inttype){if(type1)throw42;if(type2)throw出错了;if(type3)throw3.14;}intmain(){for(inti1;i3;i){try{process(i);}catch(inte){cout整数异常eendl;}catch(constchar*e){cout字符串异常eendl;}catch(doublee){cout浮点异常eendl;}}return0;}输出整数异常42 字符串异常出错了 浮点异常3.14catch块会按照书写的顺序匹配异常类型只会执行第一个匹配的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 使用标准异常#includeiostream#includestdexcept#includevectorusingnamespacestd;intgetElement(constvectorintvec,intindex){if(index0||indexvec.size()){throwout_of_range(下标 to_string(index) 越界);}returnvec[index];}intmain(){vectorintnums{10,20,30};try{coutgetElement(nums,1)endl;// 20coutgetElement(nums,5)endl;// 越界}catch(constout_of_rangee){cout异常e.what()endl;}return0;}输出20 异常下标 5 越界标准异常类都有一个what()方法返回错误描述信息。用const 引用const exception来捕获是一个好习惯可以避免不必要的拷贝。3.3 统一捕获所有异常如果不确定会抛出什么类型的异常可以用catch (...)捕获所有异常try{// 可能出错的代码}catch(constexceptione){cout标准异常e.what()endl;}catch(...){cout未知异常endl;}建议把catch (const exception)放在前面catch (...)放在最后作为兜底。4. 自定义异常类当标准异常类不能满足需求时你可以定义自己的异常类。通常的做法是继承std::exception#includeiostream#includeexception#includestringusingnamespacestd;classMyException:publicexception{private:string message;public:MyException(conststringmsg):message(msg){}constchar*what()constnoexceptoverride{returnmessage.c_str();}};voidriskyOperation(){throwMyException(自定义异常操作失败);}intmain(){try{riskyOperation();}catch(constMyExceptione){coute.what()endl;}return0;}输出自定义异常操作失败自定义异常的好处是你可以携带更多的上下文信息比如错误代码、发生位置等方便调试和日志记录。5. 异常的传播机制理解异常是怎么「传递」的对于正确使用异常处理至关重要。5.1 调用链中的异常传播#includeiostream#includestdexceptusingnamespacestd;voidfuncC(){throwruntime_error(funcC 中出错了);}voidfuncB(){funcC();// 不处理继续向上传}voidfuncA(){try{funcB();// 不处理继续向上传}catch(construntime_errore){coutfuncA 捕获e.what()endl;}}intmain(){funcA();return0;}输出funcA 捕获funcC 中出错了异常从funcC抛出经过funcB没有catch最终在funcA中被捕获。异常会沿着调用链自动向上传递直到找到匹配的catch块。5.2 没有被捕获的异常如果异常一路传到main函数都没有被捕获程序会调用std::terminate()直接终止并可能弹出系统级的错误提示。所以一定要确保所有可能抛出异常的地方都有对应的catch处理。6. RAIIC 资源管理的黄金法则异常处理有一个容易被忽视的问题资源泄漏。如果在try块中申请了内存或打开了文件异常发生后这些资源可能不会被释放。C 的解决方案是RAIIResource Acquisition Is Initialization——把资源的生命周期绑定到对象的生命周期上。当对象离开作用域时析构函数会自动释放资源即使是因为异常导致的离开。#includeiostream#includefstream#includestdexceptusingnamespacestd;voidprocessFile(conststringfilename){ifstreamfile(filename);// RAII文件在构造时打开if(!file.is_open()){throwruntime_error(无法打开文件filename);}string line;while(getline(file,line)){coutlineendl;}// 函数结束时file 的析构函数自动关闭文件// 即使中途抛出异常析构函数也会被调用}intmain(){try{processFile(test.txt);}catch(construntime_errore){cout错误e.what()endl;}return0;}STL 容器vector、string等和智能指针unique_ptr、shared_ptr都遵循 RAII 原则。在 C 中优先使用 RAII 管理资源而不是手动new/delete或open/close。7. 使用异常的最佳实践7.1 什么时候该用异常真正的异常情况文件不存在、网络断开、内存不足等不可预期的错误不适合正常流程控制不要用异常来代替if-else判断7.2 异常安全的三个级别级别保证说明基本保证程序不会泄漏资源最低要求强保证操作要么完全成功要么回到操作前的状态事务性不抛出保证函数保证不抛出异常用noexcept声明7.3 使用noexcept标记不抛出异常的函数如果你确定某个函数不会抛出异常可以用noexcept声明帮助编译器做更好的优化intadd(inta,intb)noexcept{returnab;}析构函数默认就是noexcept的你不应该在析构函数中抛出异常。8. 总结这一节我们学习了 C 的异常处理机制try包裹可能出错的代码throw抛出异常catch捕获并处理。标准异常类runtime_error、out_of_range等提供了统一的错误描述接口。异常沿着调用链自动向上传递直到被catch捕获。RAII 是 C 资源管理的核心原则确保异常发生时资源不会泄漏。异常只用于处理真正的异常情况不要用来做流程控制。异常处理是编写健壮程序的重要保障。掌握了它你的代码就能在面对意外情况时「优雅地失败」而不是直接崩溃。下一节我们将继续探索 C 的更多高级特性。加油