程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> c++異常處理機制示例及講解

c++異常處理機制示例及講解

編輯:關於C語言

      這兩天我寫了一個測試c++異常處理機制的例子,感覺有很好的示范作用,在此貼出來,給c++異常處理的初學者入門。本文後附有c++異常的知識普及,有興趣者也可以看看。

     下面的代碼直接貼到你的console工程中,可以運行調試看看效果,並分析c++的異常機制。
  

  1. #include "stdafx.h"  
  2. #include<stdlib.h>  
  3. #include<crtdbg.h>  
  4. #include <iostream>  
  5. // 內存洩露檢測機制  
  6. #define _CRTDBG_MAP_ALLOC   
  7. #ifdef _DEBUG  
  8. #define new new(_NORMAL_BLOCK, __FILE__, __LINE__)  
  9. #endif  
  10.  
  11. // 自定義異常類  
  12. class MyExcepction  
  13. {  
  14. public:  
  15.  
  16.         // 構造函數,參數為錯誤代碼  
  17.         MyExcepction(int errorId)  
  18.         {  
  19.          // 輸出構造函數被調用信息  
  20.             std::cout << "MyExcepction is called" << std::endl;  
  21.             m_errorId = errorId;  
  22.         }  
  23.  
  24.         // 拷貝構造函數  
  25.         MyExcepction( MyExcepction& myExp)  
  26.         {  
  27.          // 輸出拷貝構造函數被調用信息  
  28.             std::cout << "copy construct is called" << std::endl;  
  29.             this->m_errorId = myExp.m_errorId;  
  30.         }  
  31.  
  32.        ~MyExcepction()  
  33.         {  
  34.             // 輸出析構函數被調用信息  
  35.             std::cout << "~MyExcepction is called" << std::endl;  
  36.         }  
  37.  
  38.        // 獲取錯誤碼  
  39.         int getErrorId()  
  40.         {  
  41.             return m_errorId;  
  42.         }  
  43.  
  44. private:      
  45.         // 錯誤碼  
  46.         int m_errorId;  
  47. };  
  48.  
  49. int main(int argc, char* argv[])  
  50. {  
  51.         // 內存洩露檢測機制  
  52.         _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );  
  53.  
  54.         // 可以改變錯誤碼,以便拋出不同的異常進行測試  
  55.         int throwErrorCode = 110;  
  56.  
  57.        std::cout << " input test code :" << std::endl;  
  58.        std::cin >> throwErrorCode;  
  59.  
  60.        try 
  61.        {  
  62.             if ( throwErrorCode == 110)  
  63.             {  
  64.              MyExcepction myStru(110);  
  65.  
  66.                 // 拋出對象的地址 -> 由catch( MyExcepction*    pMyExcepction) 捕獲  
  67.                 // 這裡該對象的地址拋出給catch語句,不會調用對象的拷貝構造函數  
  68.                 // 傳地址是提倡的做法,不會頻繁地調用該對象的構造函數或拷貝構造函數  
  69.                 // catch語句執行結束後,myStru會被析構掉  
  70.                 throw    &myStru;      
  71.             }  
  72.             else if ( throwErrorCode == 119 )  
  73.             {  
  74.              MyExcepction myStru(119);  
  75.  
  76.                 // 拋出對象,這裡會通過拷貝構造函數創建一個臨時的對象傳出給catch  
  77.                 // 由catch( MyExcepction    myExcepction) 捕獲  
  78.                 // 在catch語句中會再次調用通過拷貝構造函數創建臨時對象復制這裡傳過去的對象  
  79.                 // throw結束後myStru會被析構掉  
  80.                 throw    myStru;      
  81.              }  
  82.              else if ( throwErrorCode == 120 )  
  83.              {  
  84.                   // 不提倡這樣的拋出方法  
  85.                   // 這樣做的話,如果catch( MyExcepction*    pMyExcepction)中不執行delete操作則會發生內存洩露  
  86.  
  87.                   // 由catch( MyExcepction*    pMyExcepction) 捕獲  
  88.                   MyExcepction * pMyStru = new MyExcepction(120);   
  89.                   throw pMyStru;      
  90.              }  
  91.              else 
  92.              {  
  93.                   // 直接創建新對象拋出  
  94.                   // 相當於創建了臨時的對象傳遞給了catch語句  
  95.                   // 由catch接收時通過拷貝構造函數再次創建臨時對象接收傳遞過去的對象  
  96.                   // throw結束後兩次創建的臨時對象會被析構掉  
  97.                    throw MyExcepction(throwErrorCode);      
  98.              }      
  99.         }  
  100.         catch( MyExcepction*    pMyExcepction)  
  101.         {  
  102.              // 輸出本語句被執行信息  
  103.                std::cout << "執行了 catch( MyExcepction*    pMyExcepction) " << std::endl;  
  104.  
  105.              // 輸出錯誤信息  
  106.                std::cout << "error Code : " << pMyExcepction->getErrorId()<< std::endl;  
  107.  
  108.             // 異常拋出的新對象並非創建在函數棧上,而是創建在專用的異常棧上,不需要進行delete  
  109.             //delete pMyExcepction;  
  110.         }  
  111.         catch ( MyExcepction myExcepction)  
  112.         {  
  113.             // 輸出本語句被執行信息  
  114.             std::cout << "執行了 catch ( MyExcepction myExcepction) " << std::endl;  
  115.  
  116.             // 輸出錯誤信息  
  117.             std::cout << "error Code : " << myExcepction.getErrorId()<< std::endl;  
  118.         }  
  119.         catch(...)  
  120.         {  
  121.              // 輸出本語句被執行信息  
  122.              std::cout << "執行了 catch(...) " << std::endl;  
  123.  
  124.              // 處理不了,重新拋出給上級  
  125.              throw ;  
  126.         }  
  127.  
  128.         // 暫停  
  129.         int temp;  
  130.         std::cin >> temp;  
  131.  
  132.        return 0;  


