首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C++进阶】try块和异常处理

【C++进阶】try块和异常处理

作者头像
byte轻骑兵
发布2026-01-21 15:59:57
发布2026-01-21 15:59:57
1380
举报

在 C++ 编程中,错误处理是一个至关重要的方面。当程序运行时,可能会遇到各种意外情况,如文件打开失败、内存分配不足、数组越界等。如果不妥善处理这些错误,程序可能会崩溃,导致数据丢失或产生不可预测的结果。C++ 提供了异常处理机制,通过 try 块、catch 块和 throw 语句,我们可以更优雅地处理这些错误,提高程序的健壮性和可维护性。

一、异常处理概述

1.1 什么是异常

异常是程序在执行过程中遇到的错误或异常情况。例如,当我们尝试除以零、访问不存在的文件或数组越界时,就会产生异常。异常可以分为两类:同步异常和异步异常。同步异常是由程序的特定操作引发的,如除以零;而异步异常则是由外部事件引发的,如硬件故障或用户中断。

1.2 为什么需要异常处理

在传统的C风格错误处理中,我们通常使用返回值或错误码来处理异常情况。这种方式存在三个主要问题:

  • 错误信息丢失:函数返回值可能被忽略
  • 代码可读性差:大量if-else错误检查打乱正常逻辑
  • 资源管理困难:错误发生时难以保证资源正确释放

C++异常机制通过以下特性解决这些问题:

  • 强制错误处理
  • 分离正常逻辑与错误处理
  • 自动资源释放(栈展开)

1.3 异常处理基本框架

C++ 异常处理的基本结构由 try 块、catch 块和 throw 语句组成。

代码语言:javascript
复制
try {
    // 可能抛出异常的代码
    throw SomeException();
}
catch (const SomeException& e) {
    // 处理特定异常
}
catch (...) {
    // 处理所有其他异常
}
  • try:包含可能会抛出异常的代码。当 try 块中的代码抛出异常时,程序会立即停止执行 try 块中的剩余代码,并跳转到相应的 catch 块中进行异常处理。
  • catch:用于捕获和处理异常。每个 catch 块都有一个异常类型参数,用于指定该 catch 块可以捕获的异常类型。当 try 块中抛出的异常类型与某个 catch 块的异常类型匹配时,该 catch 块会被执行。
  • throw 语句:用于抛出异常。可以在程序中使用 throw 语句手动抛出异常,也可以使用 C++ 标准库提供的异常类来抛出异常。

二、异常处理核心机制

2.1 基本语法要素

① throw表达式

代码语言:javascript
复制
void processFile(const string& filename) {
    ifstream file(filename);
    if (!file) {
        throw runtime_error("文件打开失败: " + filename);
    }
    // 文件处理逻辑...
}

②try-catch块

代码语言:javascript
复制
try {
    processFile("data.txt");
}
catch (const runtime_error& e) {
    cerr << "错误: " << e.what() << endl;
}

2.2 异常传播机制

栈展开过程:

  • 搜索当前函数中的匹配catch块
  • 函数栈帧被销毁,局部对象析构
  • 沿调用链向上查找匹配的catch块
  • 找到则处理,找不到则terminate

2.3 异常安全保证

安全等级

描述

基本保证

资源不泄漏,对象保持有效状态

强保证

操作要么完全成功,要么无影响

无异常保证

承诺不抛出任何异常

代码语言:javascript
复制
// 强保证示例:事务性操作
void transaction() {
    auto temp = resource;  // 创建副本
    modify(temp);          // 修改副本
    swap(resource, temp);  // 原子性交换
}

三、try 块和 catch 块的使用

3.1 简单的 try-catch 示例

下面是一个简单的 try-catch 示例,演示如何处理除以零的异常:

代码语言:javascript
复制
#include <iostream>

int main() {
    int numerator = 10;
    int denominator = 0;

    try {
        if (denominator == 0) {
            throw "Division by zero!";
        }
        int result = numerator / denominator;
        std::cout << "Result: " << result << std::endl;
    } catch (const char* error) {
        std::cerr << "Error: " << error << std::endl;
    }

    return 0;
}

