第五节 · 缓兵之计 · 异常处理语句

在编程的世界里,意外无处不在。用户可能输入了不合法的数据,文件可能不存在,网络可能断开……这些都是我们在编写程序时需要考虑的情况。

如果程序遇到这些意外情况时直接崩溃,那显然是不可接受的。我们需要一种机制,能够优雅地处理这些异常情况,让程序能够继续运行或至少给用户一个友好的提示。

C++ 的异常处理机制正是为此而生的。


假设我们写了一个除法函数:

int divide(int a, int b)
{
    return a / b;
}

这个函数看起来没什么问题,但如果 b 为 0 呢?在数学中,除以零是没有定义的,程序执行到这里就会出错。

传统的处理方式是在函数内部进行检查:

int divide(int a, int b)
{
    if (b == 0)
    {
        cout << "错误:除数不能为零!" << endl;
        return 0;  // 返回一个默认值
    }
    return a / b;
}

但这种方式有一个问题:我们无法区分返回的 0 是计算结果还是错误标志。而且,如果调用这个函数的代码需要知道出错了,就得额外做很多判断。


C++ 的异常处理机制提供了一种更优雅的解决方案。它使用三个关键字:trycatchthrow

  • throw:当程序遇到异常情况时,"抛出"一个异常。

  • try:包含可能产生异常的代码块。

  • catch:捕获并处理异常。

让我们用异常处理重写上面的例子:

运行这段程序,输出将是:

程序没有崩溃,而是优雅地处理了异常,并继续执行后面的代码。


让我们来详细了解这三个关键字的用法。

throw

throw 用于抛出异常。你可以抛出任何类型的值作为异常:

throw 语句执行时,程序会立即停止当前函数的执行,开始寻找能够处理这个异常的 catch 块。

try

try 块包含可能产生异常的代码:

如果 try 块中的代码抛出了异常,程序会立即跳出 try 块,开始寻找匹配的 catch 块。

catch

catch 块用于捕获和处理异常:

catch 块必须紧跟在 try 块后面。你可以有多个 catch 块来处理不同类型的异常:

catch(...) 可以捕获任何类型的异常,通常放在最后作为"兜底"。


在实际开发中,我们通常会使用标准库提供的异常类。C++ 标准库在 <stdexcept> 头文件中定义了一系列标准异常类:

常用的标准异常类包括:

异常类
说明

exception

所有标准异常的基类

runtime_error

运行时错误

logic_error

逻辑错误

invalid_argument

无效参数

out_of_range

超出范围

overflow_error

算术溢出

这些异常类都有一个 what() 成员函数,用于返回描述异常的字符串。


异常处理是有开销的,不应该滥用。以下是一些使用异常的建议:

  • 异常应该用于处理异常情况,而不是正常的程序流程控制。

  • 如果一个错误是可以预见的且容易恢复的,考虑使用返回值而不是异常。

  • 异常应该包含足够的信息,帮助调用者理解和处理问题。

  • 捕获异常时,应该尽可能捕获具体的异常类型,而不是笼统地捕获所有异常。


让我们来看一个更实际的例子——从用户输入中读取整数:

这个程序会不断提示用户输入,直到用户输入一个有效的整数为止。


习题

  • 编写一个函数 safeSqrt,计算一个数的平方根。如果参数为负数,抛出一个异常。在主函数中调用这个函数并处理可能的异常。

  • 修改上面的整数输入程序,增加对输入范围的检查(比如只接受 1 到 100 之间的数),超出范围时抛出 out_of_range 异常。

Last updated