异常处理

       一个好的程序不仅要保证能实现所需要的功能,而且还应该有很好的容错能力。在程序运行过程中如果有异常情况出现,程序本身应该能解决这些异常,而不是死机。本章介绍异常处理的基本概念、C++异常处理语句、析构函数与异常处理。通过本章的学习,掌握了C++异常处理的机制,我们就可以在编制程序时灵活地加以运用,从而使我们编制的程序在遇到异常情况时能摆脱大的影响,避免出现死机等现象

有的程序 在运行过程中会出现异常,得不到正确的运行结果,甚至导致程序不正常终止或出现死机现象,这些都说明程序中存在运行错误。运行错误相对来说比较隐蔽,是程序调试中的一个难点,该错误又可分为逻辑错误和运行异常两类。逻辑错误是由设计不当造成的,如对算法理解有误、在一系列计算过程中出现除数为 0 、数组的下标溢出等。这些错误只要我们在编程时多加留意是可以避免的。但是,运行异常是由系统运行环境造成的,导致程序中内存分配、文件操作及设备访问等操作的失败,可能会造成系统运行失常并瘫痪。
在运行没有异常处理的程序时,如果运行情况出现异常,由于程序本身不能处理,程序只能终止运行。如果在程序中设置了异常处理,则在运行情况出现异常时,由于程序本身已设定了处理的方法,于是程序的流程就转到异常处理代码段处理。
需要说明的是:只要程序运行时出现与人们期望的情况不同,都可以认为是异常,并对它进行异常处理。因此,所谓异常处理是指对程序运行时出现的差错以及其他例外情况的处理。


1、异常处理的实现

1.1 异常处理语句

C++处理异常的机制由3个部分组成:检查(try)、抛出(throw)和捕捉(catch)。把需要检查的语句放在try块中,throw用来当出现异常时抛出一个异常信息,而catch则用来捕捉异常信息,如果捕捉到了异常信息,就处理它。try-throw-catch构成了C++异常处理的基本结构。


try-throw-catch结构形式如下:

try{

    …

    if(表达式1)  throw x1

    …

    if(表达式2)  throw x2

    …

    if(表达式n)  throw xn

    …

}

catch(异常类型声明1

异常处理语句序列  }

catch(异常类型声明2

异常处理语句序列  }

catch(异常类型声明n

异常处理语句序列  }

try-throw-catch结构中,try语句块内为需要受保护的待检测异常的语句序列,如果怀疑某段程序代码在执行时有可能发生异常,就将它放入try语句块中。当这段代码的执行出现异常时,即某个if语句中的表达式的值为真时,会用其中的throw语句来抛掷这个异常。

throw语句语法:

throw表达式;

throw语句是在程序执行发生了异常时用来抛掷这个异常的,其中表达式的值可以是intfloat、字符串、类类型等,把异常抛掷给相应的处理者,即类型匹配的catch语句块。如果程序中有多处需要抛掷异常,应该用不同类型的操作数来互相区别。throw抛出的异常,通常是被catch语句捕获。

catch语句块是紧跟在try语句块后面的,即try块和catch块作为一个整体出现,catch块是try-catch结构中的一部分,必须紧跟在try块之后,不能单独使用,在二者之间也不能插入其他语句。但是在一个try-catch结构中,可以只有try块而无catch块。即在本函数中只检查而不处理,把catch块放在其他函数中。一个try-catch结构中只能有一个try块,但却可以有多个catch块,以便与不同类型的异常信息匹配。在执行try块中的语句时如果出现异常执行了throw语句,系统会根据throw抛出的异常信息类型按catch块出现的次序,依次检查每个catch参数表中的异常声明类型与抛掷的异常信息类型是否匹配,当匹配时,该catch块就捕获这个异常,执行catch块中的异常处理语句来处理该异常。

catch参数表中,一般只写异常信息的类型名,如:catch(double)

