c++中的異常處理

来源:https://www.cnblogs.com/nbk-zyc/archive/2020/03/21/12449331.html
-Advertisement-
Play Games

異常的基本處理方式、異常的重解釋(在 catch 語句塊中將捕獲到的異常再次拋出)、自定義/標準庫異常類的使用(註意:賦值相容性原則)、異常的另類寫法(try...catch...)、異常函數聲明的方式(通過 throw 關鍵字指定可拋出的具體異常類型) ...


 目錄

  1、 異常 與 Bug 的區別

  2、c++中的異常處理方式(try ... catch ...)

  3、自定義異常類的使用方式

  4、C++ 標準庫中的異常類

  5、try..catch 另類寫法 和  函數異常聲明/定義 throw()

 

1、 異常 與 Bug 的區別  

  “異常”是我們在程式開發中必須考慮的一些特殊情況,是程式運行時就可預料的執行分支(註:異常是不可避免的,如程式運行時產生除 0 的情況;打開的外部文件不存在;數組訪問的越界等等);

  “Bug”是程式的缺陷,是程式運行時不被預期的運行方式(註:Bug是人為的、可避免的;如使用野指針;堆數組使用結束後未釋放等等);

  無論是“異常”還是“Bug”,都是程式正常運行過程中可能出現的意外情況。區別是“異常”可以捕獲,並做出合適的處理,“Bug"所帶來的後果是無法預測的,需要重寫相應的代碼。

2、c++中的異常處理方式(try ... catch ...)

 (1)基本語法

1 // 異常基本語法
2 try
3 {
4     // 可能產生異常的代碼,若發生異常,通過 throw 關鍵字拋出異常
5 }
6 catch(異常類型) // 異常捕獲,註(...)代表捕獲所有異常
7 {
8     // 處理異常的代碼,該異常由try語句塊產生
9 }

 (2)基本規則 

  1)同一個 try 語句可以跟上多個 catch 語句(在工程中,將可能產生異常的代碼放在 try 語句塊中,然後後面跟上多個 catch 語句);

  2)try 語句中通過 throw 關鍵字 可以拋出任何類型的異常(int、字元串、對象等等); 

  3)catch 語句可以定義具體處理的異常類型,如 catch( int ) 只捕獲 int 類型異常, 但無法進一步獲取異常信息;catch( int a ) 只捕獲 int 類型異常,可以進一步獲取異常信息;

  4)不同類型的異常由不同的 catch 語句負責處理;

  5)catch(...) 用於處理所有類型的異常(只能放在所有 catch語句 的後面);

  6)任何異常都只能被捕獲(catch)一次;

  7)只要被 catch 捕獲一次,其它的 catch 就沒有捕獲機會了;

  8)throw 拋出異常的類型 與 catch 捕獲異常的類型  必須嚴格匹配(不能進行類型轉換);若匹配成功,則能夠捕獲該異常;否則捕獲失敗,程式停止執行。

    

  其實,為了更好的理解 異常拋出 和 異常捕獲 這兩個動作,我們可以將其想象成 函數調用,拋出異常好比是函數中實參,捕獲異常好比是函數中形參,只有當實參的類型 與 形參的類型嚴格匹配時,這次捕獲才能成功。為什麼說想象成函數調用,而不是正真的函數調用呢?原因就是:函數調用時,用實參初始化形參時可以進行隱式的類型轉換;但是在異常捕獲時,必須嚴格匹配異常類型。  

