首页 > 代码库 > C++中的异常处理

C++中的异常处理



1、C++异常处理


(1)C++内置了异常处理的语法元素,try...catch...,这是两个新的关键字在C++中


@1:try语句代码块中用来处理正常代码逻辑


@2:catch语句代码块中用来处理异常情况


@3:try语句中的异常由对应的catch语句进行处理


try

{

douuble r = divide(1, 0);

}

catch(...)

{

cout << "Divide by zero..." << endl;

}


@4:try语句代码块中用来处理可能发生异常的正常逻辑代码,当try代码块中的代码发生了异常,就会抛出异常,catch语句就会捕捉到这个异常,进入到catch语句中进行处理这个异常。


(2)那么try代码块中的语句中是如何抛出异常的呢?


@1:C++通过throw关键字抛出异常信息


如:使用throw语句进行异常抛出


double divide(double a, double b)

{

const double delta = 0.000000000001;

double ret = 0;

if ( !((-delta < b) && (b < delta)) )

{

ret = a / b;

}

else 

{

throw 0; //产生除0异常,这里是0这个字面常量来代码了当前的异常元素,异常元素可以是字符串,可以是对象,可以是一个值等等

//当程序执行到throw时,就会返回到调用这个divide函数的调用点,try就会将这个异常元素转给catch语句,catch语句块就会抓住这个异常元素

}

return ret;

}


2、C++异常处理分析


(1)throw抛出的异常必须被catch处理


@1:当前函数能够处理异常,程序继续往下执行


@2:当前函数如果无法处理收到的异常,则函数停止执行,并返回


(2)未被处理的异常会顺着函数调用栈向上传播,直到被处理为止,否则程序将停止执行。


比如:如果function1函数调用了function2函数,function2函数调用了function3函数,在function3函数中执行执行,并抛出了异常,也就是throw了,那么这个异常就会先看function3这个函数有没有能力处理这个异常(也就是在没在try中,有没有对应的catch处理的异常类型),如果有就进行异常处理了,如果没有function3这个函数就会立即停止执行,并代码着异常返回给function2调用function3函数的调用点,如果function2没有进行处理(没有在try中,也没有catch处理的相关类型),function2就会立即停止执行,带着异常返回给function1函数调用function2函数的调用点。如果都没有对throw扔出的这个异常元素进行异常处理的话(函数调用没在try中也没有对应的catch异常处理的相关类型),整个程序就会放弃执行



例:throw抛出异常,对异常进行处理,try...catch


#include <iostream>

#include <string>


using namespace std;



double divide(double a, double b)

{

const double delta = 0.00000000001;

double ret = 0;

if ( !((-delta < b) && (b < delta)) )

{

ret = a / b;

}

else 

{

throw 0; //产生除0异常,这里是0这个int类型的值来代码了当前的异常元素,异常元素可以是字符串,可以是对象,可以是一个值等等

//当程序执行到throw时,就会返回到调用这个divide函数的调用点,try就会将这个异常元素转给catch语句,catch语句块就会抓住这个异常元素 //如果没有对应的catch对这个异常进行处理,程序将会放弃执行

}

return ret;

}



int main(int argc, char *argv[])

{

double num = 0;

try

{

num = divide(1, 1); //执行到divide函数throw语句时,就会返回throw语句后面的异常元素给这个try,这个try将异常元素给了catch,catch对这个异常进行处理。

//如果没有对异常进行处理的操作,但你throw还抛出了异常,程序会停止运行

cout << "num = " << num << endl;

}

catch(...)

{

cout << "Divide by zero ...." << endl;

}


return 0;

}


(3)同一个try语句可以跟上多个catch语句


@1:catch语句可以定义具体处理的异常类型


@2:不同类型的异常由不同的catch语句负责处理


@3:try语句中可以抛出任何类型的异常


@4:catch(...)用于处理所有类型的异常,并且这个catch(...)里面是3个点的catch语句块只能放在最后catch处理的情况,当有其他catch存在时。


@5:任何异常都只能被捕获(catch)一次


(4)异常处理的匹配原则


try

{

throw 1;

}

catch (Type1 t1)

{

}

catch (Type2 t2)

{

}

catch (TypeN tn)

{


}

catch (...) //这个catch,处理任何类型的异常,当有其他catch存在时,只能作为最后的catch处理情况

{

}


