首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >《C++ 程序设计》第 12 章 - 异常处理

《C++ 程序设计》第 12 章 - 异常处理

作者头像
啊阿狸不会拉杆
发布2026-01-21 12:41:55
发布2026-01-21 12:41:55
730
举报
        大家好!今天我们来深入学习 C++ 中的异常处理机制。在程序开发中,错误处理是保证程序健壮性的核心环节,而异常处理则是 C++ 提供的一套优雅的错误处理方案。本文将按照《C++ 程序设计》第 12 章的结构,带大家从基础概念到实战应用,全面掌握异常处理技术。

本章知识思维导图

12.1 异常处理的基本思想

        在程序运行过程中,错误是不可避免的:除法运算中除数为零、数组访问越界、内存分配失败等。传统的错误处理方式主要有两种:

传统错误处理方式的缺陷

错误码返回方式:函数通过特定返回值表示错误,调用者需手动检查

代码语言:javascript
复制
// 传统错误码示例
int divide(int a, int b, int& result) {
    if (b == 0) return -1; // 返回错误码
    result = a / b;
    return 0; // 成功
}

缺点:错误码易被忽略,代码中错误处理与业务逻辑混杂。

全局错误标志:通过全局变量(如errno)标记错误 缺点:多层函数调用时,错误需逐层传递,代码冗余且易出错。

异常处理的核心思想

异常处理将错误检测错误处理分离:

  • 函数在检测到无法处理的错误时,抛出异常throw
  • 异常会沿着调用栈向上传播,直到找到合适的异常处理代码catch
  • 异常处理后,程序可继续执行,避免崩溃

异常处理的优势

  • 错误处理代码与业务逻辑分离,代码更清晰
  • 错误不会被意外忽略,确保被处理
  • 多层调用中,异常可直接传递到上层处理者

12.2 C++ 异常处理的实现

12.2.1 异常处理的语法

C++ 异常处理通过三个关键字实现:trythrowcatch,基本结构如下:

代码语言:javascript
复制
try {
    // 可能抛出异常的代码
    if (错误条件) {
        throw 异常值; // 抛出异常
    }
} catch (异常类型1 参数) {
    // 处理类型1的异常
} catch (异常类型2 参数) {
    // 处理类型2的异常
} catch (...) {
    // 处理所有未捕获的异常(兜底)
}
异常处理基本语法示例
代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

// 除法函数:除数为零时抛出异常
double divide(double a, double b) {
    if (b == 0) {
        // 抛出异常(可以是任意类型)
        throw string("除数不能为零!"); 
    }
    return a / b;
}

int main() {
    double num1, num2, result;
    cout << "请输入两个数(用空格分隔):";
    cin >> num1 >> num2;

    try {
        // 尝试执行可能抛出异常的操作
        result = divide(num1, num2);
        // 若没有异常,继续执行
        cout << num1 << " / " << num2 << " = " << result << endl;
    } 
    // 捕获string类型的异常
    catch (const string& errorMsg) { 
        cout << "错误:" << errorMsg << endl;
    } 
    // 捕获所有未处理的异常
    catch (...) { 
        cout << "发生未知错误!" << endl;
    }

    // 异常处理后,程序继续执行
    cout << "程序继续运行..." << endl;
    return 0;
}

运行结果 1(正常情况)

代码语言:javascript
复制
请输入两个数(用空格分隔):10 2
10 / 2 = 5
程序继续运行...

运行结果 2(异常情况)

代码语言:javascript
复制
请输入两个数(用空格分隔):8 0
错误:除数不能为零!
程序继续运行...

异常处理流程

12.2.2 异常接口声明

函数可以声明其可能抛出的异常类型,称为异常接口声明,语法:

代码语言:javascript
复制
返回类型 函数名(参数列表) throw(异常类型列表);
  • throw():表示函数不抛出任何异常
  • throw(Type1, Type2):表示函数可能抛出Type1Type2类型异常

⚠️ 注意:C++11 起推荐使用noexcept替代传统异常接口声明,传统方式在 C++17 中已被弃用。

代码语言:javascript
复制
#include <iostream>
#include <stdexcept>
using namespace std;

// 声明可能抛出invalid_argument异常
void setAge(int age) throw(invalid_argument) {
    if (age < 0 || age > 150) {
        throw invalid_argument("年龄必须在0-150之间");
    }
    cout << "年龄设置为:" << age << endl;
}

// 声明不抛出任何异常(C++11前写法)
void safeFunc() throw() {
    cout << "这个函数不会抛出异常" << endl;
}

