目录
一.异常的概念及使用
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; }throw一执行:当前函数立刻退出,return 被跳过,不会执行- 发生栈展开:销毁栈上局部对象,向上找 catch
- 找到 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; }