首頁>技術>

一,什麼是異常處理

一句話:異常處理就是處理程式中的錯誤,比如嘗試除以零的操作。

二,為什麼需要異常,以及異常處理的基本思想

C++之父Bjarne Stroustrup在《The C++ Programming Language》中講到:一個庫的作者可以檢測出發生了執行時錯誤,但一般不知道怎樣去處理它們(因為和使用者具體的應用有關);另一方面,庫的使用者知道怎樣處理這些錯誤,但卻無法檢查它們何時發生(如果能檢測,就可以在使用者的程式碼裡處理了,不用留給庫去發現)。

Bjarne Stroustrup說:提供異常基本目的就是為了處理上面的問題。基本思想是:讓一個函式在發現了自己無法處理的錯誤時丟擲(throw)一個異常,然後它的(直接或者間接)呼叫者能夠處理這個問題。

也就是《C++ primer》中說的:將問題檢測問題處理相分離。

一種思想:在所有支援異常處理的程式語言中,要認識到的一個思想:在異常處理過程中,由問題檢測程式碼可以丟擲一個物件給問題處理程式碼,透過這個物件的型別和內容,實際上完成了兩個部分的通訊,通訊的內容是“出現了什麼錯誤”。當然,各種語言對異常的具體實現有著或多或少的區別,但是這個通訊的思想是不變的。

三,異常出現之前處理錯誤的方式

在C語言的世界中,對錯誤的處理總是圍繞著兩種方法:一是使用整型的返回值標識錯誤;二是使用errno宏(可以簡單地理解為一個全域性整型變數)去記錄錯誤。當然C++中仍然是可以用這兩種方法的。

這兩種方法最大的缺陷就是會出現不一致問題。例如有些函式返回1表示成功,返回0表示出錯;而有些函式返回0表示成功,返回非0表示出錯。

還有一個缺點就是函式的返回值只有一個,你透過函式的返回值表示錯誤程式碼,那麼函式就不能返回其他的值。當然,你也可以透過指標或者C++的引用來返回另外的值,但是這樣可能會令你的程式略微晦澀難懂。

四,異常為什麼好

優點有以下幾點:

1. 函式的返回值可以忽略,但異常不可忽略。如果程式出現異常,但是沒有被捕獲,程式就會終止,這多少會促使程式設計師開發出來的程式更健壯一點。而如果使用C語言的error宏或者函式返回值,呼叫者都有可能忘記檢查,從而沒有對錯誤進行處理,結果造成程式莫名其面的終止或出現錯誤的結果。

2. 整型返回值沒有任何語義資訊。而異常卻包含語義資訊,有時你從類名就能夠體現出來。

3. 整型返回值缺乏相關的上下文資訊。異常作為一個類,可以擁有自己的成員,這些成員就可以傳遞足夠的資訊。

異常處理可以在呼叫跳級。這是一個程式碼編寫時的問題:假設在有多個函式的呼叫棧中出現了某個錯誤,使用整型返回碼要求你在每一級函式中都要進行處理。而使用異常處理的棧展開機制,只需要在一處進行處理就可以了,不需要每級函式都處理。

五, C++中使用異常時應注意的問題

任何事情都是兩面性的,異常有好處就有壞處。如果在你的程式碼中使用異常,那麼需要注意以下事項:

1. 效能問題。這個一般不會成為瓶頸,但是如果你編寫的是高效能或者實時性要求比較強的軟體,就需要考慮了。

2. 指標和動態分配導致的記憶體回收問題:動態記憶體不會自動回收,如果遇到異常就需要考慮是否正確地回收了記憶體。

函式的異常丟擲列表:如果沒有寫noexcept,意味著你可以丟擲任何異常

六,異常基本語法

很簡單,丟擲一場用throw,捕獲用try...catch

throw: 當問題出現時,程式會丟擲一個異常。catch: 在您想要處理問題的地方,透過異常處理程式捕獲異常。try: try 塊中的程式碼標識將被啟用得特定異常。它後面通常跟著一個或多個 catch 塊。noexcept:用於宣告函式不丟擲異常,如果函式拋了異常,則直接中斷,不能被捕獲

使用 try...catch 語句的語法如下所示:

