異常處理 Exception

在 C++ 中,異常 exception 是在程式執行期間發生的事件。它的出現會打斷正常的程式流程。一個常見的例子比如說程式嘗試將一個整數除以 0。我們都知道不能將數字除以 0,而程式碼遇到這樣的情況也會報錯並立即終止程式。但有時候,即使出現這樣的錯誤,我們還是希望程式碼可以繼續執行下去。

這就好比一場籃球比賽,當有人犯規的時候,球賽並不會因此而中止,反而裁判會吹哨並且處理判罰,處理完後再恢復比賽,讓比賽繼續。而異常處理的機制就是在扮演裁判的角色!

C++ 提供了處理異常的機制,讓我們可以在錯誤處理和程式控制流程上擁有更多的掌控權。這個機制提供了三個關鍵字:

  • try:定義一塊程式代碼塊,英文叫做 try block。這個區塊會充當觀察者,負責找尋任何在這個區塊內發生的異常。
  • throw:當錯誤發生時,throw 就會丟出一個異常。
  • catch:當觀察到異常時,我們可以利用 catch 來捕獲異常,並做出相對應的處理。

在程式碼中,異常處理的大致架構大概會長這樣:

try // 尋找異常
{
    // 丟出異常
    throw
}
catch ()
{
    // 異常處理
}

接下來,我們來一個一個講他的功能!

丟出異常 throw

在 C++ 中,我們利用 throw 語句來丟出異常,表示程式偵測到錯誤了!利用先前的籃球比賽例子就是裁判看到有人犯規並吹哨了。而犯規也有很多種,有打手犯規、進攻犯規、防守犯規等等。每一種犯規裁判都會用不同的手勢。

在 C++ 中,我們丟出的異常也可以有很多種!比如說整數、字串、變數等等,甚至是更高階的物件(以後會講到)。比如說:

throw 0;                    // 整數
throw "This is an error";   // 字串
throw error;                // 變數
throw MyClass();            // 物件

根據你的程式碼的應用,我們可以自己選擇想要丟出的異常種類,單看之後要怎麼處理這些異常。核心概念在於,每一個異常都代表著程式中出現了某種錯誤需要我們去解決。

尋找異常 try

在 C++ 中,我們利用 try 語句來偵測異常。同樣利用籃球比賽來舉例,當裁判吹哨並表示有人犯規時(丟出異常),場上的所有人都會因此停下動作,造成比賽暫時中斷。

try 在這裡就好比定義出球場上的人員,被定義在這個球場裡的所有人都必須時刻關注裁判有沒有吹哨(異常),如果偵測到了,那麼比賽(程式流程)必須因此中斷。

try
{
    // ... 表示我們寫的其他程式碼
    ...
    ... 
    throw -1;
    ...
}   // 在這個 {} 裡面的都要注意什麼時候會丟出異常(在這個例子就是 -1)

處理異常 catch

在 C++ 中,我們利用 catch 語句來處理異常。當裁判吹哨表示有人犯規,球場的所有人都停下動作後,裁判這時候就要出來處理這個犯規了。可能是罰球、重新發球、交換球權等等,完全看裁判想怎麼處理。處理完後,球賽才會繼續執行。

catch 會馬上接在 try 後面,用來處理在 try 偵測到的各種異常。並且我們也可以根據不同種類的異常寫出不只一種處理方式。

catch(int e)
{
    std::cout << "the error code is " << e << std::endl;
}
catch(string e)
{
    std::cout << e << std::endl;
}

在上面的例子我們可以看到兩個 catch  語句,分別處理不同類型的異常,第一個是整數型態,第二個則是字串型態。

這就像是根據不同的犯規,裁判會給出相應的判決一樣。這樣是不是很好理解!

如果說我們不想管那麼多呢?也就是不管偵測到什麼類型的異常,都只用一種方式處理。這也是可以做到的!我們可以這樣做:

catch(...)
{
    std::cout << "An error has occured!";
}

大總匯

現在讓我們把三個語句全部串起來!在下面的例子中,我們判斷變數 a 是不是為 0,如果是的話就丟出異常,不是的話就執行除法。並針對不同類型的異常去做相對應的處理。處理完異常後,程式碼則會繼續進行下去,最後會印出 Continue...

如果想要讓這個例子更加實用,我們可以將變數 a 改為使用者輸入。但這邊只是一個示範,因此我們盡量讓範例簡單一些。

try
{
    int a = 0;
    if (a == 0)
    {
        throw -1;
    }
    else
    {
        std::cout << 8/a << std::endl;
    }
}
catch(int e)
{
    std::cout << "integer type: " << e << std::endl;
}
catch(float e)
{
    std::cout << "float type: " << e << std::endl;
}
catch(std::string e)
{
    std::cout << "string type: " << e << std::endl;
}

std::cout << "Continue..." << std::endl;

執行完上面這個程式後,我們會看到這樣的輸出:

integer type: -1
Continue...