编写程序过程中难免出错。程序错误可分为三类,它们分别是语法错误、语义错误(或称逻辑错误)和运行时错误。针对不同错误,C++语言具有不同的解决办法,最终保证所开发的程序能够正确、稳定地运行。针对程序运行时的错误C++设计了专门的异常处理机制,即try-catch机制。C++标准库为异常处理机制提供多种不同功能的异常类。
一、程序的三类错误
1、语法错误
C++程序员未能严格按照语法规则编写程序,这就属于语法错误。如”cin << N;” 通过<<(插入运算符)读取数据就是一个语法错误。编译时,C++编译器负责检查源程序中的语法错误,如无语法错误将其翻译称目标程序,否则将提示错误信息。
2、语义错误
C++程序员在编写源程序时出现逻辑错误,导致出现的程序结果不符合预期。比如本来是两个数相除,却错写成相乘。C++编译器不能帮助程序员发现语义错误。程序员必须通过运行测试,对比程序结果才能发现语义错误。
3、运行时错误
编写的源程序既没有语法错误也没有语义错误,而是由于用户操作不当或运行环境差异导致的程序错误,我们将这种错误称为运行时错误。如用户输入0,程序计算100/0的结果时就会出现该情况。对于这类异常情况此程序员无法阻止异常情况的发生,但可以在程序中添加异常处理机制,避免因异常情况而导致程序死机等严重的运行时错误。
二、程序的异常处理机制
1、认识异常处理机制
常见的程序运行时的异常情况:用户操作不得当、输入的文件不存在、网络中断、非法访问内存单元等。一个异常处理指针包含两个部分:
1)发现异常。程序员应在可能出现异常的程序位置增加检查异常的代码,其目的就是及时发现异常。
2)处理异常。发现异常后,程序需要改变算法流程,增加异常处理流程。
下面通过一个简单示例了解异常处理机制:
#include<iostream>
using namespace std;
int Div(int n)
{
if(n<=0) //检查异常:如果n<0,则属于异常情况
return (-1); //执行异常处理流程
return(100/n);
}
int main()
{
int a;
cin >> a;
int result = Div(a);
if(result == -1) //检查异常:如果result = 1,则属于异常情况
{
cout << "输入的数据必须是正整数" <<endl; //执行异常处理流程
}else {
cout << "100 / " << a << " = "<< result << endl;
}
return 0;
}
通过上面示例注意到,在多函数结构程序中运用异常处理,将会同时涉及到主调函数和被调函数。被调函数发现异常后处理异常,并通过特定返回值(即错误码)向上级主调函数报告异常情况。如果函数未多级调用还需要逐级向各自的上级函数报告异常。
涉及主调函数与被调函数之间的异常处理机制被称为多级异常处理机制。多级异常处理机制包括三个方面的内容,它们分别是发现异常、报告异常和粗粒异常。C++为多级异常处理机制提供了一种非常方便有效的机制,这就是try-catch异常处理机制。
2、try-catch异常处理机制
C++语言通过throw语句报告异常,并通过try-catch语句提供一个捕获并处理异常的代码框架。C++程序通过这条语句就可以实现一套完整的异常处理机制。
throw语句的语法细则:
语法形式为:throw 异常表达式:异常表达式的结果可以是基本数据类型、自定义数据类型或类类型。异常表达式的数据类型被用于区别不同类型的异常,结果的值被用于描述异常的详细信息。异常表达式可以是单个常量、变量或对象。计算机执行throw语句抛出一个异常,并退出当前函数的执行。
示例:
throw 15; //抛出一个int型异常,异常的详细信息是15
throw "Invalid value"; //抛出一个字符串型的异常,异常的详细信息为"Invalid value"
try-catch语句的语法形式为:
try
{
受保护代码段
}
catch (异常类型1)
{异常类型1的处理代码}
catch (异常类型2)
{异常类型2的处理代码}
… …
try-catch语句的语法说明:
- 通过try语句将可能会出现异常的代码段保护起来。受保护的代码段如果通过throw抛出异常,将会被try后的catch捕获处理。这里的捕获不仅可以捕获被调函数的异常,还可以捕获下级被调函数的异常类型。
- catch子句负责捕获并处理异常。每个catch子句只负责处理一种类型的异常。异常类型就是抛出该异常的throw语句中表达式结果的类型。
- 若受保护代码段未报告任何异常,则所有的catch子句都不会执行。
- 若受保护代码段报告了异常,则根据异常类型依次匹配catch子句。如果在主调函数中匹配到catch子句,则进行依次处理,其他catch子句将不在匹配和执行。也就是说一个异常只能被捕获一次。若在主调函数中未被任何catch子句捕获,则逐级向上继续报告异常,直到该异常被捕获处理。假设某异常到最上级主函数main都未被捕获,则自动使用默认方法来进行处理。一般默认方法就是中止当前程序的执行。
- catch(…)形式的子句可匹配并捕获任意类型的异常,其后的其他catch子句都不在执行。一帮将其放到最后。
下面通过一个示例来更好的了解try-catch的使用:
#include<iostream>
using namespace std;
int Div(int n)
{
if(n<=0) //检查异常:如果n<0,则属于异常情况
throw (-1); //执行异常处理流程:抛出int型异常-1,并退出当前函数执行
return(100/n);
}
int main()
{
int a;
cin >> a;
try //通过try保护代码段,接收异常
{
int result = Div(a);
cout << "100 / " << a << " = "<< result << endl;
}
catch(int x) //捕获int型异常,也可以是catch(int)
{
cout << "输入的数据必须是正整数" <<endl;
cout << "异常详细信息为:" << x <<endl;
catch(...) //捕获任意类型异常
{
cout<<"发生了其他异常"<<endl;
}
return 0;
}
计算机在执行到throw语句时首先计算异常表达式,然后抛出异常并退出当前函数的执行。throw和return语句不同的是,在执行return语句将返回主调函数,继续执行函数调用语句的下一条语句,而执行throw语句将直接跳转到try子句后面catch子句,开始执行子句的异常捕获及处理操作;return语句时正常退出函数,throw语句则是异常退出。try-catch机制保证异常退出时能自动释放当前函数的形参,局部变量或对象。释放局部对象时会自动调用其析构函数。
可以看出catch子句所定义的参数相当于函数形参,可以接收throw语句中异常表达式的结果。throw语句中的异常表达式相当于时调用函数的实参。
3、使用类描述异常
throw语句所抛出的异常可以是基本数据类型、自定义或类类型。类类型可以提供更多的异常信息和异常处理能力。
#include<iostream>
using namespace std;
class Error
{
public:
int errCode; //异常代码
char errMsg[40]; //异常信息
Error(int code,char *msg) //构造函数
{ errCode = code;strcpy(errMsg,msg); }
void ShowError() //显示异常的详细信息
{ cout << errCode <<":"<<errMsg << endl; }
}
int Div(int n)
{
if(n<=0) //检查异常:如果n<0,则属于异常情况
{
Error err(-1,"输入的数据必须是正整数"); //定义异常类,并初始化
throw (err);
} //执行异常处理流程:抛出int型异常err,并退出当前函数执行
return(100/n);
}
int main()
{
int a;
cin >> a;
try //通过try保护代码段,接收异常
{
int result = Div(a);
cout << "100 / " << a << " = "<< result << endl;
}
catch(Error e) //捕获Error类的异常,也可以定义成异常类的引用:catch(Error &re)
{
e.ShowError(); //显示异常信息
catch(...) //捕获任意类型异常
{
cout<<"发生了其他异常"<<endl;
}
return 0;
}
4、try-catch异常处理机制的代码框架
try-catch异常处理机制的语法细节:
1)语句try-catch可以多层嵌套。
2)计算机执行throw语句,将跳转到包含该throw语句最下一层的catch子句进行异常类型匹配。
3)语句try-catch可以直接包含throw语句。执行throw语句时的跳转目标实际上时查找最近的catch子句,也就是本函数的函数子句。
4)子句catch在处理完异常后可以继续抛出异常。再次抛出异常的原因在于,某个异常需要经过不同层级代码多次处理,每个层级只完成整个异常处理环节的一部分。当捕获到的异常再次抛给上层的try-catch,此时throw语句可以不带异常表达式。
关键字throw可以被用在函数定义或原型声明中,其作用是声明该函数可能抛出的异常列表。其语法形式为:函数类型 函数名(形参列表)throw(异常类型列表);
示例:
void fun() throw(A,B,C); //函数fun能却只能抛出A、B或C共三种类型的异常
void fun() throw(); //函数fun不会抛出任何类型的异常
void fun(); //函数fun可能会抛出任何类型的异常