• C、C++、Java语言中异常处理机制浅析


    一、 异常处理 (ExceptionalHandling)概述

    1. 异常处理

    异常处理又称异常错误处理,它提供了处理程序运行时出现任何意外或异常情况的方法。异常处理通常是防止未知错误的发生所采取的处理措施,对于某一类型的错误,异常处理应该提供相应的处理方法。例如,在设计程序时,如果可能会碰到除0错误或者数组访问越界错误,程序员应该在程序中设计相应的异常处理代码以便发生异常情况时,程序做出相应的处理。

    2. 异常处理的两类模型

    (1)终止模型

    在这种模型中,异常是致命的,它一旦发生,将导致程序终止。这种模型被C++和Java语言所支持。

    (2)恢复模型

    当发生异常时,由异常处理方法进行处理,处理完毕后程序返回继续执行。

    二、 C语言异常处理

    1. 常用方法

    (1)使用abort()和exit()两个函数,他们声明在<stdlib.h>中;

    (2)使用assert宏调用,它位于<assert.h>中。assert(expression)当expression为0时,就好引发abort();

    (3)使用全局变量errno,它由C语言库函数提供,位于<errno.h>中;

    (4)使用goto语用局部跳转到异常处理代码处;

    (5)使用setjmp和longjmp实现全局跳转,它们声明<setjmp.h>中,一般由setjmp保存jmp_buf上下文结构体,然后由longjmp跳回到此时。

    2. 实例演示

    实例一 :使用exit()终止程序运行

     
    #include<stdio.h>
    #include<stdlib.h>
     
    voidDivideError(void)
    {
       printf("divide 0 error!
    ");
    }
    doubledivide(double x,double y)
    {
       if(y==0) exit(EXIT_FAILURE);//此时EXIT_FAILURE=1
    //也可以使用atexit()函数来注册异常处理函数,但此时异常处理函//数必须形如voidfun(void);
       else return x/y;
    }
    intmain()
    {
       double x,y,res;
      printf("x=");
      scanf("%lf",&x);
      printf("y=");
      scanf("%lf",&y);
      atexit(DivideError);
       res=divide(x,y);
       printf("result=%lf
    ",res);
       return 0;
    } 

    实例二:使用assert(expression)

     
    #include<stdio.h>
    #include<assert.h>
     
    intmain()
    {
       int a,b,res;
     res=scanf("%d,%d",&a,&b);
     //scnaf函数返回从stdin流中成功读入的数据个数
      assert(res==2); //如果res!=2,则出现异常
       return 0;
    } 

    实例三:使用全局变量errno来获取异常情况的编号

     
    #include<stdio.h>
    #include<errno.h>
     
    intmain()
    {
       char filename[80];
       errno=0;
       scanf("%s",filename);
       FILE* fp=fopen(filename,"r");
       printf("%d
    ",errno); //如果此时文件打不开,那么errno=2
       return 0;
    }  

    实例四:使用goto实现局部跳转

     
    #include<stdio.h>
    #include<stdlib.h>
     
    intmain()
    {
       double x,y,res;
       int tag=0;
       if(tag==1)
       {
           Error:
           printf("divide0 error!
    ");
           exit(1);
       }
      printf("x=");
      scanf("%lf",&x);
      printf("y=");
      scanf("%lf",&y);
       if(y==0)
       {
          tag=1; 
          goto Error;
       }
       else 
       {
         res=divide(x,y);
         printf("result =%lf
    ",res);
       }
       return 0;
    } 

    实例五:使用setjmp和longjmp实现全局跳转

     
    #include<stdio.h>
    #include<setjmp.h>
     
    jmp_buf mark; //保存跳转点上下文环境的结构体
    void DivideError()
    {
       longjmp(mark,1);
    }
    intmain()
    {
       double a,b,res;
       printf("a=");
       scanf("%lf",&a);
       printf("b=");
       scanf("%lf",&b);
       if(setjmp(mark)==0)
       {
          if(b==0) DivideError();
          else
          {
            res=a/b;
            printf("the result is%lf
    ",res);
          }
       }
       else printf("Divide 0 error!
    ");
       return 0;
    } 

    三、 C++异常处理

    1. C++异常类的编写

     
    #include<iostream>
    #include<exception>
    using namespacestd;
    class DivideError:public exception //E从exception类派生而来
    {
    public:
          const char* what() //必须实现虚函数,它在exception类中定义,
    //函数原型是 virtual const char* what() const throw()
          {
               return "除数为0错误
    ";
          }
    };
    double divide(doublex,double y) 
    {
       if(y==0) throw DivideError(); //抛出异常
       else return x/y;
    }
    void main()
    {
       double x,y;
       double res;
       try
          {
           cin>>x>>y;
           res=divide(x,y);
           cout<<res<<endl;
          }
          catch(DivideError& e)
          {
               cerr<<e.what();
          }
    }  

    2. 对try与catch的说明

    程序员应该把可能会出现异常的代码段放入try { }中,当try { }语句块中出现异常时,编译器将找相应的catch(Exception& e )来捕获异常。注意不管是用throw Exception()主动抛出异常还是在try{ }语句块中出现异常,此时异常类型必须与相应的catch(Exception& e)中异常类型一致,或者定义catch(…) { }语句块,这表明编译器在本函数中找不到异常处理,则到catch(…) { }中按照相应的代码去处理。如果这些都没有,编译器会返回上一级调用函数寻找匹配的catch,这样一级一级往上找,都找不到,则系统调用terminate,terminate调用abort()终止整个程序。

    实例:

     
    void func1()
    {
       throw 1;
    }
    void func2()
    {
       throw “helloworld”;
    }
    void func3()
    {
        throwException();
    }
    void main()
    {
       try
       {
          func1();
          func2();
          func3();
    }
    catch(int e) //捕获func1()中异常
    {
       //To do Something
    } 
    catch(const char* str) //捕获func2()中异常
    {
       //To do Something
    } 
    catch(Exception& e) //捕获func3()中异常
    {
       //To do Something
    } 
    catch(…) //都不匹配则执行此处代码
    {
      // To do Something 
    }
    } 

    3. 对throw的理解

    (1) 当我们在自己定义的函数中抛出(throw)一个异常对象时,如果此异常对象在本函数定义,那么编译器会拷贝此对象到某个特定的区域。因为当此函数返回时,原本在该函数定义的对象空间将被释放,对象也就不存在了。编译器拷贝了对象,在其他函数使用catch语句时可以访问到该对象副本。如:

     
    void func()
    {
       Exception e;
       throw e; //当func()返回时,e就不存在了
    } 

    (2) 尽量避免throw对象的指针,如下例:

     
    #include <iostream>
    #include <exception>
    using namespace std;
    class Exception: public exception
    {
    public:
       constchar* what()
       {
              return "异常出现了
    ";
       }
    };
    void func() 
    {
    thrownew E(); //抛出一个对象指针
    }
    void main()
    {
    try
       {
                func();
       }
       catch(E *p)
       {
             cerr<<p->what();
              int x,y;
             x=1;
             y=0;
             x=x/y; //出现新的异常
             deletep; //delete p得不到执行,此时申请对象的空间不会被释放,
       }
    } 

    解决方案之一:

    在程序中定义一个异常处理函数,如void handler(void);

    并且在main函数中加入代码:

     
    catch(…)
    {
        handler();
    } 

    所以我们在抛出异常时,推荐使用throw Exception(参数),相应的catch(constException& e),这样在抛出异常时,编译器会对没有看到具体名字的临时变量做出一些优化措施,同时在catch中也避免了无谓的对象拷贝。

    (3)不要在析构函数中throw异常,如下例:

     
    #include <iostream>
    #include <exception>
    #include <string>
    using namespace std;
    class E
    {
    public:
        E( ) {     }
       ~E ()
       {
             throw string("123");
       }
    };
    void main()
    {
          try
          {
                Ee;
                 throwstring("abc"); //此时抛出的异常会被下面的catch捕获
          }
          catch(string& s)
          {
                cout<<s<<endl; 
          }
     
    } //对象e的生命周期结束,系统调用其析构函数释放空间,但却throw了异常,没有catch捕获,造成程序崩溃。 

    解决方案一:

    增加一个异常处理函数

     
    void handler()
    {
       //To do Something
        abort( );
    }  

    在main函数开始处加入代码:set_terminate(handler),这样在main函数结束前,系统调用handler处理异常。

    解决方案二:

    有时我们要编写建立数据库连接的程序,此时我们定义一个Database类来管理我们的数据库,在Database类的析构函数中,我们通常希望将打开的数据库连接关闭,如果数据库关闭时出现异常,那么我们就需要处理。如下例:

     
    #include <iostream>
    #include <exception>
    using namespace std;
    class Database
    {
    public:
        Database& CreateConn()
        {  
             //To do Something
              return*this;
        }
      ~Database()
      {
             if(isclosed)//数据库确实关闭
             {
                   //Todo Something
             }
             else
             {
                   try
                      {
                            close();
                      }
                      catch(...)
                      {
                            //做出处理,如写日志文件
                      }
          }
    }
    private:
          void close() //关闭连接
          {
        //To do Something
          }
       bool isclosed;
    };
    void main()
    {
               Database db;
    } 

    也就是说在析构函数中并不是抛出异常,取而代之的是处理异常。

    (4)在构造函数中抛出异常

    构造函数的主要作用是利用构造函数参数来初始化对象,如果此时给出的参数不合法,那么应该对其进行处理。我们信奉的原则是问题早发现,早解决。如下例:

     
    #include <iostream>
    #include <exception>
    #include <string>
    using namespace std;
    const int max=1000;
    class InputException: public exception
    {
    public:
          const char* what()
          {
                     return "输入错误!
    ";
          }
    };
    class Point
    {
    private:
          int x,y;
    public:
      Point(int _x,int _y)
      {
               if(_x<0|| _x>=max || _y<0 || _y>=max) throw InputException();
          else
            {
                   x=_x;
                     y=_y;
                }
    }
    };
    void main()
    {
    int x,y;
      cout<<"x=";
      cin>>x;
      cout<<"y=";
      cin>>y;
      try
      {
            Point p(x,y);
      }
      catch(InputException& e)
      {
            cerr<<e.what();
       }
    } 

    4. 异常使用的成本

    在没有异常被抛出的情况下,使用try{ }语句块,整体代码大约膨胀了5%~10%,执行的速度也大约下降这个数。和正常函数返回相比,抛出异常导致的函数返回,其速度可能比正常情况慢三个数量级,所以在程序中使用异常处理有利有弊。

    四、 Java异常处理

    1. try…catch…finally的使用

    Java的异常处理与C++类似,try…catch子句与C++中的try…catch很相似,finally{ }表示无论是否出现异常,最终必须执行的语句块。

    实例如下:

     
    importjava.io.BufferedReader;
    importjava.io.IOException;
    importjava.io.InputStreamReader;
    class Myclass
    {
         publicstaticvoid main(String[]args) 
         {
            InputStreamReaderisr=new InputStreamReader(System.in);
           BufferedReader inputReader=new BufferedReader(isr);
           String line = null;
             try
             {
                  line=inputReader.readLine();
             }
             catch(IOException e)
             {
                  e.printStackTrace();
             }
             finally
             {
                  System.out.print(line);
             }
         }
    }  

    2. throw和throws的使用

    这里的throw和C++中的throw是一样的,用于抛出异常,但Java的throw用在方法体内部,throws用在方法定义处,如下例:

     
    void func() throws IOException
    {
         thrownew IOException();
    } 

    3. Java异常类图

    java.lang.Object

    ---java.lang.Throwable

    ---java.lang.Exception

    ---java.lang.RuntimeException java.lang.Errorjava.lang.ThreadDeath

    4. 异常处理的分类

    (1)可检测异常

    此类异常属于编译器强制捕获类,一旦抛出,那么抛出异常的方法必须使用catch捕获,不然编译器就会报错。如sqlException,它是一个可检测异常,当程序员连接到JDBC,不捕捉到这个异常,编译器就会报错。

    (2)非检测异常

    当产生此类异常时,编译器也能编译通过,但要靠程序员自己去捕获。如数组越界或除0异常等。Error类和RuntimeException类都属于非检测异常。

  • 相关阅读:
    SVN的学习
    IIS 503 错误
    Windows系统CMD下常用命令
    Linux基础整理
    JavaEESSM框架配置文件
    JavaXML整理
    Java反射、反射练习整理
    Java网络通信协议、UDP、TCP类加载整理
    Java多线程、线程池和线程安全整理
    JavaProperties类、序列化流与反序列化流、打印流、commons-IO整理
  • 原文地址:https://www.cnblogs.com/Z-D-/p/7170977.html
Copyright © 2020-2023  润新知