系统只检查所抛掷的异常信息类型是否与catch参数表中的异常声明类型相匹配,而不检查它们的值。假如变量abc都是int型,即使它们的值不同,在throw语句中写throw athrow bthrow c的作用也均是相同的。因此,如果需要检测多个不同的异常信息,应当由throw抛出不同类型的异常信息。

异常信息类型可以是C++系统预定义的标准类型,也可以是用户自定义的类型(如结构体或类)。如果由throw抛出的异常信息属于该类型或其子类型,则catchthrow二者匹配,catch捕获该异常信息。注意:系统在检查异常信息数据类型的匹配时,不会进行数据类型的默认转换,只有与所抛掷的异常信息类型精确匹配catch块才会捕获这个异常。

catch参数表中,除了指定异常信息的类型名外,还可以指定变量名,如:catch(double d)

此时,如果throw抛出的异常信息是double型的变量a,则catch在捕获异常信息a的同时,还使d获得a的值。如果我们希望在捕获异常信息时,还能利用throw抛出的异常信息的值,这时就需要在catch参数表中写出变量名。如:

catch(doubled){  cout<<"throw"<<d;   }

这时会输出d的值(也就是a)


当抛出的是类对象时,有时希望在catch块中显示该对象中的某些信息。

#include <iostream>
using namespace std;
#include <cmath>
int main( )
{
    double a, b, c;
    double disc;
    cout << "Please Enter a,b,c:";
    cin >> a >> b >> c;
    try {
        if ( a == 0 )  throw 0;
        else
        {   disc = b*b-4*a*c; //计算平方根下的值
            if ( disc < 0 ) throw "b*b-4*a*c < 0";
            cout << "x1 = " << ( -b + sqrt(disc) ) / (2*a) << endl;
            cout << "x2 = " << ( -b - sqrt(disc) ) / (2*a) << endl;
        }
    }
    catch( int ) //用catch捕捉a=0的异常信息并作相应处理
    {   cout << "a = " << a << endl << "This is not fit for a." << endl;   }
    catch( const char *s ) //用catch捕捉b*b-4*a*c<0异常信息并作相应处理
    {    cout << s << endl << "This is not fit for a, b, c." << endl;   }
    //system("pause");
    return 0;
}
现在结合程序分析异常处理的进行情况。
1 )首先在 try 后面的花括号中放置上可能出现异常的语句块或程序段。
2 )程序运行时将按正常的顺序执行到 try 块,执行 try 块中花括号内的语句。如果在执行 try 块内的语句过程中没有发生异常,则忽略所有的 catch 块,流程转到 catch 块后面的语句继续执行
3 )如果在执行 try 块内的语句过程中发生异常,则由 throw 语句抛出一个异常信息。 throw 语句抛出什么样的异常由程序设计者自定,可以是任何类型的异常,在上述例子 中抛出了整型和字符串类型的异常。
4 )这个异常信息提供给 try-catch 结构,系统会寻找与之匹配的 catch 块。如果某个 catch 参数表中的异常声明类型与抛掷的异常类型相匹配,该 catch 块就捕获这个异常,执行 catch 块中的异常处理语句来处理该异常。 只要有一个catch块捕获了异常,其余的catch块都将被忽略
当然, 异常类型可以声明为省略号(…),表示可以处理任何类型的异常 。需要说明的是, catch(…)语句块应该放在最后面 ,因为如果放在前面,它可以用来捕获任何异常,那么后面其他的 catch 语句块就不会被检查和执行了。
5 )在进行异常处理后,程序并不会自动终止,继续执行 catch 块后面的语句。
6 如果throw抛掷的异常信息找不到与之匹配的catch ,则系统就会调用一个 系统函数terminate ,在屏幕上显示“ abnormal program termination” ,并终止程序的运行。

7 )抛掷异常信息的 throw 语句可以与 try-catch 结构出现在同一个函数中,也可以不出现在同一函数中。在这种情况下,当 throw 抛出异常信息后,首先在本函数中寻找与之匹配的 catch 块,如果在本函数中无 try-catch 结构或找不到与之匹配的 catch 块,就转到离开出现异常最近的 try-catch 结构去处理。
8 )异常处理还可以应用在函数嵌套的情况下。

#include <iostream>
using namespace std;
int main( )
{
    void Func1( );
   try
   {  Func1( );  }    //调用Func1函数
   catch(double)
   {  cout << "OK0!" << endl;  }
   cout << "end0" << endl;
   //system("pause");
   return 0;
}
void Func1( )
{
    void Func2( );
    try
    {   Func2( );    }  //调用Func2函数
    catch(char)
    {   cout <<"OK1!" << endl;  }
    cout << "end1" << endl;
}
void Func2( )
{
    double a = 0;
    try
    {   throw a;   } //抛出double类型异常信息
    catch(float)
    {   cout << "OK2!" << endl;   }
    cout << "end2" << endl;
}

try块的嵌套异常处理语句

在一个 try 块中可以嵌套另一个 try 块。每个 try 块都有自己的一组 catch 块,来处理在该 try 块中抛出的异常。 try 块的 catch 块只能处理在该 try 块中抛出的异常。如:
try                             // 外层的 try 语句
{  …
   try                         // 内层的 try 语句
    {
     …
    }
    catch(elemtype a)           // 这个 catch 语句捕获在内层 try 块中抛出的异常
     {
      …
    }
 …
}
catch(elemtype b) // 这个 catch 语句捕获在外层 try 块中抛出的异常和在内层 try 块中未捕获的异常
{
}

1.2 在函数中处理异常

异常处理可以局部化为一个函数,当每次进行该函数的调用时,异常将被重置。这样编写程序能够简单明了,避免重复。
check是一个检测成绩异常的函数,当成绩达到100分以上或低于60产生异常,在60100之间的为正常成绩

#include<iostream>
using namespace std;
void check(int score)
{
    try{
       if(score>100) throw "成绩超高!";
       else if(score<60) throw "成绩不及格!";
       else cout<<"the score is OK..."<<score<<endl;
   }
   catch(char *s){cout<<s<<endl;}
}
int main()
{  check(45);
    check(90);
    check(101);
    system("pause") ;  return 0;
}

1.3 在函数调用中完成异常处理

在处理异常检测时,也可以将抛掷异常的程序代码放在一个函数中,将检测处理异常的函数代码放在另一个函数中,能让异常处理更具灵活性和实用性

异常处理从函数中独立出来,由调用函数完成。
#include<iostream>
using namespace std;
void check(int score) {
   if(score>100) throw "成绩超高!";
   else if(score<60) throw "成绩不及格!";
   else cout<<"the score is OK..."<<score<<endl;
}
int main()
{  try{  check(45);  }
   catch(char *s){  cout<<s<<endl;  }
   try{  check (90);  }
   catch(char *s){  cout<<s<<endl;  }
   try{  check (101);  }
   catch(char *s){  cout<<s<<endl;  }
   system("pause") ;  return 0;    }

1.4 限制函数异常

为便于阅读程序,使用户在看程序时能够知道所用的函数是否会抛出异常信息以及抛出的异常信息的类型, C++ 允许在函数声明时指定函数抛出的异常信息的类型,如:

   double Deta(double, double, double)throw(double);

表示 Deta 函数只能抛出 double 类型的异常信息。如果写成:

   double Deta(double, double, double)throw(int, float, double, char);

则表示 Deta 函数可以抛出 int float double char 类型的异常信息。

异常指定是函数声明的一部分,必须同时出现在函数声明和函数定义的首行中,否则编译时,编译系统会报告“类型不匹配”。如果在函数声明时不指定函数抛出的异常信息的类型,则 该函数可以抛出任何类型的异常 。如:

    int Func(int, char);   //函数Func可以抛出任何异常

如果在函数声明时指定 throw 参数表为不包括任何类型的空表,则 不允许函数抛出任何异常 。如:

    int Func(int, char) throw( );

    //不允许函数Func抛出任何异常

这时即使在函数中出现了 throw 语句,实际上在函数执行出现异常时也并不执行 throw 语句,并不抛出任何异常信息,程序将非正常终止。

