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
常用于程序中无法避免的恶性错误()
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。