简介异常处理


在〈定义类〉中,Accountdepositwithdraw,在参数值amount不正确时,都是直接显示文本模式下的消息后直接return,如果这个类不只使用在文本模式中呢?

这就需要有个方式,令depositwithdraw的调用方代码,可以得知实参不正确,方式之一是透过返回值,例如,令depositwithdraw可以返回int,以不同的返回值代表发生了什么问题,调用方必须检查返回值,知道方法是否顺利执行成功,并在返回值代表发生错误时予以处理,基于 C 风格的错误处理,就是这么一回事。

不过,因为 C/C++ 的返回值只能有一个,若方法本身就会返回执行结果,例如,若withdraw本身就会返回提领的数目,类型为double,那么就无法再以返回int值来通知错误是否发生,当然,透过适当的设计,还是可以达到通知错误的效果,例如返回负的double表示提领失败,或者是以类封装提领款项与错误码,令withdraw返回该类实例,透过检查实例中代表错误的值域来确认执行成功与否,若成功则提取实例中的款项字段,若失败进行错误处理。

然而,有时开发者会忘了要检查错误,程序也可能因此在发生错误时,继续往下执行,因而在后续发生不可预期的结果;另一方面,也许会希望有一种方式,在错误发生时,直接中断流程,而且传播错误,在每个上层的调用点都中断,直到有某个调用点处理错误为止。

当然,这也可以透过逐层编写检查错误的代码来达成,不过,若想令传播错误,在每个上层的调用点都中断,直到有某个调用点处理错误为止。这件事成为某些误发生时的默认行为呢?

C++ 可以借由抛出错误来达到,错误值可以是任何类型,例如抛出错误消息的字符串:

...略

void Account::deposit(double amount) {
    if(amount <= 0) {
        throw "必须存入正数";
    }
    this->balance += amount;
}

void Account::withdraw(double amount) {
    if(amount > this->balance) {
        throw "余额不足";
    }
    this->balance -= amount;
}

被抛出的错误称为异常(exception),在调用depositwithdraw时,若指定了错误的实参,异常会被抛出,执行流程就会中断:

Account acct = {"123-456-789", "Justin Lin", 1000};
cout << acct.to_string() << endl;

acct.deposit(-500); // terminate called after throwing an instance of 'char const*'

实际执行执行程序时,流程从抛出异常后就整个中断了,程序整个挂点,若想处理被抛出的异常,可以使用try-catch语法,例如:

Account acct = {"123-456-789", "Justin Lin", 1000};

try {
    acct.deposit(-500);
}
catch(char const* error) {
    cout << error << endl;  // 显示「必须存入正数」
}

可能抛出异常的代码,可以编写在try区块之中,如果没有发生错误,执行完try区块后,就不会执行catch区块,若执行时发生错误,流程会从异常抛出处中断,然后对应的catch区块可以指定异常类型来捕捉,异常捕捉后会指定给括号中的变量,接着执行catch区块。

Account是某程序库的类,由于错误发生时的处理方式并没有写死,而是抛出异常,让调用方自行决定是否捕捉处理;这边在文本模式执行时,捕捉异常后显示错误值;想像一下,如果Account被用于图形接口环境,捕捉异常后就可以操作图形接口,来显示相对应的错误消息。


展开阅读全文