2、异常与类

2.1 构造函数、析构函数与异常处理
构造函数是一个特殊的函数,对象创建时,构造函数自动被调用。如果构造函数中出现了问题,抛出了异常,会发生什么情况?请看下面的例子

Number 的私有数据成员 i 赋值空间是小于等于 100 的数,如果赋值大于 100 会产生异常。

#include<iostream>
using namespace std;
class Number
{public:
    Number(int i)
    {
        cout<<"in Number constructor..."<<endl;
        if(i>100) throw i;
        else number=i;
    }
    ~ Number( )
    {  cout<<"in Number destructor..."<<endl;  }
private:
    int number;
};

int main()
{
    try{  Number obj(111);  }
    catch(int e)
    {   cout<<"catch an exception when allocated Number "<<e<<endl;   }
    cout<<"in the end"<<endl;
    //system("pause") ;
    return 0;
}

C ++ 异常处理的强大功能,不仅在于它能够处理各种不同类型的异常,还在于它具有 为异常抛出前已构造的所有局部对象自动调用析构函数的能力

2.2 异常类

用来 传递异常信息 的类就是异常类。异常类可以非常简单,甚至没有任何成员;也可以同普通类一样复杂,有自己的数据成员、成员函数、构造函数、析构函数、虚函数等,还可以通过派生方式构成异常类的继承层次结构。 声明了一个同普通类一样的有数据成员和成员函数的较复杂的异常类。

设计一个堆栈,当入栈元素超出了堆栈容量时,就抛出一个栈满的异常。 

声明了一个没有任何成员的简单异常类
#include <iostream>
using namespace std;
const int MAX=3;
class Full{};	   //堆栈满时抛出的异常类
class Empty{};      //堆栈空时抛出的异常类
class Stack
{public:
    Stack(){top=-1;}
    void push(int a);
    int  pop();
private:
    int s[MAX];
    int top;
};
void Stack::push(int a)
{
    if(top>=MAX-1) throw Full();
    s[++top]=a;
}
int Stack::pop()
{
    if(top<0)  throw Empty();
    return s[top--];
}

int main(){
    Stack s;
    try{
        s.push(1);
        s.push(2);
        s.push(3);
        s.push(4);     //将产生栈满异常
        cout<<"s[0]="<<s.pop()<<endl;
        cout<<"s[1]="<<s.pop()<<endl;
        cout<<"s[2]="<<s.pop()<<endl;
        //cout<<"s[3]="<<s.pop()<<endl;//将产生栈空异常
    }
    catch(Full)
    {    cout<<"Exception: Stack Full!"<<endl;    }
    catch(Empty)
    {    cout<<"Exception: Stack Empty!"<<endl;    }
    //system("pause") ;
    return 0;
}

声明了一个同普通类一样的有数据成员和成员函数的较复杂的异常类。

#include <iostream>
using namespace std;
const int MAX=3;
class Full
{public:
    Full(int i):a(i){}
    int getValue(){return a;}
private:
    int a;
};
class Empty{};
class Stack
{public:
    Stack(){top=-1;}
    void push(int a)
    {
        if(top>=MAX-1)  throw Full(a);
        s[++top]=a;
    }
    int pop()
    {
        if(top<0)  throw Empty();
        return s[top--];
    }
private:
    int s[MAX];
    int top;
};
int main()
{
    Stack s;
    try{
        s.push(1);
        s.push(2);
        s.push(3);
        s.push(4);
    }
    catch(Full e)
    {
        cout<<"Exception:Stack Full..."<<endl;
        cout <<e.getValue()<<" is not be pushed in stack. "<<endl;
    }
    //system("pause") ;
    return 0;
}

2.3 异常对象

由异常类建立的对象称为异常对象。异常类的处理过程实际上就是异常对象的生成与传递过程。在编写程序时,如果发生异常,可以抛掷一个异常类对象,在catch语句中,可以输出这个异常类对象的相关信息。参见上例。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值