异常处理是一种错误处理机制,提高了C++程序的安全性,在团队开发过程中,可以通过异常处理机制降低产生错误的可能性,从而提高程序的可靠性。
异常处理机制
C++中异常往往使用类进行实现,以栈满或空的异常为例
class popOnEmtpy{/*内部为检测栈空的程序*/};
class pushOnFull{/*内部为检测栈满的程序*/};
template<typename T> void Stack<T>::Push(const T&data)
{
if(IsFull()) throw pushOnFull<T>(data);//后面有括号是为了将对象实例化
elements[++top]=data;
}
template<typename T>T Stack<T>::Pop()
{
if(IsEmpty()) throw popOnEmpty<T>();
return elements[top--];
}
C++要求抛出的必须是对象,所以必须要有括号,即调用构造函数建立一个对象。异常并不仅仅只有类对象,throw表达式也可以抛出任何类型的对象,比如枚举、整数等等,但一般最常用类对象。
在异常作用域内,被new出来的对象或是变量在抛出异常时不会自动被释放,所以在抛出异常前需要手动将其释放
抛出异常
C++中,使用try/catch语句将可能存在的异常抛出并捕获,使用throw关键字做到抛出异常的过程。
当throw对象(变量)引用时,拷贝的是引用的对象,而不只是拷贝引用名称
而try/catch语句的使用方式是
try{
包含有可能抛出异常的语句
}
catch(类型名[形参名])
{
}
以下事例是判断三条边能不能构成三角形的程序
#include<iostream>
#include<math.h>
using namespace std;
double sqrt_delta(double d)
{
if (d < 0)
throw 1;
return sqrt(d);
}
double delta(double a, double b, double c)
{
double d = b * b - 4 * a * c;
return sqrt_delta(d);
}
void main()
{
double a, b, c;
cout << "please input a, b, c" << endl;
cin >> a >> b >> c;
while (true)
{
try {
double d = delta(a, b, c);
cout << "x1:" << (d - b) / (2 * a);
cout << endl;
cout << "x2" << -(b + d) / (2 * a);
cout << endl;
break;
}
catch (int)
{
cout << "delta<0,please reenter a,b,c";
cin >> a >> b >> c;
}
}
}
重新抛出异常
如果在throw后面有表达式,则会抛出新的异常对象
#include<iostream>
using namespace std;
void fun(int x)
{
try {
if (x == 1)
throw 1;
if (x == 2)
throw 1.0;
if (x == 3)
throw 1;
}
catch (int)
{
cout << "catch an int in fun()" << endl;
}
catch (double)
{
cout << "catch an double in fun()" << endl;
}
cout << "testing exception in fun()" << endl;
}
void gun()
{
try {
fun(4);
}
catch (char)
{
cout << "catch a char in gun()" << endl;
}
cout << "testing exception in gun()" << endl;
}
int main()
{
gun();
return 0;
}
testing exception in fun()
testing exception in gun()
首先定义了一个fun函数,在该函数中做出判断,根据不同的情况抛出不同的异常,之后定义了一个gun函数,在该函数中调用了上一个fun函数,并又将异常重新抛出一次。
主函数中,调用了gun函数,并抛出了异常,gun调用了fun,fun的异常抛出一次,接着gun进行抛出
捕获所有的异常
因为不知道可能被抛出的全部异常,所以不是为每一种可能的异常都写一个catch以释放资源,而是以通用形式的catch字句,即catch_all
catch(...){相应的代码}
任何异常都会进入这个catch字句,花括号中的复合语句用来执行制定操作,当然包括了资源的释放
捕获到的对象只是被抛出对象的一个副本,抛出异常之后原对象就会被自动析构,也就是释放
#include<iostream>
using namespace std;
int main()
{
try
{
if (1 == 1)
throw 0.5;
}
catch (...)
{
cout << "error in try has been cleared" << endl;
}
system("pause");
return 0;
}
try模块中抛出了一个异常,也就是0.5,在捕获异常的时候,才用了捕获全部异常,并且经过处理,输出了一段文字
catch_all的字句可以单独使用,也可以与其他catch字句联合使用。如果联合使用,它必须放在相关catch子句表的最后,因为catch子句被检查的顺序与书写顺序相同,找到了一个匹配之后,后续的catch子句就形同虚设,catch_all子句可以处理所有的异常,如果放在前面,则配合使用的其他字句就不能发挥作用
不是错误的异常
在C++中,大多数情况下报告和处理逻辑错误和运行时错误的首选方式都是使用异常。当堆栈可能包含一些在可检测错误的函数和具有上下文来了解如何解决该错误的函数间的函数调用时,更是如此。异常就提供正式且定义完善的代码错误的检测方法,以在调用堆栈上向上传递信息
程序错误通常分为两类,第一类是编程错误导致的逻辑错误,第二类是超出程序员控制的运行时错误,比如网络不可用
出现异常时,C++会做以下几种处理
1.异常强制调用代码,识别错误条件并对其进行处理。若有未经处理的异常会终止程序执行
2.异常跳转到能处理错误的调用堆栈的点
3.在引发异常后,异常堆栈展开结构基于显式定义的规则销毁处于控制范围的所有对象
4.异常将介于检测错误和处理错误间的代码完全分离
注意:
抛出异常没有捕获会发生什么
如果抛出的异常一直没有函数捕获,则会一直上传到C++运行系统,导致程序终止
异常处理通过什么进行匹配
异常处理仅仅通过类型而不是值来进行匹配。所以catch块的参数可以没有参数名称,只需要参数类型
异常抛出后资源如何释放
一般在异常抛出后资源可以正常被释放,但注意如果在类的构造函数中抛出异常,系统是不会调用它的析构函数的