学而时习之:C++中的异常处理1

C++ 异常处理

异常处理是 C++ 提供的一种结构化机制 ,用于在程序运行期间检测并处理错误。常见的运行时错误包括:

  • 除以零
  • 访问非法内存
  • 文件读写失败

C++ 异常处理的工作方式

当错误发生时,不再让程序直接崩溃,而是通过"抛出 → 捕获 → 处理"的流程进行优雅恢复

  1. 抛出异常(throw)

    出现错误或意外情况时,用 throw 关键字"抛出"一个异常对象。

  2. 捕获异常(catch)

    程序沿着调用栈向上查找类型匹配catch 块,一旦找到就拦截该异常。

  3. 处理异常
    catch 块内编写应对逻辑,使程序可以恢复运行优雅退出

try-catch 块

C++ 自有的异常处理机制:

  • 可能出错 的代码放进 try 块;
  • 处理错误 的代码放进 catch 块。

语法:

cpp 复制代码
try {
    // 可能抛出异常的代码
}
catch (异常类型 变量) {
    // 异常处理代码
}

执行流程:

一旦 try 块内抛出异常,立即中断 后续语句,跳转 到类型匹配的 catch 块执行;

若无匹配 catch,异常继续沿调用链向上传递。

抛出异常(Throwing Exceptions)

"抛出异常"就是从 try 块中返回一个代表错误的值 ,系统根据该值的类型寻找匹配的 catch 块。

使用关键字 throw 完成抛出。

语法:

cpp 复制代码
try {
    throw 表达式;
}
catch (类型 变量) {
    // 处理代码
}

可抛出的值分三类:

  1. 内置类型
  2. 标准库异常类
  3. 自定义异常类

A.抛出内置类型

最简单,但几乎不带任何语义信息。示例:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    int x = 7;
    try {
        if (x % 2 != 0){
            throw -1;  // 抛出 int 值
        }       // 抛出 int 值
    }
    catch (int e) {       // 捕获 int
        cout << "捕获异常: " << e;
    }
    return 0;
}
makefile 复制代码
捕获异常: -1

缺点:必须靠错误码 判断问题,与 if-else 无异。C++ 推荐的做法是:抛出带有错误描述的类对象,而非裸值。

B.抛出标准异常

标准异常是一组已定义好的类 ,用于表示常见的各种错误。

这些类都位于头文件 <stdexcept> 中,并统一继承自基类 std::exception,形成一棵标准的异常类继承树。

(下图给出了 C++ 标准异常的层次结构。)

使用标准异常的好处:

  • 语义清晰,可携带详细的错误信息;
  • 与 C++ 标准库异常体系无缝衔接;
  • 支持 what() 成员函数,返回描述性字符串。 这些异常会被 C++ 标准库自身抛出,因此我们必须学会如何处理它们。
    每个标准异常都提供 what() 方法,返回描述异常详情的字符串。

示例:
vector::at() 在索引越界时会抛出 out_of_range 异常。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3};

    try {
        v.at(10);          // 访问越界元素
    }
    catch (out_of_range e) {
        cout << "捕获到: " << e.what();
    }
    return 0;
}
kotlin 复制代码
捕获到: vector::_M_range_check: __n (which is 10) >= this->size() (which is 3)

我们也可以用 throw 语句手动抛出这些标准异常。

C++定义好的异常 主要用途 典型抛出场景
out_of_range 访问超出有效范围 vector::at()string::at()索引越界
length_error 试图创建超过最大长度的对象 string构造函数长度过大,vector::reserve()超限
domain_error 数学运算定义域错误 数学函数参数不在有效定义域内
invalid_argument 函数参数无效 参数不符合函数要求
range_error 计算结果超出有意义的值域 浮点数运算结果精度损失
overflow_error 算术运算上溢 计算结果超出数据类型上限
underflow_error 算术运算下溢 计算结果低于数据类型下限

C.抛出自定义异常

当标准异常无法满足需求时,可以自己写一个异常类

推荐继承 std::exception,这样就能与标准库的异常体系无缝衔接(当然不强制)。

完整示例:

cpp 复制代码
#include <iostream>
#include <exception>
using namespace std;

/* ---------- 自定义异常类 ---------- */
class NegativeValueException : public exception {
private:
    int value;                      // 保存出错数值
public:
    explicit NegativeValueException(int val) : value(val) {}

    // 重写 what(),返回错误描述
    const char* what() const noexcept override {
        return "出现负值错误!";
    }

    // 可选:提供接口获取当时传入的非法值
    int getValue() const { return value; }
};

/* ---------- 可能抛出异常的函数 ---------- */
void checkValue(int x) {
    if (x < 0)
        throw NegativeValueException(x);   // 抛出自定义异常对象
    else
        cout << "数值为: " << x << endl;
}

/* ---------- 使用示例 ---------- */
int main() {
    int numbers[] = {10, -5, 20};

    for (int n : numbers) {
        try {
            checkValue(n);
        }
        catch (NegativeValueException& e) {          // 捕获自定义异常
            cout << "捕获异常: " << e.what() << " 非法值 = " << e.getValue() << endl;
        }
    }
    return 0;
}
makefile 复制代码
数值为: 10
捕获异常: 出现负值错误! 非法值 = -5
数值为: 20

捕获异常catch

如前所述,catch 块用来接收并处理 try 块中抛出的异常。 catch 括号里必须写一个与抛出异常同类型的形参:

cpp 复制代码
catch (异常类型 e) {
    // 处理语句
}

其中 e 是给异常对象起的名字。 只有当 try 块里抛出的异常类型与这里的 异常类型 匹配时,才会进入该 catch 块执行其内部的代码。

