为什么需要异常处理?
程序运行时常会碰到一些异常情况,例如:
- 做除法的时候除数为 0;
- 用户输入年龄时输入了一个负数;
- 用 new 运算符动态分配空间时,空间不够导致无法分配;
- 访问数组元素时,下标越界;
- 打开文件读取时,文件不存在。
这些异常情况,如果不能发现并加以处理,很可能会导致程序崩溃。
所以异常处理机制可以:
- 给出错误提示;
- 沿安全路径继续执行
- 在退出前完成清理工作(如将内存中的数据写入文件、关闭打开的文件、释放动态分配的内存空间等)。
异常处理的基本思想
- 函数 A 在执行过程中发现异常时可以不加处理,而只是“拋出一个异常”给 A 的调用者,假定为函数 B。
- 拋出异常而不加处理会导致函数 A 立即中止,在这种情况下,函数 B 可以选择捕获 A 拋出的异常进行处理,也可以选择置之不理。如果置之不理,这个异常就会被拋给 B 的调用者,以此类推。
- 如果一层层的函数都不处理异常,异常最终会被拋给最外层的 main 函数。main 函数应该处理异常。如果main函数也不处理异常,那么程序就会立即异常地中止。
基本语法
throw语句:
throw 表达式; // 表达式可以是任意类型:int、double、类对象等
try-catch语句:
try {
// 可能抛出异常的代码
} catch (异常类型1 e) {
// 处理类型1的异常
} catch (异常类型2 e) {
// 处理类型2的异常
} catch (...) {
// 捕获所有其他类型的异常
}
try...catch 语句的执行过程是:
执行 try 块中的语句,如果执行的过程中没有异常拋出,那么执行完后就执行最后一个 catch 块后面的语句,所有 catch 块中的语句都不会被执行;
如果 try 块执行的过程中拋出了异常,那么拋出异常后将立即跳转到第一个“异常类型”和拋出的异常类型匹配的 catch 块中执行(称作异常被该 catch 块“捕获”),执行完后再跳转到最后一个 catch 块后面继续执行。
示例一:基本使用(throw)
try {
if (n == 0) throw -1;
else cout << m / n << endl;
} catch (int e) {
cout << "捕获到整数异常: " << e << endl;
}
示例二:捕获所有异常(try-catch)
int main()
{
double m ,n;
cin >> m >> n;
try {
cout << "before dividing." << endl;
if( n == 0)
throw -1; //抛出int类型异常
else
cout << m / n << endl;
cout << "after dividing." << endl;
}
catch(double d) {
cout << "catch(double) " << d << endl;
}
catch(int e) {
cout << "catch(int) " << e << endl;
}
catch (...) {
cout << "发生未知异常" << endl;
}
cout << "finished" << endl;
return 0;
}
异常的再抛出
如果一个函数在执行过程中拋出的异常在本函数内就被 catch 块捕获并处理,那么该异常就不会拋给这个函数的调用者(也称为“上一层的函数”);如果异常在本函数中没有被处理,则它就会被拋给上一层的函数。
举例:
#include <iostream>
#include <string>
using namespace std;
// 自定义异常类
class MyException {
public:
string message;
MyException(string msg) : message(msg) {}
};
// 函数A:在内部捕获并处理异常,不会传播给调用者
void functionA() {
try {
cout << "函数A: 准备抛出异常..." << endl;
throw MyException("函数A内部的异常");
cout << "函数A: 这行不会执行" << endl;
}
catch (MyException e) {
cout << "函数A: 捕获并处理了异常: " << e.message << endl;
// 异常在这里被处理,不会传播给调用者
}
cout << "函数A: 正常继续执行" << endl;
}
// 函数B:不处理异常,异常会传播给调用者
void functionB() {
cout << "函数B: 准备抛出异常..." << endl;
throw MyException("函数B内部的异常");
cout << "函数B: 这行不会执行" << endl;
}
// 函数C:先处理异常,然后重新抛出
void functionC() {
try {
cout << "函数C: 准备抛出异常..." << endl;
throw MyException("函数C内部的异常");
}
catch (MyException e) {
cout << "函数C: 捕获到异常: " << e.message << ",但决定重新抛出" << endl;
throw; // 重新抛出当前异常
}
}
int main() {
cout << "=== 测试1: 函数内部处理异常 ===" << endl;
try {
functionA();
cout << "main: 函数A执行完毕,没有异常传播过来" << endl;
}
catch (MyException e) {
cout << "main: 捕获到来自函数A的异常: " << e.message << endl;
}
cout << "\n=== 测试2: 异常传播到调用者 ===" << endl;
try {
functionB();
cout << "main: 这行不会执行" << endl;
}
catch (MyException e) {
cout << "main: 捕获到来自函数B的异常: " << e.message << endl;
}
cout << "\n=== 测试3: 异常重新抛出 ===" << endl;
try {
functionC();
cout << "main: 这行不会执行" << endl;
}
catch (MyException e) {
cout << "main: 捕获到来自函数C重新抛出的异常: " << e.message << endl;
}
cout << "\n=== 程序正常结束 ===" << endl;
return 0;
}
虽然函数也可以通过返回值或者传引用的参数通知调用者发生了异常,但采用这种方式的话,每次调用函数时都要判断是否发生了异常,这在函数被多处调用时比较麻烦。有了异常处理机制,可以将多处函数调用都写在一个 try 块中,任何一处调用发生异常都会被匹配的 catch 块捕获并处理,也就不需要每次调用后都判断是否发生了异常。
有时,虽然在函数中对异常进行了处理,但是还是希望能够通知调用者,以便让调用者知道发生了异常,从而可以作进一步的处理。在 catch 块中拋出异常可以满足这种需要。
函数的异常声明列表
为了增强程序的可读性和可维护性,使程序员在使用一个函数时就能看出这个函数可能会拋出哪些异常,C++ 允许在函数声明和定义时,加上它所能拋出的异常的列表,具体写法如下:
void func() throw (int, double, A, B, C);
或
void func() throw (int, double, A, B, C){...}
上面的写法表明 func 可能拋出 int 型、double 型以及 A、B、C 三种类型的异常。异常声明列表可以在函数声明时写,也可以在函数定义时写。如果两处都写,则两处应一致。
如果异常声明列表如下编写,则说明 func 函数不会拋出任何异常:
void func() throw ();
一个函数如果不交待能拋出哪些类型的异常,就可以拋出任何类型的异常。
C++标准异常类
C++ 标准库中有一些类代表异常,这些类都是从 exception 类派生而来的。常用的几个异常类如图 所示。

