
文章目录
引言
在程序开发过程中,我们经常会遇到各种各样的错误,导致程序崩溃。C语言中通过错误码 处理错误,但错误码非常多,通常需要手动查询含义,效率低下;而C++中通过异常机制,将错误检测与处理分离,允许程序运行时传递更详细的错误信息,并灵活跳转至对应的处理逻辑,提升了代码的健壮性,效率更高。
1.异常的核心概念
异常时程序运行时出现的非正常情况(如逻辑错误、资源不足等),而C++的异常处理机制核心在于 "抛出异常"与"捕获异常" 的交互:
- 异常对象:是抛出异常的载体,可包含比错误码更丰富的信息(如错误描述、位置、类型等),支持自定义类型。
- 检测与处理分离:异常处理机制中,程序一部分负责检测错误并抛出异常 ,另一部分负责捕获并处理异常 ,这两部分是相互独立的。
- 优势:与C语言错误码相比,异常对象能传递更全面的错误信息 ,且无需在每层函数中手动返回错误码,进一步简化了代码逻辑。
2.异常的抛出与捕获
2.1.基本语法与流程
- 抛出异常:使用关键字
throw来抛出异常对象(可以是内置类型、自定义类型或便准库类型),抛出异常后,当前函数中的后续代码将不再执行。 - 捕获异常:通过
try-catch语句块实现,try包裹可能抛出异常的代码,catch指定要捕获的异常类型,匹配成功则执行处理逻辑。 - 核心规则:
(1) 捕获 异常类型需要与抛出 异常类型相匹配(支持部分类型转换,如:非const转const,派生类转基类);
(2) 若有多个匹配的catch块,优先执行离抛出点最近的那个;
(3) 抛出的局部异常对象会生成拷贝,在catch处理后销毁(类似于函数的传值返回)。
2.2.栈展开
当try-catch为捕获到匹配的异常时,程序会执行"栈展开"过程:
- 退出当前函数栈,并销毁该函中的所有局部变量;
- 向上层调用的函数栈帧继续查找匹配的
catch块; - 若一直追溯到main函数仍未找到,则调用
terminate函数终止运行。

代码示例:
cpp
#include <iostream>
#include <string>
using namespace std;
// 除法函数:实现两整数除法,当除数为0时抛出string类型异常
double Divide(int a, int b)
{
// 检测除数为0的错误场景
if (b == 0)
{
// 创建异常信息字符串,包含具体错误原因
string s("Divide by zero condition!");
// 抛出string类型异常对象,后续代码不再执行
throw s;
}
else
{
// 除数合法时,正常计算并返回除法结果(转换为double避免整数截断)
return ((double)a / (double)b);
}
}
// 功能函数:获取用户输入的两个整数,调用Divide函数计算并输出结果
void Func()
{
int len, time;
cin >> len >> time;
// try块:包裹可能抛出异常的代码(此处为Divide函数调用)
try
{
// 调用Divide函数计算结果并直接输出,若Divide抛出异常,此句后续代码不执行
cout << Divide(len, time) << endl;
}
// catch块:捕获特定类型的异常(此处为const char*类型)
// 注意:此catch块实际无法捕获Divide抛出的string类型异常,属于"类型不匹配"的设计
catch (const char* errmsg)
{
// 若捕获到const char*类型异常,输出异常信息
cout << errmsg << endl;
}
// 关键:无论try块中是否抛出异常、是否捕获成功,此句都会执行
// __FUNCTION__是编译器内置宏,获取当前函数名(此处为"Func");__LINE__获取当前代码行号
cout << __FUNCTION__ << ":" << __LINE__ << "行执行" << endl;
}
int main()
{
// while(1):创建无限循环,让程序可重复接收用户输入(直到手动终止)
while (1)
{
// try块:包裹可能抛出异常的函数调用(此处为Func函数)
try
{
// 调用Func函数,若Func内部抛出未被捕获的异常,会传递到此处
Func();
}
// catch块:捕获string类型异常(匹配Divide函数抛出的异常类型)
catch (const string& errmsg)
{
// 输出捕获到的异常信息
cout << errmsg << endl;
}
}
return 0;
}
运行结果:

