一、基础必背概念
1. 异常是什么
程序运行时发生意料之外的错误 (除零、越界、内存失败、参数非法等),跳出正常执行流程,统一错误处理机制。
2. 三大关键字
throw:主动抛出异常try:包裹可能出错的代码块catch:捕获并处理异常
3. 异常匹配规则
- 严格类型匹配,不做隐式类型转换
- 按
catch顺序从上到下匹配 catch(...)万能捕获,放最后
4. 栈展开(Stack Unwinding)
抛出异常后,从 throw 位置逐层退出函数栈 ,局部对象自动析构,直到找到匹配 catch。
核心意义:RAII 能自动释放资源,不用手动兜底。
二、std::exception 体系
1. 继承结构
std::exception
├─ std::logic_error 逻辑错误(编译/设计可避免)
│ ├─ invalid_argument 参数非法
│ ├─ out_of_range 下标越界
│ └─ domain_error 定义域错误
└─ std::runtime_error 运行时错误(运行才会发生)
├─ overflow_error 溢出
└─ underflow_error 下溢
还有特殊:bad_alloc、bad_cast、bad_typeid
2. 标准 exception 核心接口
class exception {
public:
virtual const char* what() const noexcept;
virtual ~exception() noexcept = default;
};
what():虚函数,返回异常信息- 析构虚函数:多态捕获不切片
noexcept:保证自身不抛异常
三、高频面试题(带答案)
1. 为什么捕获异常要用 const std::exception&?
- 引用:避免对象切片,保留子类异常信息
- const:不修改异常对象,语义安全
- 基类引用支持多态,一个 catch 捕获所有标准异常
2. 析构函数可以抛异常吗?
绝对不建议,禁止主动抛
- 异常触发栈展开时,会自动调用析构
- 若析构再抛第二个异常,程序直接
terminate崩溃 - 工程规范:析构函数必须加 noexcept
3. noexcept 作用
- 声明函数不会抛出异常
- 编译器可做优化
- STL 容器会判断移动构造是否
noexcept,决定是否走移动语义 noexcept(true/false)可做编译期判断
4. 老式异常说明 throw(type) 为什么废弃?
- 运行时检查,开销大
- 声明和实现容易不一致
- C++11 统一用
noexcept替代
5. catch(...) 作用和注意点
- 捕获所有任意类型异常
- 只能拿到异常发生,拿不到具体信息
- 必须放在所有
catch最后 - 一般用于兜底、日志记录、防止程序直接崩溃
6. 自定义异常怎么写规范?
- 公有继承
std::exception - 重写
what(),加const noexcept override - 保存错误信息字符串
- 多态捕获用基类引用
7. 异常和返回值错误码对比
| 异常 | 返回值错误码 |
|---|---|
| 正常逻辑和错误逻辑分离 | 代码耦合,每层都要判断返回值 |
| 自动栈展开,RAII 释放资源 | 容易忘记判断,漏处理错误 |
| 有运行时开销 | 开销极小 |
| 适合:致命 / 少见错误 用异常;常规可预期小错误用错误码。 |
四、手写:自定义标准异常(可直接作业 / 面试默写)
#include <iostream>
#include <exception>
#include <string>
// 自定义异常类
class MyException : public std::exception
{
private:
std::string msg;
public:
explicit MyException(const std::string& s) : msg(s) {}
// 重写what,严格带 const noexcept override
const char* what() const noexcept override
{
return msg.c_str();
}
};
void divide(int a, int b)
{
if (b == 0)
{
throw MyException("除数不能为0");
}
std::cout << "结果:" << a / b << std::endl;
}
int main()
{
try
{
divide(10, 0);
}
// 多态捕获:基类常量引用
catch (const std::exception& e)
{
std::cout << "异常捕获:" << e.what() << std::endl;
}
catch (...)
{
std::cout << "未知异常" << std::endl;
}
return 0;
}
五、工程最佳实践总结
- 继承场景基类析构虚函数,异常类同理
- 捕获永远用
const std::exception& - 自定义异常必继承标准库、重写 what
- 析构、移动构造、赋值尽量标
noexcept - 不在循环里滥用 try-catch,只在上层统一捕获
- 禁止析构函数、默认构造里抛异常