news 2026/10/2 18:02:16

从零吃透 C++ 异常:抛出捕获、栈展开、异常重抛与编码规范详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零吃透 C++ 异常:抛出捕获、栈展开、异常重抛与编码规范详解

目录

一.异常的概念及使用

1.1异常的概念

1.2异常的抛出和捕获

1.3栈展开

1.4查找匹配的处理代码

1.5异常重新抛出

1.6 异常安全问题

1.7异常规范


一.异常的概念及使用

1.1异常的概念

异常机制核心作用:分离「错误检测」和「错误处理」,异常把程序拆成两部分:一段代码只负责检测有没有出错,发现错误后把错误信息打包抛出;另一段代码专门接收异常、处理错误;检测代码不需要关心错误最终怎么处理,实现解耦。

通俗理解:

写代码时你预想一套流程:输入合法数据、计算、输出结果。 但现实会出各种意外:文件找不到、网络断了、用户输入乱填、除以 0、数组越界…… 这些不是你业务逻辑的正常结果,是运行时出的意外状况,就叫异常。

区别错误:

  • 异常:运行时才发生,可以被捕获、处理,程序有机会继续跑。
  • 语法错误:编译就报错,代码根本跑不起来。

异常 = 程序运行期间发生的非预期意外,代表某种出错状态,允许我们主动捕捉处理,避免程序直接挂掉。

1.2异常的抛出和捕获

  • 如果找到匹配 catch 捕获异常:不会崩溃,处理完 catch 之后,继续执行 catch 后面的代码。
  • 如果没有任何 catch 捕获:直接调用terminate(),程序立刻挂掉,根本不会走到 return。

1.3栈展开

情况 1:有 catch 捕获异常(不会挂)

void fun() { throw "出错"; // 执行到这里,本函数直接结束!下面的return不会跑! cout<<"这行不会执行"; return; // ❗不会执行,throw直接跳出函数 } int main() { try{ fun(); } catch(const char* e){ cout<<"捕获异常"<<e<<endl; } // catch处理完,走到这里继续跑main后续代码 return 0; }
  1. throw一执行:当前函数立刻退出,return 被跳过,不会执行
  2. 发生栈展开:销毁栈上局部对象,向上找 catch
  3. 找到 catch:执行 catch 里面的处理;catch 结束之后,执行catch 之后的代码,不是回到原来函数的 throw 后面。

不是 “继续把函数跑完走到 return”,函数早就提前退出了。

情况 2:没有 catch 捕获(程序直接挂)

void fun() { throw "出错"; return; // 依旧不执行 } int main() { fun(); // 没有try‑catch return 0; // ❗这行也不会执行!程序直接terminate崩溃 }

throw 抛出,栈展开一路向上找,到 main 还找不到匹配 catch,直接终止程序,main 的 return 也执行不到,程序挂掉。


通俗比喻

函数就像跑任务:

  • return:正常做完任务,正常回家。
  • throw:任务中途出事,直接撂挑子不干,本函数直接跑路,后面代码全部作废,向上通知上层。
    • 如果上层有人接(catch):上层处理完,继续跑上层剩下的代码。
    • 如果一层一层没人接:直接整个程序停机崩溃。

1.4查找匹配的处理代码

// 一般大型项目程序才会使用异常,下面我们模拟设计一个服务的几个模块 // 每个模块的继承都是Exception的派生类,每个模块可以添加自己的数据 // 最后捕获时,我们捕获基类就可以 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; }

1.5异常重新抛出

#include<iostream> using namespace std; // 定义统一的异常基类,包含错误信息和ID 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; }; // 具体的HTTP异常,继承自Exception 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 _SendMsg(const string& s) { if (rand() % 2 == 0) { throw HttpException("网络不稳定,发送失败", 102, "put"); } else if (rand() % 7 == 0) { throw HttpException("你已经不是对象的好友,发送失败", 103, "put"); } else { cout << "发送成功" << endl; } } // 网络不稳定,要求重试3次,均失败 void SendMsg(const string& s) { // 循环4次:1次正常发送 + 3次重试 for (size_t i = 0; i < 4; i++) { try { _SendMsg(s); // 走到这里代表成功了,跳出循环 break; } catch (const Exception& e) { // 核心逻辑:根据错误ID判断 if (e.getid() == 102) { // 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; }

1.6 异常安全问题

double Divide(int a, int b) { try { // 当b == 0时抛出异常 if (b == 0) { string s("Divide by zero condition!"); throw s; } else { return ((double)a / (double)b); } } catch (string& errid) { cout << errid << endl; throw errid; } return 0; } void func() { // 申请资源 int* array = new int[10]; int x, y; cin >> x >> y; // 中间调用了一系列函数 // 如果这里面的某个函数(比如 Divide)抛出了异常... Divide(x, y); // ...这里的 delete 就不会被执行!导致内存泄漏 delete[] array; }

1.7异常规范

noexcept:

double Divide(int a, int b) { if (b == 0) throw "Division by zero!"; // 这里明显会抛异常 return (double)a / b; } void Func() { int* array = new int[10]; // new 可能会抛 bad_alloc // ... } int main() { // 测试 1:Divide 函数内部有 throw,所以结果是 0 (false) cout << noexcept(Divide(1, 2)) << endl; // 测试 2:哪怕参数传的是安全的,只要函数定义里有 throw,结果还是 0 cout << noexcept(Divide(1, 0)) << endl; // 测试 3:Func 内部调用了 new,new 是不安全的,所以 Func 也是 0 cout << noexcept(Func()) << endl; // 测试 4:i++ 这种简单操作,绝对安全,结果是 1 (true) int i = 0; cout << noexcept(++i) << endl; return 0; }

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 18:00:19

Xshell 向 Linux 虚拟机传文件:可靠方式与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 17:57:31

JavaWeb停车场管理系统源码拆包:课程设计实战与避坑指南

简介&#xff1a;这份资源是面向高校计算机相关专业学生与JavaWeb初学者的一套停车场管理系统课程设计完整方案&#xff0c;对应大作业与实训场景&#xff0c;帮助读者在缺乏项目经验时快速完成从需求分析到功能落地的全过程。压缩包共1086个文件&#xff0c;约92.05MB&#xf…

作者头像 李华
网站建设 2026/10/2 17:50:56

STK 11.5在WIN10环境下的正规安装与配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华