目录

一、异常核心认知

1.1 什么是异常?

1.2 为什么抛弃 C 语言错误码,改用异常?

C 语言错误码的致命缺陷

C++ 异常的核心优势

1.3 异常核心三要素

二、异常基础语法

2.1 标准固定格式

2.2 最简实战案例:除零异常

2.3 多类型异常捕获 + 万能捕获

三、异常核心机制:堆栈展开

3.1 什么是堆栈展开(stack unwinding)?

3.2 堆栈展开核心特性

3.3 堆栈展开实战演示

四、C++ 标准异常体系

4.1 标准异常基类:std::exception

4.2 常用标准异常子类

4.3 标准异常使用案例

五、自定义异常类

5.1 手写自定义异常

六、异常说明规范(throw() 异常规格)

6.1 异常规格说明语法(C++11 考点)

七、异常重抛(进阶用法)

八、高频易错点 + 禁忌

九、面试满分问答

9.1 C++ 异常的原理是什么?

9.2 异常和错误码的区别?

9.3 什么是堆栈展开?有什么作用?

9.4 自定义异常为什么要继承 std::exception?

9.5 析构函数为什么不能抛异常?

十、全文总结


定位:独立完整、零基础入门、刷题工程刚需、期末必考、秋招面试底层全覆盖

说明:全文无前置依赖,从「为什么需要异常」到「底层堆栈展开」「手写异常类」「工程规范」层层递进,所有代码可直接编译运行。

一、异常核心认知

1.1 什么是异常?

程序运行过程中,发生的非语法错误、不可预期、非正常逻辑的错误情况,统称为异常。

常见异常场景:除零错误、数组越界、空指针访问、内存申请失败、文件打开失败、参数非法等。

C++ 异常是一套结构化、标准化的错误处理机制,用于替代传统 C 语言的错误码返回方式。

1.2 为什么抛弃 C 语言错误码,改用异常?

C 语言错误码的致命缺陷

  • 错误码容易被忽略:开发者可以不接收、不判断返回值,错误直接静默丢失

  • 代码冗余:每调用一个函数,都需要写大量 if 判断错误

  • 错误传递繁琐:多层函数嵌套,需要逐层 return 传递错误码

  • 无法携带详细错误信息:只能返回一个数字,无法描述错误原因、位置

C++ 异常的核心优势

  • 强制感知错误:异常不捕获直接终止程序,不会静默忽略 bug

  • 业务与错误逻辑分离:正常业务代码无冗余,错误统一集中处理

  • 跨层级传递:低层函数报错,可直接抛给顶层处理,无需逐层传递

  • 可携带丰富信息:异常对象可携带错误描述、行号、错误类型

1.3 异常核心三要素

C++ 异常机制由三个关键字完整支撑:

  • throw:抛出异常,主动触发错误

  • try:包裹可能出错的风险代码块

  • catch:捕获并处理对应类型的异常

二、异常基础语法

2.1 标准固定格式

try
{
    // 正常业务代码
    // 一旦throw抛出异常,后续代码不再执行
}
catch(异常类型1 参数)
{
    // 处理类型1的异常
}
catch(异常类型2 参数)
{
    // 处理类型2的异常
}
// ...可多个catch
catch(...)
{
    // 捕获所有未知异常(兜底)
}

2.2 最简实战案例:除零异常

#include <iostream>
using namespace std;

void divide(int a, int b)
{
    if (b == 0)
    {
        // 抛出异常,可以抛基础类型、字符串、对象
        throw "除数不能为0!";
    }
    cout << "结果:" << a / b << endl;
}

int main()
{
    try
    {
        divide(10, 0);
        cout << "正常执行完毕" << endl; // 异常后不会执行
    }
    catch(const char* err)
    {
        cout << "捕获异常:" << err << endl;
    }

    cout << "程序继续运行" << endl;
    return 0;
}

核心执行逻辑

  1. try 块内代码正常执行

  2. 遇到 throw,立即终止当前代码执行

  3. 匹配对应类型的 catch 块处理异常

  4. 处理完成后,跳出 try-catch,继续向下执行

