异常
异常,是程序运行时不正确的状态。比如数组越界、整数除零等程序需要报错的状态。我们可以对异常进行捕捉和处理(使用try-catch语句块)。那么对异常进行捕捉和处理有什么好处呢?
我们用整数除零的例子来讲述。
首先看一个最为基础的例子:
#include <iostream> using namespace std; int main() { cout << "输入两个数:" << endl; int a, b; cin >> a >> b; cout << "两数相除的结果为:" << a/b << endl; return 0; }
输入4和2:
但是输入4和0:
可以看到,语句
cout << "两数相除的结果为:" << a/b << endl;
并没有执行,并且程序返回了一个负数,说明程序运行时出现了错误而退出
这是因为整数不能够除0所导致的异常。如果我们后面还有很多代码,还要执行很多动作,那么在异常之后的语句都不会被执行了。也就是说,如果程序中出现了一处异常,会导致整个程序的崩溃。这显然不是程序员想看到的结果。
异常处理
我们使用异常处理机制,也就是捕捉和处理异常的try-catch语句来处理异常。在try-catch语句块中,try语句块用来捕捉异常,也就是说,在try语句块中的是一段有可能产生异常的代码段;而用catch语句块来处理异常,比如显示错误信息等。
如果发生了异常,try语句块会将异常捕捉并交给catch语句块处理,这样一来就避免了程序崩溃的情况,异常之后的程序仍可以运行。
加了异常处理的两数除法:
#include <iostream> using namespace std; int main() { cout << "输入两个数:" << endl; int a, b; cin >> a >> b; try{ if (b == 0) throw b; cout << "两数相除的结果为:" << a/b << endl; } catch (int ex) { cout << a << "不能除0" << endl; } return 0; }
输入4和0:
为了简单,我们这次直接抛出了一个int类型变量,但是在实际中,我们抛出的一般是异常类。
异常的抛出也可以写在函数里:
#include <iostream> using namespace std; int quotient(int a, int b) { if (b == 0) { throw b; } return a/b; } int main() { cout << "输入两个数:" << endl; int a, b; cin >> a >> b; try{ cout << a << "/" << b << "=" << quotient(a,b) << endl; } catch (int e){ cout << a << "不能除0" << endl; } return 0; }
思考:为什么使用异常处理机制
在上面的例子中,我们使用if语句也可以实现同样的效果
#include <iostream> using namespace std; int main() { cout << "输入两个数:" << endl; int a, b; cin >> a >> b; if (b == 0) { cout << a << "不能除0" << endl; } else { cout << "两数相除的结果为:" << a/b << endl; } return 0; }
那么我们为什么要“多此一举”使用异常处理呢?
比如我们存在着文件输入输出的异常,我们就没有办法去使用if语句来使程序正常运行了。
这只是理由之一,而更重要的是,if语句体现的是流程控制的思想,而异常处理体现的是一种对错误进行有效控制的思想。再者,使用if语句处理并不适合OOP,因为其语义不明显,不利于错误跟踪且类型单一。
if语句一般解决简单的逻辑错误,而异常处理是针对意外情况的。
在异常处理时,会允许一个函数向它的调用者(向上)抛出一个异常(如上面的例子中,quotient函数将其异常抛给调用它的main函数),让调用者处理异常。如果不具备异常处理的能力,函数就必须自己处理异常,或者程序停止运行。通常情况下,当错误发生时,被调用者,尤其是库函数,其自身并不知道如何处理。库函数可以检测到异常,但是只有调用者知道如何处理。
比如在发生文件的读写异常时,我们可以将异常抛给调用者,调用者决定下一步的动作是提示用户错误,还是直接报错,又或者执行其他的动作。而不是让被调用者本身处理错误。
异常处理的基本思想就是将错误检测(被调用者)和异常处理(调用者)分开。