C++基础教程面向对象(学习笔记(74))

基本异常处理

在上一节关于异常需求的课程中,我们讨论了如何使用返回代码导致控制流和错误流混合,从而限制两者。 C ++中的异常是使用三个相互协作的关键字实现的:throw,try和catch。

抛出异常

我们在现实生活中一直使用信号来记录特定事件已经发生。例如,在美式橄榄球比赛期间,如果一名球员犯了一个犯规,裁判将在地面上扔一面旗帜,然后吹哨。然后评估并执行惩罚。一旦罚款得到处理,游戏一般会恢复正常。

在C ++中,throw语句用于表示发生了异常或错误情况(想到抛出惩罚标记)。发生异常的信号通常也称为引发异常。

要使用throw语句,只需使用throw关键字,然后使用您希望用来表示发生错误的任何数据类型的值。通常,此值将是错误代码,问题描述或自定义异常类。

这里有些例子:

throw -1; // 抛出一个负整数值
throw ENUM_INVALID_INDEX; // 抛出枚举值
throw "Can not take square root of negative number"; // 出一个文字的C风格(const char *)字符串
throw dX; // 抛出先前定义的双变量
throw MyException("Fatal Error"); // 抛出MyException类的对象

这些陈述中的每一个都充当了发生某种需要处理的问题的信号。

查找异常

抛出异常只是异常处理过程的一部分。让我们回到我们的美式橄榄球比喻:一旦裁判抛出一面点球旗,接下来会发生什么?球员注意到已经发生了点球并停止比赛。足球比赛的正常流程被打乱。

在C ++中,我们使用try关键字来定义一个语句块(称为try块)。try块充当观察者,查找try块中任何语句抛出的任何异常。

这是try块的示例:

try
{
    // 可能会抛出您想要处理的异常的语句到此处
    throw -1; // 下面是一个简单的throw语句
}

请注意,try块没有定义我们将如何处理异常。它只是告诉程序,“嘿,如果这个try块中的任何语句抛出异常,抓住它!”。

处理异常

最后,我们的美式橄榄球比喻的结束:在判罚点球并且比赛停止后,裁判评估罚分并执行。换句话说,必须在正常比赛恢复之前处理罚款。

实际上处理异常是catch块的工作。所述捕获关键字被用于定义一个代码块(称为catch块来处理的异常为一个单一的数据类型)。

这是一个捕获整数异常的catch块的示例:

catch (int x)
{
    // 在这里处理int类型的异常
    std::cerr << "We caught an int exception with value" << x << '\n';
}

try块和catch块一起工作 - try块检测try块中语句抛出的任何异常,并将它们路由到适当的catch块进行处理。try块必须紧跟其后至少有一个catch块,但可能有多个catch块按顺序列出。

一旦try块捕获到异常并将其路由到catch块进行处理,就会认为异常被处理,并且在catch块之后执行将恢复正常。

Catch参数就像函数参数一样工作,参数在后续catch块中可用。基本类型的异常可以通过值捕获,但非基本类型的异常应该由const引用捕获,以避免产生不必要的副本。

就像函数一样,如果不在catch块中使用该参数,则可以省略变量名:

catch (double) // 注意:没有变量名,因为我们不在下面的catch块中使用它
{
    //在这里处理double类型的异常
    std::cerr << "We caught an exception of type double" << '\n';
}

这有助于防止编译器警告有关未使用的变量。

throw,try,并catch在一起

这是一个使用throw,try和多个catch块的完整程序:

#include <iostream>
#include <string>
 
int main()
{
    try
    {
        // 可能会抛出您想要处理的异常的语句到此处
        throw -1; // 这是一个简单的例子
    }
    catch (int x)
    {
        // 在上面的try块中抛出的int类型的任何异常都会在这里发送
        std::cerr << "We caught an int exception with value: " << x << '\n';
    }
    catch (double) // 没有变量名,因为我们不在下面的catch块中使用异常本身
    {
        // 在上面的try块中抛出的double类型的任何异常都会在这里发送
        std::cerr << "We caught an exception of type double" << '\n';
    }
    catch (const std::string &str) // 通过const引用捕获类
    {
        // 在上面的try块中抛出的类型为std :: string的任何异常都会在此处发送
        std::cerr << "We caught an exception of type std::string" << '\n';
    }
 
    std::cout << "Continuing on our merry way\n";
 
    return 0;
}

