1、什么是异常、异常处理?

异常就是程序运行期出现的错误,而异常处理就是对有可能发生错误的地方做出预见性的安排。C++异常处理机制是一个用来有效地处理运行错误的非常强大且灵活的工具,它提供了更多的弹性、安全性和稳固性,克服了传统方法所带来的问题.异常的抛出和处理主要使用了以下三个关键字: try、 throw 、 catch 。常见的一些异常包括:数组下标越界、除数为0、内存不足等等。

2、异常处理机制

(1)主要使用了try...throw...catch这3个关键字,try就是尝试的意思,尝试运行正常的逻辑代码;catch就是捕获,捕获异常;而throw就是抛出,抛出异常状态

(2)我们在有一个可能会出现异常状态的地方,通过检测异常状态出现的条件,如果条件满足就通过throw抛出异常;我们将一个可能抛出异常的函数或者是代码块放在我们的try{ }中去运行,当然你也可以把不会抛出异常的函数或者是代码块放在try{}中运行,这都是不会编译出错的,但是只是没有必要;如果try{ }块中抛出了异常状态,那么这个异常状态就可能会被被后面的catch{ }捕获并进行处理,注意我这里说的只是可能,并没说一定会被捕获,具体后面再说。

(3)try和catch必须是同时出现的,可以是一对一(一个try块对应一个catch块),也可以是一对多(一个try块对应多个catch块),基本思想:主逻辑与异常处理分离

当我们在try块中抛出了异常,那么抛出异常语句的代码之后的代码将不会被运行了。

 

(4)从上面的示意图可以看出来,如果我们当前的函数的try抛出的异常没有被下面的catch捕获到,那么这个异常就会被抛向调用这个函数的函数,也就是会向上抛出,

试图通过他的上层来处理这个异常,如果都没能处理这个异常,那么这个异常就会被操作系统处理,操作系统就会进行简单粗暴的处理方法,直接使程序终止退出。

需要理解的是:异常出现被处理之后我们程序也是被终止的,只不过我们的这种终止方式不会显得那么粗暴、无礼。

3、使用方法

 1 #include <iostream>
 2 using namespace std;
 3 
 4 static void func(void)
 5 {
 6     throw 1;
 7 }
 8 
 9 int main(void)
10 {
11     try{
12           func();               // 使能够抛出异常的代码放在try块中
13     }
14     catch (int) 
15     {
16         cout << "发生int异常" << endl;
17     }
18     catch (double)
19     {
20         cout << "发生double异常" << endl;
21     }
22     catch (...)   //  ...表示能够捕获任何异常,只能放在最后面,
23     {
24         cout << "发生异常" << endl;
25     }
26 
27     return 0;
28 }
try...catch

(1)catch (...)表示这个catch块能够捕获任何异常,并且只能放在最后一个catch,反正要么就没有这个catch,有就只能放在最后一个catch中,否则编译不能通过;主要是用来处理上面所有的catch中不能处理的异常就由他来处理。

(2)catch (xxx)中捕获的xxx是什么? 可以是数据类型的形式(int),也可以是声明变量的形式(int a);到底用哪种方式关键看你的catch块中是否需要对throw抛出的变量进行操作:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 static void func(void)
 5 {
 6     throw 1;
 7 }
 8 
 9 int main(void)
10 {
11     try{
12         func();
13     }
14     catch (int a)    //  catch (int)
15     {
16         cout << "发生int异常" << endl;
17         cout << a << endl;
18     }
19 
20     return 0;
21 }
catch捕获的对象

所以从catch捕获的对象要么是一个数据类型,要么是一个声明的变量,有点类似于我们的函数参数,如果我们是声明一个函数那么可以在参数列表中不用加上变量名,如果是在定义的时候我们就得加上变量名,因为声明的时候主要是进行类型的匹配,而定义的时候必须会对参数进行操作,所以肯定要加上名字,不然怎么操作这个变量。

(3)throw抛出的是什么? 其实这个问题上面就已经解决了,因为catch捕获的时候是通过数据类型进行捕获的,所以throw抛出的肯定是一个变量。这样才能够catch进行匹配:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 /*************************************/
 5 class Exception{
 6 public:
 7     virtual void exception(void) = 0;
 8 };
 9 