知識點: c++異常機制

一、 概述

C++自身有著非常強的糾錯能力,發展到如今,已經建立了比較完善的異常處理機制。C++的異常情況無非兩種,一種是語法錯誤,即程序中出現了錯誤的語句,函數,結構和類,致使編譯程序無法進行。另一種是運行時發生的錯誤,一般與算法有關。

關於語法錯誤,不必多說,寫代碼時心細一點就可以解決。C++編譯器的報錯機制可以讓我們輕松地解決這些錯誤。

第二種是運行時的錯誤,常見的有文件打開失敗、數組下標溢出、系統內存不足等等。而一旦出現這些問題,引發算法失效、程序運行時無故停止等故障也是常有的。這就要求我們在設計軟件算法時要全面。比如針對文件打開失敗的情況,保護的方法有很多種,最簡單的就是使用“return”命令,告訴上層調用者函數執行失敗;另外一種處理策略就是利用c++的異常機制,拋出異常。
   
二、c++異常處理機制

    C++異常處理機制是一個用來有效地處理運行錯誤的非常強大且靈活的工具,它提供了更多的彈性、安全性和穩固性,克服了傳統方法所帶來的問題.
   
    異常的拋出和處理主要使用了以下三個關鍵字: try、 throw 、 catch 。
  
    拋出異常即檢測是否產生異常,在C++中,其采用throw語句來實現,如果檢測到產生異常,則拋出異常。該語句的格式為:
    throw 表達式;
    如果在try語句塊的程序段中包括在其中調用的函數)發現了異常,且拋棄了該異常,則這個異常就可以被try語句塊後的某個catch語句所捕獲並處理,捕獲和處理的條件是被拋棄的異常的類型與catch語句的異常類型相匹配。由於C++使用數據類型來區分不同的異常,因此在判斷異常時,throw語句中的表達式的值就沒有實際意義,而表達式的類型就特別重要。
 
