異常處理機制
程序運行時常會碰到一些異常情況,例如:
- 做除法的時候除數爲 0;
- 用戶輸入年齡時輸入了一個負數;
- 用 new 運算符動態分配空間時,空間不夠導致無法分配;
- 訪問數組元素時,下標越界;
- 打開文件讀取時,文件不存在。
這些異常情況,如果不能發現並加以處理,很可能會導致程序崩潰。
所謂“處理”,可以是給出錯誤提示信息,然後讓程序沿一條不會出錯的路徑繼續執行;也可能是不得不結束程序,但在結束前做一些必要的工作,如將內存中的數據寫入文件、關閉打開的文件、釋放動態分配的內存空間等。
一發現異常情況就立即處理未必妥當,因爲在一個函數執行過程中發生的異常,在有的情況下由該函數的調用者決定如何處理更加合適。尤其像庫函數這類提供給程序員調用,用以完成與具體應用無關的通用功能的函數,執行過程中貿然對異常進行處理,未必符合調用它的程序的需要。
此外,將異常分散在各處進行處理不利於代碼的維護,尤其是對於在不同地方發生的同一種異常,都要編寫相同的處理代碼也是一種不必要的重複和冗餘。如果能在發生各種異常時讓程序都執行到同一個地方,這個地方能夠對異常進行集中處理,則程序就會更容易編寫、維護。
鑑於上述原因,C++ 引入了異常處理機制。其基本思想是:
- 函數 A 在執行過程中發現異常時可以不加處理,而只是“拋出一個異常”給 A 的調用者(假定爲函數 B),然後 A 立即中止;
- 在這種情況下,函數 B 可以選擇捕獲 A 拋出的異常進行處理,也可以選擇置之不理。如果置之不理,這個異常就會被拋給 B 的調用者,以此類推。
- 如果一層層的函數都不處理異常,異常最終會被拋給最外層的 main 函數。如果main函數也不處理異常,那麼程序就會立即中止。
異常拋出與捕獲
要處理異常,就需要捕獲異常。C++ 通過 try...catch
語句實現對異常的捕獲和處理。
try {
語句組
}
catch(異常類型) {
異常處理代碼
}
...
catch(異常類型) {
異常處理代碼
}
// catch 可以有多個,但至少要有一個
try…catch 語句的執行過程是:
- 如果在 try 塊執行的過程中沒有異常拋出,那麼執行完後就略過所有 catch 塊中的語句,繼續向下執行;
- 如果 try 塊執行的過程中中拋出了異常,那麼拋出異常後立即跳轉到第一個和拋出的異常類型相匹配的 catch 塊中執行,稱作異常被該 catch 塊“捕獲”,在其中執行完異常處理程序後,再跳轉到最後一個 catch 塊後面繼續向下執行。因爲異常而未執行的語句組將不再執行。
除了C++運行時等底層自身拋出異常外,也可以通過throw
主動拋出異常。
throw 表達式; // 表達式的值可以是基本類型,也可以是類
異常類型
C++ 標準庫中有一些類代表異常,這些類都是從 exception 類派生而來的。常用的幾個異常類如下所示。
C++ 程序在碰到某些異常時,即使程序中沒有寫 throw 語句,也會自動拋出上述異常類的對象。這些異常類還都有名爲 what()
的成員函數,返回字符串形式的異常描述信息。使用這些異常類需要包含頭文件 <exception>
。
代碼示例:拋出並捕獲異常
捕獲通用異常
#include <exception>
try
{
// do something
}
catch (std::exception & e)
{
std::cerr <<"ERROR: "<< e.what() << std::endl;
}
捕獲特定異常 *
#include <exception>
try
{
// do something
}
catch (std::bad_cast & e)
{
std::cerr <<"ERROR: "<< e.what() << std::endl;
}
捕獲throw異常
#include <string>
try
{
if(判斷語句) throw std::string("Stack is empty!");
if(判斷語句) throw -1;
// do something
}
catch (std::string & e)
{
std::cerr <<"ERROR: "<< e << std::endl;
}
catch (int & e)
{
std::cerr <<"ERROR CODE: "<< e << std::endl;
}
注意:這種throw
拋出的是基本類型,不能使用 std::exception
類來捕獲。
捕獲任何異常 *
try
{
// do something
}
catch (...)
{
std::cerr << "ERROR!" << std::endl;
}
捕獲自定義異常 *
#include <iostream>
#include <exception>
using namespace std;
struct MyException : public exception
{
const char * what () const throw ()
{
return "C++ Exception";
}
};
int main()
{
try
{
throw MyException();
}
catch(MyException& e)
{
std::cout << "MyException caught" << std::endl;
std::cout << e.what() << std::endl;
}
catch(std::exception& e)
{
//其他異常處理
}
}