2.3.类型匹配的特殊情况
除了完全匹配,C++还支持三种特殊的类型转换匹配异常:
- 权限缩小:非常量类型可转换为const类型,如:
throw string可被cathch(const string&)捕获; - 数组/函数指针转换:数组退化为指向自身元素的指针,函数退化为指向其类型的指针;
- 继承体系转换:派生类异常,可被基类类型的
catch捕获。
代码示例:
cpp
#include <iostream>
#include <string>
using namespace std;
// ------------------------------
// 准备:定义继承体系(用于测试"继承体系转换")
// 基类异常
class BaseException {
public:
// 虚函数支持多态,捕获基类引用时能调用派生类的实现
virtual string what() const {
return "BaseException: 基类异常";
}
};
// 派生类1:SQL异常(继承自BaseException)
class SqlException : public BaseException {
public:
string what() const override {
return "SqlException: SQL语法错误";
}
};
// 派生类2:网络异常(继承自BaseException)
class NetworkException : public BaseException {
public:
string what() const override {
return "NetworkException: 网络连接超时";
}
};
// ------------------------------
// 测试1:权限缩小(非常量→const)
void test_const_conversion() {
cout << "\n===== 测试1:权限缩小(非常量→const) =====" << endl;
try {
// 抛出:非常量string类型异常(无const修饰)
throw string("抛出:非const字符串异常");
}
// 捕获:const string&(权限缩小,合法转换)
catch (const string& e) {
cout << "捕获成功:" << e << endl;
cout << "说明:throw string(非const)可被catch(const string&)捕获" << endl;
}
}
// ------------------------------
// 测试2:数组/函数指针转换(数组退化为指针、函数退化为指针)
// 辅助:测试函数(用于函数指针转换)
void test_func() {
cout << "测试函数:用于函数指针转换" << endl;
}
void test_array_func_conversion() {
cout << "\n===== 测试2:数组/函数指针转换 =====" << endl;
// 子测试2.1:数组退化为指针
try {
int arr[5] = { 10, 20, 30, 40, 50 };
// 抛出:数组类型(实际会退化为int*指针)
throw arr;
}
// 捕获:int*指针(匹配数组退化后的类型,合法转换)
catch (int* p) {
cout << "子测试2.1(数组→指针):" << endl;
cout << "捕获到数组退化的指针,首元素值:" << *p << endl;
cout << "数组第3个元素值:" << *(p + 2) << endl; // 指针偏移访问
}
// 子测试2.2:函数退化为指针
try {
// 抛出:函数名(实际会退化为函数指针void(*)())
throw test_func;
}
// 捕获:函数指针类型(匹配函数退化后的类型,合法转换)
catch (void(*func_ptr)()) {
cout << "\n子测试2.2(函数→指针):" << endl;
cout << "捕获到函数指针,调用函数:";
func_ptr(); // 通过捕获的函数指针调用原函数
}
}
// ------------------------------
// 测试3:继承体系转换(派生类→基类)(实战最常用)
void test_inheritance_conversion() {
cout << "\n===== 测试3:继承体系转换(派生类→基类) =====" << endl;
// 子测试3.1:抛出SqlException(派生类),捕获BaseException(基类)
try {
throw SqlException(); // 抛出派生类异常
}
catch (const BaseException& e) { // 捕获基类引用
cout << "子测试3.1:" << e.what() << endl;
cout << "说明:SqlException(派生类)可被BaseException(基类)捕获" << endl;
}
// 子测试3.2:抛出NetworkException(派生类),捕获BaseException(基类)
try {
throw NetworkException(); // 抛出另一个派生类异常
}
catch (const BaseException& e) { // 统一捕获基类
cout << "\n子测试3.2:" << e.what() << endl;
cout << "说明:多个派生类异常可通过基类catch统一处理(实战核心用法)" << endl;
}
}
// ------------------------------
// 主函数:执行所有测试
int main() {
// 依次执行3种转换的测试
test_const_conversion();
test_array_func_conversion();
test_inheritance_conversion();
return 0;
}
运行结果:

2.4.异常重新抛出
有时catch捕获异常后,需对异常类型进行分类,其中某种特定类型异常需要进行特殊处理,其余异常交给上层处理,此时可使用可使用throw重新抛出异常。
典型案例:网络请求失败时,对"网络不稳定"异常重试3次,其余异常抛出给外层调用
cpp
#include <iostream>
#include <string>
#include <ctime> // 用于srand(time(0))初始化随机数
using namespace std;
// --------------------------
// 1. 补全选中片段隐含的异常类体系(核心依赖)
// 异常基类:所有自定义异常的父类
class Exception {
public:
// 构造函数:初始化错误信息和错误码
Exception(const string& errmsg, int id)
: _errmsg(errmsg)
, _id(id)
{
}
// 虚函数what():返回错误描述(支持多态,派生类可重写)
virtual string what() const {
return _errmsg;
}
// 获取错误码:选中片段中e.getid()依赖此函数
int getid() const {
return _id;
}
protected:
string _errmsg; // 错误描述信息
int _id; // 错误码(102=网络不稳定,103=非好友)
};
// 派生类:Http相关异常(选中片段中throw的异常类型)
class HttpException : public Exception {
public:
// 构造函数:额外传入Http请求类型(如put/get)
HttpException(const string& errmsg, int id, const string& type)
: Exception(errmsg, id) // 调用基类构造函数
, _type(type)
{
}
// 重写what():补充Http请求类型,让错误信息更详细
virtual string what() const override {
return "HttpException[" + _type + "]: " + _errmsg;
}
private:
string _type; // 存储Http请求类型(如选中片段中的"put")
};
// --------------------------
// 2. 选中片段中的核心函数:_SeedMsg(实际发送消息,内部抛异常)
// 注意:函数名前的下划线通常表示"内部辅助函数",不建议外部直接调用
void _SeedMsg(const string& s) {
// 随机模拟两种异常场景,或正常发送
if (rand() % 2 == 0) {
// 场景1:网络不稳定(错误码102,对应选中片段的重试逻辑)
throw HttpException("网络不稳定,发送失败", 102, "put");
}
else if (rand() % 7 == 0) {
// 场景2:非好友(错误码103,不重试,直接抛上层)
throw HttpException("你已经不是对象的好友,发送失败", 103, "put");
}
else {
// 场景3:发送成功
cout << "消息发送成功:" << s << endl;
}
}
// 3. 选中片段中的核心函数:SendMsg(对外接口,包含重试逻辑)
void SendMsg(const string& s) {
// 发送消息失败,则再重试3次(循环4次:0~3,共4次机会)
for (size_t i = 0; i < 4; i++) {
try {
_SeedMsg(s); // 调用内部发送函数
break; // 若发送成功,跳出循环,不再重试
}
// 捕获所有自定义异常(选中片段的核心catch逻辑)
catch (const Exception& e) {
// 分支1:错误码102(网络不稳定)→ 执行重试逻辑
if (e.getid() == 102) {
// 若重试到第3次(i=3)仍失败,不再重试,抛异常给上层
if (i == 3) {
throw; // 重新抛出当前捕获的异常(不修改异常内容)
}
// 未到最大重试次数,提示并继续循环重试
cout << "开始第" << i + 1 << "次重试(原因:" << e.what() << ")" << endl;
}
// 分支2:非102错误码(如103非好友)→ 直接抛上层,不重试
else {
throw; // 重新抛出异常,交给main函数的catch处理
}
}
}
}
// --------------------------
// 4. 选中片段中的main函数(程序入口,处理最终异常)
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 << endl;
}
}
return 0;
}
运行结果:

3.异常安全与规范
3.1.异常安全问题
异常抛出后可能会导致资源泄露:
- 资源申请后抛出异常 :如果在
new和delete之间抛出异常,则会造成资源无法释放的问题; - 析构函数抛出异常:若析构函数抛出异常,可能会导致后续资源无法正常释放。
避免资源泄露的示例:
cpp
void Func() {
int* array = new int[10]; // 申请内存
try {
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
} catch (...) {
delete[] array; // 捕获异常后释放资源
throw; // 重新抛出,交给上层处理
}
delete[] array; // 正常执行时释放资源
}
我们还可以使用智能指针(RAII机制)来避免资源泄露,这个方法会在下一篇博客中讲到。
3.2.异常规范
- C++98:函数参数列表后接
throw,表示不抛异常 ;函数参数列表后接throw(类型1,类型2,...)表示可能会抛出多种类型的异常,各种类型之间用逗号分割; - C++11:关键字
noexcept表示函数不会抛异常;不加则可能会抛异常。
注意:
(1)若标记noexcept的函数抛出异常,程序会调用terminate终止;
(2)noexcept(表达式)可作为运算符 ,判断表达式是否可能抛出异常 ,返回值为true/false。
代码示例:
cpp
// C++98风格
void* operator new(std::size_t size) throw(std::bad_alloc); // 仅抛bad_alloc
void* operator delete(std::size_t size, void* ptr) throw(); // 不抛异常
// C++11风格
double Divide(int a, int b) noexcept {
if (b == 0) throw "Division by zero!"; // 编译通过,但运行时抛异常会终止程序
return (double)a / b;
}
// noexcept作为运算符
cout << noexcept(Divide(1,2)) << endl; // true(表达式本身无异常)
cout << noexcept(Divide(1,0)) << endl; // true(编译器不分析运行时逻辑)
4.标准库异常体系
- C++标准库定义了一套自己的异常继承体系库,基类是
exception,可以直接在主函数捕获它,要获取异常信息,可以调用what函数(一个虚函数,派生类可以重写)。 - 点击查看异常继承体系库中的详细内容

结语
C++异常处理通过 "抛出-捕获"机制 ,让错误检测与处理清晰分离。它摆脱了错误码的层层传递冗余**,借栈展开保障资源安全,凭继承体系实现统一适配。简洁的语法与灵活的逻辑跳转,既提升了代码稳健性,也让业务逻辑更纯粹,帮助我们快速找到程序中的错误,是C++构建可靠程序的核心工具之一。