这篇文章介绍下 C++ 的异常处理。
讨论一种最为常见的出现异常的情况,即 0 不能作为除数。为此,我们将自定义一个除法:
#include <iostream>
using namespace std;
int divide(int a, int b)
{
return a / b;
}
int main()
{
int a = 3, b = 0;
int res = divide(a, b);
cout << "result = " << res;
}
事实上这样的代码在我的电脑上运行会卡在终端,也没有提示无法运行的报错,把 res 直接改成 3/0 就看到正常的报错了:
TestDivide.cpp: In function 'int main()':
TestDivide.cpp:14:29: warning: division by zero [-Wdiv-by-zero]
14 | cout << "result = " << 2/0;
| ~^~
result =
不使用异常机制的处理
abort
尝试在函数内部加入针对除数为0的报错,使用 abort()
int divide(int a, int b)
{
if(b==0)
{
cout << "cannot divide by 0" << endl;
abort();
}
return a / b;
}
事实上,这次除了打印我加入的一行文字以外,程序的运行并没有明显的变化。abort() 的作用是向标准错误流发送消息 abnormal program termination ,然后终止程序。它还会返回一个值告诉父进程处理失败。
不知道为什么,这里没有终止进程。
返回值处理错误
另一种在 C++ 里常用的方式是,将我们想获取的值作为引用传回来,将返回值作为错误的标记。重新设计的 divide
如下:
bool divide(int a, int b, int& res)
{
if(b==0)
{
cout << "cannot divide by zero" << endl;
return false;
}else
{
res = a/b;
return true;
}
}
调用我就不写了。
这种方式一定程度上规避掉了系统的异常机制,换句话说,这是由开发者自己“实现”的异常,编译器不会认为这段代码是处理异常的。
使用异常机制的处理
现代C++ 的异常机制多少借鉴了其他语言的机制,即 throw-try-catch-finally
机制,即引发异常-捕获异常-处理异常
引发异常
改造 divide 函数,使其能够抛出异常:
int divide(int a, int b)
{
if(b==0)
{
throw "cannot divide by ZERO!";
}
return a / b;
}
执行 throw
相当于返回了异常,因此从某种程度上来说,这里的 throw
和 return
的作用差不多。但是不同的是,throw
并不是把控制权返回给调用程序,而是会沿着调用序列后退,直到找到能处理异常的 catch
语句为止。
捕获与处理异常
main 函数改写如下:
int main()
{
int a = 3, b = 0;
int res = 0;
try
{
res = divide(a, b);
}
catch (const char *s)
{
cout << s << endl;
}
cout << "res = " << res;
}
输出如下:
cannot divide by ZERO!
res = 0
如果引发的异常最终没有与之匹配的类型 catch
,效果等同于调用 abort()
函数。
使用基于自定义类的异常机制
使用字符串处理异常需要我们编写代码打印异常类型,依据面向对象的原则,其实写一个类来处理更好。
在头文件中定义一个 bad_divide 类专门用于处理异常,其 what() 方法(此方法和 C++ 标准类中的方法在名字上是一致的)用于打印异常信息:
struct bad_divide
{
private:
int a;
int b;
public:
bad_divide(int a, int b) : a(a), b(b){};
void what()
{
cout << a << " divided by " << b << " is illegal!" << endl;
}
};
同样,调用的地方也不再捕捉字符串了,而是捕捉这个对象,并调用 what()
方法打印异常。
int divide(int a, int b)
{
if (b == 0)
{
throw bad_divide(a, b);
}
return a / b;
}
int main()
{
int a = 3, b = 0;
int res = 0;
try
{
res = divide(a, b);
}
catch (bad_divide &bd)
{
bd.what();
}
cout << "res = " << res;
}