A.捕获多种异常

一个 try 块后面可以跟多个 catch 块,用来分别处理不同类型的异常。例如:

cpp 复制代码
try {
    // 可能抛出多种异常的代码
}
catch (类型1 e) {
    // 当抛出的异常是类型1 时执行
}
catch (类型2 e) {
    // 当抛出的异常是类型2 时执行
}
catch (...) {
    // 兜底:前面都没匹配到时执行
}

说明

  • 编译器会按从上到下的顺序 寻找第一个类型匹配的 catch
  • catch(...) 称为"通配捕获",可接住任何类型 的异常,常用来做最后兜底处理。

B.按值或按引用捕获

与函数传参一样,catch 也能"按值"或"按引用"接收异常对象,两者各有用途。

B1. 按值捕获

复制一份被抛出的异常对象。 由于异常对象通常不大,复制开销一般可接受。

cpp 复制代码
catch (runtime_error e)          // 按值
{
    cout << "Caught: " << e.what();
}
B2. 按引用捕获

直接绑定到原异常对象,无复制开销 ; 核心优势在于支持多态 :可以用基类引用捕获派生类异常,从而一个 catch 就能处理整个继承体系。

cpp 复制代码
catch (exception& e)             // 按引用(基类)
{
    cout << "Caught: " << e.what();
}

示例中抛出的是 runtime_error,却被 exception& 成功捕获,这就是多态异常处理的典型用法。

异常传播

异常传播描述"异常在调用栈中如何向上传递"。

  1. 一旦 throw 抛出异常,当前代码块立即终止 ,栈上已构造的局部对象会被自动析构 (栈回溯),但 new 出来的动态资源需要手动释放。
  2. 运行时沿着调用链逐帧向上 查找类型匹配的 catch 块,这一过程称为 stack unwinding(栈展开)
  3. 找到对应 catch 即捕获并处理;若最终仍未匹配,程序调用 std::terminate() 终止。

示例流程:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

class GfG {
public:
    GfG()  { cout << "Object Created\n"; }
    ~GfG() { cout << "Object Destroyed\n"; }
};

int main() {
    try {
        cout << "Inside try block\n";
        GfG gfg;      // 构造局部对象
        throw 10;     // 抛出异常
        cout << "After throw\n";  // 不会执行
    }
    catch (int e) {
        cout << "Exception Caught\n";
    }
    cout << "After catch";
    return 0;
}
javascript 复制代码
Inside try block
Object Created
Object Destroyed     ← 栈展开时自动析构
Exception Caught
After catch

嵌套 try-catch 块

在 C++ 里,try-catch 块可以嵌套 在另一个 trycatch 块内部。结构如下:

cpp 复制代码
try {                      // 外层
    // ... 可能抛 e2
    try {                  // 内层
        // ... 可能抛 e1
    }
    catch (eType1 e1) {    // 内层只匹配 eType1
        // 处理 e1
    }
}
catch (eType2 e2) {        // 外层可处理内层未接住的其他异常
    // 处理 e2
}
  • 若内层抛出的异常不是 eType1,内层 catch 不会执行,异常继续向外层传播 ,由外层 catch 尝试匹配。
  • 如此可形成多级异常处理链。

重新抛出异常(Rethrow) 在内层 catch 中,可用空 throw 语句

cpp 复制代码
throw;   // 必须位于 catch 块内

把已捕获的异常原样再次抛出 ,保留其类型与信息,供更外层 catch 继续处理。

这种方式常用于分层处理:底层记录日志/做清理,上层决定最终策略。

异常规格说明

C++ 允许在函数声明中显式说明该函数是否可能抛出异常。现代 C++ 只有两种规格:

规格 含义
noexcept 保证 此函数不会抛出异常 ;若运行时仍抛出,将调 std::terminate() 终止程序。
noexcept(false) 可能抛出异常 ;这是默认行为(不写任何规格时即为此)。

写法示例:

cpp 复制代码
void func1(int a) noexcept          // 承诺不抛异常
{
    // ...
}

void func2(int b) noexcept(false)   // 允许抛异常(可省略不写)
{
    // ...
}

注:旧式 throw(type-list) 规格已在 C++11 起被废弃 ,现代代码应使用 noexcept

相关推荐
智者知已应修善业9 分钟前
【求等差数列个数/无序获取最大最小次大次小】2024-3-8
c语言·c++·经验分享·笔记·算法
..过云雨1 小时前
17-2.【Linux系统编程】线程同步详解 - 条件变量的理解及应用
linux·c++·人工智能·后端
量子炒饭大师1 小时前
Cyber骇客的逻辑节点美学 ——【初阶数据结构与算法】二叉树
c语言·数据结构·c++·链表·排序算法
fpcc2 小时前
C++编程实践—false_type和true_type的实践应用
c++
量子炒饭大师2 小时前
Cyber骇客神经塔尖协议 ——【初阶数据结构与算法】堆
c语言·数据结构·c++·二叉树·github·
王老师青少年编程3 小时前
2025年12月GESP(C++二级): 环保能量球
c++·算法·gesp·csp·信奥赛·二级·环保能量球
CoderCodingNo3 小时前
【GESP】C++五级真题(贪心思想考点) luogu-P11960 [GESP202503 五级] 平均分配
开发语言·c++·算法
不会写代码的里奇4 小时前
深入解析ASR技术:从原理到C++高性能实现
c++
CSDN_RTKLIB4 小时前
【类定义系列六】C++17新特性
开发语言·c++
hd51cc4 小时前
MFC文件操作
c++·mfc