try{   // 保護程式碼}catch( ExceptionName e1 ){   // catch 塊}catch( ExceptionName e2 ){   // catch 塊}catch( ExceptionName eN ){   // catch 塊}

如果 try 塊在不同的情境下會丟擲不同的異常,這個時候可以嘗試羅列多個 catch 語句,用於捕獲不同型別的異常

捕獲異常時的注意事項:

catch的匹配過程是找最先匹配的,不是最佳匹配。catch的匹配過程中,對型別的要求比較嚴格允許標準算術轉換類型別的轉換。(類型別的轉化包括種:透過建構函式的隱式型別轉化和透過轉化運算子的型別轉化)。

七,異常之棧解旋

異常被丟擲後,從進入try塊起,到異常被拋擲前,這期間在棧上構造的所有物件,都會被自動析構。

析構的順序與構造的順序相反,這一過程稱為棧的解旋(unwinding).

struct Maker{    Maker()    {        cout << "Maker() 建構函式" << endl;    }    Maker(const Maker& other)    {        cout << "Maker(Maker&) 複製建構函式" << endl;    }    ~Maker()    {        cout << "~Maker() 解構函式" << endl;    }};void fun(){    Maker m;    cout << "--------" << endl;    throw m;    cout << "fun__end" << endl;}int main(){    try    {        fun();    }    catch (Maker & m)    {        cout << "收到Maker異常" << endl;    }}

八,C++ 標準的異常

C++ 提供了一系列標準的異常,定義在 <exception> 中,我們可以在程式中使用這些標準的異常。它們是以父子類層次結構組織起來的,如下所示:

每個類所在的標頭檔案在圖下方標識出來

標準異常類的成員: ① 在上述繼承體系中,每個類都有提供了建構函式、複製建構函式、和賦值運算子過載。 ② logic_error類及其子類、runtime_error類及其子類,它們的建構函式是接受一個string型別的形式引數,用於異常資訊的描述; ③ 所有的異常類都有一個what()方法,返回const char* 型別(C風格字串)的值,描述異常資訊。

下表是對上面層次結構中出現的每個異常的說明:

異常

描述

std::exception

該異常是所有標準 C++ 異常的父類。

std::bad_alloc

該異常可以透過 new 丟擲。

std::bad_cast

該異常可以透過 dynamic_cast 丟擲。

std::bad_exception

這在處理 C++ 程式中無法預期的異常時非常有用。

std::bad_typeid

該異常可以透過 typeid 丟擲。

std::logic_error

理論上可以透過讀取程式碼來檢測到的異常。

std::domain_error

當使用了一個無效的數學域時,會丟擲該異常。

std::invalid_argument

當使用了無效的引數時,會丟擲該異常。

std::length_error

當建立了太長的 std::string 時,會丟擲該異常。

std::out_of_range

該異常可以透過方法丟擲,例如 std::vector 和 std::bitset<>::operator。

std::runtime_error

理論上不可以透過讀取程式碼來檢測到的異常。

std::overflow_error

當發生數學上溢時,會丟擲該異常。

std::range_error

當嘗試儲存超出範圍的值時,會丟擲該異常。

std::underflow_error

當發生數學下溢時,會丟擲該異常。

九、編寫自己的異常類為什麼要編寫自己的異常類? ① 標準庫中的異常是有限的; ② 在自己的異常類中,可以新增自己的資訊。(標準庫中的異常類值允許設定一個用來描述異常的字串)。如何編寫自己的異常類? ① 建議自己的異常類要繼承標準異常類。因為C++中可以丟擲任何型別的異常,所以我們的異常類可以不繼承自標準異常,但是這樣可能會導致程式混亂,尤其是當我們多人協同開發時。 ② 當繼承標準異常類時,應該過載父類的what函式虛解構函式。 ③ 因為棧展開的過程中,要複製異常型別,那麼要根據你在類中新增的成員考慮是否提供自己的複製建構函式

示例:

#include <iostream>#include <exception>using namespace std; //第一種class Out_Range : public exception{public:    explicit Out_Range(const string& _Message) : exception(_Message.c_str()) {}    explicit Out_Range(const char* _Message) : exception(_Message) {}};//第二種struct Exce :public exception{    const char* what() const override    {        return "Exce";    }}; void foo(int arr[], int len){    int i = -1;    if (i<0 || i>=len)    {        throw Out_Range("陣列越界啦~");    }    cout << arr[i] << endl;}int main(){    int arr[3] = { 0 };    try    {        foo(arr, 3);    }    catch (Out_Range& e)        //自定義錯誤    {        cout << "Out_Range& e   " << e.what() << endl;    }    catch (std::exception& e)   //其他錯誤    {        cout <<"std::exception& e   "<<e.what()<< endl;    }    return 0;}

十,來自C++之父Bjarne Stroustrup的建議

節選自《The C++ Programming Language》 ——C++之父Bjarne Stroustrup 1. 當局部的控制能夠處理時,不要使用異常; 2.使用“資源分配即初始化”技術去管理資源; 3. 儘量少用try-catch語句塊,而是使用“資源分配即初始化”技術。 4. 如果建構函式內發生錯誤,透過丟擲異常來指明。 5. 避免在解構函式中丟擲異常。 6. 保持普通程式程式碼和異常處理程式碼分開。 7. 小心透過new分配的記憶體在發生異常時,可能造成記憶體洩露。 8. 如果一個函式可能丟擲某種異常,那麼我們呼叫它時,就要假定它一定會丟擲該異常,即要進行處理。 9. 要記住,不是所有的異常都繼承自exception類。 10. 編寫的供別人呼叫的程式庫,不應該結束程式,而應該透過丟擲異常,讓呼叫者決定如何處理(因為呼叫者必須要處理丟擲的異常)。

最後

如果足下基礎比較差,正好在學習C/C++,不妨關注下人人都可以學習的影片教程,通俗易懂,深入淺出,一個影片只講一個知識點。影片不深奧,不需要鑽研,在公交、在地鐵、在廁所都可以觀看,隨時隨地漲姿勢

6
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Alteon python sdk