9.1异常处理
为什么要异常处理:程序运行时出现不正常的情况叫异常,例如,除数字0,数组下标越界,以及内存分配失败等等。这些不正常的情况是程序设计者所不能避免的,例如,动态分配内存时,内存耗尽,导致分配失败,这不是程序设计者希望看到的,同时设计者有不可避免的。因此程序设计者在设计程序时,应尽量考虑可能出现的各种异常情况,并尽量做出处理。
c++语言提供一套处理异常的机制,称之为异常处理,衣长处理的代码与正常的代码分开,使得程序抑郁编写、阅读、和维护。
关键字:throw try catch 三个关键字主要涉及。
throw:用于抛出异常对象
try:用于定义try块,try块用于包含可能爆出异常的语句。
catch:用于定义catch语句,catch子句用于捕获异常。
异常的使用:1、在编写正常代码时,在可能发生异常的地方使用throw抛出异常。
2、使用try块包含该语句、或者包含更外城的语句或函数。
3、在正常代码以外的地方,使用catch子句捕获异常、并做相应的处理。
异常的工作:1、在发现异常抛出异常对象的副本,
2、在进出catch字句前,try块中涉及到的局部变量全部释放。
3、catch字句处理异常代码处理异常之后,程序重try块往后继续执行。
9.4对象的异常抛出和捕获
除了抛出寄出数据类型的异常外,还可以抛出自定义类型对象作为异常抛出。抛出时总是抛出他们的副本
释放原则:1、栈变量 仍在是在栈展开时自动释放
2、堆变量仍然手动释放(free或delete)(**一定要记住,new出来的对象,要手动进行delete释放。系统不会自动释放)
3、静态或全局变量,仍然在程序结束时释放等
捕获异常对象:1、按值捕获 :是捕获异常对象的副本 throw 一个副本 catch 一个副本
2、按引用捕获 :是捕获异常对象的正本 throw 正本 catch一个副本
总之:不论蓝值捕获还是按引用捕获,总要产生一个副本作为异常对象,按引用捕获是可以修改异常对象,按值捕获,不可以修改异常对象。
注意:
1、使用指针作为一场对象时,只能使用指针指向静态变量全局变量、对变量。而不能指向函数中的局部变量(栈变量),因为抛出异常栈展开时,局部变量被释放,异常对象指向的就是垃圾内存。
2、throw对象的时候会调用拷贝构造函数, 所以当对象的拷贝构造函数声明为private 或protected属性时,不能放在throw后面抛出。
9.5重新抛出异常对象 throw;
使用没有操作数的throw表达式抛出的异常对象是原来的异常对象。
9.6异常对象的匹配与类型转换
try块中可以包含多条抛出异常的语句,并且这些异常的对象类型各不相同。
try后面的catch字句分别负责捕获某种具体类型的异常对象,
如果匹配成功,择catch子句捕获异常对象后,后面的catch子句不在进行匹配,如果这组catch子句都不能捕获改异常对象,该异常对象会想更外一层抛出,改层栈展开,如果最终都没有catch子句捕获该异常对象,系统调用terminate函数,该函数的默认行为是调用c语言函数abort,导致程序结束。
匹配原则:可以进行模糊的类型匹配
1、基础类型的转换,int double float 这种类型的转换是被禁止的。
2、构造函数中参数类型的隐式转换,是被禁止的。
3、某种类型的指针到void *型的转换允许发生 在函数传参时,例如int * 隐式转换void *等等。是被允许的
4、A 类型隐式转换const A ,或转换 const A & 是被允许的
5、派生类指针到基类指针的隐式转换,是被允许的
6、派生类对象到基类对象的隐式转换是被允许的, 如a是b的基类, b类型对象隐式转换a类型对象等。
c++语言提供catch(...)子句可以捕获任何类型产生的异常
catch(。。。){
//do something
}
缺点:无法获取异常对象的信息,这种作坊旺旺异常处理的最后一招,用于清理资源后返回,货重新抛出异常等待外层处理
catch语句排放原则:不能把捕获能力强的catch子句放在捕获能力若的catch子句前。这样弱的就没有办法去执行。
9.7,异常类处理与虚函数的调用
如果想在使用捕获基类对象的catch子句捕获派生类对象是调用的派生类的函数。,今儿畜类派生类的异常对象。则必须在继承结构中使用virtual函数覆盖的方法,同时按引用捕获异常对象,才能做到这一点。
(根据 引用派生类对象的基类引用能够调用派生类virtual函数的原则)
9.8函数的异常指定
为了是还曾许更加易于维护,c++允许在编写函数时,在函数首部后侧为函数指定抛出异常的类型,称为函数的异常指定,
例如void f() throw(int,char); 指定改函数尽可能抛出类型为int 或char的异常对象。函数首部没写异常指定时,该函数可以抛出任何类型的异常对象。
函数首部携程throw()时,改函数不抛出任何类型的异常对象。
函数声明中的函数指定与实现中的函数首部的异常指定应该相同。
抛出异常对象应该遵循的原则:1、外层函数在函数中如果不对内层函数抛出的异常进行处理时,内层函数的异常指定应该比其外层函数的异常指定更严格或相同,避免内层函数抛出的以藏对象的类型没能出现在外层函数的异常指定中。
2、在继承结构中使用动态多态时,派生类中virtual函数的异常指定应该比其基类中对应的virtual函数的异常指定更严格(或相同),以避免使用指向派生类对象的基类指针(或引用)调用派生类函数时,抛出基类函数未指定的类型的异常对象。
3、函数指针在生命时也可以书写异常指定,使用函数指针指向带有异常指定的函数时,被指函数的异常指定应该比函数指针的异常指定更严格(或相同),以避免使用函数指针调用函数时,函数抛出函数指针从未指定类型的异常对象。
9.9处理构造函数中的异常
一般 构造函数是:1、初始化对象中的非static成员变量
2、为该对象申请新的资源(如动态内存,要记得在析构中释放)
构造函数发生异常时,必须在异常处理代码中释放已经获取的资源,而不能期待析构函数来释放资源