C++异常处理核心知识点全解析

一、基础必背概念

1. 异常是什么

程序运行时发生意料之外的错误 (除零、越界、内存失败、参数非法等),跳出正常执行流程,统一错误处理机制。

2. 三大关键字

  • throw:主动抛出异常
  • try:包裹可能出错的代码块
  • catch:捕获并处理异常

3. 异常匹配规则

  1. 严格类型匹配,不做隐式类型转换
  2. catch 顺序从上到下匹配
  3. 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_allocbad_castbad_typeid

2. 标准 exception 核心接口

复制代码
class exception {
public:
    virtual const char* what() const noexcept;
    virtual ~exception() noexcept = default;
};
  • what():虚函数,返回异常信息
  • 析构虚函数:多态捕获不切片
  • noexcept:保证自身不抛异常

三、高频面试题(带答案)

1. 为什么捕获异常要用 const std::exception&

  1. 引用:避免对象切片,保留子类异常信息
  2. const:不修改异常对象,语义安全
  3. 基类引用支持多态,一个 catch 捕获所有标准异常

2. 析构函数可以抛异常吗?

绝对不建议,禁止主动抛

  1. 异常触发栈展开时,会自动调用析构
  2. 若析构再抛第二个异常,程序直接 terminate 崩溃
  3. 工程规范:析构函数必须加 noexcept

3. noexcept 作用

  1. 声明函数不会抛出异常
  2. 编译器可做优化
  3. STL 容器会判断移动构造是否 noexcept,决定是否走移动语义
  4. noexcept(true/false) 可做编译期判断

4. 老式异常说明 throw(type) 为什么废弃?

  1. 运行时检查,开销大
  2. 声明和实现容易不一致
  3. C++11 统一用 noexcept 替代

5. catch(...) 作用和注意点

  • 捕获所有任意类型异常
  • 只能拿到异常发生,拿不到具体信息
  • 必须放在所有 catch 最后
  • 一般用于兜底、日志记录、防止程序直接崩溃

6. 自定义异常怎么写规范?

  1. 公有继承 std::exception
  2. 重写 what(),加 const noexcept override
  3. 保存错误信息字符串
  4. 多态捕获用基类引用

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;
}

五、工程最佳实践总结

  1. 继承场景基类析构虚函数,异常类同理
  2. 捕获永远用 const std::exception&
  3. 自定义异常必继承标准库、重写 what
  4. 析构、移动构造、赋值尽量标 noexcept
  5. 不在循环里滥用 try-catch,只在上层统一捕获
  6. 禁止析构函数、默认构造里抛异常
相关推荐
CoderCodingNo1 小时前
【信奥业余科普】C++ 的奇妙之旅 | 17:面的铺展与文本的本质——二维数组与字符串
开发语言·c++
J2虾虾1 小时前
Java Lambda 表达式详解文档
java·开发语言
csbysj20201 小时前
CSS 网格元素
开发语言
lly2024061 小时前
DOM 元素:深入理解与高效运用
开发语言
鸟儿不吃草1 小时前
安卓实现左右布局聊天界面
android·开发语言·python
迷途之人不知返2 小时前
优先级队列:priority_queue
数据结构·c++
曦夜日长2 小时前
C++ STL容器string(一):string的变量细节、默认函数的认识以及常用接口的使用
java·开发语言·c++
代码中介商2 小时前
C++ STL 标准模板库完全指南:从容器到迭代器
开发语言·c++·stl
winner88812 小时前
C++ 构造函数、析构函数、虚函数、虚析构
开发语言·c++