C++异常捕获与处理
1.异常处理
1.1什么是异常处理
异常是指再程序正常运行的过程中的发生的反常行为,比如说遇到意外的用户输入情况。
1.2抛出异常
当程序的某部分监测到一个它无法处理的问题时,它首先应该发出某种信号
,以表明它遇到了无法解决的问题,并且它只负责报告异常
,至于程序会如何处理,不是它的任务。
1.3异常处理
一般而言,如果程序中有监测异常的代码,通常就会有处理异常的代码。比如说引发程序异常的问题是输入无效,那么处理异常的代码就可以是提示用户重新输入真确的数据。
2.C++实现异常处理的3个关键字
2.1 throw、try、catch
- 用“throw表达式”来抛出异常,用法是
“throw + 表达式 ;”
。如:
throw “the input is error.”;
//抛出const char* 的异常类型throw 1;
//抛出int型的异常类型throw runtime_error(“Data must refer to ISBN”);
//抛出runtime_error类类型的异常类型,其中runtime_error是一个标准的异常处理类
- 用“try语句块”来处理异常
try语句块中的program-statements放置正常运行的程序,同时它也是可能出问题的程序
。一旦有异常发生,就用throw表达式来抛出异常。这里注意,try中声明的变量在外部无法访问,catch也不行
。catch的语句块(handler-statements)就是放置异常处理代码的地方
。这里提个问题:catch是怎样匹配throw抛出的异常的呢?答案就是catch通过括号()中的异常声明(exception-declaration)来匹配。当throw抛出的是int型的异常类型,则用int声明一个变量来匹配。如下:当你输入一个<0的数时,throw -1,即抛出一个int型的异常,接着catch就能捕获到int型异常,并匹配异常声明(exception-declaration),接着就执行catch(int i)下的语句块,并且此时i的值就是-1。
int a;
try
{
cin >> a;
if (a< 0)
throw -1;
}
catch (int i){
cerr << "input < 0,please input again:" << endl;
cin >> a;
}
catch (const char* c){
cerr << "const char* error" << endl;
}
具体的代码例子会在后面给出。
2.2寻找处理代码的过程(匹配catch的过程)
在复杂的系统中,程序在遇到抛出异常的代码之前,执行路径可能已经经过多个try语句块,也就是说try语句块有嵌套。那么,在throw抛出异常后,寻找catch异常处理的代码是怎样的一个过程呢?答案就是逐层向外寻找
。即本层throw出的异常,若在本层没找到对应的catch异常处理,则在调用它的层里继续匹配寻找。
如下:getInput()函数在用户输入<0的整型数据时,会throw -1,但是在getInput()函数内并没有处理异常的代码,所以在调用它的函数继续匹配,而后就匹配到了main中catch(int i),则执行catch(int i)下的语句块。
int a;
void getInput()
{
cin >> a;
if (a< 0)
throw -1;
}
int main()
{
try{
getInput();
}
catch (int i){
cerr << "input < 0,please input again:" << endl;
cin >> a;
}
catch (const char* c){
cerr << "const char* error" << endl;
}
}
注意
: 当上述的匹配过程中最后都没能匹配到任何catch字句,程序会转到terminate的标准库函数,一般来说执行了这个函数,程序就非正常退出了。
对于没有任何try语句块的异常——即只用throw抛出了异常,却没有try catch来捕获异常,和上述情况类似,调用terminate后程序非正常退出。
下面举一个例子来说明异常处理的使用:
#include <iostream>
#include <exception>
using namespace std;
double calQuotient(double &_a, double &_b)
{
if (_b == 0) throw "输入的分母是0。"; //抛出const char* 类型的异常
return _a / _b;
}
int main()
{
double numerator, denominator;//numerator:分子。denominator:分母
while (cin >> numerator >> denominator){//用户输入分子分母
try{
double quotient = calQuotient(numerator, denominator);//calQuotient()函数是计算商的函数
std::cout << numerator<<"/"<<denominator<<" = "<<quotient << std::endl;
break;//得到了正确得商,退出while
}
catch (const char* s){//分母为0的异常处理代码
std::cout << s //此时s的值就是throw的表达式的值
<<"是否在输一次?Enter y or n"<< std::endl;
char c;
cin >> c;
if (c == 'n')
break;//用户输入n,退出while循环
}
}
system("pause");
return 0;
}
上述代码的功能是用户输入两个double类型的数,分别为分子和分母,然后调用calQuotient函数计算商。如果分母不为0,打印商;若分母为0,calQuotient函数抛出异常,并在main中用catch处理异常。异常处理的过程是询问用户是否重新输入,若不重新输入,直接退出。
代码运行结果如下:
3.C++定义的标准异常类
C++ 提供了一系列标准的异常,它们是以父子类层次结构组织起来的,如下所示:
下面是对异常类的说明:参考至:https://www.runoob.com/cplusplus/cpp-exceptions-handling.html
3.1使用C++的标准异常类实例
例子1:
以前面给出的实例为例,我们做如下修改:
-
将
if (_b == 0) throw "输入的分母是0。";
修改为
if (_b == 0) throw runtime_error("输入的分母是0。");
-
将
catch(const char* s)
修改为catch(runtime_error err)
将std::cout << s
修改为std::cout << err.what()
最后的结果和修改前一样,只不过使用的异常类。
这里注意:异常类只定义了一个名为what的成员函数,该函数没有任何参数,返回值是一个指向C风格字符串的const char*。该字符串的目的是提供有关异常的一些文本信息。就如"输入的分母是0。"提示一样。对于那些无初始值的异常类型来说,what返回的内容由编译器决定。
例子2:
参考至:http://c.biancheng.net/view/422.html
#include <iostream>
#include <stdexcept>
using namespace std;
int main()
{
//bad_alloc异常类实例
try {
char * p = new char[0x7fffffff]; //无法分配这么多空间,会抛出异常
}
catch (bad_alloc e) {
cerr << e.what() << endl;
}
//out_of_range异常类实例
string s = "I love China";
try {
char c = s.at(100); //拋出 out_of_range 异常
}
catch (out_of_range & e) {
cerr << e.what() << endl;
}
system("pause");
return 0;
}
从这个例子可以看出,有些异常是系统自动抛出,并不需要我们自己写抛出异常。
4.如何定义和使用自己的异常类
我们可以通过继承C++标准异常类的方法来定义自己的异常类。
实例:
#include <iostream>
#include <exception>
#include <stdexcept>
using namespace std;
class MyException :public exception
{
public:
MyException() :c(nullptr){}
MyException(const char* _c):c(_c){}
virtual ~MyException(){}
//覆写what成员函数
const char* what() throw()
{
if (c == nullptr)
return "MyException() error catch!";
return c;
}
private:
const char * c;
};
int main()
{
//默认参数-构造一个MyException类
try{
throw MyException();
}
catch (MyException & myexp){
cerr << myexp.what() << endl;
}
//实际传参-构造一个MyException类
try{
throw MyException("Error have been found.");
}
catch(MyException & myexp){
cerr << myexp.what() << endl;
}
system("pause");
return 0;
}
在上面的实例中,我们覆写了异常类的一个公共成员函数what,从而可以返回一些自己定义的异常的提示文本信息
这里注意
:在覆写what成员函数时,我们后面接了一个throw(),它其实是一个异常接口声明
,列出函数可能抛出的所有异常类型,增加程序的可读性和可维护性:
- 如
void fun() throw(int ,const char*);
则fun函数可能抛出int和const char*类型的异常; - 如
void fun() throw();
表明函数fun不会抛出任何异常。 - 如
void fun();
表明函数可以抛出任何异常。
函数如果拋出了其异常声明列表中没有的异常
,在编译时不会引发错误,但在运行时, Dev C++ 编译出来的程序会出错;用 Visual Studio 2013编译出来的程序会提示警告信息,但是程序依旧可以正常运行。
5.总结
- try和catch必须成对出现。catch通过()中的异常声明来匹配throw抛出的异常类型(也就是throw后接的表达式类型)
- catch的匹配是逐层向外匹配。即当前函数没有catch,则在调用它的函数里找catch
- 若throw抛出的异常最终没有匹配到catch或则根本就没有catch,系统调用terminate后退出程序
- what()函数是C++异常类的一个方法。返回值为const char *类型
- 异常接声明。列出函数可能抛出的所有异常类型,增加程序的可读性和可维护性
以上就是C++异常类的使用方法。有关C++异常类的底层实现机制,本文不做讨论,有兴趣的朋友可以自行查阅相关书籍。
本人水平有限,如有错误,欢迎各位指正。
参考书籍:C++ primer 第5版