异常抛出后,至上而下严格的匹配每一个catch语句处理的类型。异常处理匹配时,不进行任何的类型转换。所以是严格匹配的。

如果当前抛出异常的函数没有对这个异常处理,就会沿着当前函数的调用栈,顺序的返回,直到被处理,如果都没有进行这个异常处理,程序就会停止执行


例:一个try抛出异常,多个catch进行异常类型匹配处理异常的情况


#include <iostream>

#include <string>


/*

*一个try中抛出异常,多个catch进行匹配,

*匹配原则是严格的类型匹配,不会进行类型的转换,

*至上而下的进行匹配catch中的类型,catch(...)只能放在对try中抛出异常的处理最后

*/


using namespace std;



void Demo1()

{

try

{

throw 0; //直接抛出异常,这个是int类型的

}

catch (char c)

{

cout << "catch (char c)" << endl;

}

catch (double d)

{

cout << "catch (double d)" << endl;

}

catch (string s)

{

cout << "catch (string s)" << endl;

}

catch (int i)

{

cout << "catch (int i)" << endl;

}

catch (...)

{

cout << "catch (...)" << endl;

}

}



void Demo2()

{

try

{

throw "haha"; //这个const char* 类型的

}

catch (char *s)

{

cout << "catch (char *s)" << endl;

}

catch (string ss)

{

cout << "catch (string ss)" << endl;

}

catch (const char * cs)

{

cout << "catch (const char * cs)" << endl;

}

}


int main(int argc, char *argv[])

{

Demo1();

try

{

Demo2();

}

catch (...)

{

cout << "catch (...)" << endl;

}

return 0;

}


最后的执行结果,会打印catch (int i)和catch (...)



3、catch语句块中也可以抛出异常


try

{

func();

}

catch(int i)

{

throw i; //将捕获到的异常重新抛出。

}

catch(...)

{

throw; //将捕获到的异常重新抛出

}


catch中抛出的异常需要外层的try...catch...捕获。


(1)C++中之所以支持catch语句块中抛出异常,是因为我们在工程开发中,会经常的使用第三方库进行开发,如果第三方库中的func函数在使用时有可能会抛出异常,并且抛出的异常是-1,-2,-3等int类型异常,每一个异常元素对应的意思可以看第三方库中的文档来知道,但是我们在开发中,如果真遇到了第三方库抛出了异常,但是我们确无法直观的直接从这几个-1,-2,-3异常元素来知道每一个异常元素对应的是什么情况,只能去查第三方库提供的文档来知道,这是很浪费时间的,所以我们为了开发效率,所以我们将会将第三方库抛出的异常,进行统一的封装,也就是将func函数在我们自己写的Myfunc函数中调用,在Myfunc函数中,我们将第三方库func函数中可能抛出的异常元素进行重解释在抛出,这样我们就可以在工程开发中直接处理Myfunc这个函数抛出的重解释了第三方库func函数中抛出的异常。方便处理。


例:工程中在catch中抛出异常的用法,用于将第三方库中提供的函数抛出的异常进行重解释。

#include <iostream>

#include <string>


using namespace std;




/*

假设:func函数是第三方库中提供的函数,这个函数我们是无法修改的,因为我们得不到源码一般情况下

一般情况下,我们用的是第三方库提供的动态链接库。

func函数会抛出异常:

-1: 表示参数异常了

-2: 运行异常

-3: 超时异常

当这个func函数抛出异常的时候,我们无法直观的从它抛出的异常来知道究竟是发生了什么情况,只能去查阅第三方的文档。

所以为了方便,也为了架构的考虑,因为我们开发时,一般还有自己的私有库,所以我们就会对这个第三方库函数的异常进行重解释处理。

处理方法,就是我们自己写一个MyFunc函数,这个函数中调用了这个第三方库func函数,对这个func函数可能会抛出的异常进行重解释处理。

这样,我们的工程在使用func函数出现异常的时候,就只是针对于Myfunc我们自己写的这个函数的异常,同时异常的意思也被我们重解释的更清晰了

*/

void func(int i)

{

if (i < 10)

{

throw -1;

}

else if (i == 11)

{

throw -2;

}

else if (i > 100)

{

throw -3;

}

}


void MyFunc(int i) //自己提供的函数,完成和func一样的功能,只是为了重解释一下第三方库func函数抛出的异常