10 class BoundsException : public Exception
11 {
12 public:
13     virtual void exception(void) { cout << "异常:数组下标越界" << endl; }
14 };
15 
16 /***************************************************************/
17 
18 static void func(void)
19 {
20 //    throw 1;     //  抛出int类型数据
21 //    throw 0.1;   //  抛出double类型数据
22 //    throw "exception";  // 抛出char类型指针
23 //    throw string("exception");  // 抛出string类型的字符串
24 //    throw new string("exception"); // 抛出string类型的指针
25 //    throw BoundsException();   // 抛出BoundsException类对象,注意这里的原理就是:执行BoundsException()构造函数,因为创建一个类对象就是调用构造函数;当然这里你也可以直接定义一个类对象,再把对象抛出去
26 //    throw new BoundsException();  // 抛出BoundsException类对象指针
27 }
28 
29 int main(void)
30 {
31     try{
32         func();
33     }
34     catch (int)
35     {
36         // 捕获int类型异常
37     }
38     catch (double)
39     {
40         // 捕获double类型异常
41     }
42     catch (char *ptr)
43     {
44         // 捕获char *类型异常
45     }
46     catch (string &str)
47     {
48         // 捕获string &类型异常
49     }
50     catch (string *ptr)
51     {
52         // 捕获string *类型异常
53     }
54     catch (BoundsException &boun)
55     {
56         // 捕获BoundsException &类型异常
57     }
58     catch (BoundsException *ptr)
59     {
60         // 捕获BoundsException *类型异常
61     }
62     catch (...)
63     {
64         cout << "发生异常" << endl;
65     }
66 
67     return 0;
68 }
抛出的异常有哪些

 4、扩展用法

(1)什么是重新抛出异常,为什么要重新抛出异常?

在实际的异常处理中,一个catch块可能处理不完,那么就需要其他的catch块再来对这个异常进行处理,那么他的原理就是:在本个catch块中处理完成之后,我们将这个异常重新抛出,交给他的上层catch块去处理,因为我们抛出的异常就是自己捕获到的异常,所以这个异常捕获申明的时候是一个最好是一个引用。注意catch块中重新抛出的异常只能是由他的上层来处理,不能在本函数中的其他catch块中处理,因为在一个函数中同种类型的catch块只能有一个:代码如下:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 int main(void)
 5 {
 6        try{
 7            // 代码块
 8        }  
 9        catch (double &a)
10         {
11               // 处理异常
12               
13               throw;   // 本catch没有处理完成,抛给上层catch处理
14         }
15    
16         return 0;
17 }
重新抛出异常

(2)使用异常规范

异常规范在函数声明是规定了函数可以抛出且只能抛出哪些异常。空的异常规范保证函数不会抛出任何异常。如果一个函数声明没有指定异常规范,则该函数可以抛出任何类型的异常。

 

例1:函数Pop若有异常,只能抛出popOnEmpty和string类型的异常对象

 

void pop( int &value ) throw(popOnEmpty, string);

 

例2:函数no_problem()保证不会抛出任何异常

 

extern void no_problem() throw();

 

例3:函数problem()可以抛出任何类型的异常

 

extern void problem();

 

1 static void func1(void) throw(int, double);   // 只能抛出int  double类型数据
2 static void func2(void) throw();   // 不能抛出异常
3 static void func3(void);    // 可以抛出任何异常
异常规范

(3)异常处理类

我们定义一个异常处理的基类,这个基类中包含了异常的处理方法,但是是一个纯虚函数,我们通过定义具体的异常类去继承这个基类,同时定义这个具体的异常处理方法,方法名字和基类中的纯虚函数名字一样,然后我们就可以在catch中捕获基类的对象,我们在具体的异常处抛出一个具体异常类,这样就能够在catch中统一处理了:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 /*************************************/
 5 class Exception{
 6 public:
 7     virtual void exception(void) = 0;
 8 };
 9 
10 class BoundsException : public Exception
11 {
12 public:
13     virtual void exception(void) { cout << "异常:数组下标越界" << endl; }
14 };
15 
16 class DivisorException : public Exception
17 {
18 public:
19     virtual void exception(void) { cout << "异常:除数为0" << endl; }
20 };
21 
22 class MemoryException : public Exception
23 {
24 public:
25     virtual void exception(void) { cout << "异常:内存不足" << endl; }
26 };
27 
28 /***************************************************************/
29 
30 static void func(void)
31 {
32     throw BoundsException();   // 抛出BoundsException类对象
33 //    throw DivisorException();   // 抛出BoundsException类对象
34 //    throw MemoryException();   // 抛出BoundsException类对象
35 }
36 
37 int main(void)
38 {
39     try{
40         func();
41     }
42     catch (BoundsException &boun)
43     {
44         boun.exception();
45     }
46     catch (...)
47     {
48         cout << "发生异常" << endl;
49     }
50 
51     return 0;
52 }
异常类