try-catch語句形式如下 :

  1. try 
  2. {  
  3.         包含可能拋出異常的語句;  
  4. }  
  5. catch(類型名 [形參名]) // 捕獲特定類型的異常  
  6. {  
  7.  
  8. }  
  9. catch(類型名 [形參名]) // 捕獲特定類型的異常  
  10. {  
  11.  
  12. }  
  13. catch(...)    // 三個點則表示捕獲所有類型的異常  
  14. {  


范例1】處理除數為0的異常。該范例將上述除數為0的異常可以用try/catch語句來捕獲異常,並使用throw語句來拋出異常,從而實現異常處理,實現代碼如代碼清單1-1所示。
// 代碼清單1-1

  1. #include<iostream.h>     //包含頭文件  
  2. #include<stdlib.h>  
  3.  
  4. double fuc(double x, double y) //定義函數  
  5. {  
  6.     if(y==0)  
  7.     {  
  8.         throw y;     //除數為0,拋出異常  
  9.     }  
  10.     return x/y;     //否則返回兩個數的商  
  11. }  
  12.  
  13. void main()  
  14. {  
  15.     double res;  
  16.     try  //定義異常  
  17.     {  
  18.         res=fuc(2,3);  
  19.         cout<<"The result of x/y is : "<<res<<endl;  
  20.         res=fuc(4,0); 出現異常,函數內部會拋出異常  
  21.     }  
  22.     catch(double)             //捕獲並處理異常  
  23.     {  
  24.          cerr<<"error of dividing zero.\n";  
  25.          exit(1);                //異常退出程序  
  26.     }  

范例2】自定義異常類型 在本文開始的代碼中已經給出示范)

三、異常的接口聲明

為了加強程序的可讀性,使函數的用戶能夠方便地知道所使用的函數會拋出哪些異常,可以在函數的聲明中列出這個函數可能拋出的所有異常類型,例如:

void fun() throw( A,B,C,D); 這表明函數fun()可能並且只可能拋出類型(A,B,C,D)及其子類型的異常。

如果在函數的聲明中沒有包括異常的接口聲明,則此函數可以拋出任何類型的異常,例如: void fun();
 
一個不會拋出任何類型異常的函數可以進行如下形式的聲明:
  void fun() thow();

     
五、異常處理中需要注意的問題

1. 如果拋出的異常一直沒有函數捕獲(catch),則會一直上傳到c++運行系統那裡,導致整個程序的終止

2. 一般在異常拋出後資源可以正常被釋放,但注意如果在類的構造函數中拋出異常,系統是不會調用它的析構函數的,處理方法是:如果在構造函數中要拋出異常,則在拋出前要記得刪除申請的資源。

3. 異常處理僅僅通過類型而不是通過值來匹配的,所以catch塊的參數可以沒有參數名稱,只需要參數類型。

4. 函數原型中的異常說明要與實現中的異常說明一致,否則容易引起異常沖突。
 
5. 應該在throw語句後寫上異常對象時,throw先通過Copy構造函數構造一個新對象,再把該新對象傳遞給 catch.
       那麼當異常拋出後新對象如何釋放?
       異常處理機制保證:異常拋出的新對象並非創建在函數棧上,而是創建在專用的異常棧上,因此它才可以跨接多個函數而傳遞到上層,否則在棧清空的過程中就會被銷毀。所有從try到throw語句之間構造起來的對象的析構函數將被自動調用。但如果一直上溯到main函數後還沒有找到匹配的catch塊,那麼系統調用terminate()終止整個程序,這種情況下不能保證所有局部對象會被正確地銷毀。
  
6. catch塊的參數推薦采用地址傳遞而不是值傳遞,不僅可以提高效率,還可以利用對象的多態性。另外,派生類的異常撲獲要放到父類異常撲獲的前面,否則,派生類的異常無法被撲獲。
  
7. 編寫異常說明時,要確保派生類成員函數的異常說明和基類成員函數的異常說明一致,即派生類改寫的虛函數的異常說明至少要和對應的基類虛函數的異常說明相同,甚至更加嚴格,更特殊。

本文出自 “對影成三人” 博客,請務必保留此出處http://ticktick.blog.51cto.com/823160/191881

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved