详解C++异常处理(trycatchthrow)完全攻略
程序运⾏时常会碰到⼀些异常情况,例如:
做除法的时候除数为 0;
⽤户输⼊年龄时输⼊了⼀个负数;
⽤ new 运算符动态分配空间时,空间不够导致⽆法分配;
访问数组元素时,下标越界;打开⽂件读取时,⽂件不存在。
这些异常情况,如果不能发现并加以处理,很可能会导致程序崩溃。
所谓“处理”,可以是给出错误提⽰信息,然后让程序沿⼀条不会出错的路径继续执⾏;也可能是不得不结束程序,但在结束前做⼀些必要的⼯作,如将内存中的数据写⼊⽂件、关闭打开的⽂件、释放动态分配的内存空间等。
⼀发现异常情况就⽴即处理未必妥当,因为在⼀个函数执⾏过程中发⽣的异常,在有的情况下由该函数的调⽤者决定如何处理更加合适。尤其像库函数这类提供给程序员调⽤,⽤以完成与具体应⽤⽆关的通⽤功能的函数,执⾏过程中贸然对异常进⾏处理,未必符合调⽤它的程序的需要。
此外,将异常分散在各处进⾏处理不利于代码的维护,尤其是对于在不同地⽅发⽣的同⼀种异常,都要编写相同的处理代码也是⼀种不必要的重复和冗余。如果能在发⽣各种异常时让程序都执⾏到同⼀个地⽅,这个地⽅能够对异常进⾏集中处理,则程序就会更容易编写、维护。
鉴于上述原因,C++ 引⼊了异常处理机制。其基本思想是:函数 A 在执⾏过程中发现异常时可以不加处理,⽽只是“拋出⼀个异常”给 A 的调⽤者,假定为函数 B。
拋出异常⽽不加处理会导致函数 A ⽴即中⽌,在这种情况下,函数 B 可以选择捕获 A 拋出的异常进⾏处理,也可以选择置之不理。如果置之不理,这个异常就会被拋给 B 的调⽤者,以此类推。
如果⼀层层的函数都不处理异常,异常最终会被拋给最外层的 main 函数。main 函数应该处理异常。如果main函数也不处理异常,那么程序就会⽴即异常地中⽌。
C++异常处理基本语法
C++ 通过 throw 语句和 atch 语句实现对异常的处理。throw 语句的语法如下:
throw  表达式;
该语句拋出⼀个异常。异常是⼀个表达式,其值的类型可以是基本类型,也可以是类。
try {
语句组
}
catch(异常类型) {
异常处理代码
}
...
catch(异常类型) {
异常处理代码
}
catch 可以有多个,但⾄少要有⼀个。
不妨把 try 和其后{}中的内容称作“try块”,把 catch 和其后{}中的内容称作“catch块”。
执⾏ try 块中的语句,如果执⾏的过程中没有异常拋出,那么执⾏完后就执⾏最后⼀个 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;
}
程序的运⾏结果如下:
9 6↙
before dividing.
1.5
after dividing.
finished
说明当 n 不为 0 时,try 块中不会拋出异常。因此程序在 try 块正常执⾏完后,越过所有的 catch 块继续执⾏,catch 块⼀个也不会执⾏。
程序的运⾏结果也可能如下:
9 0↙
before dividing.
catch\(int) -1
finished
当 n 为 0 时,try 块中会拋出⼀个整型异常。拋出异常后,try 块⽴即停⽌执⾏。该整型异常会被类型匹配的第⼀个 catch 块捕获,即进⼊catch(int e)块执⾏,该 catch 块执⾏完毕后,程序继续往后执⾏,直到正常结束。
如果拋出的异常没有被 catch 块捕获,例如,将catch(int e),改为catch(char e),当输⼊的 n 为 0 时,拋出的整型异常就没有catch 块能捕获,这个异常也就得不到处理,那么程序就会⽴即中⽌,atch 后⾯的内容都不会被执⾏。
能够捕获任何异常的 catch 语句
如果希望不论拋出哪种类型的异常都能捕获,可以编写如下 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
当 n 为 0 时,拋出的整型异常被catchy(...)捕获。
程序的运⾏结果也可能如下:
0 6↙
before dividing.
catch (double) -1
finished
当 m 为 0 时,拋出⼀个 double 类型的异常。虽然catch (double)和catch(...)都能匹配该异常,但是catch(double)是第⼀个能匹配的 catch 块,因此会执⾏它,⽽不会执⾏catch(...)块。
由于catch(...)能匹配任何类型的异常,它后⾯的 catch 块实际上就不起作⽤,因此不要将它写在其他 catch 块前⾯。
异常的再拋出
如果⼀个函数在执⾏过程中拋出的异常在本函数内就被 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;try catch的使用方法
}
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
CountTa 函数拋出异常后⾃⾏处理,这个异常就不会继续被拋给调⽤者,即 main 函数。因此在 main 函数的 try 块
中,CountTax 之后的语句还能正常执⾏,即会执⾏f = Devide(3, 0);。
第 35 ⾏,Devide 函数拋出了异常却不处理,该异常就会被拋给 Devide 函数的调⽤者,即 main 函数。拋出此异常
后,Devide 函数⽴即结束,第 14 ⾏不会被执⾏,函数也不会返回⼀个值,这从第 35 ⾏ f 的值不会被修改可以看出。
Devide 函数中拋出的异常被 main 函数中类型匹配的 catch 块捕获。第 38 ⾏中的 e 对象是⽤复制构造函数初始化的。
如果拋出的异常是派⽣类的对象,⽽ 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
第 14 ⾏的throw;没有指明拋出什么样的异常,因此拋出的就是 catch 块捕获到的异常,即 string("zero salary")。这个异常会被 main 函数中的 catch 块捕获。
函数的异常声明列表
为了增强程序的可读性和可维护性,使程序员在使⽤⼀个函数时就能看出这个函数可能会拋出哪些异常,C++ 允许在函数声明和定义时,加上它所能拋出的异常的列表,具体写法如下:
void func() throw (int, double, A, B, C);
void func() throw (int, double, A, B, C){...}
上⾯的写法表明 func 可能拋出 int 型、double 型以及 A、B、C 三种类型的异常。异常声明列表可以在函数声明时写,也可以在函数定义时写。如果两处都写,则两处应⼀致。
如果异常声明列表如下编写:
void func() throw ();
则说明 func 函数不会拋出任何异常。
⼀个函数如果不交待能拋出哪些类型的异常,就可以拋出任何类型的异常。
函数如果拋出了其异常声明列表中没有的异常,在编译时不会引发错误,但在运⾏时, Dev C++ 编译出来的程序会出错;⽤Visual Studio 2010 编译出来的程序则不会出错,异常声明列表不起实际作⽤。
C++标准异常类
C++ 标准库中有⼀些类代表异常,这些类都是从 exception 类派⽣⽽来的。常⽤的⼏个异常类如图 1 所⽰。
图1:常⽤的异常类
bad_typeid、bad_cast、bad_alloc、ios_base::failure、out_of_range 都是 exception 类的派⽣类。C++ 程序在碰到某些异常时,即使程序中没有写 throw 语句,也会⾃动拋出上述异常类的对象。这些异常类还都有名为 what 的成员函数,返回字符串形式的异常描述信息。使⽤这些异常类需要包含头⽂件 stdexcept。
下⾯分别介绍以上⼏个异常类。本节程序的输出以 Visual Studio 2010为准,Dev C++ 编译的程序输出有所不同。
1) bad_typeid
使⽤ typeid 运算符时,如果其操作数是⼀个多态类的指针,⽽该指针的值为 NULL,则会拋出此异常。
2) bad_cast
在⽤ dynamic_cast 进⾏从多态基类对象(或引⽤)到派⽣类的引⽤的强制类型转换时,如果转换是不安全的,则会拋出此异常。程序⽰例如下:
#include <iostream>
#include <stdexcept>
using namespace std;
class Base
{
virtual void func() {}
};
class Derived : public Base
{
public:
void Print() {}
};
void PrintObj(Base & b)
{
try {
Derived & rd = dynamic_cast <Derived &>(b);
//此转换若不安全,会拋出 bad_cast 异常
rd.Print();
}
catch (bad_cast & e) {
cerr << e.what() << endl;
}
}
int main()
{
Base b;
PrintObj(b);
return 0;
}
程序的输出结果如下:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。