2.3 多类型异常捕获 + 万能捕获

一个 try 可以对应多个 catch,精准匹配不同异常类型,catch(...) 捕获所有异常,必须放在最后。

#include <iostream>
using namespace std;

void test(int x)
{
    if (x == 0) throw 0;
    if (x == 1) throw "参数不合法";
    if (x == 2) throw 3.14;
}

int main()
{
    try
    {
        test(2);
    }
    catch(int e)
    {
        cout << "捕获int异常:" << e << endl;
    }
    catch(const char* e)
    {
        cout << "捕获字符串异常:" << e << endl;
    }
    catch(...)
    {
        cout << "捕获未知类型异常" << endl;
    }
    return 0;
}

考点:万能捕获 catch(...) 只能兜底,不能获取异常信息,工程中尽量精准捕获。

三、异常核心机制:堆栈展开(面试重中之重)

3.1 什么是堆栈展开(stack unwinding)?

面试满分定义:当函数抛出异常且本函数未捕获时,当前函数终止,局部对象自动析构,异常向上一层调用函数传递,逐层回溯,直到找到匹配的 catch 块,这个逐层退出函数、析构局部对象、向上传递异常的过程,叫做堆栈展开。

3.2 堆栈展开核心特性

  • 异常抛出后,当前函数后续代码全部不执行

  • 函数栈帧中所有局部栈对象会自动调用析构函数,无内存泄漏

  • 异常会逐层向上传递,直至被捕获

  • 全程未被捕获 → 程序直接终止(abort)

3.3 堆栈展开实战演示

#include <iostream>
using namespace std;

class Test
{
public:
    ~Test()
    {
        cout << "局部对象析构" << endl;
    }
};

void func3()
{
    Test t;
    throw "func3 异常";
    cout << "func3 后续代码" << endl; // 不执行
}

void func2()
{
    func3();
    cout << "func2 后续代码" << endl; // 不执行
}

void func1()
{
    func2();
}

int main()
{
    try
    {
        func1();
    }
    catch(const char* err)
    {
        cout << "捕获异常:" << err << endl;
    }
    return 0;
}

输出顺序证明:先析构局部对象,再上层捕获异常,完美验证堆栈展开机制。

核心价值:C++ 异常机制保证栈对象资源自动释放,有效避免内存泄漏。

四、C++ 标准异常体系(工程必备)

4.1 标准异常基类:std::exception

C++ 所有标准异常都继承自 std::exception,定义在 <stdexcept> 头文件。

核心虚函数:virtual const char* what() const throw(),用于返回异常描述信息。

4.2 常用标准异常子类(全覆盖)

  • logic_error:逻辑错误(代码逻辑问题,可提前规避)

  • runtime_error:运行时错误(运行中突发,无法提前预判)

  • out_of_range:下标越界异常

  • invalid_argument:参数非法异常

  • bad_alloc:new 内存申请失败异常

  • bad_cast:类型转换失败异常

4.3 标准异常使用案例

#include <iostream>
#include <stdexcept>
using namespace std;

int main()
{
    try
    {
        throw out_of_range("数组下标越界错误");
    }
    catch(const exception& e)
    {
        // 统一调用what()获取错误信息
        cout << "标准异常:" << e.what() << endl;
    }
    return 0;
}

工程规范:捕获标准异常建议使用 const exception& 引用捕获,实现多态匹配所有子类异常。

五、自定义异常类

实际开发中,标准异常信息太笼统,需要自定义异常类,携带错误类型、行号、详细描述。

5.1 手写自定义异常

#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;

// 自定义异常类:继承标准异常基类
class MyException : public exception
{
private:
    string msg;
public:
    // 构造函数接收错误信息
    MyException(const string& errMsg) : msg(errMsg) {}

    // 重写what虚函数,返回自定义错误信息
    virtual const char* what() const throw()
    {
        return msg.c_str();
    }
};

void testFunc(int age)
{
    if (age < 0)
    {
        // 抛出自定义异常对象
        throw MyException("年龄数值非法,不能为负数!");
    }
    cout << "年龄合法" << endl;
}

