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!\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. 對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 "異常出現了\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. 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類都屬於非檢測異常。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM