C++異常處理入門(mén)(try和catch)
開(kāi)發(fā)程序是一項(xiàng)“燒腦”的工作,程序員不但要經(jīng)過(guò)長(zhǎng)期的知識(shí)學(xué)習(xí)和思維訓(xùn)練,還要做到一絲不茍,注意每一個(gè)細(xì)節(jié)和邊界。即使這樣,也不能防止程序出錯(cuò)。
專(zhuān)家指出,長(zhǎng)期作息不規(guī)律 + 用腦過(guò)度的危害很大,可能會(huì)誘發(fā)神經(jīng)衰弱、失眠等疾病。我就是受害者之一,曾被失眠困擾了好幾年,不但入睡困難,還容易早醒。程序員要注意勞逸結(jié)合,多去健身房,多跑步,多打球,多陪女朋友旅游等,千萬(wàn)不要熬夜,以為深夜寫(xiě)代碼效率高,這樣會(huì)透支年輕的身體。
程序的錯(cuò)誤大致可以分為三種,分別是語(yǔ)法錯(cuò)誤、邏輯錯(cuò)誤和運(yùn)行時(shí)錯(cuò)誤:
1) 語(yǔ)法錯(cuò)誤在編譯和鏈接階段就能發(fā)現(xiàn),只有 100% 符合語(yǔ)法規(guī)則的代碼才能生成可執(zhí)行程序。語(yǔ)法錯(cuò)誤是最容易發(fā)現(xiàn)、最容易定位、最容易排除的錯(cuò)誤,程序員最不需要擔(dān)心的就是這種錯(cuò)誤。
2) 邏輯錯(cuò)誤是說(shuō)我們編寫(xiě)的代碼思路有問(wèn)題,不能夠達(dá)到最終的目標(biāo),這種錯(cuò)誤可以通過(guò)調(diào)試來(lái)解決。
3) 運(yùn)行時(shí)錯(cuò)誤是指程序在運(yùn)行期間發(fā)生的錯(cuò)誤,例如除數(shù)為 0、內(nèi)存分配失敗、數(shù)組越界、文件不存在等。C++ 異常(Exception)機(jī)制就是為解決運(yùn)行時(shí)錯(cuò)誤而引入的。
運(yùn)行時(shí)錯(cuò)誤如果放任不管,系統(tǒng)就會(huì)執(zhí)行默認(rèn)的操作,終止程序運(yùn)行,也就是我們常說(shuō)的程序崩潰(Crash)。C++ 提供了異常(Exception)機(jī)制,讓我們能夠捕獲運(yùn)行時(shí)錯(cuò)誤,給程序一次“起死回生”的機(jī)會(huì),或者至少告訴用戶(hù)發(fā)生了什么再終止程序。
【例1】一個(gè)發(fā)生運(yùn)行時(shí)錯(cuò)誤的程序:
#include <iostream> #include <string> using namespace std; int main(){ string str = "http://c.biancheng.net"; char ch1 = str[100]; //下標(biāo)越界,ch1為垃圾值 cout<<ch1<<endl; char ch2 = str.at(100); //下標(biāo)越界,拋出異常 cout<<ch2<<endl; return 0; }
運(yùn)行代碼,在控制臺(tái)輸出 ch1 的值后程序崩潰。下面我們來(lái)分析一下原因。
at() 是 string 類(lèi)的一個(gè)成員函數(shù),它會(huì)根據(jù)下標(biāo)來(lái)返回字符串的一個(gè)字符。與[ ]不同,at() 會(huì)檢查下標(biāo)是否越界,如果越界就拋出一個(gè)異常;而[ ]不做檢查,不管下標(biāo)是多少都會(huì)照常訪(fǎng)問(wèn)。
所謂拋出異常,就是報(bào)告一個(gè)運(yùn)行時(shí)錯(cuò)誤,程序員可以根據(jù)錯(cuò)誤信息來(lái)進(jìn)一步處理。
上面的代碼中,下標(biāo) 100 顯然超出了字符串 str 的長(zhǎng)度。由于第 6 行代碼不會(huì)檢查下標(biāo)越界,雖然有邏輯錯(cuò)誤,但是程序能夠正常運(yùn)行。而第 8 行代碼則不同,at() 函數(shù)檢測(cè)到下標(biāo)越界會(huì)拋出一個(gè)異常,這個(gè)異常可以由程序員處理,但是我們?cè)诖a中并沒(méi)有處理,所以系統(tǒng)只能執(zhí)行默認(rèn)的操作,也即終止程序執(zhí)行。
捕獲異常
我們可以借助 C++ 異常機(jī)制來(lái)捕獲上面的異常,避免程序崩潰。捕獲異常的語(yǔ)法為:
try{ // 可能拋出異常的語(yǔ)句 }catch(exceptionType variable){ // 處理異常的語(yǔ)句 }
try和catch都是 C++ 中的關(guān)鍵字,后跟語(yǔ)句塊,不能省略{ }。try 中包含可能會(huì)拋出異常的語(yǔ)句,一旦有異常拋出就會(huì)被后面的 catch 捕獲。從 try 的意思可以看出,它只是“檢測(cè)”語(yǔ)句塊有沒(méi)有異常,如果沒(méi)有發(fā)生異常,它就“檢測(cè)”不到。catch 是“抓住”的意思,用來(lái)捕獲并處理 try 檢測(cè)到的異常;如果 try 語(yǔ)句塊沒(méi)有檢測(cè)到異常(沒(méi)有異常拋出),那么就不會(huì)執(zhí)行 catch 中的語(yǔ)句。
這就好比,catch 告訴 try:你去檢測(cè)一下程序有沒(méi)有錯(cuò)誤,有錯(cuò)誤的話(huà)就告訴我,我來(lái)處理,沒(méi)有的話(huà)就不要理我!
catch 關(guān)鍵字后面的exceptionType variable指明了當(dāng)前 catch 可以處理的異常類(lèi)型,以及具體的出錯(cuò)信息。我們稍后再對(duì)異常類(lèi)型展開(kāi)講解,當(dāng)務(wù)之急是演示一下 try-catch 的用法,先讓讀者有一個(gè)整體上的認(rèn)識(shí)。
【例2】修改上面的代碼,加入捕獲異常的語(yǔ)句:
#include <iostream> #include <string> #include <exception> using namespace std; int main(){ string str = "http://c.biancheng.net"; try{ char ch1 = str[100]; cout<<ch1<<endl; }catch(exception e){ cout<<"[1]out of bound!"<<endl; } try{ char ch2 = str.at(100); cout<<ch2<<endl; }catch(exception &e){ //exception類(lèi)位于<exception>頭文件中 cout<<"[2]out of bound!"<<endl; } return 0; }
運(yùn)行結(jié)果:
(
[2]out of bound!
可以看出,第一個(gè) try 沒(méi)有捕獲到異常,輸出了一個(gè)沒(méi)有意義的字符(垃圾值)。因?yàn)閇 ]不會(huì)檢查下標(biāo)越界,不會(huì)拋出異常,所以即使有錯(cuò)誤,try 也檢測(cè)不到。換句話(huà)說(shuō),發(fā)生異常時(shí)必須將異常明確地拋出,try 才能檢測(cè)到;如果不拋出來(lái),即使有異常 try 也檢測(cè)不到。所謂拋出異常,就是明確地告訴程序發(fā)生了什么錯(cuò)誤。
第二個(gè) try 檢測(cè)到了異常,并交給 catch 處理,執(zhí)行 catch 中的語(yǔ)句。需要說(shuō)明的是,異常一旦拋出,會(huì)立刻被 try 檢測(cè)到,并且不會(huì)再執(zhí)行異常點(diǎn)(異常發(fā)生位置)后面的語(yǔ)句。本例中拋出異常的位置是第 17 行的 at() 函數(shù),它后面的 cout 語(yǔ)句就不會(huì)再被執(zhí)行,所以看不到它的輸出。
說(shuō)得直接一點(diǎn),檢測(cè)到異常后程序的執(zhí)行流會(huì)發(fā)生跳轉(zhuǎn),從異常點(diǎn)跳轉(zhuǎn)到 catch 所在的位置,位于異常點(diǎn)之后的、并且在當(dāng)前 try 塊內(nèi)的語(yǔ)句就都不會(huì)再執(zhí)行了;即使 catch 語(yǔ)句成功地處理了錯(cuò)誤,程序的執(zhí)行流也不會(huì)再回退到異常點(diǎn),所以這些語(yǔ)句永遠(yuǎn)都沒(méi)有執(zhí)行的機(jī)會(huì)了。本例中,第 18 行代碼就是被跳過(guò)的代碼。
執(zhí)行完 catch 塊所包含的代碼后,程序會(huì)繼續(xù)執(zhí)行 catch 塊后面的代碼,就恢復(fù)了正常的執(zhí)行流。
為了演示「不明確地拋出異常就檢測(cè)不到異?!?,大家不妨將第 10 行代碼改為char ch1 = str[100000000];,訪(fǎng)問(wèn)第 100 個(gè)字符可能不會(huì)發(fā)生異常,但是訪(fǎng)問(wèn)第 1 億個(gè)字符肯定會(huì)發(fā)生異常了,這個(gè)異常就是內(nèi)存訪(fǎng)問(wèn)錯(cuò)誤。運(yùn)行更改后的程序,會(huì)發(fā)現(xiàn)第 10 行代碼產(chǎn)生了異常,導(dǎo)致程序崩潰了,這說(shuō)明 try-catch 并沒(méi)有捕獲到這個(gè)異常。
關(guān)于「如何拋出異?!?,我們將在下節(jié)講解,這里重點(diǎn)是讓大家明白異常的處理流程:
拋出(Throw)--> 檢測(cè)(Try) --> 捕獲(Catch)
發(fā)生異常的位置
異??梢园l(fā)生在當(dāng)前的 try 塊中,也可以發(fā)生在 try 塊所調(diào)用的某個(gè)函數(shù)中,或者是所調(diào)用的函數(shù)又調(diào)用了另外的一個(gè)函數(shù),這個(gè)另外的函數(shù)中發(fā)生了異常。這些異常,都可以被 try 檢測(cè)到。
1) 下面的例子演示了 try 塊中直接發(fā)生的異常:
#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; }
運(yùn)行結(jié)果:
Unknown Exception
throw關(guān)鍵字用來(lái)拋出一個(gè)異常,這個(gè)異常會(huì)被 try 檢測(cè)到,進(jìn)而被 catch 捕獲。關(guān)于 throw 的用法,我們將在下節(jié)深入講解,這里大家只需要知道,在 try 塊中直接拋出的異常會(huì)被 try 檢測(cè)到。
2) 下面的例子演示了 try 塊中調(diào)用的某個(gè)函數(shù)中發(fā)生了異常:
#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; }
運(yùn)行結(jié)果:
Unknown Exception
func() 在 try 塊中被調(diào)用,它拋出的異常會(huì)被 try 檢測(cè)到,進(jìn)而被 catch 捕獲。從運(yùn)行結(jié)果可以看出,func() 中的 cout 和 try 中的 cout 都沒(méi)有被執(zhí)行。
3) try 塊中調(diào)用了某個(gè)函數(shù),該函數(shù)又調(diào)用了另外的一個(gè)函數(shù),這個(gè)另外的函數(shù)拋出了異常:
#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; }
運(yùn)行結(jié)果:
Unknown Exception
發(fā)生異常后,程序的執(zhí)行流會(huì)沿著函數(shù)的調(diào)用鏈往前回退,直到遇見(jiàn) try 才停止。在這個(gè)回退過(guò)程中,調(diào)用鏈中剩下的代碼(所有函數(shù)中未被執(zhí)行的代碼)都會(huì)被跳過(guò),沒(méi)有執(zhí)行的機(jī)會(huì)了。
到此這篇關(guān)于C++異常處理入門(mén)(try和catch)的文章就介紹到這了,更多相關(guān)C++異常處理 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實(shí)現(xiàn)調(diào)用系統(tǒng)時(shí)間簡(jiǎn)單示例
這篇文章主要介紹了C++實(shí)現(xiàn)調(diào)用系統(tǒng)時(shí)間,需要的朋友可以參考下2014-07-07C++ 實(shí)現(xiàn)自定義類(lèi)型的迭代器操作
這篇文章主要介紹了C++ 實(shí)現(xiàn)自定義類(lèi)型的迭代器操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12C++循環(huán)隊(duì)列實(shí)現(xiàn)模型
這篇文章主要介紹了C++循環(huán)隊(duì)列實(shí)現(xiàn)模型,較為詳細(xì)的分析了循環(huán)隊(duì)列算法的原理與實(shí)現(xiàn)方法,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2014-12-12