bad_typeid、bad_cast、bad_alloc、ios_base::failure、out_of_range 都是 exception 类的派生类。C++ 程序在碰到某些异常时,即使程序中没有写 throw 语句,也会自动拋出上述异常类的对象。这些异常类还都有名为 what 的成员函数,返回字符串形式的异常描述信息。使用这些异常类需要包含头文件 stdexcept。
bad_typeid
使用 typeid 运算符时,如果其操作数是一个多态类的指针,而该指针的值为 NULL,则会拋出此异常。
#include <typeinfo>
typeid 的操作对象既可以是表达式,也可以是数据类型,下面是它的两种使用方法:
typeid( dataType )
typeid( expression )
dataType 是数据类型,expression 是表达式,这和 sizeof 运算符非常类似,只不过 sizeof 有时候可以省略括号( ),而 typeid 必须带上括号。
bad_cast
在用 dynamic_cast 进行从多态基类对象(或引用)到派生类的引用的强制类型转换时,如果转换是不安全的,则会拋出此异常。
bad_alloc
在用 new 运算符进行动态内存分配时,如果没有足够的内存,则会引发此异常。

ios_base::failure
在默认状态下,输入输出流对象不会拋出此异常。如果用流对象的 exceptions 成员函数设置了一些标志位,则在出现打开文件出错、读到输入流的文件尾等情况时会拋出此异常。
out_of_range
用 vector 或 string 的 at 成员函数根据下标访问元素时,如果下标越界,则会拋出此异常。

1608

被折叠的 条评论
为什么被折叠?