int main() {
    try {
        setAge(200); // 会抛出异常
        safeFunc();
    } catch (const invalid_argument& e) {
        cout << "错误:" << e.what() << endl;
    }
    return 0;
}

12.3 异常处理中的构造与析构

        当异常被抛出时,程序会退出当前try块,并自动销毁在try块中创建的所有局部对象,这个过程称为栈展开(Stack Unwinding)。这保证了资源不会因为异常而泄露。

栈展开机制演示
代码语言:javascript
复制
#include <iostream>
using namespace std;

// 测试类:跟踪对象创建和销毁
class TestObject {
private:
    string name;
public:
    // 构造函数
    TestObject(const string& objName) : name(objName) {
        cout << "对象 [" << name << "] 被创建" << endl;
    }
    
    // 析构函数
    ~TestObject() {
        cout << "对象 [" << name << "] 被销毁(析构函数调用)" << endl;
    }
};

// 三级函数:抛出异常
void thirdLevel() {
    TestObject obj3("三级对象");
    cout << "在thirdLevel中抛出异常..." << endl;
    throw "异常来自thirdLevel"; // 抛出异常
}

// 二级函数:调用三级函数
void secondLevel() {
    TestObject obj2("二级对象");
    thirdLevel(); // 调用可能抛出异常的函数
    cout << "thirdLevel执行完成(这句不会执行)" << endl;
}

// 一级函数:调用二级函数
void firstLevel() {
    TestObject obj1("一级对象");
    secondLevel();
    cout << "secondLevel执行完成(这句不会执行)" << endl;
}

int main() {
    TestObject mainObj("主函数对象");
    try {
        firstLevel();
    } catch (const char* errorMsg) {
        cout << "捕获异常:" << errorMsg << endl;
    }
    return 0;
}

运行结果

代码语言:javascript
复制
对象 [主函数对象] 被创建
对象 [一级对象] 被创建
对象 [二级对象] 被创建
对象 [三级对象] 被创建
在thirdLevel中抛出异常...
对象 [三级对象] 被销毁(析构函数调用)
对象 [二级对象] 被销毁(析构函数调用)
对象 [一级对象] 被销毁(析构函数调用)
捕获异常:异常来自thirdLevel

结论

  • 异常抛出后,程序会按构造相反的顺序销毁所有局部对象
  • 析构函数的自动调用确保了资源(内存、文件句柄等)的正确释放
  • 全局对象(如mainObj)不会在栈展开时销毁,会在程序结束时正常销毁

12.4 标准程序库异常处理

        C++ 标准库定义了一套异常类体系,所有标准异常都继承自std::exception基类,该类提供了what()方法返回异常描述信息。

标准异常类层次结构
代码语言:javascript
复制
@startuml
title C++标准异常类层次结构
exception exception {
    + what(): const char*
}

exception bad_alloc
exception bad_cast
exception bad_typeid

exception logic_error {
    + logic_error(const string& msg)
}
exception domain_error
exception invalid_argument
exception length_error
exception out_of_range

exception runtime_error {
    + runtime_error(const string& msg)
}
exception range_error
exception overflow_error
exception underflow_error

exception <|-- bad_alloc
exception <|-- bad_cast
exception <|-- bad_typeid
exception <|-- logic_error
exception <|-- runtime_error

logic_error <|-- domain_error
logic_error <|-- invalid_argument
logic_error <|-- length_error
logic_error <|-- out_of_range

runtime_error <|-- range_error
runtime_error <|-- overflow_error
runtime_error <|-- underflow_error
@enduml
常用标准异常使用示例
代码语言:javascript
复制
#include <iostream>
#include <stdexcept> // 标准异常头文件
#include <vector>
using namespace std;

int main() {
    try {
        // 1. 动态内存分配失败(bad_alloc)
        // int* largeArray = new int[1000000000000ULL];
        
        // 2. 无效参数(invalid_argument)
        // throw invalid_argument("参数必须为正数");
        
        // 3. 数组越界访问(out_of_range)
        vector<int> numbers(5); // 大小为5的向量
        cout << "尝试访问numbers[10]:";
        numbers.at(10); // at()方法会抛出out_of_range异常
        
        // 4. 运行时错误(runtime_error)
        // throw runtime_error("文件读取失败");
    }
    // 捕获内存分配异常
    catch (const bad_alloc& e) {
        cout << "内存分配失败:" << e.what() << endl;
    }
    // 捕获越界异常
    catch (const out_of_range& e) {
        cout << "越界错误:" << e.what() << endl;
    }
    // 捕获参数错误
    catch (const invalid_argument& e) {
        cout << "参数错误:" << e.what() << endl;
    }
    // 捕获所有标准异常(基类)
    catch (const exception& e) {
        cout << "标准异常:" << e.what() << endl;
    }
    return 0;
}

