C++----异常
·
异常的概念及使用
1.异常的概念
C 语言错误处理:
- 函数出错时,返回一个数字(错误码)
- 或者用全局变量
errno
C++ 异常的设计目标:
- 出错地方不立即处理,沿调用链往上抛
- 抛出一个对象,携带完整错误信息(编号 + 描述 + 现场)
- 检测与处理分离,代码更清晰
2.异常基本语法:throw /try/catch
2.1抛出异常:throw
- 用
throw抛出任意类型(int /string/ 自定义对象) - 抛出后执行流直接跳转,throw 后面代码不再执行
throw string("除零错误");
//除零错误
int a = 10;
int b = 0;
if (b == 0)
{
// 除数是0,非法!
// 主动报错,抛出异常
throw string("除数不能为0,除零错误!");
}
// 下面这句 a / b 根本不会走到!
int res = a / b;
为什么 b==0 就要抛异常?
数学里:
10 ÷ 0 本身就是非法运算,电脑算不出来,程序直接崩
所以我们不能真的执行 a/b
一旦执行 10/0 程序直接死机崩溃
所以我们提前判断:
if(b==0) → 发现除数是 0
→ 主动抛出异常,告诉上层:这里出错了,不能算
//抛 int 数字异常
int num = -1;
if (num < 0)
{
throw 1001; // 抛出数字错误码
}
cout << "正常逻辑代码" << endl;
规定:num 不能是负数
发现 num<0 不符合规则
直接 throw 1001 扔一个数字错误编号出去
throw 一执行,下面 cout 永远不运行
//抛 string 字符串异常
throw string("参数非法错误");
cout << "不会执行" << endl;
一上来就出错,直接扔一段中文文字说明错误
不用查表,一看就知道哪里错
扔完之后,后面代码全部作废
//抛自定义类对象异常
class MyError
{
public:
string msg;
MyError(string m) : msg(m) {}
};
throw MyError("自定义运行异常");
cout << "不会执行" << endl;
自己写一个异常类
可以存:错误编号、错误原因、出错位置、时间…… 超多信息
直接扔整个对象出去,携带信息最全
同样:throw 之后后面代码绝不执行
2.2 捕获异常:try + catch
try {
// 可能出错的代码
}
catch (string err) {
// 处理错误
}
#include <iostream>
using namespace std;
int main()
{
int num = -1;
try
{
if(num < 0)
{
throw 1001;
}
cout << "正常数字" << endl;
}
catch(int err)
{
cout << "错误编号:" << err << endl;
}
return 0;
}
try:放可能出错的代码
throw:抛出错误
catch:接住并处理错误
2.3 捕获规则(重点)
- 类型必须匹配
- 沿调用链向上查找,找到最近且匹配的 catch
- 找到就执行 catch,之后程序继续正常运行
- 没找到 → 程序终止(闪退)
3.栈展开
异常抛出后,并不是 “直接飞” 到 catch,而是:
- 暂停当前函数
- 逐层退出函数
- 每层局部对象正常析构
- 直到找到匹配 catch
这叫 栈展开(Stack Unwinding)
注意:
- 栈展开不会内存泄漏
- 局部对象会被正确析构
- 但动态分配的内存(new/malloc)不会自动释放(重点坑)
看例子:

