1. C的异常处理。
在C语言的世界中,对错误的处理总是围绕着两种方法:一是使用整型的返回值标识错误;二是使用errno宏(可以简单的理解为一个全局整型变量)去记录错误。当然C++中仍然是可以用这两种方法的。
这两种方法最大的缺陷就是会出现不一致问题。例如有些函数返回1表示成功,返回0表示出错;而有些函数返回0表示成功,返回非0表示出错。
还有一个缺点就是函数的返回值只有一个,你通过函数的返回值表示错误代码,那么函数就不能返回其他的值。当然,你也可以通过指针或者C++的引用来返回另外的值,但是这样可能会令你的程序略微晦涩难懂。
2. c++异常机制比C语言异常处理的优势
函数的返回值可以忽略,但异常不可忽略。如果程序出现异常,但是没有被捕获,程序就会终止,这多少会促使程序员开发出来的程序更健壮一点。而如果使用C语言的error宏或者函数返回值,调用者都有可能忘记检查,从而没有对错误进行处理,结果造成程序莫名其妙的终止或出现错误的结果。
整型返回值没有任何语义信息。而异常却包含语义信息,有时你从类名就能够体现出来。
整型返回值缺乏相关的上下文信息。异常作为一个类,可以拥有自己的成员,这些成员就可以传递足够的信息。
异常处理可以在调用跳级。这是一个代码编写时的问题:假设在有多个函数的调用栈中出现了某个错误,使用整型返回码要求你在每一级函数中都要进行处理。而使用异常处理的栈展开机制,只需要在一处进行处理就可以了,不需要每级函数都处理。
3.C++异常的基本概念:
一句话:异常处理就是处理程序中的错误。所谓错误是指在程序运行的过程中发生的一些异常事件(如:除0溢出,数组下标越界,所要读取的文件不存在,空指针,内存不足等等)。
在异常处理过程中,由问题检测代码可以抛出一个对象给问题处理代码,通过这个对象的类型和内容知道 “出现了什么错误”
异常基本语法:
- 将可能抛出异常的程序段放到try块之中。
- 若有异常则通过throw操作创建一个异常对象并抛出。(向上抛出,除非遇到catch,否则其他的代码不运行了)
- 如果在try段执行期间没有引起异常,那么跟在try后面的catch字句就不会执行。
- catch子句会根据出现的先后顺序被检查,匹配的catch语句捕获并处理异常(或继续抛出异常)
catch(对应捕获的类型),抛出的异常 可以是自定义的数据类型。任意的类型 catch(…)。
- 如果匹配的处理未找到,则运行函数terminate将自动被调用,其缺省功能调用abort终止程序。
- 处理不了的异常,或者当前位置不想去处理,catch(){}函数体中使用throw;向上抛。
- 异常机制和函数机制互不干涉,但是catch捕捉方式是通过严格类型匹配。
4. 栈的解旋(unwinding).
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反,这一过程称为栈的解旋(unwinding).
//throw异常将栈区对象清空,不会运行throw后面的代码,之后直接跳转到上一个函数。直到遇到catch(){}函数体继续运行
//堆区程序员自己管理,堆区无效
//try之前栈上内容的不会析构
5.
- 异常的接口声明,只允许抛出指定类型的异常
- 函数声明后面 + throw (类型)
- 如果()中空的,不允许抛出任何类型的异常,一个不抛任何类型异常的函数可声明为:void func() throw()
- 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexcepted函数会被调用,该函数默认行为调用terminate函数中断程序。
Qt and Linux 可以使用异常接口声明! visual studio中无效。
6.异常变量的生命周期
类信息如下:
class MyException
{
public:
MyException()
{
cout << "默认构造函数调用" << endl;
}
MyException(const MyException& p)
{
cout << "拷贝构造函数调用" << endl;
}
~MyException()
{
cout << "析构函数调用" << endl;
}
};
调用方式一:catch(值){}
void doWork()
{
throw MyException(); //throw一个匿名对象
}
void test01()
{
try
{
doWork();
cout << ”sss” << endl; //doWork()函数中有异常抛出,则这句话不会执行。
}
catch (MyException s)
{
cout << "MyException s 异常被捕获" << endl;
cout << 111 << endl;
}
cout << 222 << endl;
}
默 认 构 造 函 数 调 用
拷 贝 构 造 函 数 调 用
MyException s 异 常 被 捕 获
111
析 构 函 数 调 用
析 构 函 数 调 用
222
1当catch的是值(非引用或者非指针):
由结果可知,也就是throw的变量A生命周期是throw开始到catch的函数体结束,另外catch到的
变量是拷贝构造函数构造的副本B,继续走catch(){}的函数体,当函数体结束,则将throw的变量A析构,
同时析构刚才的副本B。
如果此时catch(){}函数体中没有处理异常而是继续抛出,则上一次throw的变量A会被析构,而刚才的副本B不会析构,
将刚才的副本B继续抛出,当遇到下一个catch(){}函数时,以副本B调用拷贝构造函数制造另一个副本C,catch(){}的函数体结束,则将throw的副本B析构,同时析构刚才的副本C。依次类推)
上面throw的是匿名对象,如果throw的是局部变量如:
void doWork()
{
MyException m; //创建一个局部变量
throw m; //throw的时候会调用拷贝构造函数,throw的其实是m的副本,原m在函数体结束后就会被释放(局部变量同时也是栈解旋)
}
值传递总结:如果throw的是一个匿名对象A,该匿名对象的生命周期为throw到遇到的第一个catch(){}函数体结束。如果throw的是一个局部变量,如上面,则其实throw的是局部变量的副本B,这个副本的生命周期为遇到的第一个catch(){}函数体结束。所以throw可以延长被抛掷的变量的生命周期,比如刚才的匿名对象A和局部变量的副本B;
catch(){}中()里面的变量,形参生命周期是创建(拷贝构造函数)到函数体结束。
调用方式二:catch(引用){}
void doWork()
{
throw MyException();
}
void test01()
{
try
{
doWork();
cout << 123 << endl; //doWork()函数中有异常抛出,则这句话不会执行。
}
catch (MyException& s)
{
cout << "MyException s 异常被捕获" << endl;
}
}
默 认 构 造 函 数 调 用
MyException s 异 常 被 捕 获
析 构 函 数 调 用
2当catch的是引用:
不会产生副本,catch(){}函数体结束后throw的变量被析构。最好的catch方式。也是系统的异常catch采用的方式。
调用方式三:catch(指向栈区的指针){}
void doWork()
{
throw &MyException(); //throw一个栈区地址。本函数体结束之后就会被释放
}
void test01()
{
try
{
doWork();
cout << 123 << endl; //doWork()函数中有异常抛出,则这句话不会执行。
}
catch (MyException* s)
{
//s->(...) 由于被析构了,此时利用s指针调用就是非法操作,虽然可以调用成功
//是因为系统做了优化,不推荐这样做。
cout << "MyException s 异常被捕获" << endl;
}
}
默 认 构 造 函 数 调 用
析 构 函 数 调 用
MyException s 异 常 被 捕 获
3当catch是指向栈区的指针,会导致匿名对象提前被释放,之后操作指向栈区的指针就是非法操作,虽然可以成功原因是系统做了优化,不推荐这样做。
调用方式四:catch(指向堆区的指针){};
//指向堆区的指针
void doWork()
{
throw new MyException(); //throw一个堆区地址。需要自己管理释放
}
void test01()
{
try
{
doWork();
cout << 123 << endl; //doWork()函数中有异常抛出,则这句话不会执行。
}
catch (MyException* s)
{
//s->(...) 合法操作
cout << "MyException s 异常被捕获" << endl;
delete s; //缺点需要自己释放堆区空间
}
}
默 认 构 造 函 数 调 用
我 的 异 常 捕 获
析 构 函 数 调 用
4当catch是指向堆区的指针,缺点是需要自己管理堆区指针,所以最好的方法是catch(引用){} ;
7.异常变量的多态使用
- 写一个异常的基类 基类中有个纯虚函数 printError
- 派生类中 重写 纯虚函数 ,在抛出异常的时候 可以抛出各种类型的异常对象
- 捕获异常的时候,用父类的指针或者引用接受即可
8. 使用系统提供的标准异常类
- 引入头文件 stdexcept
- throw out_of_range(“aaaa”); //抛出异常
- catch(out_of_range & e ) cout << e.what(); //类型相同
- catch ( exception & e){cout << e.what() << endl;} //相互继承类型,多态的使用
9.编写自己的异常类
- 继承 exception 类
- 重写 虚析构函数 虚what函数
- 内部维护一个字符串 用于保存用户输入的字符串
- Stirng变量调用.c_str()可以将string转化为char*类型数据
- 将char*转换为string,可以用string(char*)调用string的有参构造函数
class myException :public exception
{
public:
myException( const string str)
{
this->m_Error = str;
}
myException(const char * str)
{
this->m_Error = string( str); //利用char*调用string的构造函数
}
virtual ~myException()
{
}
virtual const char * what() const
{
// string 转 char *
return this->m_Error.c_str();
}
string m_Error; //保存用户提供的字符串
};