异常处理
1.处理错误的传统机制
传统的C语言在函数执行过程中遇到语法错误或者逻辑错误的时候,通过函数返回值表明所遇到的错误类型。
2.思想
在C++里面,通过异常,跨越函数来通知整个程序错误发生并对错误进行相应的处理。
- C++的异常处理机制使得异常的引发和异常的处理不必在同一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以再适当的位置设计对不同类型异常的处理。
总的来说,异常是为了在程序出错的时候能够捕获并判断错误类型,且对错误做出合理的处理。增强了程序的健壮性。
3.基本语法
- 若有异常则通过throw操作创建一个异常对象并抛掷。
- 将可能抛出异常的程序段嵌在try块之中。控制通过正常的顺序执行到达try语句,然后执行try块内的保护段。
- 如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行。程序从try块后跟随的最后一个catch子句后面的语句继续执行下去。
- catch子句按其在try块后出现的顺序被检查。匹配的catch子句将捕获并处理异常(或继续抛掷异常)。
- 如果匹配的处理器未找到,则运行函数terminate将被自动调用,其缺省功能是调用abort终止程序。
- 处理不了的异常,可以在catch的最后一个分支,使用throw语法,向上扔。
案例1:被零整除案例
int divide(int x, int y ){ if (y ==0) { throw x; } return x/y;}void main(){ 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 ;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
案例2,类对象作为异常变量被抛出
class A{};void f(){ if(...) throw A();}void g(){ try{ f(); }catch(A){ cout<<“exception A\n”; }}int main(){ g();}
案例3
构造函数没有返回类型,无法通过返回值来报告运行状态,所以只通过一种非函数机制的途径,即异常机制,来解决构造函数的出错问题。
异常机制与函数机制互不干涉,但捕捉的方式是基于类型匹配。捕捉相当于函数返回类型的匹配,而不是函数参数的匹配,所以捕捉不用考虑一个抛掷中的多种数据类型匹配问题,因为函数返回值只会有一个。
比如:
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");}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
catch代码块必须出现在try后,并且在try块后可以出现多个catch代码块,以捕捉各种不同类型的抛掷。
异常机制是基于这样的原理:程序运行实质上是数据实体在做一些操作,因此发生异常现象的地方,一定是某个实体出了差错,该实体所对应的数据类型便作为抛掷和捕捉的依据。
- 异常捕捉严格按照类型匹配
异常捕捉的类型匹配之苛刻程度可以和模板的类型匹配媲美,它不允许相容类型的隐式转换,比如,抛掷char类型用int型就捕捉不到.例如下列代码不会输出“int exception.”
,从而也不会输出“That’s ok.”
因为出现异常后提示退出
int main(){ try{ throw ‘H’; }catch(int){ cout<<"int exception.\n"; } cout<<"That's ok.\n";}
4.栈解旋
异常被抛出后,从进入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 { myFunc(); } catch(MyException ) { cout << "接收到MyException类型异常" << endl; } catch(...) { cout << "未知类型异常" << endl; } system("pause"); return ;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
5.接口声明
- 为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型,例如:
void func() throw (A, B, C , D);//这个函数func()能够且只能抛出类型A B C D及其子类型的异常。
- 如果在函数声明中没有包含异常接口声明,则此函数可以抛掷任何类型的异常,例如:
void func();
- 一个不抛掷任何类型异常的函数可以声明为:
void func() throw();
- 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,
unexpected
函数会被调用,该函数默认行为调用terminate
函数中止程序。
6.异常类型和变量的生命周期
- throw的异常是有类型的,可以是,数字、字符串、类对象。
- throw的异常是有类型的,catch严格按照类型进行匹配。
注意 异常对象的内存模型 。
传统处理错误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 ) { writelen = fwrite(buf, 1, readlen, fp2); if (readlen != readlen) { return 3; } } fclose(fp1); fclose(fp2); return 0;}void main(){ 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; } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
**throw int类型异常**/文件的二进制copyvoid filecopy02(char *filename2, char *filename1 ){ FILE *fp1= NULL, *fp2 = NULL; fp1 = fopen(filename1, "rb"); if (fp1 == NULL) { throw 1; } fp2 = fopen(filename2, "wb"); if (fp1 == NULL) { throw 2; } char buf[256]; int readlen, writelen; while ( (readlen = fread(buf, 1, 256, fp1)) > 0 ) { writelen = fwrite(buf, 1, readlen, fp2); if (readlen != readlen) { throw 3; } } fclose(fp1); fclose(fp2); return ;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
**throw字符类型异常**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 ) { writelen = fwrite(buf, 1, readlen, fp2); if (readlen != readlen) { throw "拷贝文件过程中失败"; } } fclose(fp1); fclose(fp2); return ;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
**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 BadSrcFile(); } fp2 = fopen(filename2, "wb"); if (fp1 == NULL) { throw BadDestFile(); } char buf[256]; int readlen, writelen; while ( (readlen = fread(buf, 1, 256, fp1)) > 0 ) { writelen = fwrite(buf, 1, readlen, fp2); if (readlen != readlen) { throw BadCpyFile(); } } fclose(fp1); fclose(fp2); return ;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
**测试**void main(){ try { 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"); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
**综合案例**#include <iostream>using namespace std;void my_strcpy1(char *to, char *from){ if (from == NULL) { throw 1; } if (to == NULL) { throw 2; } if (*from == 'a') { throw 3; } while (*from != '\0') { *to = *from; to ++; from ++; } *to = '\0';}void my_strcpy2(char *to, char *from){ if (from == NULL) { throw "源buf出错"; } if (to == NULL) { throw "目的buf出错"; } if (*from == 'a') { throw "copy过程出错"; } while (*from != '\0') { *to = *from; to ++; from ++; } *to = '\0';}class BadSrcType {};class BadDestType {};class BadProcessType{public: BadProcessType() { cout << "BadProcessType构造函数do \n"; } BadProcessType(const BadProcessType &obj) { cout << "BadProcessType copy构造函数do \n"; } ~BadProcessType() { cout << "BadProcessType析构函数do \n"; }};void my_strcpy3(char *to, char *from){ if (from == NULL) { throw BadSrcType(); } if (to == NULL) { throw BadDestType(); } if (*from == 'a') { printf("开始 BadProcessType类型异常 \n"); throw BadProcessType(); } if (*from == 'b') { throw &(BadProcessType()); } if (*from == 'c') { throw new BadProcessType; } while (*from != '\0') { *to = *from; to ++; from ++; } *to = '\0';}void main(){ int ret = 0; char buf1[] = "cbbcdefg"; char buf2[1024] = {0}; try { my_strcpy3(buf2, buf1); } catch (int e) { cout << e << " int类型异常" << endl; } catch(char *e) { cout << e << " char* 类型异常" << endl; } catch(BadSrcType e) { cout << " BadSrcType 类型异常" << endl; } catch(BadDestType e) { cout << " BadDestType 类型异常" << endl; } catch( BadProcessType *e) { cout << " BadProcessType 类型异常" << endl; delete e; } catch (...) { cout << "未知 类型异常" << endl; } cout<<"hello..."<<endl; system("pause"); return ;}int my_strcpy(char *to, char *from){ if (from == NULL) { return 1; } if (to == NULL) { return 2; } if (*from == 'a') { return 3; } while (*from != '\0') { *to = *from; to ++; from ++; } *to = '\0'; return 0;}void main41(){ int ret = 0; char buf1[] = "zbcdefg"; char buf2[1024] = {0}; ret = my_strcpy(buf2, buf1); if (ret != 0) { switch(ret) { case 1: printf("源buf出错!\n"); break; case 2: printf("目的buf出错!\n"); break; case 3: printf("copy过程出错!\n"); break; default: printf("未知错误!\n"); break; } } printf("buf2:%s \n", buf2); cout<<"hello..."<<endl; system("pause"); return ;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
结论1: 如果接受异常的时候使用一个异常变量,则copy构造异常变量. 调用拷贝构造函数。
结论2: 使用引用的话 会直接使用throw时候的那个对象。
结论3: 指针可以和引用/元素写在一块但是引用/元素不能写在一块。
结论4: 异常变量是类对象时, 使用引用比较合适
7.异常和继承
- 由于异常变量可以是一个类对象,所以可以实现一个异常类
- 异常类可以派生出子类,也可以继承自其他类
- 父类引用或者指针指向子类对象可以实现多态,从而实现异常类框架,实现代码复用。
案例:设计一个数组类 MyArray
- 重载[]操作,
- 数组初始化时,对数组的个数进行有效检查
- index<0 抛出异常eNegative
- index = 0 抛出异常 eZero
- index>1000抛出异常eTooBig
- index<10 抛出异常eTooSmall
- eSize类是以上类的父类,实现有参数构造、并定义virtual void printErr()输出错误。
#include <iostream>using namespace std;class MyArray{public: MyArray(int len); ~MyArray();public: int & operator[](int index); int getLen(); class eSize { public: eSize(int size) { m_size = size; } virtual void printErr() { cout << "size:" << m_size << " "; } protected: int m_size; }; class eNegative : public eSize { public: eNegative(int size) : eSize(size) { ; } virtual void printErr() { cout << "eNegative 类型 size:" << m_size << " "; } }; class eZero : public eSize { public: eZero(int size) : eSize(size) { ; } virtual void printErr() { cout << "eZero 类型 size:" << m_size << " "; } }; class eTooBig : public eSize { public: eTooBig(int size) : eSize(size) { ; } virtual void printErr() { cout << "eTooBig 类型 size:" << m_size << " "; } }; class eTooSmall : public eSize { public: eTooSmall(int size) : eSize(size) { ; } virtual void printErr() { cout << "eTooSmall 类型 size:" << m_size << " "; } };private: int *m_space; int m_len;};MyArray::MyArray(int len){ if (len < 0) { throw eNegative(len); } else if (len == 0) { throw eZero(len); } else if (len > 1000) { throw eTooBig(len); } else if (len < 3) { throw eTooSmall(len); } m_len = len; m_space = new int[len];}MyArray::~MyArray(){ if (m_space != NULL) { delete [] m_space; m_space = NULL; m_len = 0; }}int & MyArray::operator[](int index){ return m_space[index];}int MyArray::getLen(){ return m_len;}void main(){ try { MyArray a(-5); for (int i=0; i<a.getLen(); i++) { a[i] = i+1; printf("%d ", a[i]); } } catch(MyArray::eSize &e) { e.printErr(); } catch (...) { } cout<<"hello..."<<endl; system("pause"); return ;}void main51(){ try { MyArray a(-5); for (int i=0; i<a.getLen(); i++) { a[i] = i+1; printf("%d ", a[i]); } } catch(MyArray::eNegative e) { cout << "eNegative 类型异常" << endl; } catch(MyArray::eZero e) { cout << "eZero 类型异常" << endl; } catch(MyArray::eTooBig e) { cout << "eTooBig 类型异常" << endl; } catch(MyArray::eTooSmall e) { cout << "eTooSmall 类型异常" << endl; } catch (...) { } cout<<"hello..."<<endl; system("pause"); return ;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
8.标准异常库
#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");}