Exception handling mechanism


就是它要这个类型数值,你给它一个不合格的,反馈给你看错误提示。


异常是一种程序控制机制,与函数机制互补。

函数是一种以栈结构展开的上下函数衔接的程序控制系统。

异常是另一种控制结构,它可以在出现“意外”时中断当前函数,并以某种机制(类型匹配)回馈给隔代的调用者相关的信息。


C++ Primer中关于异常的解释:(p172)


异常是指存在于运行时的反常行为,这些行为超出了函数正常功能的范围。

典型的异常包括失去数据库链接以及遇到意外输入等。

处理反常行为可能是纯设计所有系统中最难的一部分。


通过返回值实现异常处理机制

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
#include <stdio.h>
#include <stdlib.h>

#define BUFSIZE 1024

//实现文件的二进制拷贝
int copyfile(const char* dest, const char* src)
{
FILE* fp1 = NULL, * fp2 = NULL;

//rb 只读方式打开一个二进制文件,只允许读取数据
fopen_s(&fp1, src, "rb");

if (fp1 == NULL)
{
return -1;
}

//wb 以只写的方式打开或新建一个二进制文件,只允许写数据。
fopen_s(&fp2, dest, "wb");
if (fp2 == NULL)
{
return -2;
}

char buffer[BUFSIZE];
int readlen, writelen;

//如果读到数据,则大于0
while ((readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0)
{
writelen = fwrite(buffer, 1, readlen, fp2);
if (readlen != writelen)
{
return -3;
}
}

fclose(fp1);
fclose(fp2);
return 0;
}

int main()
{
int ret = 0;
ret = copyfile("dest.txt", "src.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;
}
}


return 0;
}

throw & try-catch实现异常处理机制

对上面的代码加以修改

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
// demo 15-15  
#include <stdio.h>
#include <stdlib.h>
#include <string>

using namespace std;

#define BUFSIZE 1024

int copyfile2(char* dest, char* src)
{
FILE* fp1 = NULL, * fp2 = NULL;

fopen_s(&fp1, src, "rb");

if (fp1 == NULL)
{
throw new string("文件不存在");
}

fopen_s(&fp2, dest, "wb");
if (fp2 == NULL)
{
throw - 2;
}

char buffer[BUFSIZE];
int readlen, writelen;

while ((readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0)
{
writelen = fwrite(buffer, 1, readlen, fp2);
if (readlen != writelen)
{
throw - 3;
}
}
fclose(fp1);
fclose(fp2);
return 0;
}

int copyfile1(char* dest, char* src) {
return copyfile2(dest, src);
}

int main()
{
int ret = 0;

try
{
ret = copyfile1("dest.txt", "src.txt");

}
catch (int error)
{
printf("出现异常啦!%d\n", error);
}
catch (string* error)
{
printf("捕捉到字符串异常:%s\n", error->c_str());
delete error;
}

system("pause");
return 0;
}

解释
thorw——抛出异常
程序的异常检测部分使用throw表达式引发一个异常。throw表达式包括关键字throw和紧随其后的一个表达式,这个表达式的类型就是抛出的异常类型。

1
throw 表达式;

try——捕捉异常:

try语句块一开始是关键字try,随后紧跟一个快,这个块就像大多数一样式花括号括起来的语句序列。

跟在try块之后的是一个或多个catch子句。

catch字句包括三个部分:关键字catch、括号内一个(可能是未命名的)对象的声明(称作异常声明——就是上面抛出来的异常类型)以及一个块。

从哪调用忘拿抛,try。

1
2
3
4
5
6
7
8
9
try{
xxx
这里写可能抛出异常的程序段。-出现异常直接进入到catch
xxx,如果上面的这条语句执行了异常,那么这行就不会被执行。
}catch(异常处理类型 xxx(可写可不写)){

}catch(){

}catch(...){} //接受所有异常

如果抛出的异常类型没有对应捕捉的方式,则会直接中断程序(调用abort)。

得到的异常可以不处理继续抛出去。:处理不了的异常,我们可以在catch的最后一个分支,使用throw语法,继续向调用者throw。

异常接口声明

可以在函数声明中列出可能抛出的所有异常类型,加强程序的课读性。声明了这几种,那就只能抛出这几种。如果抛出没有声明的异常类型,程序有可能直接终止。

如下所示:

1
void function (xxx,xxx) throw (float, string *, int)

如果没有包含异常接口的声明,此函数可以抛出任何类型的异常。

如果一个函数不想抛出任何异常,用throw()来声明

1
void function(xxx,xxx) throw()

异常类型和声明周期

image-20210926171925746

throw字符串类型char*,实际上抛出的是指针,而且前面修饰指针的const也要严格进行匹配。

抛出类对象异常:
可以抛出一个匿名对象

1
throw classname();

这里编译器指定给我们生成了一个匿名对象。

image-20210926174726826

所以我们可以直接抛出一个匿名的对象,并且用引用接收这个匿名对象。

image-20210926175202336

抛出类异常类型最佳方式是抛出匿名对象,并用引用接收。

用指针接收记得释放掉。

注意:引用和普通的形参传值不能共存。

异常与继承

异常也是类,我们可以创建自己的异常类。

案例解释:


案例:设计一个数组类容器 Vector,重载[]操作,数组初始化时,对数组的个数进行有效检查

1) index<0 抛出异常errNegativeException

