C++复习:异常
异常处理机制专题
前言
1)异常是一种程序控制机制,与函数机制独立和互补
函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它依附于栈结构,却可以同时设置多个异常类型作为网捕条件,从而以类型匹配在栈机制中跳跃回馈.
2)异常设计目的:
栈机制是一种高度节律性控制机制,面向对象编程却要求对象之间有方向、有目的的控制传动,从一开始,异常就是冲着改变程序控制结构,以适应面向对象程序更有效地工作这个主题,而不是仅为了进行错误处理。
异常设计出来之后,却发现在错误处理方面获得了最大的好处。
1 异常处理的基本思想
1.1传统错误处理机制
通过函数返回值来处理错误。
1.2异常处理的基本思想
1)C++的异常处理机制使得异常的引发和异常的处理不必在同一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以再适当的位置设计对不同类型异常的处理。
2)异常是专门针对抽象编程中的一系列错误处理的,C++中不能借助函数机制,因为栈结构的本质是先进后出,依次访问,无法进行跳跃,但错误处理的特征却是遇到错误信息就想要转到若干级之上进行重新尝试,如图
3)异常超脱于函数机制,决定了其对函数的跨越式回跳。
4)异常跨越函数
2 C++异常处理的实现
2.1异常基本语法
1) 若有异常则通过throw操作创建一个异常对象并抛掷。
2) 将可能抛出异常的程序段嵌在try块之中。控制通过正常的顺序执行到达try语句,然后执行try块内的保护段。
3) 如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行。程序从try块后跟随的最后一个catch子句后面的语句继续执行下去。
4) catch子句按其在try块后出现的顺序被检查。匹配的catch子句将捕获并处理异常(或继续抛掷异常)。
5) 如果匹配的处理器未找到,则运行函数terminate将被自动调用,其缺省功能是调用abort终止程序。
6)处理不了的异常,可以在catch的最后一个分支,使用throw语法,向上扔。
案例1:被零整除案例
int divide(int x, int y) { if (y == 0) { throw x; } return x / y; }
void main41() { try { cout << "8/2 = " << divide(8, 2) << endl; cout << "10/0 =" << divide(10, 0) << endl; } catch (int e) { cout << "e" << " is divided by zero!" << endl; } catch (...) { cout << "未知异常" << endl; }
cout << "ok" << endl; system("pause"); return; } |
案例2:
class A {}; void f() { if (...) throw A; } void g() { try { f(); } catch (B) { cout << "exception B\n"; } } int main() { g(); }
|
throw A将穿透函数f,g和main,抵达系统的最后一道防线——激发terminate函数.
该函数调用引起运行终止的abort函数.
最后一道防线的函数可以由程序员设置.从而规定其终止前的行为.
修改系统默认行为:
- 可以通过set_terminate函数修改捕捉不住异常的默认处理器,从而使得发生捉不住异常时,被自定义函数处理:
- void myTerminate(){cout<<"HereIsMyTerminate\n";}
- set_terminate(myTerminate);
- set_terminate函数在头文件exception中声明,参数为函数指针void(*)().
案例3:
- 构造函数没有返回类型,无法通过返回值来报告运行状态,所以只通过一种非函数机制的途径,即异常机制,来解决构造函数的出错问题。
7)异常机制与函数机制互不干涉,但捕捉的方式是基于类型匹配。捕捉相当于函数返回类型的匹配,而不是函数参数的匹配,所以捕捉不用考虑一个抛掷中的多种数据类型匹配问题
比如:
class A {}; class B {};
int main() { try { int j = 0; double d = 2.3; char str[20] = "Hello"; cout << "Please input a exception number: "; int a; cin >> a; switch (a) { case 1: throw d; case 2: throw j; case 3: throw str; case 4: throw A(); case 5: throw B(); default: cout << "No throws here.\n"; } } catch (int) { cout << "int exception.\n"; } catch (double) { cout << "double exception.\n"; } catch (char*) { cout << "char* exception.\n"; } catch (A) { cout << "class A exception.\n"; } catch (B) { cout << "class B exception.\n"; } cout << "That's ok.\n"; system("pause"); }//==================================== |
catch代码块必须出现在try后,并且在try块后可以出现多个catch代码块,以捕捉各种不同类型的抛掷。
异常机制是基于这样的原理:程序运行实质上是数据实体在做一些操作,因此发生异常现象的地方,一定是某个实体出了差错,该实体所对应的数据类型便作为抛掷和捕捉的依据。
8)异常捕捉严格按照类型匹配
- 异常捕捉的类型匹配之苛刻程度可以和模板的类型匹配媲美,它不允许相容类型的隐式转换,比如,抛掷char类型用int型就捕捉不到.例如下列代码不会输出"int exception.",从而也不会输出"That's ok." 因为出现异常后提示退出
int main(){
try{
throw 'H';
}catch(int){
cout<<"int exception.\n";
}
cout<<"That's ok.\n";
}
2.2栈解旋(unwinding)
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反。这一过程称为栈的解旋(unwinding)。
class MyException {};
class Test { public: Test(int a = 0, int b = 0) { this->a = a; this->b = b; cout << "Test 构造函数执行" << "a:" << a << " b: " << b << endl; } void printT() { cout << "a:" << a << " b: " << b << endl; } ~Test() { cout << "Test 析构函数执行" << "a:" << a << " b: " << b << endl; } private: int a; int b; };
void myFunc() throw (MyException) { Test t1; Test t2;
cout << "定义了两个栈变量,异常抛出后测试栈变量的如何被析构" << endl;
throw MyException(); }
void main() { //异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象, //都会被自动析构。析构的顺序与构造的顺序相反。 //这一过程称为栈的解旋(unwinding) try { myFunc(); } //catch(MyException &e) //这里不能访问异常对象 catch (MyException) //这里不能访问异常对象 { cout << "接收到MyException类型异常" << endl; } catch (...) { cout << "未知类型异常" << endl; }
system("pause"); return; } |
2.3异常接口声明
1)为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型,例如:
void func() throw (A, B, C , D); //这个函数func()能够且只能抛出类型A B C D及其子类型的异常。
2)如果在函数声明中没有包含异常接口声明,则次函数可以抛掷任何类型的异常,例如:
void func();
3)一个不抛掷任何类型异常的函数可以声明为:
void func() throw();
4) 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexpected函数会被调用,该函数默认行为调用terminate函数中止程序。
2.4异常类型和异常变量的生命周期
1)throw的异常是有类型的,可以使,数字、字符串、类对象。
2)throw的异常是有类型的,catch严格按照类型进行匹配。
3)注意 异常对象的内存模型 。
2.2.1 传统处理错误
//文件的二进制copy int filecopy01(char *filename2, char *filename1) { FILE *fp1 = NULL, *fp2 = NULL;
fp1 = fopen(filename1, "rb"); if (fp1 == NULL) { return 1; }
fp2 = fopen(filename2, "wb"); if (fp1 == NULL) { return 2; }
char buf[256]; int readlen, writelen; while ((readlen = fread(buf, 1, 256, fp1)) > 0) //如果读到数据,则大于0 { writelen = fwrite(buf, 1, readlen, fp2); if (readlen != readlen) { return 3; } }
fclose(fp1); fclose(fp2); return 0; } |
测试程序 |
void main11() { int ret; ret = filecopy01("c:/1.txt", "c:/2.txt"); if (ret != 0) { switch (ret) { case 1: printf("打开源文件时出错!\n"); break; case 2: printf("打开目标文件时出错!\n"); break; case 3: printf("拷贝文件时出错!\n"); break; default: printf("发生未知错误!\n"); break; } } } |
2.2.2 throw int类型异常
/ 文件的二进制copy void filecopy02(char *filename2, char *filename1) { FILE *fp1 = NULL, *fp2 = NULL;
fp1 = fopen(filename1, "rb"); if (fp1 == NULL) { //return 1; throw 1; }
fp2 = fopen(filename2, "wb"); if (fp1 == NULL) { //return 2; throw 2; }
char buf[256]; int readlen, writelen; while ((readlen = fread(buf, 1, 256, fp1)) > 0) //如果读到数据,则大于0 { writelen = fwrite(buf, 1, readlen, fp2); if (readlen != readlen) { //return 3; throw 3; } }
fclose(fp1); fclose(fp2); return; } |
2.2.3 throw字符类型异常
//文件的二进制copy void filecopy03(char *filename2, char *filename1) { FILE *fp1 = NULL, *fp2 = NULL;
fp1 = fopen(filename1, "rb"); if (fp1 == NULL) { throw "打开源文件时出错"; }
fp2 = fopen(filename2, "wb"); if (fp1 == NULL) { throw "打开目标文件时出错"; }
char buf[256]; int readlen, writelen; while ((readlen = fread(buf, 1, 256, fp1)) > 0) //如果读到数据,则大于0 { writelen = fwrite(buf, 1, readlen, fp2); if (readlen != readlen) { throw "拷贝文件过程中失败"; } }
fclose(fp1); fclose(fp2); return; } |
2.2.4 throw类对象类型异常
//throw int类型变量 //throw 字符串类型 //throw 类类型 class BadSrcFile { public: BadSrcFile() { cout << "BadSrcFile 构造 do " << endl; } ~BadSrcFile() { cout << "BadSrcFile 析构 do " << endl; } BadSrcFile(BadSrcFile & obj) { cout << "拷贝构造 do " << endl; } void toString() { cout << "aaaa" << endl; }
}; class BadDestFile {}; class BadCpyFile {};;
void filecopy04(char *filename2, char *filename1) { FILE *fp1 = NULL, *fp2 = NULL;
fp1 = fopen(filename1, "rb"); if (fp1 == NULL) { //throw new BadSrcFile(); throw BadSrcFile(); }
fp2 = fopen(filename2, "wb"); if (fp1 == NULL) { throw BadDestFile(); }
char buf[256]; int readlen, writelen; while ((readlen = fread(buf, 1, 256, fp1)) > 0) //如果读到数据,则大于0 { writelen = fwrite(buf, 1, readlen, fp2); if (readlen != readlen) { throw BadCpyFile(); } }
fclose(fp1); fclose(fp2); return; } |
main测试案例 |
//结论://C++编译器通过throw 来产生对象,C++编译器再执行对应的catch分支,相当于一个函数调用,把实参传递给形参。 void main11() { try { //filecopy02("c:/1.txt","c:/2.txt"); // filecopy03("c:/1.txt","c:/2.txt"); filecopy04("c:/1.txt", "c:/2.txt"); } catch (int e) { printf("发生异常:%d \n", e); } catch (const char * e) { printf("发生异常:%s \n", e); } catch (BadSrcFile *e) { e->toString(); printf("发生异常:打开源文件时出错!\n"); } catch (BadSrcFile &e) { e.toString(); printf("发生异常:打开源文件时出错!\n"); } catch (BadDestFile e) { printf("发生异常:打开目标文件时出错!\n"); } catch (BadCpyFile e) { printf("发生异常:copy时出错!\n"); } catch (...) //抓漏网之鱼 { printf("发生了未知异常! 抓漏网之鱼\n"); } //class BadSrcFile {}; //class BadDestFile {}; //class BadCpyFile {};; } |
2.5异常的层次结构(继承在异常中的应用)
- 异常是类 – 创建自己的异常类
- 异常派生
- 异常中的数据:数据成员
- 按引用传递异常
- 在异常中使用虚函数
案例:设计一个数组类 MyArray,重载[]操作,
数组初始化时,对数组的个数进行有效检查
- index<0 抛出异常eNegative
- index = 0 抛出异常 eZero
3)index>1000抛出异常eTooBig
4)index<10 抛出异常eTooSmall
5)eSize类是以上类的父类,实现有参数构造、并定义virtual void printErr()输出错误。
3标准程序库异常
案例1:
// out_of_range #include "iostream" using namespace std; #include <stdexcept>
class Teacher { public: Teacher(int age) //构造函数, 通过异常机制 处理错误 { if (age > 100) { throw out_of_range("年龄太大"); } this->age = age; } protected: private: int age; };
void mainxx() { try { Teacher t1(102); } catch (out_of_range e) {
cout << e.what() << endl; }
exception e; system("pause"); } |
案例2
class Dog { public: Dog() { parr = new int[1024 * 1024 * 100]; //4MB } private: int *parr; };
int main31() { Dog *pDog; try { for (int i = 1; i<1024; i++) //40GB! { pDog = new Dog(); cout << i << ": new Dog 成功." << endl; } } catch (bad_alloc err) { cout << "new Dog 失败: " << err.what() << endl; }
return 0;
} |
案例3