int main()
{
    try
    {
        testFunc(-10);
    }
    catch(const MyException& e)
    {
        cout << "自定义异常捕获:" << e.what() << endl;
    }
    catch(const exception& e)
    {
        cout << "系统异常:" << e.what() << endl;
    }
    return 0;
}

面试满分要点:自定义异常必须继承 std::exception、重写 what() 函数、使用 const 修饰、抛出对象而非基础类型。

六、异常说明规范(throw() 异常规格)

6.1 异常规格说明语法(C++11 考点)

用于声明函数允许抛出哪些异常,提高代码可读性与安全性。

  • void func() throw():函数不抛出任何异常

  • void func() throw(int):只允许抛出 int 类型异常

  • void func() noexcept:C++11 新标准,声明无异常(推荐)

注意:C++11 弃用动态异常规格,推荐使用 noexcept

七、异常重抛(进阶用法)

捕获异常后,无法完全处理,可以重新抛出异常,交给上层逻辑处理。

#include <iostream>
#include <stdexcept>
using namespace std;

void inner()
{
    throw runtime_error("底层读写失败");
}

void outer()
{
    try
    {
        inner();
    }
    catch(const exception& e)
    {
        cout << "局部日志记录:" << e.what() << endl;
        throw; // 重抛当前异常,交给上层处理
    }
}

int main()
{
    try
    {
        outer();
    }
    catch(const exception& e)
    {
        cout << "顶层处理异常:" << e.what() << endl;
    }
    return 0;
}

核心规则throw; 无参数重抛,保留原异常信息,是工程分层处理异常的核心用法。

八、高频易错点 + 禁忌(避坑必背)

  • 异常不捕获直接崩:throw 抛出的异常若无任何 catch 捕获,程序调用 abort 终止

  • 匹配顺序从上到下:catch 顺序必须先子类后父类,父类放前面会拦截所有子类异常

  • 禁止值捕获异常:尽量用引用捕获,避免拷贝开销、避免切片问题

  • throw后代码终止:try 块内抛出异常后,后续代码绝对不会执行

  • 析构函数禁止抛异常:析构函数抛异常会导致程序直接崩溃、资源混乱

  • 不要滥用万能捕获:catch(...) 会隐藏未知 bug,不利于问题排查

九、面试满分问答

9.1 C++ 异常的原理是什么?

当代码执行 throw 时,触发异常机制,启动堆栈展开,逐层析构栈对象、向上传递异常,直到匹配到对应类型的 catch 块完成处理;若全程无捕获,程序终止。异常机制实现了错误与业务逻辑解耦,保证栈资源自动释放。

9.2 异常和错误码的区别?

错误码需要逐层判断、逐层传递,代码冗余、易忽略错误;异常自动跨层传递、强制处理、错误信息丰富,业务代码干净,容错性更强。

9.3 什么是堆栈展开?有什么作用?

异常未被当前函数捕获时,逐层退出函数、析构局部栈对象、向上传递异常的过程就是堆栈展开。作用是自动释放栈资源,避免内存泄漏,保证程序资源安全

9.4 自定义异常为什么要继承 std::exception?

继承标准异常基类,可以统一重写 what() 接口,统一异常捕获规范,兼容标准异常处理逻辑,实现多态统一管理所有异常类型。

9.5 析构函数为什么不能抛异常?

异常触发时会自动调用析构函数释放资源,若析构函数内部再次抛异常,会导致双重异常触发,程序直接异常终止,造成资源泄露与程序崩溃。

十、全文总结

1、C++ 异常由 try、throw、catch 构成,用于替代传统错误码,实现优雅的错误处理;

2、核心机制为堆栈展开,自动析构栈对象,规避栈内存泄漏;

3、所有标准异常继承 exception 基类,通过 what() 获取错误信息;

4、工程优先使用自定义异常,精准区分错误类型,支持分层重抛处理;

5、核心禁忌:析构不抛异常、catch 先子类后父类、少用万能捕获、优先引用捕获。

更多推荐