基本异常处理
在上一节关于异常需求的课程中,我们讨论了如何使用返回代码导致控制流和错误流混合,从而限制两者。 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块处理。