C++ 异常 完整讲解
C++ 异常是程序运行时错误处理机制 ,用于分离错误抛出 和错误处理代码,替代传统返回值判错,让代码结构更清晰、容错性更强。
一、基本语法
核心三关键字:
throw:主动抛出异常try:包裹可能出错的代码块catch:捕获并处理对应类型的异常
基础格式
cpp
try {
// 正常业务代码,可能抛出异常
throw 异常数据;
}
catch(异常类型 变量) {
// 捕获并处理异常
}
二、简单示例
1. 抛出、捕获基本类型异常
cpp
#include <iostream>
using namespace std;
void divide(int a, int b) {
if (b == 0) {
// 抛出 int 类型异常
throw 100;
}
cout << a / b << endl;
}
int main() {
try {
divide(10, 0);
}
// 捕获 int 类型异常
catch (int err) {
cout << "捕获异常,错误码:" << err << ",除数不能为0" << endl;
}
return 0;
}
2. 抛出字符串异常
cpp
void test() {
throw "参数非法";
}
int main() {
try {
test();
}
catch (const char* msg) {
cout << "异常信息:" << msg << endl;
}
return 0;
}
三、多异常捕获(多个 catch)
一个 try 可搭配多个 catch ,按从上到下顺序匹配,匹配到即执行,不再向后匹配。
cpp
#include <iostream>
#include <string>
using namespace std;
void func(int num) {
if (num == 0)
throw 0;
else if (num == 1)
throw "数值为1";
else
throw string("未知错误");
}
int main() {
try {
func(1);
}
catch (int e) {
cout << "捕获int异常:" << e << endl;
}
catch (const char* e) {
cout << "捕获字符串异常:" << e << endl;
}
catch (string e) {
cout << "捕获string异常:" << e << endl;
}
return 0;
}
万能捕获 catch(...)
... 可以捕获所有类型异常 ,必须放在所有 catch 的最后面。
cpp
try {
// 任意出错代码
}
catch (int) { }
catch (const char*) { }
catch (...) {
cout << "捕获未知类型异常" << endl;
}
四、异常的传递(跨函数传播)
异常不会被函数直接消化:
- 当前函数没有
try-catch,异常会向上层调用函数传递 - 逐层向上,直到被
catch捕获 - 若最终没人捕获,程序直接终止
cpp
void A() { throw "A函数出错"; }
void B() { A(); }
void C() { B(); }
int main() {
try {
C();
}
catch (const char* e) {
cout << e << endl;
}
return 0;
}
五、C++ 标准异常类(重点)
C++ 内置一套标准异常体系 ,定义在头文件 <stdexcept>,所有标准异常都继承自 std::exception。
1. 常用标准异常
| 异常类 | 说明 |
|---|---|
exception |
所有标准异常基类 |
runtime_error |
运行时异常(运行阶段出错) |
logic_error |
逻辑错误(代码设计问题) |
out_of_range |
下标越界 |
invalid_argument |
无效参数 |
overflow_error |
数值溢出 |
2. 基类核心方法
what():返回异常描述字符串,虚函数,子类可重写。
3. 使用标准异常示例
cpp
#include <iostream>
#include <stdexcept>
using namespace std;
int main() {
try {
throw out_of_range("数组下标越界啦");
}
// 捕获基类 exception,可接收所有子类异常
catch (const exception& e) {
cout << "异常信息:" << e.what() << endl;
}
return 0;
}
建议:捕获时使用 引用
const exception&,避免拷贝、防止切片。
六、自定义异常类(工程常用)
继承 std::exception,重写 what() 实现自定义异常。
cpp
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
// 自定义异常类
class MyException : public exception {
private:
string msg;
public:
MyException(const string& s) : msg(s) {}
// 重写 what 方法
const char* what() const noexcept override {
return msg.c_str();
}
};
void test() {
throw MyException("自定义业务异常:权限不足");
}
int main() {
try {
test();
}
catch (const MyException& e) {
cout << e.what() << endl;
}
return 0;
}
noexcept:C++11 及以上,表示该函数不会抛出异常。
七、异常接口声明(了解)
C++ 允许声明函数允许抛出哪些异常,现代 C++ 已逐渐弱化:
void f() throw();// C++03:不抛出任何异常void f() throw(int);// 只允许抛出 int 异常
C++11 推荐使用
noexcept关键字:
cppvoid func() noexcept; // 保证函数不抛异常
八、异常与析构函数
析构函数绝对不要抛出异常 !
原因:
- 异常触发时栈展开会自动调用析构函数
- 若析构函数再抛异常,程序会直接崩溃
规范:析构函数必须加noexcept。
九、栈展开(Stack Unwinding)
抛出异常后,程序会从抛出点向上回溯:
- 依次销毁当前作用域内的局部对象(调用析构)
- 直到找到匹配的
catch - 这个过程称为栈展开
十、noexcept 关键字(C++11 核心)
- 修饰函数:表明函数不会抛出异常
- 编译器会做检查,违反则终止程序
- 作用:提升性能、保证容器/移动语义安全
cpp
void safeFunc() noexcept {
// 这里不能 throw
}
十一、异常优缺点总结
优点
- 错误处理与业务代码分离,可读性强
- 异常可跨多层函数传递,不用每层判断返回值
- 支持标准异常、自定义异常,分类清晰
缺点
- 有一定性能开销(栈展开、对象构造析构)
- 流程跳转复杂,调试难度略高
- 不能用于嵌入式、高实时性等对性能极致要求的场景
十二、使用规范(面试/工程建议)
- 优先使用 C++ 标准异常,业务场景再自定义异常
- 捕获使用引用
const 异常类&,避免拷贝和切片 catch(...)只做兜底,尽量精确捕获异常类型- 析构函数、移动构造/移动赋值必须
noexcept,禁止抛异常 - 不要在循环、高频调用场景滥用异常(性能差)
- 异常只处理运行时错误,正常逻辑不要用异常跳转