运行结果

代码语言:javascript
复制
尝试访问numbers[10]:越界错误:vector::_M_range_check: __n (which is 10) >= this->size() (which is 5)

12.5 综合实例 —— 对个人银行账户管理程序的改进

我们基于银行账户管理程序,加入异常处理机制,处理存款负数、取款超额等异常情况。

完整实现代码
代码语言:javascript
复制
#include <iostream>
#include <string>
#include <stdexcept> // 标准异常基类
using namespace std;

// 银行账户异常基类(继承自标准异常)
class AccountException : public exception {
protected:
    string errorMsg; // 异常信息
public:
    // 构造函数
    AccountException(const string& msg) : errorMsg(msg) {}
    
    // 重写what()方法,返回异常描述
    const char* what() const noexcept override {
        return errorMsg.c_str();
    }
};

// 无效存款金额异常
class InvalidDepositException : public AccountException {
public:
    InvalidDepositException(double amount) 
        : AccountException("无效存款金额:" + to_string(amount) + "(必须大于0)") {}
};

// 无效取款金额异常
class InvalidWithdrawException : public AccountException {
public:
    InvalidWithdrawException(double amount) 
        : AccountException("无效取款金额:" + to_string(amount) + "(必须大于0)") {}
};

// 余额不足异常
class InsufficientFundsException : public AccountException {
public:
    InsufficientFundsException(double balance, double amount) 
        : AccountException("余额不足:当前余额" + to_string(balance) + 
                          ",尝试取款" + to_string(amount)) {}
};

// 银行账户类
class BankAccount {
private:
    string accountId;  // 账号
    string ownerName;  // 户主姓名
    double balance;    // 账户余额
public:
    // 构造函数
    BankAccount(const string& id, const string& name, double initialBalance = 0.0)
        : accountId(id), ownerName(name), balance(initialBalance) {
        // 初始余额不能为负
        if (initialBalance < 0) {
            throw InvalidDepositException(initialBalance);
        }
    }

    // 获取账号
    string getAccountId() const { return accountId; }
    
    // 获取户主姓名
    string getOwnerName() const { return ownerName; }
    
    // 获取当前余额
    double getBalance() const { return balance; }
    
    // 存款操作
    void deposit(double amount) {
        if (amount <= 0) {
            throw InvalidDepositException(amount); // 抛出异常
        }
        balance += amount;
        cout << "存款成功!存入:" << amount << ",当前余额:" << balance << endl;
    }
    
    // 取款操作
    void withdraw(double amount) {
        if (amount <= 0) {
            throw InvalidWithdrawException(amount); // 抛出异常
        }
        if (amount > balance) {
            throw InsufficientFundsException(balance, amount); // 抛出异常
        }
        balance -= amount;
        cout << "取款成功!取出:" << amount << ",当前余额:" << balance << endl;
    }
    
    // 显示账户信息
    void showAccountInfo() const {
        cout << "\n===== 账户信息 =====" << endl;
        cout << "账号:" << accountId << endl;
        cout << "户主:" << ownerName << endl;
        cout << "当前余额:" << balance << " 元" << endl;
        cout << "====================\n" << endl;
    }
};

// 账户管理函数
void manageAccount(BankAccount& account) {
    int choice;
    double amount;
    
    do {
        cout << "\n===== 账户管理菜单 =====" << endl;
        cout << "1. 查看账户信息" << endl;
        cout << "2. 存款" << endl;
        cout << "3. 取款" << endl;
        cout << "4. 退出" << endl;
        cout << "请选择操作:";
        cin >> choice;
        
        try {
            switch (choice) {
                case 1:
                    account.showAccountInfo();
                    break;
                case 2:
                    cout << "请输入存款金额:";
                    cin >> amount;
                    account.deposit(amount);
                    break;
                case 3:
                    cout << "请输入取款金额:";
                    cin >> amount;
                    account.withdraw(amount);
                    break;
                case 4:
                    cout << "感谢使用,再见!" << endl;
                    break;
                default:
                    cout << "无效选择,请重新输入!" << endl;
            }
        }
        // 捕获账户相关异常
        catch (const AccountException& e) {
            cout << "操作失败:" << e.what() << endl;
        }
        // 捕获其他标准异常
        catch (const exception& e) {
            cout << "错误:" << e.what() << endl;
        }
    } while (choice != 4);
}

int main() {
    try {
        // 创建银行账户(初始余额可能抛出异常)
        BankAccount account("6222021001234567890", "张三", 1000.0);
        cout << "账户创建成功!户主:" << account.getOwnerName() << endl;
        
        // 管理账户
        manageAccount(account);
    }
    // 捕获账户初始化异常
    catch (const exception& e) {
        cout << "程序启动失败:" << e.what() << endl;
        return 1; // 异常退出
    }
    
    return 0;
}

代码说明

  1. 定义了自定义异常类层次,继承自std::exception,便于统一处理
  2. 账户操作中检测到错误时(如存款为负、余额不足)抛出相应异常
  3. 异常处理确保程序在出错时不会崩溃,而是给出友好提示
  4. 即使发生异常,局部对象也会通过栈展开机制正确析构

运行示例(异常情况)

代码语言:javascript
复制
账户创建成功!户主:张三

===== 账户管理菜单 =====
1. 查看账户信息
2. 存款
3. 取款
4. 退出
请选择操作:3
请输入取款金额:1500
操作失败:余额不足:当前余额1000.000000,尝试取款1500.000000

12.6 深度探索

12.6.1 异常安全性问题

        异常安全性(Exception Safety)指函数在抛出异常时,程序仍能保持一致状态的能力,分为三个级别:

  1. 基本保证(Basic Guarantee):异常抛出后,对象状态有效但不确定,资源不泄露
  2. 强保证(Strong Guarantee):异常抛出后,程序状态与函数调用前完全一致(要么成功,要么无变化)
  3. 不抛出保证(No-Throw Guarantee):函数绝对不会抛出异常
强保证实现示例(复制 - 交换技术)
代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <stdexcept>
using namespace std;

// 基本保证实现:异常可能导致部分修改
void basicGuarantee(vector<int>& v, int index, int value) {
    if (index < 0 || index >= v.size()) {
        throw out_of_range("索引越界");
    }
    v[index] = value; // 可能已修改
    if (value < 0) {
        throw invalid_argument("值不能为负"); // 抛出异常
    }
}

// 强保证实现:使用复制-交换技术
void strongGuarantee(vector<int>& v, int index, int value) {
    if (index < 0 || index >= v.size()) {
        throw out_of_range("索引越界");
    }
    if (value < 0) {
        throw invalid_argument("值不能为负");
    }
    
    // 1. 复制原对象
    vector<int> temp = v;
    // 2. 修改副本
    temp[index] = value;
    // 3. 交换副本与原对象(无异常操作)
    swap(v, temp);
}

int main() {
    vector<int> v = {1, 2, 3, 4, 5};
    
    // 测试基本保证
    try {
        basicGuarantee(v, 2, -10); // 会抛出异常
    } catch (const exception& e) {
        cout << "基本保证测试:" << e.what() << endl;
        cout << "修改后的值:" << v[2] << endl; // 已被修改为-10
    }
    
    // 恢复原始值
    v = {1, 2, 3, 4, 5};
    
    // 测试强保证
    try {
        strongGuarantee(v, 2, -10); // 会抛出异常
    } catch (const exception& e) {
        cout << "强保证测试:" << e.what() << endl;
        cout << "值未被修改:" << v[2] << endl; // 仍为3
    }
    
    return 0;
}
12.6.2 避免异常发生时的资源泄露

        异常抛出时若未正确释放资源(内存、文件句柄等),会导致资源泄露。RAII(资源获取即初始化) 是解决该问题的最佳实践:资源在对象构造时获取,在析构时释放。

资源泄露对比与 RAII 解决方案
代码语言:javascript
复制
#include <iostream>
#include <fstream>
#include <memory> // 智能指针
using namespace std;

// 1. 手动管理资源:可能泄露
void riskyResourceManagement() {
    // 分配资源
    int* data = new int[100]; // 动态内存
    ofstream file("data.txt"); // 文件资源
    
    // 模拟异常
    throw runtime_error("发生错误!");
    
    // 以下代码不会执行,导致内存泄露
    delete[] data; // 内存未释放
    // 文件会在ofstream析构时关闭(因为file是局部对象)
}

// 2. RAII管理资源:安全可靠
void safeResourceManagement() {
    // 智能指针自动管理内存(RAII)
    unique_ptr<int[]> data(new int[100]); // 异常时自动释放
    
    ofstream file("data.txt"); // RAII管理的文件资源
    
    // 模拟异常
    throw runtime_error("发生错误!");
    
    // 无需手动释放资源,析构函数会自动调用
}