try 块中检查分母是否为零。如果分母为零,使用 throw 语句抛出一个字符串常量 "Division by zero!"。然后,程序会跳转到 catch 块中,该 catch 块的异常类型为 const char*,用于捕获字符串常量类型的异常。在 catch 块中,输出错误信息。

3.2 多个 catch 块的使用

可以在一个 try 块后面使用多个 catch 块,每个 catch 块用于捕获不同类型的异常。下面是一个示例:

代码语言:javascript
复制
#include <iostream>
#include <stdexcept>  // 包含标准异常类

int main() {
    int numerator = 10;
    int denominator = 0;

    try {
        if (denominator == 0) {
            throw std::runtime_error("Division by zero!");
        }
        int result = numerator / denominator;
        std::cout << "Result: " << result << std::endl;
    } catch (const std::runtime_error& e) {
        std::cerr << "Runtime error: " << e.what() << std::endl;
    } catch (const char* error) {
        std::cerr << "Error: " << error << std::endl;
    }

    return 0;
}

抛出了一个 std::runtime_error 类型的异常。std::runtime_error 是 C++ 标准库提供的一个异常类,用于表示运行时错误。使用两个 catch 块来捕获不同类型的异常,第一个 catch 块用于捕获 std::runtime_error 类型的异常,第二个 catch 块用于捕获字符串常量类型的异常。

3.3 catch 块的匹配规则

try 块中抛出异常时,程序会按照 catch 块的顺序依次检查每个 catch 块的异常类型是否与抛出的异常类型匹配。匹配规则如下:

  • 异常类型必须完全匹配,或者可以通过继承关系进行匹配。例如,如果抛出的异常类型是 std::runtime_error,那么可以被 catch (const std::runtime_error& e)catch (const std::exception& e) 捕获,因为 std::runtime_errorstd::exception 的派生类。
  • 一旦找到匹配的 catch 块,程序会执行该 catch 块中的代码,并跳过其他 catch 块。
  • 如果没有找到匹配的 catch 块,程序会调用 std::terminate() 函数,终止程序的执行。

3.4 catch 块的参数传递

catch 块的参数可以是值传递、引用传递或指针传递。通常建议使用引用传递,因为引用传递可以避免对象的复制,提高性能。下面是一个使用引用传递的示例:

代码语言:javascript
复制
#include <iostream>

class MyException {
public:
    MyException(const std::string& message) : message_(message) {}
    const std::string& what() const { return message_; }
private:
    std::string message_;
};

