news 2026/4/16 12:28:20

C++11:异常完全指南(从语法到实战,优雅处理程序错误)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++11:异常完全指南(从语法到实战,优雅处理程序错误)

一. 异常的核心概念与基本语法\

  • 异常处理机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并做出相应的处理,异常使得我们能够将问题的检测与解决问题的过程分开,程序的一部分负责检测问题的出现,然后解决问题的任务传递给程序的另一部分,检测环节无需知道问题的处理模块的所有细节
  • C语言主要通过错误码的形式处理错误,错误码的本质就是对错误信息进行分类编号,拿到错误码以后还要去查询错误信息,比较麻烦。而异常是抛出一个对象,这个对象可以函数更全面的拿到各种信息。

1.1 异常的核心思想

  • 抛出(throw):程序遇到错误时,通过throw抛出一个异常对象(可是任意类型,推荐自定义异常类);
  • 捕获(catch):通过catch语句捕获指定类型的异常,执行对应的处理逻辑;
  • try 块try包裹可能抛出异常的代码,后续紧跟一个或多个catch块,用于匹配异常

分析

  • 程序出现问题时,我们通过抛出(throw)一个对象来引发一个异常,该对象的类型以及当前调用链决定了应该由那个catch的处理代码来处理该异常。
  • 被选中的处理代码是调用链中与该对象类型匹配且抛出异常位置最近的那一个。根据抛出对象的类型和内容,程序的抛出异常部分告知异常处理部分到底发生了什么错误。
  • 当 throw 执行时,throw 后面的语句将不再被执行。程序的执行从throw位置跳到与之匹配的 catch 模块,catch可能是同一函数中的一个局部的 catch,也可能是调用链中另一个函数的catch,控制权从throw位置转移到了catch位置。这里还有两个重要的含义:1. 沿着调用链的函数可能提早结束退出。2. 一旦程序开始执行异常处理,沿着调用链创建的对象都将销毁。
  • 抛出异常对象后,会生成一个异常对象的1拷贝,因为抛出的异常对象可能是一个局部对象,所以会生成一个拷贝对象,这个拷贝的对象会在catch子句后销毁。( 这里的处理类似于函数的传值返回)

1.2 基础语法格式和最简示例

基本语法格式

/*----------------------------------------------------------------- try { // 可能抛出异常的代码 可能出错的函数(); } catch (异常类型1& e) { // 处理类型1异常 } catch (异常类型2& e) { // 处理类型2异常 } catch (...) { // 捕获任意类型异常(兜底处理) } -----------------------------------------------------------------*/

最简示例(除零异常):

#include<exception> double Divide(int a, int b) { // 当 b == 0 时抛出异常 if (b == 0) { //string s("Divide by zero condition!"); //throw s; throw exception("Divide by zero condition!"); } else { return ((double)a / (double)b); } } void Func() { try { int len, time; cin >> len >> time; cout << Divide(len, time) << endl; } catch (const exception& e) { cout << e.what() << endl; } cout << "Func():" << __LINE__<< endl; } int main() { while (1) { try { Func(); } // 异常会先匹配最适配的 catch (const string& s) { cout << s << endl; } catch (const exception& e) { cout << e.what() << endl; } catch (...) // 任意类型的对象 { cout << "未知异常" << endl; } cout << "Func():" << __LINE__ << endl; } return 0; }

二. 异常的核心机制:栈展开与匹配规则

2.1 栈展开

抛出异常后,程序会暂停当前函数执行,沿调用链向上查找匹配的catch块,这个过程称为 “栈展开”:

  1. 检查当前函数的try/catch块,若找到匹配的catch,则执行处理逻辑;
  2. 若未找到,销毁当前函数的局部对象,退出当前函数,继续向上查找;
  3. 重复步骤 1-2,直到找到匹配的catch
  4. 若到达main函数仍未找到,调用terminate函数终止程序。

补充

栈展开示例:

void Func1() { throw "Func1抛出异常"; // 抛出异常 } void Func2() { Func1(); // 调用Func1,不处理异常 } void Func3() { Func2(); // 调用Func2,不处理异常 } int main() { try { Func3(); // 调用Func3 } catch (const char* errmsg) { // 捕获Func1抛出的异常(栈展开:Func1→Func2→Func3→main) cout << "捕获异常:" << errmsg << endl; } return 0; }

2.2 异常捕获的匹配规则

捕获异常时,遵循 “精确匹配优先、兼容转换次之” 的原则:

补充

三. 自定义异常体系:大型项目的最佳实践

在大型项目中,直接抛出基本类型(如字符串、整数)的异常难以区分错误类型,推荐自定义异常类体系(基于继承),统一异常接口,便于管理和扩展。

代码实现:

#include<thread> // 一般大型项目程序才会使用异常,下面我们模拟设计一个服务的几个模块 // 每个模块的继承都是Expection的派生类,每个模块可以添加自己的数据 // 最后捕获的时候,我们捕获基类就可以,通过多态可以打印不同信息 class Exception { public: Exception(const string& errmsg,int id) :_errmsg(errmsg) ,_id(id) { } virtual string what() const { return _errmsg; } int getid()const { return _id; } protected: string _errmsg; int _id; }; class SqlException :public Exception { public: SqlException(const string& errmsg,int id,const string& sql) :Exception(errmsg,id) ,_sql(sql) { } virtual string what() const { string str = "SqlException:"; str += _errmsg; str += "->"; str += _sql; return str; } private: const string _sql; }; class CacheException : public Exception { public: CacheException(const string& errmsg, int id) :Exception(errmsg, id) { } virtual string what() const { string str = "CacheException:"; str += _errmsg; return str; } }; class HttpException : public Exception { public: HttpException(const string& errmsg, int id, const string& type) :Exception(errmsg, id) , _type(type) { } virtual string what() const { string str = "HttpException:"; str += _type; str += ":"; str += _errmsg; return str; } private: const string _type; }; void SQLMgr() { if (rand() % 7 == 0) { throw SqlException("权限不足", 100, "select * from name = '张三'"); } else { cout << "SQLMgr 调用成功" << endl; } } void CacheMgr() { if (rand() % 5 == 0) { throw CacheException("权限不足", 100); } else if (rand() % 6 == 0) { throw CacheException("数据不存在", 101); } else { cout << "CacheMgr 调用成功" << endl; } SQLMgr(); } void HttpServer() { if (rand() % 3 == 0) { throw HttpException("请求资源不存在", 100, "get"); } else if (rand() % 4 == 0) { throw HttpException("权限不足", 101, "post"); } else { cout << "HttpServer调用成功" << endl; } CacheMgr(); } int main() { srand(time(0)); while (1) { this_thread::sleep_for(chrono::seconds(1)); try { HttpServer(); } catch (const Exception& e) // 这里捕获基类,基类对象和派生类对象都可以被捕获 { // 多态调用 cout << e.what() << endl; } catch (...) { cout << "Unkown Exception" << endl; } } return 0; }

部分输出演示

四. 异常的高级用法

4.1 异常重新抛出

有时捕获异常后,无法完全处理(如仅记录日志),或需要根据错误类型分流处理,可通过throw;重新抛出异常,让外层调用链继续处理。

示例:网络请求重试

// 下面程序模拟展示了聊天时发送消息,发送失败补货异常,但是可能在 // 电梯地下室等场景手机信号不好,则需要多次尝试 // 如果多次尝试都发送不出去,则就需要捕获异常再重新抛出, // 其次如果不是网络差导致的错误,捕获后也要重新抛出。 void _SendMsg(const string& s) { if (rand() % 2 == 0) { throw HttpException("网络不稳定,发送失败",102, "put"); } else if (rand() % 7 == 0) { throw HttpException("你已经不是对方的好友,发送失败", 102, "put"); } else { cout << "发送成功" << endl; } } // 网络不稳定,要求重试三次,均失败 void SendMsg(const string& s) { for (size_t i = 0; i < 4; i++) { try { _SendMsg(s); // 走到这里,如果没有抛异常导致结束 // 那就代表成功了,可以执行到这个break,跳出循环 break; } catch (const Exception& e) { if (e.getid() == 102) { if (i == 3) throw; cout << "开始第" << i + 1 << "重试" << endl; } else { // 重新抛出异常 // throw e; throw; } } } } int main() { srand(time(0)); string str; while (cin >> str) { try { SendMsg(str); } catch (const Exception& e) { cout << e.what() << endl << endl; } catch (...) { cout << "Unkown Exception" << endl; } } return 0; }

4.2 异常安全:避免资源泄漏

解决方案:

示例:

double Divide(int a, int b) { // 当b == 0时抛出异常 if (b == 0) { throw "Division by zero condition!"; } return (double)a / (double)b; } void Func() { // 这里可以看到如果发生除0错误抛出异常,那下面的array就没有得到释放。 // 所以这里捕获异常后并不处理异常, // 异常还是交给外层处理,这里捕获了再重新抛出去。 int* array = new int[10]; int len, time; cin >> len >> time; try { cout << Divide(len, time) << endl; } catch (...) { cout << "delete []" << array << endl; delete[] array; // 重新抛出,捕获到什么抛出什么 throw; } cout << "delete []" << array << endl; delete[] array; } int main() { try { Func(); } catch (const char* errmsg) { cout << errmsg << endl; } catch (...) { cout << "Unkown Exception" << endl; } return 0; }

4.3 异常规范( noexcept )

C++11 提供noexcept关键字,用于声明函数是否会抛出异常,帮助编译器优化代码:

补充

实际示例

// C++11标记不会抛异常的方法 // double Divide(int a, int b) noexcept // C++98用来标记会抛异常的方法 // double Divide(int a,int b) throw(const char*) // C++98 // 这里表示这个函数只会抛出bad_alloc的异常 // void* operator new (std::size_t size) throw (std::bad_alloc); // 这里表示这个函数不会抛出异常 // void* operator delete (std::size_t size, void* ptr) throw(); // C++11 // size_type size() const noexcept; // iterator begin() noexcept; // const_iterator begin() const noexcept; double Divide(int a, int b) noexcept { // 当b == 0时抛出异常 if (b == 0) { throw "Division by zero condition!"; } return (double)a / (double)b; } int main() { try { int len, time; cin >> len >> time; cout << Divide(len, time) << endl; } catch (const char* errmsg) { cout << errmsg << endl; } catch (...) { cout << "Unkown Exception" << endl; } int i = 0; cout << noexcept(Divide(1, 2)) << endl; cout << noexcept(Divide(1, 0)) << endl; cout << noexcept(++i) << endl; return 0; }

五. C++ 标准库异常体系

C++ 标准库提供了一套预定义的异常继承体系,基类为std::exception,派生类对应不同类型的标准异常(如内存分配失败、数组越界),可直接使用或继承扩展。

标准库异常体系核心类:

异常类用途错误信息获取方式
std::exception所有标准异常的基类what()(虚函数)
std::bad_allocnew分配内存失败时抛出what()返回 “bad alloc”
std::out_of_range数组/容器越界时抛出what()返回越界信息
std::invalid_argument无效参数时抛出what()返回参数错误信息
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 10:20:26

**新手写小说软件2025推荐,3大核心维度帮你找到创作利器

新手写小说软件2025推荐&#xff0c;3大核心维度帮你找到创作利器对于刚踏入创作领域的新手而言&#xff0c;选择一款合适的写作软件&#xff0c;是开启创作之路的关键一步。据《2025中国网络文学创作工具发展报告》显示&#xff0c;2025年国内使用AI辅助创作的新手作者数量同比…

作者头像 李华
网站建设 2026/4/16 10:21:14

2026年热门文旅二维码推荐,让您的旅程更精彩

在2026年&#xff0c;文旅二维码正逐渐成为旅游行业不可或缺的工具。游客只需扫一扫&#xff0c;就能快速获取当地特色、目的地介绍和行程安排等信息。这种便捷的方式让旅程变得更加轻松愉快。 文旅二维码应用广泛&#xff0c;覆盖景点、酒店和博物馆等场所&#xff0c;信息传…

作者头像 李华
网站建设 2026/4/12 6:16:55

工业触摸屏:投影电容式触摸屏(PCAP)原理详解

投影电容式触摸屏&#xff08;PCAP&#xff09;原理详解 投影电容式触摸屏&#xff08;Projected Capacitive Touchscreen&#xff0c;简称PCAP&#xff09;是当前主流触摸技术&#xff0c;尤其在工业HMI、智能设备中占比超过60%。它通过感应手指&#xff08;或导电物&#xff…

作者头像 李华
网站建设 2026/4/16 10:16:03

程序员如何转型AI产品经理,把握大模型时代新机遇_AI产品经理是未来五年最稀缺的岗位之一!

AI正重塑各类消费产品&#xff0c;催生AI产品经理这一需求增长178%的新兴职业。该岗位需要将AI技术与商业需求结合的复合型人才&#xff0c;处于"技术—设计—体验—商业"交汇中心。传统执行型岗位被AI取代&#xff0c;而战略型人才需求提升。即使本科未涉足相关领域…

作者头像 李华