本篇文章為大家展示了中try catch方法怎么在c++中使用,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
專注于為中小企業(yè)提供成都網(wǎng)站制作、網(wǎng)站建設(shè)服務(wù),電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業(yè)海南州免費做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了超過千家企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實現(xiàn)規(guī)模擴充和轉(zhuǎn)變。
在c++中,可以直接拋出異常之后自己進行捕捉處理,如:(這樣就可以在任何自己得到不想要的結(jié)果的時候進行中斷,比如在進行數(shù)據(jù)庫事務(wù)操作的時候,如果某一個語句返回SQL_ERROR則直接拋出異常,在catch塊中進行事務(wù)回滾(回滾怎么理解?))。
#include#include using namespace std; int main () { try { throw 1; throw "error"; } catch(char *str) { cout << str << endl; } catch(int i) { cout << i << endl; } }
也可以自己定義異常類來進行處理:
#include#include using namespace std; //可以自己定義Exception class myexception: public exception { virtual const char* what() const throw() { return "My exception happened"; } }myex; int main () { try { if(true) //如果,則拋出異常; throw myex; } catch (exception& e) { cout << e.what() << endl; } return 0; }
同時也可以使用標準異常類進行處理:
#include#include using namespace std; int main () { try { int* myarray= new int[100000]; } catch (exception& e) { cout << "Standard exception: " << e.what() << endl; } return 0; }
一、簡單的例子
首先通過一個簡單的例子來熟悉C++ 的 try/catch/throw(可根據(jù)單步調(diào)試來熟悉,try catch throw部分是如何運行的):
#include#include "iostream" using namespace std; double fuc(double x, double y) //定義函數(shù) { if(y==0) { throw y; //除數(shù)為0,拋出異常 } return x/y; //否則返回兩個數(shù)的商 } int _tmain(int argc, _TCHAR* argv[]) { double res; try //定義異常 { res=fuc(2,3); cout<<"The result of x/y is : "< catch 的數(shù)據(jù)類型需要與throw出來的數(shù)據(jù)類型相匹配的。
二、catch(...)的作用
catch(…)能夠捕獲多種數(shù)據(jù)類型的異常對象,所以它提供給程序員一種對異常對象更好的控制手段,使開發(fā)的軟件系統(tǒng)有很好的可靠性。因此一個比較有經(jīng)驗的程序員通常會這樣組織編寫它的代碼模塊,如下:
void Func() { try { // 這里的程序代碼完成真正復(fù)雜的計算工作,這些代碼在執(zhí)行過程中 // 有可能拋出DataType1、DataType2和DataType3類型的異常對象。 } catch(DataType1& d1) { } catch(DataType2& d2) { } catch(DataType3& d3) { } /********************************************************* 注意上面try block中可能拋出的DataType1、DataType2和DataType3三 種類型的異常對象在前面都已經(jīng)有對應(yīng)的catch block來處理。但為什么 還要在最后再定義一個catch(…) block呢?這就是為了有更好的安全性和 可靠性,避免上面的try block拋出了其它未考慮到的異常對象時導(dǎo)致的程 序出現(xiàn)意外崩潰的嚴重后果,而且這在用VC開發(fā)的系統(tǒng)上更特別有效,因 為catch(…)能捕獲系統(tǒng)出現(xiàn)的異常,而系統(tǒng)異常往往令程序員頭痛了,現(xiàn) 在系統(tǒng)一般都比較復(fù)雜,而且由很多人共同開發(fā),一不小心就會導(dǎo)致一個 指針變量指向了其它非法區(qū)域,結(jié)果意外災(zāi)難不幸發(fā)生了。catch(…)為這種 潛在的隱患提供了一種有效的補救措施?! ?********************************************************/ catch(…) { } }三、異常中采用面向?qū)ο蟮奶幚?/strong>
首先看下面的例子:
void OpenFile(string f) { try { // 打開文件的操作,可能拋出FileOpenException } catch(FileOpenException& fe) { // 處理這個異常,如果這個異??梢院芎玫牡靡曰謴?fù),那么處理完畢后函數(shù) // 正常返回;否則必須重新拋出這個異常,以供上層的調(diào)用函數(shù)來能再次處 // 理這個異常對象 int result = ReOpenFile(f); if (result == false) throw; } } void ReadFile(File f) { try { // 從文件中讀數(shù)據(jù),可能拋出FileReadException } catch(FileReadException& fe) { // 處理這個異常,如果這個異??梢院芎玫牡靡曰謴?fù),那么處理完畢后函數(shù) // 正常返回;否則必須重新拋出這個異常,以供上層的調(diào)用函數(shù)來能再次處 // 理這個異常對象 int result = ReReadFile(f); if (result == false) throw; } } void WriteFile(File f) { try { // 往文件中寫數(shù)據(jù),可能拋出FileWriteException } catch(FileWriteException& fe) { // 處理這個異常,如果這個異??梢院芎玫牡靡曰謴?fù),那么處理完畢后函數(shù) // 正常返回;否則必須重新拋出這個異常,以供上層的調(diào)用函數(shù)來能再次處理這個異常對象 int result = ReWriteFile(f); if (result == false) throw; } } void Func() { try { // 對文件進行操作,可能出現(xiàn)FileWriteException、FileWriteException // 和FileWriteException異常 OpenFile(…); ReadFile(…); WriteFile(…); } // 注意:FileException是FileOpenException、FileReadException和FileWriteException // 的基類,因此這里定義的catch(FileException& fe)能捕獲所有與文件操作失敗的異 // 常。 catch(FileException& fe) { ExceptionInfo* ef = fe.GetExceptionInfo(); cout << “操作文件時出現(xiàn)了不可恢復(fù)的錯誤,原因是:”<< fe << endl; } }下面是更多面向?qū)ο蠛彤惓L幚斫Y(jié)合的例子:
#includeclass ExceptionClass { char* name; public: ExceptionClass(const char* name="default name") { cout<<"Construct "< name=name; } ~ExceptionClass() { cout<<"Destruct "< 這是輸出信息:
Construct Test
Construct my throw
Destruct my throw
****************
Destruct my throw (這里是異常處理空間中對異常類的拷貝的析構(gòu))
Destruct Test
======================================不過一般來說我們可能更習(xí)慣于把會產(chǎn)生異常的語句和要throw的異常類分成不同的類來寫,下面的代碼可以是我們更愿意書寫的:
class ExceptionClass { public: ExceptionClass(const char* name="Exception Default Class") { cout<<"Exception Class Construct String"<name=name; } ~ArguClass() { cout<<"Destruct String::"< 輸出Message:
Construct String::haha
Exception Class Construct String
Exception Class Destruct String
Exception Class:: This is Report Error Message
Exception Class Destruct String
Destruct String::haha四、構(gòu)造和析構(gòu)中的異常拋出
先看個程序,假如我在構(gòu)造函數(shù)的地方拋出異常,這個類的析構(gòu)會被調(diào)用嗎?可如果不調(diào)用,那類里的東西豈不是不能被釋放了?
#include#include class ExceptionClass1 { char* s; public: ExceptionClass1() { cout<<"ExceptionClass1()"< 結(jié)果為:
ExceptionClass1()
throw a exception在這兩句輸出之間,我們已經(jīng)給S分配了內(nèi)存,但內(nèi)存沒有被釋放(因為它是在析構(gòu)函數(shù)中釋放的)。應(yīng)該說這符合實際現(xiàn)象,因為對象沒有完整構(gòu)造。
為了避免這種情況,我想你也許會說:應(yīng)避免對象通過本身的構(gòu)造函數(shù)涉及到異常拋出。即:既不在構(gòu)造函數(shù)中出現(xiàn)異常拋出,也不應(yīng)在構(gòu)造函數(shù)調(diào)用的一切東西中出現(xiàn)異常拋出。
但是在C++中可以在構(gòu)造函數(shù)中拋出異常,經(jīng)典的解決方案是使用STL的標準類auto_ptr。那么,在析構(gòu)函數(shù)中的情況呢?我們已經(jīng)知道,異常拋出之后,就要調(diào)用本身的析構(gòu)函數(shù),如果這析構(gòu)函數(shù)中還有異常拋出的話,則已存在的異常尚未被捕獲,會導(dǎo)致異常捕捉不到。
五、標準C++異常類
標準異常都派生自一個公共的基類exception。基類包含必要的多態(tài)性函數(shù)提供異常描述,可以被重載。下面是exception類的原型:
class exception { public: exception() throw(); exception(const exception& rhs) throw(); exception& operator=(const exception& rhs) throw(); virtual ~exception() throw(); virtual const char *what() const throw(); }; C++有很多的標準異常類: namespace std { //exception派生 class logic_error; //邏輯錯誤,在程序運行前可以檢測出來 //logic_error派生 class domain_error; //違反了前置條件 class invalid_argument; //指出函數(shù)的一個無效參數(shù) class length_error; //指出有一個超過類型size_t的最大可表現(xiàn)值長度的對象的企圖 class out_of_range; //參數(shù)越界 class bad_cast; //在運行時類型識別中有一個無效的dynamic_cast表達式 class bad_typeid; //報告在表達試typeid(*p)中有一個空指針p //exception派生 class runtime_error; //運行時錯誤,僅在程序運行中檢測到 //runtime_error派生 class range_error; //違反后置條件 class overflow_error; //報告一個算術(shù)溢出 class bad_alloc; //存儲分配錯誤 }標準庫異常類定義在以下四個頭文件中
1、exception頭文件:定義了最常見的標準異常類,其類名為exception。只通知異常的產(chǎn)生,但不會提供更多的信息
2、stdexcept頭文件定義了以下幾種常見異常類
函數(shù) 功能或作用
exception 最常見的問題
runtime_error 運行時錯誤:僅在運行時才能檢測到的問題
range_error 運行時錯誤:生成的結(jié)果超出了有意義的值域范圍
overflow_error 運行時錯誤:計算上溢
underflow_error 運行時錯誤:計算下溢
logic_error 邏輯錯誤:可在運行前檢測到的問題
domain_error 邏輯錯誤:參數(shù)的結(jié)果值不存在
invalid_argument 邏輯錯誤:不合適的參數(shù)
length_error 邏輯錯誤:試圖生成一個超出該類型最大長度的對象
out_of_range 邏輯錯誤:使用一個超出有效范圍的值
3、new頭文件定義了bad_alloc異常類型,提供因無法分配內(nèi)存而由new拋出的異常
4、type_info頭文件定義了bad_cast異常類型(要使用type_info必須包含typeinfo頭文件)
下面是使用異常類的例子:
首先,我定義了幾個異常類,這些類也可以從標準異常類進行派生,如下
class BadInitializers { public: BadInitializers() {} }; class OutOfBounds { public: OutOfBounds(int i) { cout<<"Size "<然后要在程序中需要的地方使用throw來拋出異常類,兩個拋出異常類的例子如下
templateArray1D ::Array1D(int sz) { if(sz<0) { //throw BadInitializers(); throw invalid_argument("Size has to be bigger than 0!!!"); } size=sz; element=new T[size]; } template T &Array1D ::operator[](int i) const { if(i<0||i>=size) { throw OutOfBounds(i); } return element[i]; } 然后在主程序中使用try...catch...來捕獲異常,并進行相應(yīng)的處理,如下
try { int i=0; Array1Da1(5); a1[0]=1; a1[1]=3; a1[2]=5; a1[3]=7; a1[4]=8; Array1D a2(a1); for(i=0;i a3(5); a3=a1+a2; cout< 六、try finally使用
__try
{
file://保護塊
}
__finally
{
file://結(jié)束處理程序
}
在上面的代碼段中,操作系統(tǒng)和編譯程序共同來確保結(jié)束處理程序中的__f i n a l l y代碼塊能夠被執(zhí)行,不管保護體(t r y塊)是如何退出的。不論你在保護體中使用r e t u r n,還是g o t o,或者是longjump,結(jié)束處理程序(f i n a l l y塊)都將被調(diào)用。我們來看一個實列:(返回值:10, 沒有Leak,性能消耗:小)
DWORD Func_SEHTerminateHandle() { DWORD dwReturnData = 0; HANDLE hSem = NULL; const char* lpSemName = "TermSem"; hSem = CreateSemaphore(NULL, 1, 1, lpSemName); __try { WaitForSingleObject(hSem,INFINITE); dwReturnData = 5; } __finally { ReleaseSemaphore(hSem,1,NULL); CloseHandle(hSem); } dwReturnData += 5; return dwReturnData; }這段代碼應(yīng)該只是做為一個基礎(chǔ)函數(shù),我們將在后面修改它,來看看結(jié)束處理程序的作用:
====================
在代碼加一句:(返回值:5, 沒有Leak,性能消耗:中下)DWORD Func_SEHTerminateHandle() { DWORD dwReturnData = 0; HANDLE hSem = NULL; const char* lpSemName = "TermSem"; hSem = CreateSemaphore(NULL, 1, 1, lpSemName); __try { WaitForSingleObject(hSem,INFINITE); dwReturnData = 5; return dwReturnData; } __finally { ReleaseSemaphore(hSem,1,NULL); CloseHandle(hSem); } dwReturnData += 5; return dwReturnData; }在try塊的末尾增加了一個return語句。這個return語句告訴編譯程序在這里要退出這個函數(shù)并返回dwTemp變量的內(nèi)容,現(xiàn)在這個變量的值是5。但是,如果這個return語句被執(zhí)行,該線程將不會釋放信標,其他線程也就不能再獲得對信標的控制??梢韵胂?,這樣的執(zhí)行次序會產(chǎn)生很大的問題,那些等待信標的線程可能永遠不會恢復(fù)執(zhí)行。
通過使用結(jié)束處理程序,可以避免return語句的過早執(zhí)行。當return語句試圖退出try塊時,編譯程序要確保finally塊中的代碼首先被執(zhí)行。要保證finally塊中的代碼在try塊中的return語句退出之前執(zhí)行。在程序中,將ReleaseSemaphore的調(diào)用放在結(jié)束處理程序塊中,保證信標總會被釋放。這樣就不會造成一個線程一直占有信標,否則將意味著所有其他等待信標的線程永遠不會被分配CPU時間。
在finally塊中的代碼執(zhí)行之后,函數(shù)實際上就返回。任何出現(xiàn)在finally塊之下的代碼將不再執(zhí)行,因為函數(shù)已在try塊中返回。所以這個函數(shù)的返回值是5,而不是10。
讀者可能要問編譯程序是如何保證在try塊可以退出之前執(zhí)行finally塊的。當編譯程序檢查源代碼時,它看到在try塊中有return語句。這樣,編譯程序就生成代碼將返回值(本例中是5)保存在一個編譯程序建立的臨時變量中。編譯程序然后再生成代碼來執(zhí)行f i n a l l y塊中包含的指令,這稱為局部展開。更特殊的情況是,由于try塊中存在過早退出的代碼,從而產(chǎn)生局部展開,導(dǎo)致系統(tǒng)執(zhí)行finally塊中的內(nèi)容。在finally塊中的指令執(zhí)行之后,編譯程序臨時變量的值被取出并從函數(shù)中返回。
可以看到,要完成這些事情,編譯程序必須生成附加的代碼,系統(tǒng)要執(zhí)行額外的工作。finally塊的總結(jié)性說明
我們已經(jīng)明確區(qū)分了強制執(zhí)行finally塊的兩種情況:
? 從try塊進入finally塊的正??刂屏鳌?br/>? 局部展開:從try塊的過早退出(goto、long jump、continue、break、return等)強制控制轉(zhuǎn)移到finally塊。
第三種情況,全局展開( global unwind),這個以后再看。七、C++異常參數(shù)傳遞
從語法上看,在函數(shù)里聲明參數(shù)與在catch子句中聲明參數(shù)是一樣的,catch里的參數(shù)可以是值類型,引用類型,指針類型。例如:
try { ..... } catch(A a) { } catch(B& b) { } catch(C* c) { }盡管表面是它們是一樣的,但是編譯器對二者的處理卻又很大的不同。調(diào)用函數(shù)時,程序的控制權(quán)最終還會返回到函數(shù)的調(diào)用處,但是拋出一個異常時,控制權(quán)永遠不會回到拋出異常的地方。
class A; void func_throw() { A a; throw a; //拋出的是a的拷貝,拷貝到一個臨時對象里 } try { func_throw(); } catch(A a) //臨時對象的拷貝 { }當我們拋出一個異常對象時,拋出的是這個異常對象的拷貝。當異常對象被拷貝時,拷貝操作是由對象的拷貝構(gòu)造函數(shù)完成的。該拷貝構(gòu)造函數(shù)是對象的靜態(tài)類型(static type)所對應(yīng)類的拷貝構(gòu)造函數(shù),而不是對象的動態(tài)類型(dynamic type)對應(yīng)類的拷貝構(gòu)造函數(shù)。此時對象會丟失RTTI信息。
異常是其它對象的拷貝,這個事實影響到你如何在catch塊中再拋出一個異常。比如下面這兩個catch塊,乍一看好像一樣:
catch (A& w) // 捕獲異常 { // 處理異常 throw; // 重新拋出異常,讓它繼續(xù)傳遞 } catch (A& w) // 捕獲Widget異常 { // 處理異常 throw w; // 傳遞被捕獲異常的拷貝 }第一個塊中重新拋出的是當前異常(current exception),無論它是什么類型。(有可能是A的派生類)
第二個catch塊重新拋出的是新異常,失去了原來的類型信息。
一般來說,你應(yīng)該用throw來重新拋出當前的異常,因為這樣不會改變被傳遞出去的異常類型,而且更有效率,因為不用生成一個新拷貝。
看看以下這三種聲明:
catch (A w) ... // 通過傳值
catch (A& w) ... // 通過傳遞引用,一個被異常拋出的對象(總是一個臨時對象)可以通過普通的引用捕獲
catch (const A& w) ... //const引用catch (A w) ... // 通過傳值捕獲
會建立兩個被拋出對象的拷貝,一個是所有異常都必須建立的臨時對象,第二個是把臨時對象拷貝進w中。實際上,編譯器會優(yōu)化掉一個拷貝。同樣,當我們通過引用捕獲異常時,
catch (A& w) ... // 通過引用捕獲
catch (const A& w) ... //const引用捕獲
這仍舊會建立一個被拋出對象的拷貝:拷貝是一個臨時對象。相反當我們通過引用傳遞函數(shù)參數(shù)時,沒有進行對象拷貝。話雖如此,但是不是所有編譯器都如此。通過指針拋出異常與通過指針傳遞參數(shù)是相同的。不論哪種方法都是一個指針的拷貝被傳遞。你不能認為拋出的指針是一個指向局部對象的指針,因為當異常離開局部變量的生存空間時,該局部變量已經(jīng)被釋放。Catch子句將獲得一個指向已經(jīng)不存在的對象的指針。這種行為在設(shè)計時應(yīng)該予以避免。
另外一個重要的差異是在函數(shù)調(diào)用者或拋出異常者與被調(diào)用者或異常捕獲者之間的類型匹配的過程不同。在函數(shù)傳遞參數(shù)時,如果參數(shù)不匹配,那么編譯器會嘗試一個類型轉(zhuǎn)換,如果存在的話。而對于異常處理的話,則完全不是這樣。見一下的例子:void func_throw() { CString a; throw a; //拋出的是a的拷貝,拷貝到一個臨時對象里 } try { func_throw(); } catch(const char* s) { }盡管如此,在catch子句中進行異常匹配時可以進行兩種類型轉(zhuǎn)換。第一種是基類與派生類的轉(zhuǎn)換,一個用來捕獲基類的catch子句也可以處理派生類類型的異常。反過來,用來捕獲派生類的無法捕獲基類的異常。
第二種是允許從一個類型化指針(typed pointer)轉(zhuǎn)變成無類型指針(untyped pointer),所以帶有const void* 指針的catch子句能捕獲任何類型的指針類型異常:
catch (const void*) ... //可以捕獲所有指針異常
另外,你還可以用catch(...)來捕獲所有異常,注意是三個點。
傳遞參數(shù)和傳遞異常間最后一點差別是catch子句匹配順序總是取決于它們在程序中出現(xiàn)的順序。因此一個派生類異常可能被處理其基類異常的catch子句捕獲,這叫異常截獲,一般的編譯器會有警告。class A { public: A() { cout << "class A creates" << endl; } void print() { cout << "A" << endl; } ~A() { cout << "class A destruct" << endl; } }; class B: public A { public: B() { cout << "class B create" << endl; } void print() { cout << "B" << endl; } ~B() { cout << "class B destruct" << endl; } }; void func() { B b; throw b; } try { func(); } catch( B& b) //必須將B放前面,如果把A放前面,B放后面,那么B類型的異常會先被截獲。 { b.print(); } catch (A& a) { a.print() ; }上述內(nèi)容就是中try catch方法怎么在c++中使用,你們學(xué)到知識或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識儲備,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
標題名稱:中trycatch方法怎么在c++中使用
文章地址:http://weahome.cn/article/gdocoe.html