#include <iostream>
#include <string>
using namespace std;
// 最内层函数:抛出异常
void func1()
{
throw string("func1 出现除零错误!");
}
// 第二层:调用func1,自己不处理异常
void func2()
{
func1();
}
// 第三层:调用func2,自己不处理异常
void func3()
{
func2();
}
// 最顶层main:统一捕获处理所有异常
int main()
{
try
{
func3();
}
catch(string err)
{
// 一路从func1跑上来,在这里接住
cout << "main捕获到异常:" << err << endl;
}
cout << "异常处理完毕,程序正常结束" << endl;
return 0;
}
执行过程
main运行 → 调用func3func3运行 → 调用func2func2运行 → 调用func1func1执行throw,抛出异常- ❌
func1没有 catch → 函数立刻结束,异常往上走- ❌
func2没有 catch → 函数立刻结束,异常继续往上走- ❌
func3没有 catch → 函数立刻结束,异常继续往上走- ✅ 跑到
main,main有匹配 catch → 处理错误- 程序正常往下运行,不会崩溃
4.查找匹配的处理代码
4.1异常常规匹配规则
- 默认情况下:throw 异常类型 和 catch 类型必须严格匹配
- 多个 catch 匹配时,优先选择离抛出位置最近的 catch 语句
- 异常沿着函数调用链逐层向上栈展开查找
4.2异常类型特殊转换(允许匹配例外)
C++ 异常不是死匹配,支持几种合法类型转换:
- 非常量类型 → 常量类型转换(权限缩小)
- 数组类型 → 对应数组指针
- 函数类型 → 函数指针
#include <iostream>
#include <string>
using namespace std;
// 基类
class Base {
public:
virtual void show() { cout << "Base异常" << endl; }
};
// 派生类
class Derive : public Base {
public:
void show() override { cout << "Derive异常" << endl; }
};
int main() {
try {
// 1. 抛 派生类对象
throw Derive();
}
// 2. 基类能接住(派生类→基类 允许转换)
catch (const Base& e) {
e.show();
}
// 3. 万能兜底:捕获所有未知异常
catch (...) {
cout << "未知异常" << endl;
}
}
/////////////////////
throw 类型和 catch 类型可以不是严格匹配
这里 throw Derive ()
catch (Base&) 能接住 ✔
支持派生类 → 基类转换(最重要)
子类异常,父类能捕获 ✔
catch (...) 万能兜底
任何异常都能接住,防止程序崩溃 ✔
4.3万能捕获 catch (...)
- 可以捕获任意类型的异常,没有类型限制
- 缺点:无法获取异常具体信息,不知道错在哪
- 作用:放在所有 catch 最后做兜底,避免异常一路跑到 main 外导致程序直接终止崩溃
例子:
#include <iostream>
using namespace std;
int main() {
try {
throw 123;
}
catch (int a) {
cout << "捕获int:" << a << endl;
}
catch (...) {
cout << "捕获所有其他异常" << endl;
}
}
5.C++ 异常重新抛出 throw
5.1什么是异常重新抛出
抓到异常以后分两种情况:
- 临时可恢复错误(网络卡顿):本地重试处理
- 无法修复错误(参数非法):不用处理,直接向上交给外层
语法格式:只写 throw; 后面不加任何东西
作用:把当前捕获到的异常,原封不动、原样继续向上传递
例子:
#include <iostream>
#include <string>
using namespace std;
// 异常基类
class Err
{
public:
int code;
Err(int c) : code(c) {}
virtual string msg() { return "错误"; }
};
// 余额不足:可以重试换金额
class MoneyErr : public Err
{
public:
MoneyErr() : Err(1) {}
string msg() override { return "余额不足,可更换金额重试"; }
};
// 银行卡冻结:没法处理,直接上报
class CardErr : public Err
{
public:
CardErr() : Err(2) {}
string msg() override { return "银行卡冻结,无法取款"; }
};
// 底层:取钱操作
void getMoney()
{
int type = rand() % 2;
if(type == 0)
throw MoneyErr(); // 余额不够
else
throw CardErr(); // 卡冻结
}
// 中层:最多重试2次取钱
void bank()
{
for(int i=0; i<2; i++)
{
try
{
getMoney();
cout << "取款成功!" << endl;
return;
}
catch(const Err& e)
{
// 余额不足:重试
if(e.code == 1)
{
cout << "第" << i+1 << "次重试取款..." << endl;
if(i == 1) throw; // 2次都失败,重新抛出
}
else
{
throw; // 卡冻结,直接上交
}
}
}
}
int main()
{
srand(time(0));
try
{
bank();
}
catch(const Err& e)
{
cout << "银行最终提示:" << e.msg() << endl;
}
}
- 余额不足 → 多试两次
- 两次还不行 →
throw;往上抛- 银行卡冻结 → 根本没法重试,直接 throw; 上交
throw;不带东西 = 原样把错误传给上层
6.异常安全问题
6.1异常安全
异常抛出后,后面的代码就不再执行,前面申请了资源(内存、锁等),后面进行释放,但是中间可能会抛异常就会导致资源没有释放,这里由于异常就引发了资源泄漏,产生安全性的问题。
异常安全 = 程序抛异常时,不能丢资源!
资源包括:
- new 出来的内存
- 打开的文件
- 加的锁
- 连接的数据库
- 套接字
6.2异常导致的灾难:
你先申请资源 → 中间抛异常(后面代码没有执行) → 释放资源的代码没跑到→ 资源泄漏
double Divide(int a , int b)
{
if (b == 0)
{
// 除 0 错误,直接抛异常
throw "Division by zero condition !";
}
return (double)a / (double)b;
}
void Func()
{
int* array = nullptr; // 先初始化为空
try
{
// 1. 先申请内存
array = new int[10];
int len , time;
cin >> len >> time;
// 2. 这里可能抛异常!
cout << Divide(len , time) << endl;
}
catch (...) // 捕获所有异常
{
// 3. 异常来了!必须先释放内存!
cout << "delete []" << array << endl;
delete[] array;
// 4. 释放完,再把异常抛给外层处理
throw;
}
// 正常路径:也会释放
cout << "delete []" << array << endl;
delete[] array;
}
执行过程:

6.3异常安全终极方案
RAII = Resource Acquisition Is Initialization(智能指针)
资源获取即初始化
大白话:
- 把资源交给一个对象管理
- 构造函数拿资源
- 析构函数自动释放
- 离开作用域就自动释放
- 抛异常也会自动析构 → 绝对不泄漏
#include <iostream>
#include <memory>
using namespace std;
double Divide(int a, int b) {
if (b == 0)
throw "除零错误"; // 抛异常
return (double)a / b;
}
void Func() {
unique_ptr<int[]> array(new int[10]); // 智能指针
int a, b;
cin >> a >> b;
cout << Divide(a, b) << endl; // 这里可能抛异常
}
int main() {
try {
Func();
}
catch (const char* msg) {
cout << msg << endl;
}
return 0;
}
7.异常规范(noexcept)
7.1 C++98 旧写法(了解)
throw()表示不抛异常throw(int, string)表示可能抛这些类型- 缺点:太麻烦、实际几乎不用
7.2 C++11 新写法(记住)
noexcept:表示不会抛异常- 不加:表示可能抛异常
7.3 重要规则
- 编译器不检查你是否真的不抛
- 如果你加了
noexcept却抛了异常→ 程序直接崩溃!
7.4 noexcept 运算符
noexcept(表达式)- 不会抛异常 → 返回
true - 会抛异常 → 返回
false
#include <iostream>
using namespace std;
// 1. 声明不抛异常
void func() noexcept
{
cout << "我承诺不抛异常\n";
// throw "异常"; // 一旦打开,程序直接崩溃!
}
// 2. 没加 noexcept → 可能抛异常
double Div(int a, int b)
{
if (b == 0)
throw "除零错误";
return (double)a / b;
}
// 3. noexcept 运算符:检测是否会抛异常
int main()
{
cout << noexcept(func()) << endl; // 1(不抛)
cout << noexcept(Div(1,0)) << endl; // 0(会抛)
try {
Div(1, 0);
}
catch (const char* msg) {
cout << msg << endl;
}
return 0;
}更多推荐

所有评论(0)