int main() {
    try {
        throw MyException("This is a custom exception!");
    } catch (const MyException& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

定义了一个自定义的异常类 MyException,并在 try 块中抛出了一个 MyException 对象。在 catch 块中,使用引用传递来捕获异常对象,并调用 what() 方法输出异常信息。

四、throw 语句的使用

4.1 手动抛出异常

可以在程序中使用 throw 语句手动抛出异常。throw 语句的语法如下:

代码语言:javascript
复制
throw expression;

其中,expression 可以是任意类型的表达式,通常是一个异常对象。下面是一个手动抛出异常的示例:

代码语言:javascript
复制
#include <iostream>
#include <stdexcept>  // 包含标准异常类

void divide(int numerator, int denominator) {
    if (denominator == 0) {
        throw std::invalid_argument("Denominator cannot be zero!");
    }
    int result = numerator / denominator;
    std::cout << "Result: " << result << std::endl;
}

int main() {
    try {
        divide(10, 0);
    } catch (const std::invalid_argument& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }

    return 0;
}

divide 函数用于进行除法运算。在函数中,检查分母是否为零,如果为零,则抛出一个 std::invalid_argument 类型的异常。在 main 函数中,调用 divide 函数,并使用 try-catch 块来捕获和处理异常。

4.2 重新抛出异常

catch 块中,可以使用 throw 语句重新抛出异常。重新抛出异常可以将异常传递给上一层的 try-catch 块进行处理。下面是一个重新抛出异常的示例:

代码语言:javascript
复制
#include <iostream>
#include <stdexcept>  // 包含标准异常类

void func() {
    try {
        throw std::runtime_error("Something went wrong!");
    } catch (const std::runtime_error& e) {
        std::cerr << "Caught exception in func(): " << e.what() << std::endl;
        throw; // 重新抛出异常
    }
}

int main() {
    try {
        func();
    } catch (const std::runtime_error& e) {
        std::cerr << "Caught exception in main(): " << e.what() << std::endl;
    }

    return 0;
}

func 函数中抛出了一个 std::runtime_error 类型的异常,并在 catch 块中重新抛出该异常。在 main 函数中,再次捕获该异常,并输出异常信息。

五、C++ 标准库中的异常类

5.1 异常类的继承层次结构

代码语言:javascript
复制
exception
├── logic_error
│   ├── invalid_argument
│   ├── domain_error
│   ├── length_error
│   └── out_of_range
├── runtime_error
│   ├── range_error
│   ├── overflow_error
│   └── underflow_error
└── bad_alloc

C++ 标准库提供了一系列的异常类,这些异常类构成了一个继承层次结构。顶层的异常类是 std::exception,它是所有标准异常类的基类。std::exception 类定义了一个纯虚函数 what(),用于返回异常信息。

以下是一些常见的标准异常类:

  • std::logic_error:表示逻辑错误,如无效的参数、越界访问等。
  • std::runtime_error:表示运行时错误,如内存分配失败、文件打开失败等。
  • std::bad_alloc:表示内存分配失败。
  • std::out_of_range:表示越界访问。

5.2 使用标准异常类

可以直接使用 C++ 标准库提供的异常类来抛出和捕获异常。下面是一个使用 std::out_of_range 异常类的示例:

代码语言:javascript
复制
#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3};

    try {
        int value = vec.at(10); // 越界访问
        std::cout << "Value: " << value << std::endl;
    } catch (const std::out_of_range& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }

    return 0;
}

使用 std::vectorat() 方法访问向量的元素。如果访问的索引越界,at() 方法会抛出一个 std::out_of_range 类型的异常。使用 try-catch 块来捕获和处理该异常。

六、自定义异常类

6.1 自定义异常类的定义

除了使用 C++ 标准库提供的异常类,还可以自定义异常类。自定义异常类通常继承自 std::exception 或其派生类,并实现 what() 方法。下面是一个自定义异常类的示例:

代码语言:javascript
复制
#include <iostream>
#include <stdexcept>  // 包含标准异常类
#include <string>

class MyException : public std::exception {
public:
    MyException(const std::string& message) : message_(message) {}
    const char* what() const noexcept override {
        return message_.c_str();
    }
private:
    std::string message_;
};

void doSomething() {
    throw MyException("This is a custom exception!");
}

int main() {
    try {
        doSomething();
    } catch (const MyException& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

定义了一个自定义异常类 MyException,它继承自 std::exception。在 MyException 类中,实现了 what() 方法,用于返回异常信息。在 doSomething 函数中,抛出了一个 MyException 对象。在 main 函数中,使用 try-catch 块来捕获和处理该异常。

6.2 自定义异常类的优点

自定义异常类可以使代码更加清晰和易于维护。通过自定义异常类,可以为不同的错误情况定义不同的异常类型,从而使异常处理更加精确。例如,可以为文件操作定义一个 FileException 类,为数据库操作定义一个 DatabaseException 类,这样在捕获异常时,可以根据异常类型来区分不同的错误情况。

七、异常处理的最佳实践

7.1 避免过度使用异常

异常处理是一种比较昂贵的操作,因为它涉及到栈展开和对象析构等操作。因此,我们应该避免在性能敏感的代码中过度使用异常。对于一些可以通过简单的错误检查来处理的情况,应该优先使用条件判断语句。

7.2 异常安全性

在编写代码时,我们应该考虑异常安全性。异常安全性是指在异常发生时,程序能够保持数据的一致性和完整性。为了实现异常安全性,可以使用 RAII(资源获取即初始化)技术,确保资源在对象的生命周期内被正确管理。例如,使用 std::unique_ptrstd::shared_ptr 来管理动态分配的内存,使用 std::lock_guard 来管理互斥锁。

7.3 异常的传递和记录

在处理异常时,我们应该考虑异常的传递和记录。对于一些无法在当前函数中处理的异常,应该将其传递给上一层的调用者进行处理。同时,应该记录异常信息,以便在调试和维护时能够快速定位问题。可以使用日志库来记录异常信息,如 spdlogglog

八、常见错误及解决方案

8.1 异常未被捕获

代码语言:javascript
复制
void riskyFunction() {
    throw runtime_error("未处理异常");
}

int main() {
    riskyFunction();
    // 程序终止,输出terminate called after throwing an instance of...
}

解决方案:使用全局异常捕获

代码语言:javascript
复制
int main() try {
    riskyFunction();
}
catch (...) {
    cerr << "未知异常" << endl;
}

8.2 异常导致资源泄漏

代码语言:javascript
复制
void process() {
    int* arr = new int[100];
    throw exception();
    delete[] arr;  // 永远不会执行
}

解决方案:使用智能指针

代码语言:javascript
复制
void safeProcess() {
    auto arr = make_unique<int[]>(100);
    throw exception();
    // 自动释放内存
}

九、总结与最佳实践

9.1 异常处理原则

  • 只对异常情况使用异常:不要用异常控制正常流程
  • 保持异常层次合理:自定义异常应继承自std::exception
  • 保证异常安全:至少提供基本安全保证
  • 优先使用RAII:确保资源自动释放
  • 避免在析构函数中抛出异常

9.2 异常处理决策树

代码语言:javascript
复制
是否可恢复? --> 是 --> 使用异常
              |
             否 --> 使用断言/终止程序

9.3 现代C++推荐做法

  • 使用noexcept标记不会抛出异常的函数
  • 优先使用std::make_shared/std::make_unique
  • 对于不可恢复错误,使用std::terminate
  • 使用类型安全的异常参数

通过合理运用异常处理机制,可以构建出更健壮、更易维护的C++应用程序。理解异常处理的底层原理,结合现代C++特性,能够帮助我们在保证程序可靠性的同时,兼顾代码的执行效率。

希望这篇博客能够帮助你更好地理解 C++ 中的 try 块和异常处理。如果你有任何疑问或建议,欢迎在评论区留言。


十、参考资料

  • 《C++ Primer(第 5 版)》这本书是 C++ 领域的经典之作,对 C++ 的基础语法和高级特性都有深入讲解。
  • 《Effective C++(第 3 版)》书中包含了很多 C++ 编程的实用建议和最佳实践。
  • 《C++ Templates: The Complete Guide(第 2 版)》该书聚焦于 C++ 模板编程,而using声明在模板编程中有着重要应用,如定义模板类型别名等。
  • C++ 官方标准文档:C++ 标准文档是最权威的参考资料,可以查阅最新的 C++ 标准(如 C++11、C++14、C++17、C++20 等)文档。例如,ISO/IEC 14882:2020 是 C++20 标准的文档,可从相关渠道获取其详细内容。
  • cppreference.com:这是一个非常全面的 C++ 在线参考网站,提供了详细的 C++ 语言和标准库文档。
  • LearnCpp.com:该网站提供了系统的 C++ 教程,配有丰富的示例代码和清晰的解释,适合初学者学习和理解相关知识。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、异常处理概述
    • 1.1 什么是异常
    • 1.2 为什么需要异常处理
    • 1.3 异常处理基本框架
  • 二、异常处理核心机制
    • 2.1 基本语法要素
    • 2.2 异常传播机制
    • 2.3 异常安全保证
  • 三、try 块和 catch 块的使用
    • 3.1 简单的 try-catch 示例
    • 3.2 多个 catch 块的使用
    • 3.3 catch 块的匹配规则
    • 3.4 catch 块的参数传递
  • 四、throw 语句的使用
    • 4.1 手动抛出异常
    • 4.2 重新抛出异常
  • 五、C++ 标准库中的异常类
    • 5.1 异常类的继承层次结构
    • 5.2 使用标准异常类
  • 六、自定义异常类
    • 6.1 自定义异常类的定义
    • 6.2 自定义异常类的优点
  • 七、异常处理的最佳实践
    • 7.1 避免过度使用异常
    • 7.2 异常安全性
    • 7.3 异常的传递和记录
  • 八、常见错误及解决方案
    • 8.1 异常未被捕获
    • 8.2 异常导致资源泄漏
  • 九、总结与最佳实践
    • 9.1 异常处理原则
    • 9.2 异常处理决策树
    • 9.3 现代C++推荐做法
  • 十、参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档