在前面的教程中,叙述了模板函数以及模板类的相关概念,在本节教程中,笔者将着重叙述 C++
中的异常机制,所谓异常,是程序在执行期间产生的问题,异常提供一种转移程序控制权的方式。而且异常处理主要涉及到三个关键字:try、catch、throw,下面将对异常这个概念进行详细叙述。
为什么要引入异常这个机制呢,假设有如下一个调用关系:
A ----> B ----> C
那如果说是,C
函数中出现了一个问题,那要将这个问题找到,就需要在 C
函数里返回一个值,然后根据这个值一级一级地向上处理,有多少层就要往上传递多少层,下面是伪代码示意图:
int C()
{
return XXX;
}
int B()
{
if (C())
{...}
else
return -Err;
}
int A()
{
if (B())
{...}
else
{/*处理问题*/}
}
由上述这样一个伪代码可以看出,如果通过返回值这样地方式来处理子函数出现地问题的时候,那么当函数调用很深的时候,这种处理方式的弊端也就显现出来了,代码会看起来很冗长,且略显复杂,所以也就有了异常处理机制。
在最开始讲述这个概念的时候,我们依旧采用刚刚所述的那个背景,A ----> B ----> C
,再有了异常机制之后,我们就可以用异常的处理机制来解决这个问题,那基于这样一个背景,同样是C
函数里出现了问题,我们用依据话来概括异常,也就是函数A捕捉函数C发出的异常。我们将上述这句话进行剖析也就剖析成如下这样几句话:
基于上述这样一个机制,我们来简单地编写一个异常处理代码:
void C()
{
throw 1;
}
void B()
{
C();
}
void A()
{
try
{
B();
}
catch (i)
{
cout<<"catch exception "<<i<<endl;
}
}
上述代码啥意思呢,try
后面接一个函数调用,它的意思实际也就是说,试着运行一下B()
,如果B()
中存在问题,那么就捕获这个错误,这里的catch
和C()
函数里的throw 1
是所对应起来的。程序运行结果如下所示:
image-20210226110245582
也就说,throw
的值是多少,那么这里捕获的变量也就是多少。
我们继续丰富上面的代码,如果说抛出的异常有很多个该如何进行处理呢?我们同样是基于刚才的背景进行叙述,函数之间的调用关系跟上述一样,我们给出各个函数的代码:
void C(int i)
{
int a = 1;
double b= 1.2;
float c = 1.3;
if (i == 0)
{
cout<<"In C, it is OK"<<endl;
}
else if (i == 1)
throw a;
else if (i == 2)
throw b;
else
throw c;
}
void B(int i)
{
cout<<"call C ..."<<endl;
C(i);
cout<<"After call C"<<endl;
}
void A(int i)
{
try
{
B(i);
}
catch (int j)
{
cout<<"catch int exception "<<j<<endl;
}
catch (double d)
{
cout<<"catch double exception "<<d<<endl;
}
catch (...){
cout<<"catch other exception "<<endl;
}
}
我们会根据传入到 B()
中的参数,依次抛出不同类型的异常,然后去设置不同的捕获方式,在编写捕获代码的时候,涉及到其他的这个选项,可以用...
来替代,下面是主函数的代码,:
int main(int argc, char **argv)
{
int i;
if (argc != 2)
{
cout<<"Usage: "<<endl;
cout<<argv[0]<<" <0|1|2|3>"<<endl;
return -1;
}
i = strtoul(argv[1], NULL, 0);
A(i);
return 0;
}
其中上述中的主函数中的argc
表示的是当前输入的参数个数,而argv[1]
表示的是输入的第二个参数,通过strtoul
将字符串转换为整型。下面是函数运行的结果:
image-20210226162409999
通过上述可以看到运行程序时输入的命令是./exception3 2
,那么这个时候也就是说argc
的值等于2
,而argv[0]
的值等于./exception3
(字符串),argv[1]
的值等于2
(字符)。
上述中,我们讲述了异常处理机制时在扔出各个类型的异常时的处理方法,在整个C++
教程中,贯穿始终的一直是类
这个概念,那么对于C++
来说,抛出异常的时候可以抛出类
异常么,答案是可以的
。下面我们就来看一个抛出类
异常的代码,代码如下所示,首先是类的代码:
class MyException
{
public:
void what(void) {cout << "This is MyException" << endl;}
};
紧接着,我们来编写各个函数的代码:
void C(int i)
{
int a = 1;
double b= 1.2;
float c = 1.3;
if (i == 0)
{
cout<<"In C, it is OK"<<endl;
}
else if (i == 1)
throw a;
else if (i == 2)
throw b;
else if (i == 3)
throw c;
else if (i == 4)
throw MyException();
}
void B(int i)
{
cout<<"call C ..."<<endl;
C(i);
cout<<"After call C"<<endl;
}
void A(int i)
{
try
{
B(i);
}
catch (int j)
{
cout<<"catch int exception "<<j<<endl;
}
catch (double d)
{
cout<<"catch double exception "<<d<<endl;
}
catch (MyException &e) /* 捕获类异常 */
{
e.what();
}
catch (...)
{
cout<<"catch other exception "<<endl;
}
}
上述代码就是异常扔出类
的一个例子,通过上述可见,当传入的参数为4
的时候,抛出的异常就是类
,throw MyException();
,在捕获异常的时候,方法也是跟之前的一样。
如果在上述类的基础上,又多出来一个子类的异常,又该如何编写呢,下面是子类继承自父类的代码:
class MySubException : public MyException
{
public:
void what(void) { cout<<"This is MySubException"<<endl; }
};
抛出异常的代码与之前的MyException
是一样的,代码如下:
void C(int i)
{
int a = 1;
double b= 1.2;
float c = 1.3;
if (i == 0)
{
cout<<"In C, it is OK"<<endl;
}
else if (i == 1)
throw a;
else if (i == 2)
throw b;
else if (i == 3)
throw c;
else if (i == 4)
throw MyException();
else if (i == 5)
throw MySubException();
}
捕获异常的代码与之前相比,就需要额外注意几个地方了,代码如下所示:
void A(int i)
{
try
{
B(i);
}
catch (int j)
{
cout<<"catch int exception "<<j<<endl;
}
catch (double d)
{
cout<<"catch double exception "<<d<<endl;
}
catch (MyException &e)
{
e.what();
}
catch (MySubException &e)
{
e.what();
}
catch (...)
{
cout<<"catch other exception "<<endl;
}
}
B()
函数与主函数代码的写法与之前一致,基于此,我们编译当前的代码,编译结果如下所示:
image-20210226164620229
通过警告信息,我们可以知道,MySubException
这个异常的会被更早地被MyException
捕获,也就是说实际上MySubException
这个异常是不会被捕获到的,下面是程序运行的结果:
image-20210226164938499
可以看到,即便输入的参数是5
,但是捕获到的异常是MyException
的。
如何更改呢这个错误呢,其实也很简单,只要在捕获MyException
和MySubException
时,将向后顺序进行颠倒就好了,修改之后的代码如下:
/*省略之前的代码*/
catch (MySubException &e)
{
e.what();
}
catch (MyException &e)
{
e.what();
}
/*省略之后的代码*/
这样一来就能够捕获到子类的异常了。那除了上述这种按照顺序捕获父类和子类的异常以外,我们也可以使用多态的方法来捕获到子类的异常,抛出异常的代码和之前的一样 ,B 函数的代码和主函数的代码与之前一样,我们先来看捕获的代码。
void A(int i)
{
try
{
B(i);
}
catch (int j)
{
cout<<"catch int exception "<<j<<endl;
}
catch (double d)
{
cout<<"catch double exception "<<d<<endl;
}
catch (MyException &e)
{
e.what();
}
catch (...)
{
cout<<"catch other exception "<<endl;
}
}
可以看到,在捕获类的异常的代码的时候,并没有关于MySubException
的捕获代码,但是又是如何实现对于MySubException
的捕获呢?我们继续来看类的代码:
class MyException
{
public:
virtual void what(void) { cout<<"This is MyException"<<endl; }
};
class MySubException : public MyException
{
public:
void what(void) { cout<<"This is MySubException"<<endl; }
};
上述代码中,可以看到MyException
中的 void what(void);
方法前加了virtual
,也就是虚函数,从而实现了多态,这个时候,就能够捕获到MySubException
的异常。
另外一方面,我们在编写抛出异常的代码的时候,也可以采用如下的方式事先声明抛出哪些类型的异常,代码如下所示:
void C(int i) throw(int, double)
{
int a = 1;
double b= 1.2;
float c = 1.3;
if (i == 0)
{
cout<<"In C, it is OK"<<endl;
}
else if (i == 1)
throw a;
else if (i == 2)
throw b;
else if (i == 3)
throw c;
else if (i == 4)
throw MyException();
else if (i == 5)
throw MySubException();
}
我们看到,在上述中,我们只声明了抛出异常的类型只有int
和double
,那么也就是说不能够再去抛出其他类型的异常了,即便下面写的代码中有抛出其他类型,其他代码在上述的基础上不变,我们看代码执行的结果:
image-20210226173027356
可以看到,这个时候,就出错了,并没有能够抛出MyException
异常。
通过上述的错误信息,我们可以看到当程序执行错误的时候,会终止,并输出terminate called after throwing an instance of 'MyException'
,实际上这个错误信息,我们可以自定义输出,在出现错误的时候,大致分为两类,一个是unexpected
函数,一个是terminate
函数,我们都可以重新定义这两个函数,首先,我们在主函数编写如下两句话:
int main(int argc, char** argv)
{
/* 省略相关代码 */
set_unexpected (my_unexpected_func);
set_terminate(my_terminate_func);
/* 省略相关代码 */
}
然后,我们来实现这两个函数:
void my_unexpected_func()
{
cout<<"my_unexpected_func"<<endl;
}
void my_terminate_func () { cout<<"my_terminate_func"<<endl; }
这个时候,我们再执行代码,程序出错的时候,就会输出如下所示的信息:
image-20210226174031134
本节的内容到此结束,所涉及的代码可以通过百度云链接的方式获取:
链接:https://pan.baidu.com/s/1xRJ5fePEQU2-bY1l7Ti1Kw 提取码:s6wn