C++: 异常处理
最后更新:2026-08-26
程序运行时,总会遇到意外:文件不存在、内存不足、网络断开……
如果不管,程序就会崩溃。
异常处理就是程序的"安全气囊"——出问题时,优雅地应对,而不是直接崩溃。
1. 异常处理基础
(1) 1.1 什么是异常?
异常是程序运行时出现的错误或意外情况。
常见异常:
- 除以零
- 访问空指针
- 文件打开失败
- 内存分配失败
(2) 1.2 异常处理三要素
C++异常处理用三个关键字:
| 关键字 | 功能 |
|---|---|
try |
监控可能出错的代码 |
catch |
捕获并处理异常 |
throw |
抛出异常 |
(3) 1.3 基本示例
示例:除以零异常(难度⭐)
▶ 示例 1:代码示例(难度⭐)
#include <iostream>
int divide(int a, int b) {
if (b == 0) {
throw "除数不能为0!"; // 抛出异常
}
return a / b;
}
int main() {
try {
std::cout << divide(10, 2) << std::endl; // 正常
std::cout << divide(10, 0) << std::endl; // 抛出异常
}
catch (const char* msg) { // 捕获异常
std::cerr << "错误:" << msg << std::endl;
}
std::cout << "程序继续运行" << std::endl;
return 0;
}
输出:
5
错误:除数不能为0!
程序继续运行
运行结果:
5
错误:除数不能为0!
程序继续运行
💡 提示:
- 异常被捕获后,程序不会崩溃,而是继续执行
2. 异常类型
(1) 2.1 抛出的类型
throw 可以抛出任何类型:
| 类型 | 示例 |
|---|---|
| 基本类型 | throw 42; |
| 字符串 | throw "error"; |
| 标准异常 | throw std::runtime_error("error"); |
| 自定义异常 | throw MyException(); |
(2) 2.2 捕获多个异常
示例:捕获不同类型的异常(难度⭐⭐)
#include <iostream>
### ▶ 示例 2:代码示例(难度⭐)
#include <string>
void process(int value) {
if (value == 0) {
throw 0; // 抛出int
}
if (value < 0) {
throw std::string("负数"); // 抛出string
}
}
int main() {
try {
process(-5);
}
catch (int e) {
std::cerr << "捕获int异常:" << e << std::endl;
}
catch (const std::string& e) {
std::cerr << "捕获string异常:" << e << std::endl;
}
return 0;
}
3. 标准异常
(1) 3.1 std::exception层次
C++标准库定义了一套异常类,都在 stdexcept 中:
std::exception
├── std::logic_error
│ ├── std::invalid_argument
│ ├── std::out_of_range
│ └── std::length_error
└── std::runtime_error
├── std::overflow_error
└── std::underflow_error
(2) 3.2 常用标准异常
| 异常类 | 说明 |
|---|---|
std::invalid_argument |
参数非法 |
std::out_of_range |
越界 |
std::runtime_error |
运行时错误 |
std::bad_alloc |
内存分配失败(new失败) |
示例:使用标准异常(难度⭐⭐)
#include <iostream>
#include <stdexcept>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3};
try {
v.at(10); // 越界访问
}
catch (const std::out_of_range& e) {
std::cerr << "捕获异常:" << e.what() << std::endl;
}
return 0;
}
4. 自定义异常
(1) 4.1 继承std::exception
最佳实践: 自定义异常类继承 std::exception。
示例:自定义异常类(难度⭐⭐⭐)
#include <iostream>
#include <exception>
#include <string>
// 自定义异常类
class MyException : public std::exception {
private:
std::string msg;
public:
MyException(const std::string& msg) : msg(msg) {}
const char* what() const noexcept override {
return msg.c_str();
}
};
int main() {
try {
throw MyException("自定义异常");
}
catch (const MyException& e) {
std::cerr << "捕获自定义异常:" << e.what() << std::endl;
}
return 0;
}
5. 异常安全
(1) 5.1 什么是异常安全?
异常安全是指:即使抛出异常,程序也能保持一致状态(不泄漏资源、不破坏数据)。
三个保证层次:
- 基本保证:程序状态一致,但可能变化
- 强保证:操作要么成功,要么完全回滚(像没发生过)
- 不抛异常保证:函数绝不抛出异常
(2) 5.2 RAII——资源管理的利器
RAII(Resource Acquisition Is Initialization):用对象管理资源,析构函数自动释放。
示例:用智能指针保证异常安全(难度⭐⭐⭐)
#include <iostream>
#include <memory>
void process() {
// 用unique_ptr管理内存,即使抛出异常也会自动释放
std::unique_ptr<int> p(new int(42));
// ... 可能抛出异常的代码 ...
std::cout << *p << std::endl;
} // p自动释放
int main() {
try {
process();
}
catch (...) {
std::cerr << "捕获异常" << std::endl;
}
return 0;
}
6. noexcept关键字
(1) 6.1 基本用法
C++11引入 noexcept,声明函数不抛出异常。
示例:声明不抛异常(难度⭐)
void safeFunction() noexcept {
// 这个函数保证不抛出异常
}
void unsafeFunction() {
// 可能抛出异常
}
💡 提示:
- 析构函数默认
noexcept noexcept有助于编译器优化
❓ 常见问题
Q:什么时候用异常? A:用于真正意外的情况,不用于正常控制流。
Q:catch(...)是什么? A:捕获所有异常,通常用于清理资源后重新抛出。
try {
// ...
}
catch (...) {
// 清理资源
throw; // 重新抛出
}
Q:构造函数能抛异常吗? A:可以,但要小心内存泄漏。用智能指针或RAII管理资源。
▶ 示例 3:捕获异常(难度⭐)
#include <iostream>
#include <stdexcept>
double divide(double a, double b) {
if (b == 0) {
throw std::runtime_error("除数不能为0");
}
return a / b;
}
int main() {
try {
std::cout << divide(10, 2) << std::endl;
std::cout << divide(10, 0) << std::endl;
} catch (const std::exception& e) {
std::cout << "错误:" << e.what() << std::endl;
}
return 0;
}
输出:
错误:除数不能为0
try-catch 捕获异常,throw 抛出异常。what() 返回错误信息。
| 知识点 | 要点 |
|---|---|
| try-catch-throw | 异常处理三要素 |
| 标准异常 | std::runtime_error等 |
| 自定义异常 | 继承 std::exception |
| 异常安全 | RAII保证资源不泄漏 |
| noexcept | 声明函数不抛异常 |
📖 小节
- try/catch:捕获并处理异常
- throw:抛出异常
- std::exception:标准异常基类
- 自定义异常:继承 std::exception 实现自己的异常类
📝 作业
-
**基础题 (Difficulty ⭐):写一个除法函数,当除数为 0 时抛出 std::runtime_error,在 main 中用 try/catch 捕获并输出错误信息。
-
**进阶题 (Difficulty ⭐⭐):自定义一个异常类(继承 std::exception),包含错误码和错误描述两个字段,throw 后在 catch 中获取。
-
**挑战题 (Difficulty ⭐⭐⭐):实现一个"资源守卫"类,构造函数获取资源,析构函数释放资源。即使中间抛出异常,资源也能正确释放(RAII 原理)。
- 异常处理三部分:try 监视、throw 抛出、catch 捕获
- 标准异常类继承自 std::exception
- catch 按类型匹配,多个 catch 从上到下
- 栈展开:异常抛出后逐层寻找 catch
- noexcept 声明函数不抛异常
下一课:文件操作进阶(#41)