int main() {
    try {
        riskyResourceManagement();
    } catch (const exception& e) {
        cout << "捕获异常:" << e.what() << endl;
        cout << "注意:riskyResourceManagement可能导致内存泄露!" << endl;
    }
    
    try {
        safeResourceManagement();
    } catch (const exception& e) {
        cout << "捕获异常:" << e.what() << endl;
        cout << "safeResourceManagement资源已正确释放!" << endl;
    }
    
    return 0;
}

RAII 核心思想:将资源封装在对象中,利用对象的生命周期管理资源,确保异常发生时资源被自动释放。

12.6.3 noexcept 异常说明

C++11 引入noexcept说明符,用于声明函数是否可能抛出异常:

  • void func() noexcept;:函数不会抛出任何异常
  • void func() noexcept(condition);:当condition为真时不抛出异常

noexcept相比传统throw()的优势:

  • 编译器可进行更多优化
  • 明确表达函数意图,提高代码可读性
  • 是移动操作、析构函数的最佳实践
noexcept 使用示例
代码语言:javascript
复制
#include <iostream>
#include <vector>
using namespace std;

// 声明不会抛出异常
void safeOperation() noexcept {
    cout << "这是一个安全的操作" << endl;
}

// 条件noexcept:当T的移动构造函数不抛异常时,本函数也不抛
template <typename T>
void swapObjects(T& a, T& b) noexcept(noexcept(T(std::move(a)))) {
    T temp(std::move(a)); // 若T的移动构造函数noexcept,则本操作安全
    a = std::move(b);
    b = std::move(temp);
}

// 可能抛出异常的函数
void riskyOperation(bool shouldThrow) {
    if (shouldThrow) {
        throw runtime_error("发生异常");
    }
    cout << "操作成功" << endl;
}

int main() {
    // 测试noexcept函数
    safeOperation();
    
    // 测试条件noexcept
    vector<int> v1 = {1, 2, 3};
    vector<int> v2 = {4, 5, 6};
    swapObjects(v1, v2);
    
    // 测试noexcept运算符
    cout << "safeOperation是否不抛异常:" << noexcept(safeOperation()) << endl;
    cout << "riskyOperation是否不抛异常:" << noexcept(riskyOperation(false)) << endl;
    
    // 调用可能抛异常的函数
    try {
        riskyOperation(true);
    } catch (const exception& e) {
        cout << "捕获异常:" << e.what() << endl;
    }
    
    return 0;
}

最佳实践

  • 析构函数默认noexcept,不应抛出异常
  • 移动构造函数和移动赋值运算符应尽可能标记为noexcept
  • 简单的、不会失败的函数应标记为noexcept

12.7 小结

本章我们系统学习了 C++ 异常处理机制,核心要点总结:

  1. 异常处理基本语法try块包含可能出错的代码,throw抛出异常,catch捕获并处理
  2. 栈展开机制:异常抛出时,局部对象的析构函数会被自动调用,确保资源不泄露
  3. 异常类设计:自定义异常应继承std::exception,便于统一处理和信息传递
  4. 异常安全性:追求强保证,使用 RAII(如智能指针)避免资源泄露
  5. 现代 C++ 实践:优先使用noexcept声明,替代传统异常接口;合理使用标准库异常类

        异常处理是编写健壮 C++ 程序的关键技术,合理使用能显著提高代码的可读性和可靠性,但也需避免过度使用(简单错误可用返回值处理)。

        希望本文能帮助大家掌握 C++ 异常处理的核心知识!所有代码均可直接编译运行,建议动手实践加深理解。如有疑问,欢迎在评论区交流~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本章知识思维导图
  • 12.1 异常处理的基本思想
    • 传统错误处理方式的缺陷
    • 异常处理的核心思想
  • 12.2 C++ 异常处理的实现
    • 12.2.1 异常处理的语法
    • 异常处理基本语法示例
    • 12.2.2 异常接口声明
  • 12.3 异常处理中的构造与析构
    • 栈展开机制演示
  • 12.4 标准程序库异常处理
    • 标准异常类层次结构
    • 常用标准异常使用示例
  • 12.5 综合实例 —— 对个人银行账户管理程序的改进
    • 完整实现代码
  • 12.6 深度探索
    • 12.6.1 异常安全性问题
    • 强保证实现示例(复制 - 交换技术)
    • 12.6.2 避免异常发生时的资源泄露
    • 资源泄露对比与 RAII 解决方案
    • 12.6.3 noexcept 异常说明
    • noexcept 使用示例
  • 12.7 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档