C、C++、Java語言中異常處理機制淺析

openkk 12年前發布 | 25K 次閱讀 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!\n");

}

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\n",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\n",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!\n");

       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\n",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\n",res);

      }

   }

   else printf("Divide 0 error!\n");

   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錯誤\n";

      }

};

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.     trycatch的說明

程序員應該把可能會出現異常的代碼段放入try { },try { }語句塊中出現異常時,編譯器將找相應的catch(Exception& e )來捕獲異常。注意不管是用throw Exception()主動拋出異常還是在try{ }語句塊中出現異常,此時異常類型必須與相應的catch(Exception& e)中異常類型一致,或者定義catch(…) { }語句塊,這表明編譯器在本函數中找不到異常處理,則到catch(…) { }中按照相應的代碼去處理。如果這些都沒有,編譯器會返回上一級調用函數尋找匹配的catch,這樣一級一級往上找,都找不到,則系統調用terminateterminate調用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 "異常出現了\n";

   }

};

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 "輸入錯誤!\n";

      }

};

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.     throwthrows的使用

這里的throwC++中的throw是一樣的,用于拋出異常,但Javathrow用在方法體內部,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類都屬于非檢測異常。

 本文由用戶 openkk 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!