C++異常處理-----(try和catch)
阿新 • • 發佈:2019-02-18
開發程式是一項“燒腦”的工作,程式設計師不但要經過長期的知識學習和思維訓練,還要做到一絲不苟,注意每一個細節和邊界。即使這樣,也不能防止程式出錯。
1) 語法錯誤在編譯和連結階段就能發現,只有 100% 符合語法規則的程式碼才能生成可執行程式。語法錯誤是最容易發現、最容易定位、最容易排除的錯誤,程式設計師最不需要擔心的就是這種錯誤。
2) 邏輯錯誤是說我們編寫的程式碼思路有問題,不能夠達到最終的目標,這種錯誤可以通過除錯來解決。
3) 執行時錯誤是指程式在執行期間發生的錯誤,例如除數為 0、記憶體分配失敗、陣列越界、檔案不存在等。C++ 異常(Exception)機制就是為解決執行時錯誤而引入的。
執行時錯誤如果放任不管,系統就會執行預設的操作,終止程式執行,也就是我們常說的程式崩潰(Crash)。C++ 提供了異常(Exception)機制,讓我們能夠捕獲執行時錯誤,給程式一次“起死回生”的機會,或者至少告訴使用者發生了什麼再終止程式。
【例1】一個發生執行時錯誤的程式:
at() 是 string 類的一個成員函式,它會根據下標來返回字串的一個字元。與
這就好比,catch 告訴 try:你去檢測一下程式有沒有錯誤,有錯誤的話就告訴我,我來處理,沒有的話就不要理我!
catch 關鍵字後面的
【例2】修改上面的程式碼,加入捕獲異常的語句:
(
[2]out of bound!
可以看出,第一個 try 沒有捕獲到異常,輸出了一個沒有意義的字元(垃圾值)。因為
第二個 try 檢測到了異常,並交給 catch 處理,執行 catch 中的語句。需要說明的是,異常一旦丟擲,會立刻被 try 檢測到,並且不會再執行異常點(異常發生位置)後面的語句。本例中丟擲異常的位置是第 17 行的 at() 函式,它後面的 cout 語句就不會再被執行,所以看不到它的輸出。
說得直接一點,檢測到異常後程序的執行流會發生跳轉,從異常點跳轉到 catch 所在的位置,位於異常點之後的、並且在當前 try 塊內的語句就都不會再執行了;即使 catch 語句成功地處理了錯誤,程式的執行流也不會再回退到異常點,所以這些語句永遠都沒有執行的機會了。本例中,第 18 行程式碼就是被跳過的程式碼。
執行完 catch 塊所包含的程式碼後,程式會繼續執行 catch 塊後面的程式碼,就恢復了正常的執行流。
為了演示「不明確地丟擲異常就檢測不到異常」,大家不妨將第 10 行程式碼改為
關於「如何丟擲異常」,我們將在下節講解,這裡重點是讓大家明白異常的處理流程:
1) 下面的例子演示了 try 塊中直接發生的異常:
Unknown Exception
2) 下面的例子演示了 try 塊中呼叫的某個函式中發生了異常:
Unknown Exception
func() 在 try 塊中被呼叫,它丟擲的異常會被 try 檢測到,進而被 catch 捕獲。從執行結果可以看出,func() 中的 cout 和 try 中的 cout 都沒有被執行。
3) try 塊中呼叫了某個函式,該函式又呼叫了另外的一個函式,這個另外的函式丟擲了異常:
Unknown Exception
發生異常後,程式的執行流會沿著函式的呼叫鏈往前回退,直到遇見 try 才停止。在這個回退過程中,呼叫鏈中剩下的程式碼(所有函式中未被執行的程式碼)都會被跳過,沒有執行的機會了。
專家指出,長期作息不規律 + 用腦過度的危害很大,可能會誘發神經衰弱、失眠等疾病。我就是受害者之一,曾被失眠困擾了好幾年,不但入睡困難,還容易早醒。程式設計師要注意勞逸結合,多去健身房,多跑步,多打球,多陪女朋友旅遊等,千萬不要熬夜,以為深夜寫程式碼效率高,這樣會透支年輕的身體。程式的錯誤大致可以分為三種,分別是語法錯誤、邏輯錯誤和執行時錯誤:
1) 語法錯誤在編譯和連結階段就能發現,只有 100% 符合語法規則的程式碼才能生成可執行程式。語法錯誤是最容易發現、最容易定位、最容易排除的錯誤,程式設計師最不需要擔心的就是這種錯誤。
2) 邏輯錯誤是說我們編寫的程式碼思路有問題,不能夠達到最終的目標,這種錯誤可以通過除錯來解決。
3) 執行時錯誤是指程式在執行期間發生的錯誤,例如除數為 0、記憶體分配失敗、陣列越界、檔案不存在等。C++ 異常(Exception)機制就是為解決執行時錯誤而引入的。
執行時錯誤如果放任不管,系統就會執行預設的操作,終止程式執行,也就是我們常說的程式崩潰(Crash)。C++ 提供了異常(Exception)機制,讓我們能夠捕獲執行時錯誤,給程式一次“起死回生”的機會,或者至少告訴使用者發生了什麼再終止程式。
【例1】一個發生執行時錯誤的程式:
- #include <iostream>
- #include <string>
- using namespace std;
- int main(){
- stringstr = "http://c.biancheng.net";
- char ch1 = str[100]; //下標越界,ch1為垃圾值
- cout<<ch1<<endl;
- char ch2 = str.at(100); //下標越界,丟擲異常
- cout<<ch2<<endl;
- return 0;
- }
at() 是 string 類的一個成員函式,它會根據下標來返回字串的一個字元。與
[
]
不同,at() 會檢查下標是否越界,如果越界就丟擲一個異常;而[
]
不做檢查,不管下標是多少都會照常訪問。
所謂丟擲異常,就是報告一個執行時錯誤,程式設計師可以根據錯誤資訊來進一步處理。上面的程式碼中,下標 100 顯然超出了字串 str 的長度。由於第 6 行程式碼不會檢查下標越界,雖然有邏輯錯誤,但是程式能夠正常執行。而第 8 行程式碼則不同,at() 函式檢測到下標越界會丟擲一個異常,這個異常可以由程式設計師處理,但是我們在程式碼中並沒有處理,所以系統只能執行預設的操作,也即終止程式執行。
捕獲異常
我們可以藉助 C++ 異常機制來捕獲上面的異常,避免程式崩潰。捕獲異常的語法為:
try{
// 可能丟擲異常的語句
}catch(exceptionType variable){
// 處理異常的語句
}
try
和catch
都是
C++ 中的關鍵字,後跟語句塊,不能省略{
}
。try 中包含可能會丟擲異常的語句,一旦有異常丟擲就會被後面的 catch 捕獲。從 try 的意思可以看出,它只是“檢測”語句塊有沒有異常,如果沒有發生異常,它就“檢測”不到。catch 是“抓住”的意思,用來捕獲並處理 try
檢測到的異常;如果 try 語句塊沒有檢測到異常(沒有異常丟擲),那麼就不會執行 catch 中的語句。這就好比,catch 告訴 try:你去檢測一下程式有沒有錯誤,有錯誤的話就告訴我,我來處理,沒有的話就不要理我!
catch 關鍵字後面的
exceptionType
variable
指明瞭當前 catch 可以處理的異常型別,以及具體的出錯資訊。我們稍後再對異常型別展開講解,當務之急是演示一下 try-catch 的用法,先讓讀者有一個整體上的認識。【例2】修改上面的程式碼,加入捕獲異常的語句:
- #include <iostream>
- #include <string>
- #include <exception>
- using namespace std;
- int main(){
- stringstr = "http://c.biancheng.net";
- try{
- char ch1 = str[100];
- cout<<ch1<<endl;
- }catch(exceptione){
- cout<<"[1]out of bound!"<<endl;
- }
- try{
- char ch2 = str.at(100);
- cout<<ch2<<endl;
- }catch(exception&e){ //exception類位於<exception>標頭檔案中
- cout<<"[2]out of bound!"<<endl;
- }
- return 0;
- }
(
[2]out of bound!
可以看出,第一個 try 沒有捕獲到異常,輸出了一個沒有意義的字元(垃圾值)。因為
[
]
不會檢查下標越界,不會丟擲異常,所以即使有錯誤,try 也檢測不到。換句話說,發生異常時必須將異常明確地丟擲,try
才能檢測到;如果不丟擲來,即使有異常 try 也檢測不到。所謂丟擲異常,就是明確地告訴程式發生了什麼錯誤。第二個 try 檢測到了異常,並交給 catch 處理,執行 catch 中的語句。需要說明的是,異常一旦丟擲,會立刻被 try 檢測到,並且不會再執行異常點(異常發生位置)後面的語句。本例中丟擲異常的位置是第 17 行的 at() 函式,它後面的 cout 語句就不會再被執行,所以看不到它的輸出。
說得直接一點,檢測到異常後程序的執行流會發生跳轉,從異常點跳轉到 catch 所在的位置,位於異常點之後的、並且在當前 try 塊內的語句就都不會再執行了;即使 catch 語句成功地處理了錯誤,程式的執行流也不會再回退到異常點,所以這些語句永遠都沒有執行的機會了。本例中,第 18 行程式碼就是被跳過的程式碼。
執行完 catch 塊所包含的程式碼後,程式會繼續執行 catch 塊後面的程式碼,就恢復了正常的執行流。
為了演示「不明確地丟擲異常就檢測不到異常」,大家不妨將第 10 行程式碼改為
char
ch1 = str[100000000];
,訪問第 100 個字元可能不會發生異常,但是訪問第 1 億個字元肯定會發生異常了,這個異常就是記憶體訪問錯誤。執行更改後的程式,會發現第
10 行程式碼產生了異常,導致程式崩潰了,這說明 try-catch 並沒有捕獲到這個異常。關於「如何丟擲異常」,我們將在下節講解,這裡重點是讓大家明白異常的處理流程:
丟擲(Throw)--> 檢測(Try) --> 捕獲(Catch)
發生異常的位置
異常可以發生在當前的 try 塊中,也可以發生在 try 塊所呼叫的某個函式中,或者是所呼叫的函式又呼叫了另外的一個函式,這個另外的函式中發生了異常。這些異常,都可以被 try 檢測到。1) 下面的例子演示了 try 塊中直接發生的異常:
- #include <iostream>
- #include <string>
- #include <exception>
- using namespace std;
- int main(){
- try{
- throw "Unknown Exception"; //丟擲異常
- cout<<"This statement will not be executed."<<endl;
- }catch(const char* &e){
- cout<<e<<endl;
- }
- return 0;
- }
Unknown Exception
throw
關鍵字用來丟擲一個異常,這個異常會被
try 檢測到,進而被 catch 捕獲。關於 throw 的用法,我們將在下節深入講解,這裡大家只需要知道,在 try 塊中直接丟擲的異常會被 try 檢測到。2) 下面的例子演示了 try 塊中呼叫的某個函式中發生了異常:
- #include <iostream>
- #include <string>
- #include <exception>
- using namespace std;
- void func(){
- throw "Unknown Exception"; //丟擲異常
- cout<<"[1]This statement will not be executed."<<endl;
- }
- int main(){
- try{
- func();
- cout<<"[2]This statement will not be executed."<<endl;
- }catch(const char* &e){
- cout<<e<<endl;
- }
- return 0;
- }
Unknown Exception
func() 在 try 塊中被呼叫,它丟擲的異常會被 try 檢測到,進而被 catch 捕獲。從執行結果可以看出,func() 中的 cout 和 try 中的 cout 都沒有被執行。
3) try 塊中呼叫了某個函式,該函式又呼叫了另外的一個函式,這個另外的函式丟擲了異常:
- #include <iostream>
- #include <string>
- #include <exception>
- using namespace std;
- void func_inner(){
- throw "Unknown Exception"; //丟擲異常
- cout<<"[1]This statement will not be executed."<<endl;
- }
- void func_outer(){
- func_inner();
- cout<<"[2]This statement will not be executed."<<endl;
- }
- int main(){
- try{
- func_outer();
- cout<<"[3]This statement will not be executed."<<endl;
- }catch(const char* &e){
- cout<<e<<endl;
- }
- return 0;
- }
Unknown Exception
發生異常後,程式的執行流會沿著函式的呼叫鏈往前回退,直到遇見 try 才停止。在這個回退過程中,呼叫鏈中剩下的程式碼(所有函式中未被執行的程式碼)都會被跳過,沒有執行的機會了。