{

try

{

func(i);

}

catch (int i)

{

switch (i) //对第三方func函数抛出的异常进行重解释。

{

case -1:

throw "Invalid Exception";

break;

case -2:

throw "Run Exception";

break;

case -3:

throw "Timeout Exceptin";

break;

}


}

}


int main(void)

{

try

{

MyFunc(101);

}

catch (const char *cs)

{

cout << "Exception Info: " << cs << endl; 

}

return 0;

}



4、异常的类型可以是自定义的类类型


(1)对于类类型的匹配依然是至上而下的严格匹配


(2)赋值兼容性原则在异常匹配中依然适用(子类的异常对象,可以被父类的catch语句块抓住)


(3)所以一般而言:

@1:匹配子类异常的catch放在上部

@2:匹配父类异常的catch放在下部



(5)在工程中会定义一系列的异常类


@1:每个类代表工程中可能出现的一种异常类型


@2:代码复用 时可能需要重解释不同的异常类


@3:在定义catch语句块时如果使用的异常是类对象,那么推荐使用引用作为参数,因为这样可以避开拷贝构造,提高程序效率


例:用异常类对异常进行重解释


#include <iostream>

#include <string>


using namespace std;


/*

工程中一般会常使用异常类,自定义一个异常类,来表示出现异常时的详细信息

*/


class Base

{

};


class Exception : public Base //继承了Base,所以catch接受这个类抛出的异常时,catch接受这个父类的异常处理要放到后面

{

private:

int m_id; //异常的ID号,也就是第三方库func函数中抛出异常的异常元素号。

string m_desc; //异常的信息描述

public:

Exception(int id, string desc)

{

m_id = id;

m_desc = desc;

}

int id() const

{

return m_id;

}

string description() const

{

return m_desc;

}

};


/*

假设:func函数是第三方库中提供的函数,这个函数我们是无法修改的,因为我们得不到源码一般情况下

一般情况下,我们用的是第三方库提供的动态链接库。

func函数会抛出异常:

-1: 表示参数异常了

-2: 运行异常

-3: 超时异常

当这个func函数抛出异常的时候,我们无法直观的从它抛出的异常来知道究竟是发生了什么情况,只能去查阅第三方的文档。

所以为了方便,也为了架构的考虑,因为我们开发时,一般还有自己的私有库,所以我们就会对这个第三方库函数的异常进行重解释处理。

处理方法,就是我们自己写一个MyFunc函数,这个函数中调用了这个第三方库func函数,对这个func函数可能会抛出的异常进行重解释处理。

这样,我们的工程在使用func函数出现异常的时候,就只是针对于Myfunc我们自己写的这个函数的异常,同时异常的意思也被我们重解释的更清晰了

*/

void func(int i)

{

if (i < 10)

{

throw -1;

}

else if (i == 11)

{

throw -2;

}

else if (i > 100)

{

throw -3;

}

}



void MyFunc(int i) //自己提供的函数,完成和func一样的功能,只是为了重解释一下第三方库func函数抛出的异常

{

try

{

func(i);

}

catch (int i)

{

switch (i) //对第三方func函数抛出的异常进行重解释。

{

case -1:

throw Exception(-1, "Invalid Exception");

break;

case -2:

throw Exception(-2, "Run Exception");

break;

case -3:

throw Exception(-3, "Timeout Exceptin");

break;

}


}

}


int main(void)

{

try

{

MyFunc(111);

}

catch (const Exception& e)

{

cout << "Exception Info: " << endl;

cout << "ID: " << e.id() << endl;

cout << "Description: " << e.description() << endl;

}

catch (const Base& e) //父类的接受异常要放到后面,因为赋值兼容性原则,如果这个接受异常放在了前面,那么抛出的异常就会被父类接受到了

{

cout << "catch (const Base& e)" << endl;

}

return 0;

}


6、C++标准库中提供了实用异常类族,使用时要包含<stdexcept>这个头文件,并且要声明使用的命名空间是std


(1)标准库中的异常都是从exception顶层父类派生的


(2)exception类有两个主要分支,在于异常的类型是不一样的


@1:logic_error

常用于程序中的可避免逻辑错误,(out_of_range("可以有参数,字符串参数,只是哪个函数发生的异常");数组访问越界,参数错误等)

@2:runtime_error

常用于程序中无法避免的恶性错误()


C++中的异常处理