异常处理【C++】
1、异常的概念
程序运行过程中,由于环境变化、用户操作失误以及其它方面的原因而产生的运行时不正常的情况,它要求程序立即进行处理,否则将会引起程序错误甚至崩溃的现象。这些现象就是异常。
常见的异常有:空间内存耗尽、请求打开不存在的文件、被零除、数组越界访问等。
C++ 程序是由一些相互分离的模块组成的,程序中出现错误和解决出现的错误就会分成两个部分:
(1)某个模块 A 出现错误,但它并没有能力在模块 A 内解决这个错误,因此它就给出关于这个错误的报告。
(2)某个模块 B 能够检测到模块 A 发出的错误报告,并处理这个错误,使出现错误造成的损失减到最小。
2、异常处理机制
(1)抛出异常
如果程序发生异常情况,而在当前的上下文环境中获取不到处理这个异常的足够信息,程序将创建一个包含出错信息的对象并将该对象抛出当前上下文环境,将错误信息发送到更大的上下文环境中,这个过程称为抛出(throw)异常。
(2)捕捉异常
对于一个抛出的异常,如果某一个模块能够(或想要)处理这个异常,它就可以获得程序的控制权处理该异常,这个过程称为捕捉(catch)异常。
(3)处理异常
当某个 catch 块捕捉到异常后,它就根据事先制定的策略对异常进行处理,这就是处理异常。在 C++ 中,只有 catch 块能够捕获异常并进行处理,因此 catch 块又称为异常处理器。
(4)C++ 的异常处理机制
C++ 的异常处理机制就是将抛出异常与捕捉异常、处理异常分离开来。抛出异常的模块并不负责异常的处理,它只是报告某个地方存在错误,这个报告可以帮助异常处理器解决这个错误。而异常处理器则根据抛出异常模块的报告来处理异常,如果没有模块抛出异常,就不会有异常的处理。
3、throw 语句
抛出异常的语法格式为:
throw 表达式
throw 后的表达式表示异常的类型,它可以是一个变量或者一个对象。在语法上与 return 语句相似。异常抛出后,程序的控制权就从异常抛出的地方交出,由编译器寻找匹配的异常处理器进行相应的处理。
4、try 块
try 块的语法格式为:
try {
被检查语句
}
try 块必须包围能够抛出异常的语句。它提示编译器到哪里寻找异常处理器,没有跟在 try 块后的 catch 块是没有用的。try 块可以包含任何语句,甚至包含整个函数。
5、catch 块
catch 块的语法格式为:
catch(异常类型声明1){
异常处理语句
}
catch(异常类型声明2){
异常处理语句
}
......
【例】处理除零异常
#include <iostream> using namespace std; int Div(int x, int y); int main() { try { cout << "5/2=" << Div(5, 2) << endl; cout << "8/0=" << Div(8, 0) << endl; cout << "7/1=" << Div(7, 1) << endl; } catch (int) { cout << "除数为零" << endl; } cout << "正确" << endl; return 0; } int Div(int x, int y) { if (y == 0) throw y; return x / y; }
6、异常处理的过程
异常处理的执行过程如下:
(1)控制通过正确的顺序执行到 try 语句,然后执行 try 块内的保护段;
(2)如果在保护段执行期间没有引起异常,那么跟着 try 块后的 catch 语句就不执行,程序从异常被抛出的 try 块后跟随的最后一个 catch 语句后面的语句继续执行下去;
(3)如果在保护段执行期间或在保护段调用的任何函数中有异常被抛出,则从通过 throw 运算数创建的对象中创建一个异常对象;
(4)如果匹配的处理器未找到,则函数 terminate 将被自动调用,而函数 terminate 的默认功能是调用 abort 终止程序。
(5)如果找到了一个匹配的 catch 处理程序,且它通过值进行捕获,则其形参通过拷贝异常对象进行初始化。
7、异常处理模式
C++ 的异常处理模式有两种基本模式:
(1)终止模式
异常抛出后,捕捉异常并退出导致异常的子程序或子系统,退出需要关闭适当的文件,析构适当的对象,释放适当的内存,处理需要处理的设备等,这种方法称为终止模式。通常情况下,C++ 异常处理机制采用终止方法。
【例】
#include <iostream> using namespace std; void fun() { // 一段有可能引起异常的代码 throw 1; cout << "其它程序语句!" << endl; } void main() { try { fun(); // 抛出的异常值为 1 cout << "异常处理结束后继续执行!" << endl; } catch(int x){ cout << "处理了int类型的异常!" << endl; } cout << "程序结束!" << endl; }
(2)恢复模式
异常抛出后,捕捉异常并试图去纠正或调整引起异常的条件,然后从发生异常的地方继续执行,这种方法称为恢复模式。 恢复模式实现起来非常困难,在实际应用中,除了一些特殊的领域外,一般都不采用恢复模式处理异常。
例如在上例中若采用了恢复模式处理异常则输出结果为:
处理了int类型的异常! 其它程序语句! 异常处理结束后继续执行! 程序结束!
8、重新抛出
在异常处理过程中也可能存在 “单个 catch 子句不能完全处理这个异常” 的情况。那么该异常处理器在做完局部能够做的事情后,会再一次抛出这个异常,让函数调用链中更上级的函数来处理,这个过程称作重新抛出(rethrow)。
重新抛出的语法格式为:
throw;
重新抛出的还是原来捕捉到的那个异常,重新抛出只能出现在 catch 块中。有的时候,异常处理器在重新抛出之前会对异常信号进行一些修改,这个修改能够影响更高级函数调用链中的异常处理器对该异常的处理。
【例】 重新抛出捕捉的异常例题
#include <iostream> using namespace std; void fun(int x) { try { // 一段有可能引起异常的代码 throw x; } catch (int x) { if (x <= 0) { // 对异常的处理 } else { cout << "重新抛出异常!" << endl; throw; } } } void main() { try { fun(1); // 程序其它部分 } catch(int x){ // 对异常的处理 cout << "处理了int类型的异常!" << endl; } }
9、异常规范
异常规范规定:随着函数声明列出该函数可能抛出的异常,并保证该函数不会抛出其它类型的异常。常见附带异常说明的函数说明有以下三种情况:
(1)函数列出所有可能抛出的异常类型;
函数返回类型 函数名(参数列表) throw (类型列表);
(2)函数不会抛出任何类型的异常;
函数返回类型 函数名(参数列表) throw();
(3)函数可能抛出任何类型的异常。
函数返回类型 函数名(参数列表);
异常规范并非强制规定,因此,没有在函数说明后附带异常说明并非语法错误。
【例】异常规范的处理例题
void fun1()(int x)throw(int, char*){ // x == 0,抛出 int 型异常; // x < 0,抛出 char* 型异常; // x > 0,什么也不做 if (x == 0) throw 0; if (x < 0) throw "error"; } void fun2()throw() { // 本函数完成 10 个指令周期的延时,不抛出任何异常 int i = 0; while(i < 10) i++; }
10、未被捕捉的异常
如果任意层的异常处理器都没有捕获到这个异常,那么这个异常最终会抛给 main() 函数,如果在 main() 中还没有找到合适的匹配,则称这个异常是 “未捕捉的” 或 “未处理的” 。如果一个异常未被捕捉,就会调用函数 terminate(),终止本程序的运行。
【例】未被捕捉的异常处理例题
#include <iostream> using namespace std; void fun() { throw 0; } int main() { try { fun(); } catch(double d){ cout << "进行了异常处理!" << endl; } return 0; }
11、catch(...) 的使用
C++ 在异常处理中提供了一个能捕捉所以异常的 catch 块。catch 块的语法格式为:
catch(...) {
异常处理语句
}
其中,列表中的 “…” 表示可捕获所有的异常,但使用省略号就不可能有参数,也不可能知道所接受到的异常为何种类型。其它部分和普通 catch 块完全一样。
【例】使用 catch(...) 语句的异常处理例题
#include <iostream> using namespace std; void fun(int x) throw(int,char*) { // x == 0,抛出 int 型异常 // x < 0,抛出 char* 型异常 if (x == 0) throw 0; if (x < 0) throw "error"; } int main() { try { int x = 0; cout << "请输入一个 int 型数据:"; cin >> x; fun(x); } catch(...){ cout << "处理了所有类型的异常!" << endl; } cout << "程序结束!" << endl; return 0; }