熱線電話:0755-23712116
郵箱:contact@shuangyi-tech.com
地(dì / de)址:深圳市寶安區沙井街道(dào)後亭茅洲山工業園工業大(dà)廈全至科技創新園科創大(dà)廈2層2A
程序運行時(shí)常會碰到(dào)一(yī / yì /yí)些異常情況,例如:
• 做除法的(de)時(shí)候除數爲(wéi / wèi) 0;
• 用戶輸入年齡時(shí)輸入了(le/liǎo)一(yī / yì /yí)個(gè)負數;
• 用 new 運算符動态分配空間時(shí),空間不(bù)夠導緻無法分配;
• 訪問數組元素時(shí),下标越界;打開文件讀取時(shí),文件不(bù)存在(zài)。
這(zhè)些異常情況,如果不(bù)能發現并加以(yǐ)處理,很可能會導緻程序崩潰。
所謂“處理”,可以(yǐ)是(shì)給出(chū)錯誤提示信息,然後讓程序沿一(yī / yì /yí)條不(bù)會出(chū)錯的(de)路徑繼續執行;也(yě)可能是(shì)不(bù)得不(bù)結束程序,但在(zài)結束前做一(yī / yì /yí)些必要(yào / yāo)的(de)工作,如将内存中的(de)數據寫入文件、關閉打開的(de)文件、釋放動态分配的(de)内存空間等。
一(yī / yì /yí)發現異常情況就(jiù)立即處理未必妥當,因爲(wéi / wèi)在(zài)一(yī / yì /yí)個(gè)函數執行過程中發生的(de)異常,在(zài)有的(de)情況下由該函數的(de)調用者決定如何處理更加合适。尤其像庫函數這(zhè)類提供給程序員調用,用以(yǐ)完成與具體應用無關的(de)通用功能的(de)函數,執行過程中貿然對異常進行處理,未必符合調用它的(de)程序的(de)需要(yào / yāo)。
此外,将異常分散在(zài)各處進行處理不(bù)利于(yú)代碼的(de)維護,尤其是(shì)對于(yú)在(zài)不(bù)同地(dì / de)方發生的(de)同一(yī / yì /yí)種異常,都要(yào / yāo)編寫相同的(de)處理代碼也(yě)是(shì)一(yī / yì /yí)種不(bù)必要(yào / yāo)的(de)重複和(hé / huò)冗餘。如果能在(zài)發生各種異常時(shí)讓程序都執行到(dào)同一(yī / yì /yí)個(gè)地(dì / de)方,這(zhè)個(gè)地(dì / de)方能夠對異常進行集中處理,則程序就(jiù)會更容易編寫、維護。
鑒于(yú)上(shàng)述原因,C++ 引入了(le/liǎo)異常處理機制。其基本思想是(shì):函數 A 在(zài)執行過程中發現異常時(shí)可以(yǐ)不(bù)加處理,而(ér)隻是(shì)“拋出(chū)一(yī / yì /yí)個(gè)異常”給 A 的(de)調用者,假定爲(wéi / wèi)函數 B。
拋出(chū)異常而(ér)不(bù)加處理會導緻函數 A 立即中止,在(zài)這(zhè)種情況下,函數 B 可以(yǐ)選擇捕獲 A 拋出(chū)的(de)異常進行處理,也(yě)可以(yǐ)選擇置之(zhī)不(bù)理。如果置之(zhī)不(bù)理,這(zhè)個(gè)異常就(jiù)會被拋給 B 的(de)調用者,以(yǐ)此類推。
如果一(yī / yì /yí)層層的(de)函數都不(bù)處理異常,異常最終會被拋給最外層的(de)main 函數。main 函數應該處理異常。如果main函數也(yě)不(bù)處理異常,那麽程序就(jiù)會立即異常地(dì / de)中止。
C++異常處理基本語法
C++通過 throw 語句和(hé / huò) try...catch 語句實現對異常的(de)處理。throw 語句的(de)語法如下:
throw 表達式;
該語句拋出(chū)一(yī / yì /yí)個(gè)異常。異常是(shì)一(yī / yì /yí)個(gè)表達式,其值的(de)類型可以(yǐ)是(shì)基本類型,也(yě)可以(yǐ)是(shì)類。
try...catch 語句的(de)語法如下:
try {
語句組
}
catch(異常類型) {
異常處理代碼
}
...
catch(異常類型) {
異常處理代碼
}
catch可以(yǐ)有多個(gè),但至少要(yào / yāo)有一(yī / yì /yí)個(gè)。
不(bù)妨把 try 和(hé / huò)其後{}中的(de)内容稱作“try塊”,把 catch 和(hé / huò)其後{}中的(de)内容稱作“catch塊”。
try...catch 語句的(de)執行過程是(shì):
• 執行 try 塊中的(de)語句,如果執行的(de)過程中沒有異常拋出(chū),那麽執行完後就(jiù)執行最後一(yī / yì /yí)個(gè) catch 塊後面的(de)語句,所有 catch 塊中的(de)語句都不(bù)會被執行;
• 如果 try 塊執行的(de)過程中拋出(chū)了(le/liǎo)異常,那麽拋出(chū)異常後立即跳轉到(dào)第一(yī / yì /yí)個(gè)“異常類型”和(hé / huò)拋出(chū)的(de)異常類型匹配的(de) catch 塊中執行(稱作異常被該 catch 塊“捕獲”),執行完後再跳轉到(dào)最後一(yī / yì /yí)個(gè) catch 塊後面繼續執行。
例如下面的(de)程序:
#includeusing namespace std; int main() { double m ,n; cin >> m >> n; try { cout << "before dividing." << endl; if( n == 0) throw -1; //抛出(chū)int類型異常 else cout << m / n << endl; cout << "after dividing." << endl; } catch(double d) { cout << "catch(double) " << d << endl; } catch(int e) { cout << "catch(int) " << e << endl; } cout << "finished" << endl; return 0; }
程序的(de)運行結果如下:
9 6↙
before dividing.
1.5
after dividing.
finished
說(shuō)明當 n 不(bù)爲(wéi / wèi) 0 時(shí),try 塊中不(bù)會拋出(chū)異常。因此程序在(zài) try 塊正常執行完後,越過所有的(de) catch 塊繼續執行,catch 塊一(yī / yì /yí)個(gè)也(yě)不(bù)會執行。
程序的(de)運行結果也(yě)可能如下:
9 0↙
before dividing.
catch\(int) -1
finished
當 n 爲(wéi / wèi) 0 時(shí),try 塊中會拋出(chū)一(yī / yì /yí)個(gè)整型異常。拋出(chū)異常後,try 塊立即停止執行。該整型異常會被類型匹配的(de)第一(yī / yì /yí)個(gè) catch 塊捕獲,即進入catch(inte)塊執行,該 catch 塊執行完畢後,程序繼續往後執行,直到(dào)正常結束。
如果拋出(chū)的(de)異常沒有被 catch 塊捕獲,例如,将catch(int e),改爲(wéi / wèi)catch(chare),當輸入的(de) n 爲(wéi / wèi) 0 時(shí),拋出(chū)的(de)整型異常就(jiù)沒有catch 塊能捕獲,這(zhè)個(gè)異常也(yě)就(jiù)得不(bù)到(dào)處理,那麽程序就(jiù)會立即中止,try...catch 後面的(de)内容都不(bù)會被執行。
能夠捕獲任何異常的(de) catch 語句
如果希望不(bù)論拋出(chū)哪種類型的(de)異常都能捕獲,可以(yǐ)編寫如下 catch 塊:
catch(...) {
...
}
這(zhè)樣的(de)catch 塊能夠捕獲任何還沒有被捕獲的(de)異常。例如下面的(de)程序:
#includeusing namespace std; int main() { double m, n; cin >> m >> n; try { cout << "before dividing." << endl; if (n == 0) throw - 1; //抛出(chū)整型異常 else if (m == 0) throw - 1.0; //拋出(chū) double 型異常 else cout << m / n << endl; cout << "after dividing." << endl; } catch (double d) { cout << "catch (double)" << d << endl; } catch (...) { cout << "catch (...)" << endl; } cout << "finished" << endl; return 0; }
程序的(de)運行結果如下:
9 0↙
before dividing.
catch (...)
finished
當 n 爲(wéi / wèi) 0 時(shí),拋出(chū)的(de)整型異常被catchy(...)捕獲。
程序的(de)運行結果也(yě)可能如下:
0 6↙
before dividing.
catch (double) -1
finished
當 m 爲(wéi / wèi) 0 時(shí),拋出(chū)一(yī / yì /yí)個(gè) double類型的(de)異常。雖然catch (double)和(hé / huò)catch(...)都能匹配該異常,但是(shì)catch(double)是(shì)第一(yī / yì /yí)個(gè)能匹配的(de) catch 塊,因此會執行它,而(ér)不(bù)會執行catch(...)塊。
由于(yú)catch(...)能匹配任何類型的(de)異常,它後面的(de) catch 塊實際上(shàng)就(jiù)不(bù)起作用,因此不(bù)要(yào / yāo)将它寫在(zài)其他(tā) catch 塊前面。
異常的(de)再拋出(chū)
如果一(yī / yì /yí)個(gè)函數在(zài)執行過程中拋出(chū)的(de)異常在(zài)本函數内就(jiù)被 catch 塊捕獲并處理,那麽該異常就(jiù)不(bù)會拋給這(zhè)個(gè)函數的(de)調用者(也(yě)稱爲(wéi / wèi)“上(shàng)一(yī / yì /yí)層的(de)函數”);如果異常在(zài)本函數中沒有被處理,則它就(jiù)會被拋給上(shàng)一(yī / yì /yí)層的(de)函數。例如下面的(de)程序:
#include#include using namespace std; class CException { public: string msg; CException(string s) : msg(s) {} }; double Devide(double x, double y) { if (y == 0) throw CException("devided by zero"); cout << "in Devide" << endl; return x / y; } int CountTax(int salary) { try { if (salary < 0) throw - 1; cout << "counting tax" << endl; } catch (int) { cout << "salary < 0" << endl; } cout << "tax counted" << endl; return salary * 0.15; } int main() { double f = 1.2; try { CountTax(-1); f = Devide(3, 0); cout << "end of try block" << endl; } catch (CException e) { cout << e.msg << endl; } cout << "f = " << f << endl; cout << "finished" << endl; return 0; }
程序的(de)輸出(chū)結果如下:
salary < 0
tax counted
devided by zero
f=1.2
finished
CountTa 函數拋出(chū)異常後自行處理,這(zhè)個(gè)異常就(jiù)不(bù)會繼續被拋給調用者,即 main 函數。因此在(zài) main 函數的(de) try 塊中,CountTax 之(zhī)後的(de)語句還能正常執行,即會執行f =Devide(3, 0);。
第 35 行,Devide 函數拋出(chū)了(le/liǎo)異常卻不(bù)處理,該異常就(jiù)會被拋給 Devide 函數的(de)調用者,即 main 函數。拋出(chū)此異常後,Devide 函數立即結束,第 14 行不(bù)會被執行,函數也(yě)不(bù)會返回一(yī / yì /yí)個(gè)值,這(zhè)從第 35 行 f 的(de)值不(bù)會被修改可以(yǐ)看出(chū)。
Devide 函數中拋出(chū)的(de)異常被 main 函數中類型匹配的(de) catch 塊捕獲。第 38 行中的(de)e 對象是(shì)用複制構造函數初始化的(de)。
如果拋出(chū)的(de)異常是(shì)派生類的(de)對象,而(ér) catch 塊的(de)異常類型是(shì)基類,那麽這(zhè)兩者也(yě)能夠匹配,因爲(wéi / wèi)派生類對象也(yě)是(shì)基類對象。
雖然函數也(yě)可以(yǐ)通過返回值或者傳引用的(de)參數通知調用者發生了(le/liǎo)異常,但采用這(zhè)種方式的(de)話,每次調用函數時(shí)都要(yào / yāo)判斷是(shì)否發生了(le/liǎo)異常,這(zhè)在(zài)函數被多處調用時(shí)比較麻煩。有了(le/liǎo)異常處理機制,可以(yǐ)将多處函數調用都寫在(zài)一(yī / yì /yí)個(gè) try 塊中,任何一(yī / yì /yí)處調用發生異常都會被匹配的(de) catch 塊捕獲并處理,也(yě)就(jiù)不(bù)需要(yào / yāo)每次調用後都判斷是(shì)否發生了(le/liǎo)異常。
有時(shí),雖然在(zài)函數中對異常進行了(le/liǎo)處理,但是(shì)還是(shì)希望能夠通知調用者,以(yǐ)便讓調用者知道(dào)發生了(le/liǎo)異常,從而(ér)可以(yǐ)作進一(yī / yì /yí)步的(de)處理。在(zài) catch 塊中拋出(chū)異常可以(yǐ)滿足這(zhè)種需要(yào / yāo)。例如:
#include#include using namespace std; int CountTax(int salary) { try { if( salary < 0 ) throw string("zero salary"); cout << "counting tax" << endl; } catch (string s ) { cout << "CountTax error : " << s << endl; throw; //繼續抛出(chū)捕獲的(de)異常 } cout << "tax counted" << endl; return salary * 0.15; } int main() { double f = 1.2; try { CountTax(-1); cout << "end of try block" << endl; } catch(string s) { cout << s << endl; } cout << "finished" << endl; return 0; }
程序的(de)輸出(chū)結果如下:
CountTax error:zero salary
zero salary
finished
第 14 行的(de)throw;沒有指明拋出(chū)什麽樣的(de)異常,因此拋出(chū)的(de)就(jiù)是(shì)catch 塊捕獲到(dào)的(de)異常,即 string("zero salary")。這(zhè)個(gè)異常會被 main 函數中的(de) catch 塊捕獲。
函數的(de)異常聲明列表
爲(wéi / wèi)了(le/liǎo)增強程序的(de)可讀性和(hé / huò)可維護性,使程序員在(zài)使用一(yī / yì /yí)個(gè)函數時(shí)就(jiù)能看出(chū)這(zhè)個(gè)函數可能會拋出(chū)哪些異常,C++ 允許在(zài)函數聲明和(hé / huò)定義時(shí),加上(shàng)它所能拋出(chū)的(de)異常的(de)列表,具體寫法如下:
void func() throw (int, double, A, B, C);
或
void func() throw (int, double, A, B, C){...}
上(shàng)面的(de)寫法表明 func 可能拋出(chū) int 型、double型以(yǐ)及 A、B、C 三種類型的(de)異常。異常聲明列表可以(yǐ)在(zài)函數聲明時(shí)寫,也(yě)可以(yǐ)在(zài)函數定義時(shí)寫。如果兩處都寫,則兩處應一(yī / yì /yí)緻。
如果異常聲明列表如下編寫:
void func() throw ();
則說(shuō)明func 函數不(bù)會拋出(chū)任何異常。
一(yī / yì /yí)個(gè)函數如果不(bù)交待能拋出(chū)哪些類型的(de)異常,就(jiù)可以(yǐ)拋出(chū)任何類型的(de)異常。
函數如果拋出(chū)了(le/liǎo)其異常聲明列表中沒有的(de)異常,在(zài)編譯時(shí)不(bù)會引發錯誤,但在(zài)運行時(shí), Dev C++ 編譯出(chū)來(lái)的(de)程序會出(chū)錯;用 Visual Studio 2010 編譯出(chū)來(lái)的(de)程序則不(bù)會出(chū)錯,異常聲明列表不(bù)起實際作用。
C++标準異常類
C++标準庫中有一(yī / yì /yí)些類代表異常,這(zhè)些類都是(shì)從 exception 類派生而(ér)來(lái)的(de)。常用的(de)幾個(gè)異常類如圖 1 所示。
bad_typeid、bad_cast、bad_alloc、ios_base::failure、out_of_range 都是(shì) exception 類的(de)派生類。C++ 程序在(zài)碰到(dào)某些異常時(shí),即使程序中沒有寫 throw 語句,也(yě)會自動拋出(chū)上(shàng)述異常類的(de)對象。這(zhè)些異常類還都有名爲(wéi / wèi) what 的(de)成員函數,返回字符串形式的(de)異常描述信息。使用這(zhè)些異常類需要(yào / yāo)包含頭文件stdexcept。
下面分别介紹以(yǐ)上(shàng)幾個(gè)異常類。本節程序的(de)輸出(chū)以(yǐ) Visual Studio 2010爲(wéi / wèi)準,Dev C++ 編譯的(de)程序輸出(chū)有所不(bù)同。
1) bad_typeid
使用typeid 運算符時(shí),如果其操作數是(shì)一(yī / yì /yí)個(gè)多态類的(de)指針,而(ér)該指針的(de)值爲(wéi / wèi) NULL,則會拋出(chū)此異常。
2) bad_cast
在(zài)用dynamic_cast 進行從多态基類對象(或引用)到(dào)派生類的(de)引用的(de)強制類型轉換時(shí),如果轉換是(shì)不(bù)安全的(de),則會拋出(chū)此異常。程序示例如下:
#include#include using namespace std; class Base { virtual void func() {} }; class Derived : public Base { public: void Print() {} }; void PrintObj(Base & b) { try { Derived & rd = dynamic_cast (b); //此轉換若不(bù)安全,會拋出(chū) bad_cast 異常 rd.Print(); } catch (bad_cast & e) { cerr << e.what() << endl; } } int main() { Base b; PrintObj(b); return 0; }
程序的(de)輸出(chū)結果如下:
Bad dynamic_cast!
在(zài) PrintObj 函數中,通過 dynamic_cast 檢測 b 是(shì)否引用的(de)是(shì)一(yī / yì /yí)個(gè) Derived 對象,如果是(shì),就(jiù)調用其 Print 成員函數;如果不(bù)是(shì),就(jiù)拋出(chū)異常,不(bù)會調用 Derived::Print。
3) bad_alloc
在(zài)用 new運算符進行動态内存分配時(shí),如果沒有足夠的(de)内存,則會引發此異常。程序示例如下:
#include#include using namespace std; int main() { try { char * p = new char[0x7fffffff]; //無法分配這(zhè)麽多空間,會抛出(chū)異常 } catch (bad_alloc & e) { cerr << e.what() << endl; } return 0; }
程序的(de)輸出(chū)結果如下:
bad allocation
ios_base::failure
在(zài)默認狀态下,輸入輸出(chū)流對象不(bù)會拋出(chū)此異常。如果用流對象的(de) exceptions 成員函數設置了(le/liǎo)一(yī / yì /yí)些标志位,則在(zài)出(chū)現打開文件出(chū)錯、讀到(dào)輸入流的(de)文件尾等情況時(shí)會拋出(chū)此異常。此處不(bù)再贅述。
4) out_of_range
用vector 或 string 的(de) at 成員函數根據下标訪問元素時(shí),如果下标越界,則會拋出(chū)此異常。例如:
#include#include #include #include using namespace std; int main() { vector v(10); try { v.at(100) = 100; //拋出(chū) out_of_range 異常 } catch (out_of_range & e) { cerr << e.what() << endl; } string s = "hello"; try { char c = s.at(100); //拋出(chū) out_of_range 異常 } catch (out_of_range & e) { cerr << e.what() << endl; } return 0; }
程序的(de)輸出(chū)結果如下:
invalid vector
invalid string position
如果将v.at(100)換成v[100],将s.at(100)換成s[100],程序就(jiù)不(bù)會引發異常(但可能導緻程序崩潰)。因爲(wéi / wèi) at 成員函數會檢測下标越界并拋出(chū)異常,而(ér) operator[] 則不(bù)會。operator [] 相比 at 的(de)好處就(jiù)是(shì)不(bù)用判斷下标是(shì)否越界,因此執行速度更快。