【C++ 筆記】例外處理(Exception Handling) - part 28 === 目錄(Table of Contents): [TOC] --- 很感謝你點進來這篇文章。 你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧! 例外是什麼? --- 例外(exception)又稱為異常,是指程式在執行過程當中發生的例外情況或錯誤事件。如將兩數相除的過程中,有一個數除於 0,這會是一個例外,因為可能會導致未定義的錯誤。 而處理例外的過程就稱為例外處理,對 XD。 基本例外處理 --- ### try-catch 語法: ```cpp= try { // 這可能會拋出一個例外 // Code that might throw an exception } catch (ExceptionType e) { // 例外處理的地方 // exception handling code } ``` 當 try 區塊發生例外的時候,於 try 的區塊會停止,catch 就會去捕捉這個例外,然後在 catch 區塊裡面做一些處理。 ExceptionType e:看 catch 要捕捉什麼,如 `int e` 就是捕捉 int 型態的例外。 ### throw 拋出例外 用 throw 關鍵字拋出例外,catch 會偵測這個例外是什麼東西,如果符合的話就會去處理,如: ```cpp= try { throw val } catch (ExceptionType e) { // exception handling code } ``` 範例: ```cpp= #include <iostream> #include <string> using namespace std; int main(){ // 整數型態的例外捕捉 try { int val = 10; if (val > 5){ throw val; } cout << "No Exception." << endl; } catch (int e){ cout << "Caught an integer exception with value : " << e << endl; } // 字串型態的例外捕捉 try{ string error_msg = "Something wrong!"; throw error_msg; } catch (string e){ cout << "Caught a string exception : " << e << endl; } return 0; } ``` Output: ``` Caught an integer exception with value : 10 Caught a string exception : Something wrong! ``` 標準例外 --- 標準例外是 C++ STL 裡面定義的一組例外類別,大多被定義在 `<stdexcept>` 或 `<typeinfo>`(如 `bad_cast`) 中。 主要分成兩大類: - 邏輯錯誤(logic_error) - 執行期錯誤(runtime_error) 以下是標準錯誤的層次結構圖: ![cpp-exception-hierarchy](https://hackmd.io/_uploads/B1zh35yBle.png) Image Source:[GeeksForGeeks](https://www.geeksforgeeks.org/exception-handling-c/) 以下是為以上層次結構圖製作的表格: | 例外類型 | 說明 | | ------------------------ | ------------------------------------------------------------ | | `std::logic_error` | 邏輯錯誤。表示程式中存在不合理的邏輯問題,通常是可預先檢查出的錯誤。 | | `std::invalid_argument` | 無效的參數。通常因傳遞了錯誤或非法的參數值而引發。屬於 `logic_error` 的子類。 | | `std::domain_error` | 數學定義域錯誤。當參數不在函數允許的數學定義域內時拋出,例如平方根的負數輸入。屬於 `logic_error` 的子類。 | | `std::length_error` | 長度錯誤。當容器長度超過其最大容量限制時拋出。屬於 `logic_error` 的子類。 | | `std::out_of_range` | 超出範圍。當使用無效索引存取容器元素時拋出。屬於 `logic_error` 的子類。 | | `std::runtime_error` | 執行期間錯誤。表示在執行階段發生非預期狀況,無法在編譯期間預測。 | | `std::range_error` | 範圍錯誤。當數值運算結果超出有效範圍但仍合法(如浮點精度損失)時拋出。屬於 `runtime_error` 的子類。 | | `std::overflow_error` | 溢位錯誤。當數值計算超過資料型態的上限時拋出。屬於 `runtime_error` 的子類。 | | `std::underflow_error` | 下溢錯誤。當浮點數計算結果太接近零而無法正確表示時拋出。屬於 `runtime_error` 的子類。 | | `std::bad_alloc` | 記憶體配置失敗。當 `new` 無法配置足夠記憶體時拋出。屬於 `std::exception` 的子類。 | | `std::bad_function_call` | 錯誤的函數呼叫。當呼叫尚未設定目標的 `std::function` 物件時拋出。 | | `std::bad_cast` | 錯誤的型態轉換。當使用 `dynamic_cast` 進行不合法的轉型時拋出。 | 在以上每個標準例外中,都有一個 what() 方法提供這些標準例外的相關資訊。 以下是個範例: ```cpp= #include <iostream> #include <stdexcept> #include <cmath> using namespace std; double safe_sqrt(double x){ if (x < 0){ throw domain_error("輸入錯誤!平方根的定義域為正數"); } return sqrt(x); } int main(){ try{ double val = -5.0; cout << "計算 " << val << " 的平方根..." << endl; double result = safe_sqrt(val); cout << "結果是: " << result << endl; } catch (domain_error e){ cout << "捕捉到 domain_error 例外: " << e.what() << endl; } return 0; } ``` Output: ``` 計算 -5 的平方根... 捕捉到 domain_error 例外: 輸入錯誤!平方根的定義域為正數 ``` catch 多個例外 --- 不只是 catch 一個例外而已,也可以多個例外: ```cpp= try { // Code that might throw an exception } catch (type1 e) { // executed when exception is of type1 } catch (type2 e) { // executed when exception is of type2 } catch (...) { // executed when no matching catch is found } ``` 以下是個範例: ```cpp= #include <iostream> #include <stdexcept> using namespace std; int divide(int a, int b){ if (b == 0){ throw invalid_argument("除數不能為0"); } if (a == 0){ throw 0; } return a / b; } int main(){ int x, y; cout << "請輸入兩個整數 (a, b) :"; cin >> x >> y; try{ int result = divide(x, y); cout << "除法運算結果 : " << result << endl; } catch (invalid_argument e){ cout << "捕捉到 invalid_argument 例外: " << e.what() << endl; } catch (int e){ cout << "捕捉到整數型態例外,值為: " << e << endl; } return 0; } ``` ### catch 所有例外 就是 `catch(...)` 即可捕捉所有的例外,延續上個範例,增加這個上去: ```cpp= #include <iostream> #include <stdexcept> using namespace std; int divide(int a, int b){ if (a == 9999){ // 新增處 throw "abc"; } if (b == 0){ throw invalid_argument("除數不能為0"); } if (a == 0){ throw 0; } return a / b; } int main(){ int x, y; cout << "請輸入兩個整數 (a, b) :"; cin >> x >> y; try{ int result = divide(x, y); cout << "除法運算結果 : " << result << endl; } catch (invalid_argument e){ cout << "捕捉到 invalid_argument 例外: " << e.what() << endl; } catch (int e){ cout << "捕捉到整數型態例外,值為: " << e << endl; } catch (...){ // 新增處 cout << "捕捉到未知的例外"; } return 0; } ``` 當輸入 a = 9999, b = 任意數時,就會輸出「捕捉到未知的例外」。 ### catch by reference 這個方法只需要傳遞對拋出例外的參考,不需要建立他的 copy,能減少這部分的效能開銷。除了這之外,主要拿來捕捉多型的例外。 ```cpp= #include <iostream> #include <stdexcept> using namespace std; int main() { try { throw runtime_error("發生錯誤:無效的操作"); } catch (const runtime_error& e) { cout << "捕捉到例外: " << e.what() << endl; } return 0; } ``` Output: ``` 捕捉到例外: 發生錯誤:無效的操作 ``` 例外規格(Exception Specification) --- 1. `noexcept` or `noexcept(true)`:如其名,告訴函數保證不會引發例外。 2. `noexcept(false)`:表示可能會拋出例外。若未使用任何的例外規格,這會是預設的,也就是說可加可不加,加了會提升可讀性。 ```cpp= void func1(int a) noexcept { ... } void func2(int b) noexcept(false) { ... } ``` 總結 --- ### 什麼是例外(Exception)? - 例外是指程式執行過程中發生的異常狀況或錯誤事件,如除數為 0。 - 例外處理是指對這些異常狀況進行捕捉與處理的機制,避免程式異常終止。 ### 基本例外處理語法 - 使用 `try` 區塊包著可能拋出例外的程式碼。 - 使用 `catch` 區塊捕捉並處理特定類型的例外。 語法: ```cpp= try { // 可能拋出例外的程式碼 } catch (ExceptionType e) { // 例外處理程式碼 } ``` `throw` 關鍵字用於拋出例外: ```cpp throw val; ``` ### 標準例外類別 定義於 `<stdexcept>` or `<typeinfo>`。 分為兩大類: - 邏輯錯誤(logic_error):程式邏輯錯誤,可預先檢查。 - 執行期錯誤(runtime_error):執行時非預期錯誤。 標準例外表: | 例外類型 | 說明 | | ------------------------ | ------------------------------------------------------------ | | `std::logic_error` | 邏輯錯誤。表示程式中存在不合理的邏輯問題,通常是可預先檢查出的錯誤。 | | `std::invalid_argument` | 無效的參數。通常因傳遞了錯誤或非法的參數值而引發。屬於 `logic_error` 的子類。 | | `std::domain_error` | 數學定義域錯誤。當參數不在函數允許的數學定義域內時拋出,例如平方根的負數輸入。屬於 `logic_error` 的子類。 | | `std::length_error` | 長度錯誤。當容器長度超過其最大容量限制時拋出。屬於 `logic_error` 的子類。 | | `std::out_of_range` | 超出範圍。當使用無效索引存取容器元素時拋出。屬於 `logic_error` 的子類。 | | `std::runtime_error` | 執行期間錯誤。表示在執行階段發生非預期狀況,無法在編譯期間預測。 | | `std::range_error` | 範圍錯誤。當數值運算結果超出有效範圍但仍合法(如浮點精度損失)時拋出。屬於 `runtime_error` 的子類。 | | `std::overflow_error` | 溢位錯誤。當數值計算超過資料型態的上限時拋出。屬於 `runtime_error` 的子類。 | | `std::underflow_error` | 下溢錯誤。當浮點數計算結果太接近零而無法正確表示時拋出。屬於 `runtime_error` 的子類。 | | `std::bad_alloc` | 記憶體配置失敗。當 `new` 無法配置足夠記憶體時拋出。屬於 `std::exception` 的子類。 | | `std::bad_function_call` | 錯誤的函數呼叫。當呼叫尚未設定目標的 `std::function` 物件時拋出。 | | `std::bad_cast` | 錯誤的型態轉換。當使用 `dynamic_cast` 進行不合法的轉型時拋出。 | 註:每個標準例外都有 `what()` public 方法可取得錯誤訊息。 ### 多重 catch 與 catch 所有例外 可以有多個 catch: ```cpp= try { // 可能拋出多種例外 } catch (type1 e) { // 處理 type1 例外 } catch (type2 e) { // 處理 type2 例外 } catch (...) { // 捕捉所有未匹配的例外 } ``` - `catch(...)` 用來捕捉所有未知例外。 ### 以參考捕捉例外(catch by reference) 使用 `catch (ExceptionType& e)` 避免 copy,提高效率,也能捕捉多型例外。 ### 例外規格(Exception Specification) - noexcept:保證函數不會拋出例外。 - noexcept(false):函數可能會拋出例外,為預設行為。 參考資料 --- [例外規格(Exception Specifications)](https://openhome.cc/Gossip/CppGossip/ExceptionSpecifications.html) [How to Catch All Exceptions in C++? - GeeksforGeeks](https://www.geeksforgeeks.org/cpp/how-to-catch-all-exceptions-in-cpp/) [Exception Handling in C++ - GeeksforGeeks](https://www.geeksforgeeks.org/exception-handling-c/) [C++ 异常处理 | 菜鸟教程](https://www.runoob.com/cplusplus/cpp-exceptions-handling.html)