11. 异常

84 阅读4分钟

11. 异常


abort() 程序异常终止

#include<cstdlib>if(a == -b)  // 判断可能出现的异常
    cout<< "Error!\n";
    abort();  // 调用函数,发送错误消息,终止程序。

abort()向标准错误流发送消息abnormal program termination(程序异常终止),然后终止程序。

try-throw-catch 异常处理机制

基本类型作为异常类型

try  // 标识其中特定异常可能被激活
{
    ...;
    if(异常)  // 检测异常
    {
        throw 抛出的异常;  // 抛出异常
    }
 
}
catch (异常类型)  // 用以处理前面try块对应异常类型的异常
{
    ...;  // 异常处理
}
  • 如果try中某条语句导致异常被引发,则后面的catch块将对异常进行处理,如果是try块外引发的异常将不会被处理
  • 执行throw语句类似于执行函数返回,但不同的是其不是返回给调用程序的下一条语句,而是后退到包含try块的函数, 然后找到try块后面对应的catch块,进行异常处理。
  • 执行完try,如果没有引发任何异常,将跳过try后面的catch块。
  • 当引发异常后,只有对应类型的异常处理块会被执行。
  • 没有操作数的throw表达式将重新引发当前正在处理的异常。
  • 可以使用省略号代表任何异常catch(...),这代表处理任何异常
// error3.cpp#include <iostream>
​
using namespace std;
​
double hmean(double a, double b);
​
int main()
{
    double x, y, z;
    cout << "Enter two number: ";
    while(cin >> x >> y)
    {
        try  // try块,标识可能会出现异常的地方
        {
            z = hmean(x, y);
        }
        catch (const char *s)  // 用以处理try块内throw抛出的字符串类异常
        {
            cout << s << endl;
            cout << "Enter a new pair numbers: ";
            continue;
        }
        cout << "Harmonic mean of " << x " and " << y
            << " is " << z << endl;
        cout << "Enter new pair <q tu quit>: ";
    }
    cout << "Bye!\n";
    return 0;
    
}
​
double hmean(double a, double b)
{
    if(a == -b)
        throw "bad hmean() arguments: a = -b not allowed!";  // 抛出异常
    return 2.0 * a *b /(a +b);
}

当-10和10 被传递给hmean()后,if导致hmean()引发异常。这将终止hmean()的运行,程序向后搜索发现,heam()是从main()内的try块中调用的,因此程序开始查找main()内此try后的catch异常处理程序。直到发现程序中唯一的catch块的参数为char*,这与引发的异常类型匹配。程序将字符串"bad hmean() arguments: a = -b not allowed!"传递给变量s,然后执行catch块内程序。

将对象作为异常类型

  1. 将对象作为异常类型的优点

    1. 用以区别不同异常。
    2. 对象可以携带信息,用以指出更多的信息。
class bad_hmean
{
private:
    double v1;
    double v2;
public:
    bad_hmean(int a = 0, int b = 0) : v1(a), v2(b) {}
    void mesg();
    
};
​
inline void bad_hmean::mesg()
{
    cout << "hmean(" << v1 << ", " << v2 << "): "
        << "invalid arguments: a = -b\n";
}
​
// ...
try
{
    if(a == -b)
        throw bad_hmean(a, b);  // 这将会调用构造函数bad_hmean()以初始化对象,使其存储参数值。
}
catch (bad_hmean & bh)  // 将交由此catch块处理
{
    ...;
}
catch (bad_gmean & bg)
{
    ...;
}
​

栈解退

当try块没有直接调用了引发throw的函数,而是调用了对引发异常的函数进行调用的函数,则程序将从引发异常的函数跳到包含try和catch块的函数中。这涉及到了栈解退。

  1. 函数调用与返回 return

    当函数被调用时,将进行函数栈帧的压栈。程序将调用函数的指令的地址(返回地址)放到栈中。当被调用函数执行完后,程序将使用该地址来确定从调用函数的哪里开始继续执行,将返回调用函数后面的第一条语句。除调用函数的指令的地址外,传入参数、被调用函数内部创建的局部变量也将随之被压栈,如果被调用函数又调用了另一个函数,那么这个函数的信息也被压栈,依此类推。当函数执行结束后,程序将跳到该函数被调时存储的地址处。同时栈顶的元素被释放,此过程中自动变量也将被释放,这个函数返回的过程将销毁栈帧。这是函数的调用与返回过程,栈帧将逐级创建,逐级释放。

  2. 栈解退 throw

    当函数由于出现异常而非正常返回时,程序也将释放栈中的内存,但不会在释放栈的第一个返回地址后停止,而是继续释放栈,直到找到一个位于try块中的地址,随后程序将跳转到位于try尾的相应类型的异常处理程序catch,而不是函数调用后面的第一条语句。这个过程叫做栈解退。

exception类

RTTI 运行阶段类型识别