前言
1)异常是一种程序控制机制,与函数机制独立和互补
函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它依附于栈结构,却可以同时设置多个异常类型作为网捕条件,从而以类型匹配在栈机制中跳跃回馈.
2)异常设计目的:
栈机制是一种高度节律性控制机制,面向对象编程却要求对象之间有方向、有目的的控制传动,从一开始,异常就是冲着改变程序控制结构,以适应面向对象程序更有效地工作这个主题,而不是仅为了进行错误处理。
异常设计出来之后,却发现在错误处理方面获得了最大的好处。
一、异常处理的基本思想:
在 h()函数中专注于业务的处理,而不用去处理异常
让 f()函数实现综合的处理
1)C++的异常处理机制使得异常的引发和异常的处理不必在同一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以再适当的位置设计对不同类型异常的处理。
2)异常是专门针对抽象编程中的一系列错误处理的,C++中不能借助函数机制,因为栈结构的本质是先进后出,依次访问,无法进行跳跃,但错误处理的特征却是遇到错误信息就想要转到若干级之上进行重新尝试,如图:
3)异常超脱于函数机制,决定了其对函数的跨越式回跳。
4)异常跨越函数
二、C++异常处理的实现:
1、异常基本语法:
1)若有异常则通过throw操作创建一个异常对象并抛掷。
2)将可能抛出异常的程序段嵌在try块之中。控制通过正常的顺序执行到达try语句,然后执行try块内的保护段。
3)如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行。程序从try块后跟随的最后一个catch子句后面的语句继续执行下去。
4) catch子句按其在try块后出现的顺序被检查。匹配的catch子句将捕获并处理异常(或继续抛掷异常)。
5)如果匹配的处理器未找到,则运行函数terminate将被自动调用,其缺省功能是调用abort终止程序。
6)处理不了的异常,可以在catch的最后一个分支,使用throw语法,向上扔。
三、异常机制举例:
int divide(int x, int y ) // 抛异常
{
if (y ==0)
{
throw x;
}
return x/y;
}
void main41()
{
Try 接异常
{
cout << "8/2 = " << divide(8, 2) << endl;
cout << "10/0 =" << divide(10, 0) << endl;
}
catch (int e)
{
cout << "e" << " is divided by zero!" << endl;
}
catch(...)
{
cout << "未知异常" << endl;
}
cout << "ok" << endl;
system("pause");
return ;
}
//如果main函数不做异常处理:将会发生错误:
// 假设我们我们要做 x/y ,y就不能为0
void divide(int x, int y) //抛异常
{
if (y == 0) // y为0 就是 异常
{
throw x; //抛出 int类型 异常 (简单) ,如果是一个类就变得复杂了
}
cout << "divide结果:" << x/y<< endl;
}
void myDivide(int x, int y)
{
try
{
divide(x, y);
}
catch (...)
{
cout << "我接受了 divide的异常 但是我没有处理 我向上抛出" << endl;
throw ; //接收了异常之后继续往外抛
}
}
void main22()
{
myDivide(100, 0); //main函数不做异常处理,将会发生错误。
cout<<"hello..."<
上述代码出现的错误是:
四、异常捕捉需要严格按照类型匹配:
异常捕捉严格按照类型匹配
异常捕捉的类型匹配之苛刻程度可以和模板的类型匹配媲美,它不允许相容类型的隐式转换,比如,抛掷char类型用int型就捕捉不到.例如下列代码不会输出“int exception.”,从而也不会输出“That’s ok.” 因为出现异常后提示退出
int main(){
try{
throw ‘H’; // 因为扔的是一个 char 的类型,接收的是一个int类型,会接收不了
}catch(int){ //本来char可以隐式转化为int 类型的,可是需要严格按照类型,会去接接不了
cout<<"int exception.\n";
}
cout<<"That's ok.\n";
}
五、栈解旋:
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反。这一过程称为栈的解旋(unwinding)。
#include
using namespace std;
class Test3
{
public:
Test3(int a=0, int b=0)
{
this->a = a;
this->b = b;
cout << "构造函数do \n";
}
~Test3()
{
cout << "析构函数do \n";
}
private:
int a;
int b;
};
void myDivide() throw (int, char, char *)
{
Test3 t1(1, 2), t2(3, 4);
cout << "myDivide ...要发生异常\n" ;
throw Test3;
//throw 1;
}
void main()
{
try //异常之后会自动发生所有的析构函数
{
myDivide();
}
catch (int a)
{
cout << "int类型 异常\n" ;
cout <
会严格执行构造函数和析构函数:
//异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,
//都会被自动析构。析构的顺序与构造的顺序相反。
//这一过程称为栈的解旋(unwinding)
六、异常接口声明:
1)为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型,例如:
void func() throw (A, B, C , D); //这个函数func()能够且只能抛出类型A BC D及其子类型的异常。 //这个地方又设计多态的理念
2)如果在函数声明中没有包含异常接口声明,则次函数可以抛掷任何类型的异常,例如:
void func();
3)一个不抛掷任何类型异常的函数可以声明为:
void func() throw();
1) 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexpected函数会被调用,该函数默认行为调用terminate函数中止程序。