运行上面的try / catch块会产生以下结果:

We caught an int exception with value -1
Continuing on our merry way

throw语句用于引发值为-1的异常,其类型为int。然后,throw语句被封闭的try块捕获,并路由到处理int类型异常的相应catch块。此catch块打印了相应的错误消息。

一旦处理了异常,程序在捕获块后继续正常,打印“继续我们的快乐方式”。

重置异常处理

异常处理实际上非常简单,以下两段涵盖了您需要记住的大部分内容:

当引发异常时(使用throw),程序的执行立即跳转到最近的封闭try块(如果需要,向上传播堆栈以找到一个封闭的try块 - 我们将在下一课中更详细地讨论这个)。如果附加到try块的任何catch处理程序处理该类型的异常,则执行该处理程序并认为处理该异常。

如果不存在适当的catch处理程序,则程序的执行将传播到下一个封闭的try块。如果在程序结束之前找不到合适的catch处理程序,则程序将失败并出现异常错误。

请注意,在将异常与catch块匹配时,编译器不会执行隐式转换或促销!例如,char异常与int catch块不匹配。int异常与float catch块不匹配。

这就是它的全部内容。本章的其余部分将致力于展示这些原则的实例。

例外情况会立即处理

这是一个简短的程序,演示了如何立即处理异常:

#include <iostream>
 
int main()
{
    try
    {
        throw 4.5; //抛出double类型的异常
        std::cout << "This never prints\n";
    }
    catch(double x) // 处理double类型的异常
    {
        std::cerr << "We caught a double of value: " << x << '\n';
    }
 
    return 0;
}

这个程序就像它获得的一样简单。这是发生的事情:throw语句是第一个被执行的语句 - 这会导致引发类型为double的异常。执行立即移动到最近的封闭try块,这是该程序中唯一的try块。然后检查catch处理程序以查看是否有任何处理程序匹配。我们的异常是double类型,所以我们正在寻找double类型的catch处理程序。我们有一个,所以它执行。

因此,该计划的结果如下:

We caught a double of value: 4.5

请注意,永远不会打印“This never prints”,因为异常导致执行路径立即跳转到双精度的异常处理程序。

一个更现实的例子

让我们来看一个不太学术的例子:

#include "math.h" // sqrt() 
#include <iostream>
 
int main()
{
    std::cout << "Enter a number: ";
    double x;
    std::cin >> x;
 
    try // L查找try块中发生的异常并路由到附加的catch块
    {
        // 如果用户输入了负数,则这是错误情况
        if (x < 0.0)
            throw "Can not take sqrt of negative number"; // t抛出类型const char *的异常
 
        // 然后,打印答案
        std::cout << "The sqrt of " << x << " is " << sqrt(x) << '\n';
    }
    catch (const char* exception) // 捕获const char类型的异常*
    {
        std::cerr << "Error: " << exception << '\n';
    }
}

在此代码中,要求用户输入一个数字。如果它们输入正数,则if语句不会执行,不会抛出异常,并打印数字的平方根。因为在这种情况下不会抛出异常,所以catch块内的代码永远不会执行。结果是这样的:

Enter a number: 9
The sqrt of 9 is 3

如果用户输入负数,我们抛出类型为const char *的异常。因为我们在try块中并且找到了匹配的异常处理程序,所以控件立即转移到const char *异常处理程序。结果是:

Enter a number: -4
Error: Can not take sqrt of negative number

到现在为止,您应该了解异常背后的基本概念。在下一课中,我们将举几个例子来说明灵活的例外情况。

捕获块通常做什么

如果将异常路由到catch块,即使catch块为空,也会将其视为“已处理”。但是,通常你会希望你的catch块做一些有用的事情。当catch捕获异常时,catch会执行以下三种常见操作:

首先,catch块可能会输出错误(无论是控制台还是日志文件)。

其次,catch块可能会将值或错误代码返回给调用者。

第三,catch块可能抛出另一个异常。因为catch块在try块之外,所以在这种情况下新抛出的异常不会被前面的try块处理 - 它由下一个封闭的try块处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值