转载于http://c.biancheng.net/view/422.html
C++ 通过 throw 语句和 try…catch 语句实现对异常的处理。
throw 语句
语法如下:throw 表达式;
该语句拋出一个异常。异常是一个表达式,其值的类型可以是基本类型,也可以是类。
try…catch 语句
语法如下:
try {
try块
}
catch(异常类型) {
“catch块”
}
...
catch(异常类型) {
“catch块”
}
catch 可以有多个,但至少要有一个。
try…catch 语句的执行过程是:
- 执行 try 块中的语句,如果执行的过程中没有异常拋出(执行throw语句即为抛出),那么执行完后就执行最后一个 catch 块后面的语句(即跳过所有catch),所有 catch 块中的语句都不会被执行;
- 如果 try 块执行的过程中拋出了异常,那么拋出异常后立即跳转到第一个“异常类型”和拋出的异常类型匹配的 catch 块中执行(称作异常被该 catch 块“捕获”),执行完后再跳转到最后一个 catch 块后面继续执行。
实例
#include <iostream>
using namespace std;
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;
}
cout << "finished" << endl;
return 0;
}
能够捕获任何异常的 catch 语句
不论拋出哪种类型的异常都能捕获,可以编写如下 catch 块:
catch(...) {
...
}
实例
#include <iostream>
using namespace std;
int main()
{
double m, n;
cin >> m >> n;
try {
cout << "before dividing." << endl;
if (n == 0)
throw - 1; //抛出整型异常
else if (m == 0)
throw - 1.0; //拋出 double 型异常
else
cout << m / n << endl;
cout << "after dividing." << endl;
}
catch (double d) {
cout << "catch (double)" << d << endl;
}
catch (...) {
cout << "catch (...)" << endl;
}
cout << "finished" << endl;
return 0;
}
程序的运行结果如下:
9 0↙
before dividing.
catch (...)
finished
或者
0 6↙
before dividing.
catch (double) -1
finished
异常的再拋出
如果一个函数在执行过程中拋出的异常在本函数内就被 catch 块捕获并处理,那么该异常就不会拋给这个函数的调用者(也称为“上一层的函数”);如果异常在本函数中没有被处理,则它就会被拋给上一层的函数。例如下面的程序:
#include <iostream>
#include <string>
using namespace std;
class CException
{
public:
string msg;
CException(string s) : msg(s) {}
};
double Devide(double x, double y)
{
if (y == 0)
throw CException("devided by zero");
cout << "in Devide" << endl;
return x / y;
}
int CountTax(int salary)
{
try {
if (salary < 0)
throw - 1;
cout << "counting tax" << endl;
}
catch (int) {
cout << "salary < 0" << endl;
}
cout << "tax counted" << endl;
return salary * 0.15;
}
int main()
{
double f = 1.2;
try {
CountTax(-1);
f = Devide(3, 0);
cout << "end of try block" << endl;
}
catch (CException e) {
cout << e.msg << endl;
}
cout << "f = " << f << endl;
cout << "finished" << endl;
return 0;
}
程序的输出结果如下:
salary < 0
tax counted
devided by zero
f=1.2
finished
CountTax 函数拋出异常后自行处理(自带catch语句),这个异常就不会继续被拋给调用者,即 main 函数。因此在 main 函数的 try 块中,CountTax 之后的语句还能正常执行,即会执行cout << "tax counted" << endl;
和f = Devide(3, 0)
;。
Devide 函数拋出了异常却不处理(没有catch语句),该异常就会被拋给 Devide 函数的调用者,即 main 函数。拋出此异常后,Devide 函数立即结束,cout << "in Devide" << endl;
不会被执行,函数也不会返回一个值,这从cout << "f = " << f << endl;
中f 的值不会被修改可以看出。
如果拋出的异常是派生类的对象,而 catch 块的异常类型是基类,那么这两者也能够匹配,因为派生类对象也是基类对象。
虽然函数也可以通过返回值或者传引用的参数通知调用者发生了异常,但采用这种方式的话,每次调用函数时都要判断是否发生了异常,这在函数被多处调用时比较麻烦。有了异常处理机制,可以将多处函数调用都写在一个 try 块中,任何一处调用发生异常都会被匹配的 catch 块捕获并处理,也就不需要每次调用后都判断是否发生了异常。
有时,虽然在函数中对异常进行了处理,但是还是希望能够通知调用者,以便让调用者知道发生了异常,从而可以作进一步的处理。在 catch 块中拋出异常可以满足这种需要。例如:
#include <iostream>
#include <string>
using namespace std;
int CountTax(int salary)
{
try {
if( salary < 0 )
throw string("zero salary");
cout << "counting tax" << endl;
}
catch (string s ) {
cout << "CountTax error : " << s << endl;
throw; //继续抛出捕获的异常
}
cout << "tax counted" << endl;
return salary * 0.15;
}
int main()
{
double f = 1.2;
try {
CountTax(-1);
cout << "end of try block" << endl;
}
catch(string s) {
cout << s << endl;
}
cout << "finished" << endl;
return 0;
}
程序的输出结果如下:
CountTax error:zero salary
zero salary
finished
C++标准异常类
C++ 标准库中有一些类代表异常,这些类都是从 exception 类派生而来的。常用的几个异常类如图所示。
bad_typeid、bad_cast、bad_alloc、ios_base::failure、out_of_range 都是 exception 类的派生类。C++ 程序在碰到某些异常时,即使程序中没有写 throw 语句,也会自动拋出上述异常类的对象。这些异常类还都有名为 what 的成员函数,返回字符串形式的异常描述信息。使用这些异常类需要包含头文件 stdexcept。
out_of_range
用 vector 或 string 的 at 成员函数根据下标访问元素时,如果下标越界,则会拋出此异常。例如:
#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<int> v(10);
try {
v.at(100) = 100; //拋出 out_of_range 异常
}
catch (out_of_range & e) {
cerr << e.what() << endl;
}
string s = "hello";
try {
char c = s.at(100); //拋出 out_of_range 异常
}
catch (out_of_range & e) {
cerr << e.what() << endl;
}
return 0;
}
实例中的cerr是一个ostream对象,关联到标准错误,具体信息可转到C++:标准错误流Cerr。
程序的输出结果如下:
invalid vector <T> subscript
invalid string position
如果将v.at(100)
换成v[100]
,将s.at(100)
换成s[100]
,程序就不会引发异常(但可能导致程序崩溃)。因为 at 成员函数会检测下标越界并拋出异常,而 operator[] 则不会。operator [] 相比 at 的好处就是不用判断下标是否越界,因此执行速度更快。