(3)異常拋出(throw exception)的邏輯分析

  情況1:異常代碼沒有放到  try{ }  語句中,這也意味著沒有對應的 catch 語句,其實就是普通函數調用,若此時拋出異常(throw),則程式停止執行;

  情況2:異常代碼放到  try{ throw exception... }  語句中,這也意味著有對應的 catch 語句,則拋出異常時就會與 catch語句嚴格匹配;若匹配成功,則可以捕獲該異常,否則不能捕獲該異常,程式停止執行。

  throw 拋出異常後,在發生異常的函數中至上而下的嚴格匹配每一個 catch 語句捕獲的異常類型,來判斷是否能夠捕獲該異常;

   1) 若發生異常的函數中  能夠捕獲該異常,則程式接著往下執行;

   2) 若發生異常的函數中  不能捕獲該異常,則未被處理的異常會順著函數調用棧向上傳播,直到該異常被捕獲為止,否則程式將停止執行;

  總結:throw 拋出的異常必須被 對應的 catch 捕獲,否則程式將停止執行;

    

  通過上圖來說明異常拋出後的執行順序:

  1)在 函數 function3 中 拋出異常 throw 1;但是在 function3 中並不能捕獲該異常,則異常繼續向外層函數 function2 拋出

  2)在 函數 function2 中,異常依舊沒有被捕獲,則異常繼續向外層函數 function1 拋出;

  3)在 函數 function1 中,異常被 catch 捕獲和處理,然後程式繼續向下執行;

    註:若在 函數 function1 中,異常還是沒有被捕獲,則異常會一直向外層函數拋出,直到該異常被捕獲為止,否在程式停止執行。 

  代碼展示:

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 double divide(double a, double b)
 7 {
 8     const double delta = 0.000000001;
 9     double ret = 0;
10 
11     if( !((-delta < b) && (b < delta)) )
12     {
13         ret = a / b;
14     }
15     else
16     {
17         throw "Divided by zero..."; 
18     }
19 
20     cout << "divide(double a, double b)" << endl;
21      
22     return ret;
23 }
24 
25 double ExceptionFunc()
26 {
27     double d = divide(2, 0);
28     
29     cout << "ExceptionFunc()" << endl;
30     
31     return d;
32 }
33 
34 int main(int argc, char *argv[])
35 {      
36     double d = ExceptionFunc();
37     
38     cout << "result = " << d << endl;
39 
40     return 0;
41 }
42 
43 /**
44  * 運行結果:
45  * terminate called after throwing an instance of 'char const*'
46  * Aborted (core dumped)
47  */
48  
49 /**
50  * 分析:
51  * throw "Divided by zero..."; 拋出異常後,divide(double a, double b) 函數不能捕獲該異常,則異常繼續拋給 ExceptionFunc();
52  * 在 ExceptionFunc() 中,也不能捕獲該異常,則異常繼續拋給 main();
53  * 在 main()中,也不能捕獲該異常,則程式停止執行。
54  */
情況1 :異常案列復現

  

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 double divide(double a, double b)
 7 {
 8     const double delta = 0.000000001;
 9     double ret = 0;
10     
11     try
12     {
13         if( !((-delta < b) && (b < delta)) )
14         {
15             ret = a / b;
16         }
17         else
18         {
19             throw "Divided by zero..."; 
20         }
21     }
22     catch(char const* s)  
23     {
24         cout << s << endl;
25     } 
26         
27     cout << "divide(double a, double b)" << endl;
28  
29     return ret;
30 }
31 
32 double ExceptionFunc()
33 {   
34     double d = divide(2, 0);
35     
36     cout << "ExceptionFunc()" << endl;
37     
38     return d;
39 }
40 
41 int main(int argc, char *argv[])
42 { 
43     ExceptionFunc();
44       
45     cout << "test end!" << endl;
46 
47     return 0;
48 }
49 
50 /**
51  * 運行結果:
52  * Divided by zero...
53  * divide(double a, double b)
54  * ExceptionFunc()
55  * test end!
56  */
57  
58 /**
59  * 分析:
60  * throw "Divided by zero..."; 拋出異常後,在divide(double a, double b) 中,異常被捕獲,則程式繼續向下執行;
61  *  catch(char const* s)    // throw "Divided by zero..."
62  *  {
63  *     cout << s << endl;
64  *  }   
65  *  cout << "divide(double a, double b)" << endl;
66  *
67  *  divide(2, 0); 函數調用結束,返回到 ExceptionFunc() 中,
68  *  cout << "ExceptionFunc()" << endl;  
69  *   
70  *  ExceptionFunc()調用結束,返回 main()中,繼續向下執行;
71  *  cout << "test end!" << endl;
72  */
情況2 :異常案列復現1

 

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 double divide(double a, double b)
 7 {
 8     const double delta = 0.000000001;
 9     double ret = 0;
10           
11     if( !((-delta < b) && (b < delta)) )
12     {
13         ret = a / b;
14     }
15     else
16     {
17         throw "Divided by zero..."; 
18     }
19 
20     cout << "divide(double a, double b)" << endl;
21 
22     return ret;
23 }
24 
25 double ExceptionFunc()
26 {
27     double d;
28     
29     try
30     {
31         d = divide(2, 0);
32             
33         cout << "d = " << d << endl;
34     }
35     catch(char const* s)
36     {
37         cout << s << endl;
38     }           
39     
40     cout << "ExceptionFunc()" << endl;
41     
42     return d;
43 }
44 
45 int main(int argc, char *argv[])
46 { 
47     ExceptionFunc();
48     
49     cout << "test end!" << endl;
50 
51     return 0;
52 }
53 
54 /**
55  * 運行結果:
56  * Divided by zero...
57  * ExceptionFunc()
58  * test end!
59  */
60  
61 /**
62  * 分析:
63  * throw "Divided by zero..."; 拋出異常後,divide(double a, double b) 函數不能捕獲該異常,則異常繼續拋給 ExceptionFunc();
64  * 在 ExceptionFunc() 中,異常被捕獲,則程式繼續向下執行;
65  *  catch(char const* s)    // throw "Divided by zero..."
66  *  {
67  *     cout << s << endl;
68  *  }   
69  *  cout << "ExceptionFunc()" << endl;     
70  *  ExceptionFunc()調用結束,返回 main()中,繼續向下執行;
71  *  cout << "test end!" << endl;
72  */
情況2 :異常案列復現2

 

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 double divide(double a, double b)
 7 {
 8     const double delta = 0.000000001;
 9     double ret = 0;
10 
11     if( !((-delta < b) && (b < delta)) )
12     {
13         ret = a / b;
14     }
15     else
16     {
17         throw "Divided by zero..."; 
18     }
19  
20     cout << "divide(double a, double b)" << endl;
21 
22     return ret;
23 }
24 
25 double ExceptionFunc()
26 {
27     double d = divide(2, 0);
28     
29     cout << "ExceptionFunc()" << endl;
30     
31     return d;
32 }
33 
34 int main(int argc, char *argv[])
35 { 
36     try
37     {
38         double d = ExceptionFunc();
39             
40         cout << "d = " << d << endl;
41     }
42     catch(char const* s)
43     {
44         cout << s << endl;
45     }        
46     
47     cout << "test end!" << endl;
48 
49     return 0;
50 }
51 
52 /**
53  * 運行結果:
54  * Divided by zero...
55  * test end!
56  */
57  
58 /**
59  * 分析:
60  * throw "Divided by zero..."; 拋出異常後,divide(double a, double b) 函數不能捕獲該異常,則異常繼續拋給 ExceptionFunc();
61  * 在 ExceptionFunc() 中,也不能捕獲該異常,則異常繼續拋給 main();
62  * 在 main()中,異常與被捕獲,則程式繼續向下執行
63  *  catch(char const* s)    // throw "Divided by zero..."
64  *  {
65  *     cout << s << endl;
66  *  }   
67  *  cout << "test end!" << endl;     
68  */
情況2 :異常案列復現3

 

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 void Demo1()
 7 {
 8     try
 9     {   
10         throw 2.0;
11     }
12     catch(char c)
13     {
14         cout << "catch(char c)" << endl;
15     }
16     catch(short c)
17     {
18         cout << "catch(short c)" << endl;
19     }
20     catch(double c)   // throw 2.0;
21     {
22         cout << "catch(double c)" << endl;
23     }
24     catch(...)   // 表示捕獲任意類型的異常
25     {
26         cout << "catch(...)" << endl;
27     }   
28 }
29 
30 void Demo2()
31 {
32     throw string("D.T.Software"); 
33 }
34 
35 int main(int argc, char *argv[])
36 {    
37     Demo1();
38     
39     try
40     {
41         Demo2();      
42     }
43     catch(char* s)
44     {
45         cout << "catch(char *s)" << endl;
46     }
47     catch(const char* cs)
48     {
49         cout << "catch(const char *cs)" << endl;
50     }
51     catch(string ss)    // throw string("D.T.Software"); 
52     {
53         cout << "catch(string ss)" << endl;
54     }  
55     
56     return 0;
57 }
58 /**
59  * 運行結果:
60  * catch(double c)
61  * catch(string ss)
62  */
63 
64 // 結論:異常類型嚴格匹配,(...)表示捕獲任意類型的異常
情況2 :多分支的異常捕獲

   總結:

  情況1:只拋出異常,沒有對應地異常捕獲;(  沒有 try ... catch ... 結構  )

  情況2:在try語句塊中拋出異常(直接在try語句塊中使用 throw 拋出異常;或者try語句塊中放入有異常的函數,間接地在函數中使用 throw 拋出異常),然後通過catch語句捕獲同類型的異常併進行異常處理;( 現象: 一個 try ... catch ... 結構 )

  那麼現在我們考慮能不能在情況2的基礎上,將捕獲到異常繼續拋出呢?(在 catch 語句塊中重新拋出異常?)

  可以在 catch 語句中重新拋出異常,此時需要外層的 try ... catch ...捕獲這個異常;

  註:catch 語句中只拋出異常,什麼也不做;當捕獲任意類型的異常時,直接使用 throw 就行。

  

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 void Demo()
 7 {
 8     try
 9     {
10         throw 'c';
11     }
12     catch(int i)
13     {
14         cout << "Inner: catch(int i)" << endl;
15         throw i;
16     }
17     catch(...)
18     {
19         cout << "Inner: catch(...)" << endl;
20         throw;
21     }
22 }
23 
24 int main(int argc, char *argv[])
25 {
26     Demo();
27     
28     return 0;
29 }
30 
31 /**
32  * 運行結果:
33  * Inner: catch(...)
34  * terminate called after throwing an instance of 'char'
35  * Aborted (core dumped)
36  */
37  // 錯誤原因:異常重解釋之後,沒有被捕獲
異常的重新解釋案列(error)

 

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 void Demo()
 7 {
 8     try
 9     {
10         try
11         {
12             throw 'c';
13         }
14         catch(int i)
15         {
16             cout << "Inner: catch(int i)" << endl;
17             throw i;
18         }
19         catch(...)
20         {
21             cout << "Inner: catch(...)" << endl;
22             throw;
23         }
24     }
25     catch(...)
26     {
27         cout << "Outer: catch(...)" << endl;
28     }
29 }
30 
31 int main(int argc, char *argv[])
32 {
33     Demo();
34     
35     return 0;
36 }
37 /**
38  * 運行結果:
39  * Inner: catch(...)
40  * Outer: catch(...)
41  */
異常的重新解釋案列

   為什麼要在 catch 語句塊中重新拋出異常?

  在工程中,利用在 catch 語句塊中重新解釋異常並拋出這一特性,可以統一異常類型。(很晦澀,看下麵解釋....)

  

  我們通過上圖來詳細說明這個情況:

  背景介紹:出於開發效率考慮,在工程開發中一般會基於第三方庫來開發,但是,第三方庫中的某些功能並不完善(可讀性差),此時就需要封裝第三方庫中的這個功能。

  在上圖中,由於第三方庫中 func()函數的異常類型是 int 類型,可讀性很差,不能夠直接通過異常結果知道該異常所代表的意思;基於這種情況,我們通過私有庫中的 MyFunc()函數對第三方庫中func()進行了封裝,使得 MyFunc()函數中的異常類型可以顯示更多的異常信息(MyFunc() 函數中的異常類型是自定義類型,可以是字元串、類類型等等),增強代碼的可讀性;為了將 func()中的異常類型 和 MyFunc() 中異常類型統一起來,我們可以在私有庫中的MyFunc() 函數中去捕獲第三方庫中的 func() 函數拋出的異常,然後根據捕獲到的異常重新解釋為我們想要的異常,這樣我們工程開發中所面對的異常類型就是一致的;接下來我們用代碼復現這個場景:

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 /*
 7     假設:當前的函數是第三方庫中的函數,因此,我們無法修改源代碼
 8     
 9     函數名: void func(int i)
10     拋出異常的類型: int
11                         -1 ==》 參數異常
12                         -2 ==》 運行異常
13                         -3 ==》 超時異常
14 */
15 void func(int i)
16 {
17     if( i < 0 )
18     {
19         throw -1;
20     }
21     
22     if( i > 100 )
23     {
24         throw -2;
25     }
26     
27     if( i == 11 )
28     {
29         throw -3;
30     }
31     
32     cout << "Run func..." << endl;
33 }
<

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [toc] 領域驅動設計 領域對象的生命周期 每個對象都有生命周期,如圖6 1所示。對象自創建後,可能會經歷各種不同的狀態,直至最終消亡——要麼存檔,要麼刪除。當然,很多對象是簡單的臨時對象,僅通過調用構造函數來創建,用來做一些計算,而後由垃圾收集器回收。這類對象沒必要搞得那麼複雜。但有些對象具有更 ...
  • [toc] 軟體中所表示的模型 表示模型的3種模型元素模式:ENTITY、VALUE OBJECT和SERVICE。從錶面上看,定義那些用來捕獲領域概念的對象很容易,但要想反映其含義卻很困難。這要求我們明確區分各種模型元素的含義,並與一系列設計實踐結合起來,從而開發出特定類型的對象。 個人理解:就是 ...
  • [toc] 模型驅動設計的構造塊 分離領域 在軟體中,雖然專門用於解決領域問題的那部分通常只占整個軟體系統的很小一部分,但其卻出乎意料的重要。我們需要著眼於模型中的元素並且將它們視為一個系統。絕不能像在夜空中辨認星座一樣,被迫從一大堆混雜的對象中將領域對象挑選出來。我們需要將領域對象與系統中的其他功 ...
  • [toc] 領域驅動設計 運用領域模型 綁定模型和實現 聰明的項目組成員花費了幾個月的時間進行仔細的研究並且開發出了詳盡的領域模型(類圖)。然而對類圖研究不能讓我深入地瞭解該應用程式的代碼和設計,這讓我備感困擾。當開發人員開始實現應用程式時,他們很快就發現,儘管分析人員說得頭頭是道,他們依然無法將這 ...
  • [toc] 運用領域模型 交流與語言的使用 非原創,感謝《領域驅動設計》這本書 領域模型可成為軟體項目通用語言的核心。該模型是一組得自於項目人員頭腦中的概念,以及反映了領域深層含義的術語和關係。這些術語和相互關係提供了模型語言的語義,雖然語言是為領域量身定製的,但就技術開發而言,其依然足夠精確。正是 ...
  • [toc] 運用領域模型 消化知識 非原創,感謝《領域驅動設計》這本書 有效建模的要素 (1) 模型和實現的綁定。最初的原型雖然簡陋,但它在模型與實現之間建立了早期鏈接,而且在所有後續的迭代中我們一直在維護該鏈接。 (2) 建立了一種基於模型的語言。隨著項目的進展,雙方都能夠直接使用模型中的術語,並 ...
  • 子類在覆蓋父類時,如果父類的方法拋出異常,那麼子類的覆蓋方法,只能拋出父類的異常或者該異常的子類,或者不拋。 如果父類方法拋出多個異常,那麼子類在覆蓋該方法時,只能拋出父類異常的子集。 如果父類或者介面的方法中沒有異常拋出,那麼子類在覆蓋方法時,也不可以拋出異常,如果子類方法發生異常,一定要進行tr ...
  • python爬蟲-UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8b in position 1: invalid start byte ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...