2) index = 0 抛出异常 errZeroException

3)index>1000抛出异常errTooBigException

4)index<10 抛出异常errTooSmallException

5)errSizeException类是以上类的父类,实现有参数构造、并定义virtual void printError()输出错误。


代码实现

Vector.cpp

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
#include<iostream>
#include"err.h"
using namespace std;


class Vector
{
public:
Vector(int len = 128);
Vector(const Vector& other);
int GetLength()const;
int& operator[](int index)const;
Vector& operator=(const Vector& other);
~Vector();
friend ostream& operator<<(ostream& os, const Vector& other);
private:
int* m_base;
int m_len;

};
Vector::Vector(int len)
{
if (len < 0)
{
errNegativeException err(len);
throw &err;
}
else if (len == 0)
{
errZeroException err(len);
throw &err;
}
else if (len > 1000)
{
errTooBigException err(len);
throw& err;
}
else if (len < 10)
{
errTooSmallException err(len);
throw &err;
}
m_len = len;
m_base = new int[m_len];
}

Vector::Vector(const Vector& other)
{
m_len = other.m_len;
delete[] m_base;
m_base = new int[m_len];
for (int i = 0; i < m_len; i++)
{
m_base[i] = other.m_base[i];
}

}

Vector::~Vector()
{
if (m_base)
{
delete[] m_base;
m_len = 0;

}
}

int Vector::GetLength()const
{
return m_len;
}

int& Vector::operator[](int index)const
{
return m_base[index];
}



ostream& operator<<(ostream& os, const Vector& other)
{
for (int i = 0; i < other.GetLength(); i++)
{
os << other.m_base[i] << " ";
}
os << endl;
return os;
}


Vector& Vector::operator=(const Vector& other)
{
delete[] this->m_base;
this->m_len = other.m_len;
this->m_base = new int[this->m_len];
for (int i = 0; i < this->m_len; i++)
{
this->m_base[i] = other.m_base[i];
}
return *this;
}

int main(void)
{

try
{
Vector v(1111);
//上面没有问题才会执行到下面的这个for
for (int i = 0; i < v.GetLength(); i++)
{
v[i] = i;
}
cout << v;
}
//多态实现父类指针指向子类对象
catch (errSizeException* err)
{
err->printError();
}
/*catch (errNegativeException& err)
{
cout << "errNegativeException" << endl;
}
catch (errZeroException& err)
{
cout << "errZeroException" << endl;
}
catch (errTooBigException& err)
{
cout << "errTooBigException" << endl;
}
catch (errTooSmallException& err)
{
cout << "errTooSmallException" << endl;
}*/



return 0;
}


err.h

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
#pragma once
#include<iostream>
using namespace std;

/*


1) index<0 抛出异常errNegativeException
2)index = 0 抛出异常 errZeroException
3)index>1000抛出异常errTooBigException
4)index<10 抛出异常errTooSmallException

errSizeException类是以上类的父类,实现有参数构造、并定义virtual void printError()输出错误。
*/
class errSizeException
{
public:
errSizeException(int size):m_size(size)
{

}
virtual void printError()const
{
cout << "size:" << m_size << endl;
}
protected:
int m_size;
};

class errNegativeException:public errSizeException
{
public:
errNegativeException(int size):errSizeException(size)
{

}
virtual void printError()const
{
cout << "errNegativeException size:" << m_size << endl;
}
};
class errZeroException :public errSizeException
{
public:
errZeroException(int size) :errSizeException(size)
{

}
virtual void printError()const
{
cout << "errZeroException size:" << m_size << endl;
}
};
class errTooBigException :public errSizeException
{
public:
errTooBigException(int size) :errSizeException(size)
{

}
virtual void printError()const
{
cout << "errTooBigException size:" << m_size << endl;
}
};
class errTooSmallException :public errSizeException
{
public:
errTooSmallException(int size) :errSizeException(size)
{

}
virtual void printError()const
{
cout << "errTooSmallException size:" << m_size << endl;
}
};

注意:重载下标引用操作符需要返回引用、这样才可修改内部数据。这样操作的才是那他的内部数据。


补充:
匿名对象不同于创建临时对象。

typename();——创建临时对象,它的生命到下一行就结束了。


异常处理的基本思想

C++的异常处理机制使得异常的引发和异常的处理不必在同一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以再适当的位置设计对不同类型异常的处理。

异常是专门针对抽象编程中的一系列错误进行处理的,C++中不能借助函数机制实现异常,因为栈结构的本质是先进后出,依次访问,无法进行跳跃,但错误处理的特征却是遇到错误信息就想要转到若干级之上进行重新尝试。

(多级调用时可以直接越级提示)

标准库异常

代码示例:

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
#include<iostream>
using namespace std;
class Student
{
public:
Student(int age )
{
if (age > 249)
{
throw out_of_range("年龄过大");
}
m_age = age;
m_space = new int[1024 * 1024 * 100];
}

private:
int m_age;
int* m_space;
};


int main(void)
{
try
{
for (int i = 1; i < 1024; i++)
{
Student* s = new Student(18);
}


}
catch (out_of_range &e)
{
cout << e.what() << endl;
}
catch (bad_alloc& e)
{
cout << e.what() << endl;
}

return 0;
}