C++程序設(shè)計第十五章異常_第1頁
C++程序設(shè)計第十五章異常_第2頁
C++程序設(shè)計第十五章異常_第3頁
C++程序設(shè)計第十五章異常_第4頁
C++程序設(shè)計第十五章異常_第5頁
已閱讀5頁,還剩27頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

1、C+程序設(shè)計第15章異常程序中經(jīng)常要檢查處理各種錯誤情形,如果用傳統(tǒng)的流程控制語句來處理,很容易使程序邏輯混亂。異常(exception)就是一種專門用于檢測錯誤并處理的一種機制,使程序保持邏輯清晰,并改進(jìn)程序的可靠性。C+語言提供了基本的異常處理機制。本章主要介紹異常的概念、語句、異常類型架構(gòu)及應(yīng)用??煽康木幊虘?yīng)盡可能地、及時地檢測到各種異常情形,盡可能在本地處理。盡管有時自己不能處理,也應(yīng)該向調(diào)用方提供詳細(xì)的出錯信息,使調(diào)用方能得到充分信息,從而采取合適方式來處理異常。15.1異常的概念異常是什么概念?異常就是在程序運行中發(fā)生的難以預(yù)料的、不正常的事件而導(dǎo)致偏離正常流程的現(xiàn)象。例如:訪問數(shù)

2、組元素的下標(biāo)越界,在越界時又寫入了數(shù)據(jù);用new動態(tài)申請內(nèi)存而返回空指針(可能是因內(nèi)存不足);算術(shù)運算上溢出或下溢出;整數(shù)除法中除數(shù)為0;調(diào)用函數(shù)時提供了無效實參,如指針實參為空指針(如用空指針來調(diào)用strlen函數(shù));通過掛空指針或掛空引用來訪問對象;輸入整數(shù)或浮點數(shù)失??;I/O錯誤,等等。上面列出的情形之一如果發(fā)生,就可能導(dǎo)致運行錯誤而終止程序。發(fā)生異常將導(dǎo)致正常流程不能進(jìn)行,就需要對異常進(jìn)行處理。那么異常處理是什么概念?異常處理(exceptionhandling)就是在運行時刻對異常進(jìn)行檢測、捕獲、提示、傳遞等過程。如果采用傳統(tǒng)的if-else語句來檢測處理所有可能發(fā)生的異常,很容易導(dǎo)

3、致程序流程混亂,分不清正常流程與異常處理,而且在處理一個異常時往往又引入了新的異常。假設(shè)要設(shè)計一個函數(shù),從一個文本文件中讀取數(shù)據(jù)得到一個float矩陣。該文件應(yīng)存放一個m*n的float矩陣,頭兩個整數(shù)說明其行數(shù)m和列數(shù)n。你要把它讀入并創(chuàng)建一個矩陣對象,以備下一步計算。如果你認(rèn)為文本文件不會有錯,完全按正常編程,不超過10條語句就能完成。如果這個文本文件是別人提供的,而且你的函數(shù)將提供給其它人使用,那么你在每一步都要考慮可能出現(xiàn)的錯誤,此時就可能需要30條語句來處理。例如,可能的出錯情形如下:打開文件出錯,文件名可能有誤;讀取行數(shù)m或者列數(shù)n可能出錯;讀取每個元素時都可能出錯;矩陣數(shù)據(jù)可能不

4、完整,也會出錯。如果你用傳統(tǒng)方式來判斷處理以上這些問題,就會發(fā)現(xiàn)正常的流程被淹沒在多種異常判斷處理之中。此時就需要有一種統(tǒng)一的機制能將正常流程與異常處理分開描述,而保持程序邏輯清晰可讀,同時各種異常情形能被集中處理。C+提供了引發(fā)異常語句throw和捕獲處理異常語句try-catch。它們構(gòu)成了一種特殊的流程控制。用throw引發(fā)的每個異常都可以描述為一個對象或一個值。在程序中,每一種異常都可以描述為一種類型,可能是自定義的類,也可能是簡單的整數(shù)或字符串。在比較完善的編程中,經(jīng)常用不同的類來描述不同的異常,建立一個異常類型的繼承結(jié)構(gòu),以方便對異常類型的管理和重用。一個函數(shù)中當(dāng)檢測到某種異常發(fā)生

5、,但自己往往不知道應(yīng)該如何處理,此時就應(yīng)該通知調(diào)用方知道發(fā)生了什么異常。處理異常的一般方式是:在一個函數(shù)中發(fā)現(xiàn)一個錯誤但不能處理,就用throw語句引發(fā)一個異常,希望它的(直接或間接)調(diào)用方能夠捕獲并處理這個異常。函數(shù)的調(diào)用方如果能解決該異常,就可使用try-catch語句來捕獲并處理這種異常。如果調(diào)用方不能捕獲處理該異常,異常就被傳遞到它自己的調(diào)用方,最后到達(dá)main函數(shù)。異常的發(fā)生、傳遞與處理的過程與函數(shù)調(diào)用堆棧相關(guān)。如圖15.1所示。main函數(shù)中調(diào)用f函數(shù),f函數(shù)再調(diào)用g函數(shù)。如果g函數(shù)執(zhí)行return就正常返回到f。如果f執(zhí)行到return就正常返回到main。這是正常流程。如果g函

6、數(shù)在運行時因檢測到某種錯誤而用throw語句引發(fā)一個異常,而自己也沒有捕獲處理,此時該異常就被傳遞到f的調(diào)用方g函數(shù),而且g函數(shù)執(zhí)行終止(注意,不是返回)。對于f來說就是g函數(shù)調(diào)用發(fā)生異常。此時如果f函數(shù)沒有捕獲該異常,那么異常又被傳遞到它的調(diào)用方main函數(shù),此時f函數(shù)執(zhí)行終止。同理,此時如果main也沒有捕獲該異常,那么程序就必須終止。此時系統(tǒng)可能會跳出一個對話框告知你發(fā)生了運行錯誤。在發(fā)生異常、傳遞異常的過程中,如果有一個函數(shù)用try-catch捕獲了該異常,就不會導(dǎo)致程序終止。在運行時刻,一個異常只能被捕獲一次。假設(shè)f函數(shù)捕獲了這個異常,那么對于它的調(diào)用方main函數(shù)來說,就等于沒有發(fā)

7、生異常。異常編程的目的是改善程序的可靠性。在大型復(fù)雜程序中,完全不發(fā)生異常幾乎不可能,用傳統(tǒng)的if-else語句來檢查所有可能的異常情形,也有很大困難。編程正確性總是依賴某些假設(shè)成立為前提,異常編程就是要分析識別這些假設(shè)不成立的情形,采用面向?qū)ο缶幊碳夹g(shù)建立各種異常類型并形成繼承性架構(gòu),以處理程序中可能發(fā)生的各類異常。15.異2常類型的架構(gòu)C+的異常類型可以是任何類型,既可以是基本類型,如int整數(shù)、char*字符串,也可以是自定義類型。在比較規(guī)范的編程中,往往不能將基本類型作為異常類型。這是因為基本類型所能表示的異常種類有限。例如在一個程序中int類型只能表示一種異常情形,如果在不同函數(shù)中多

8、處引發(fā)不同語義的int異常,就很難區(qū)別不同int值的含義??赡鼙硎驹L問數(shù)組的下標(biāo)越界,也可能表示打開文件不成功。在比較規(guī)范的編程中往往根據(jù)各種錯誤情形,利用類的繼承性建立一個異常類型的架構(gòu),作用如下:對所處理的各種錯誤情形進(jìn)行準(zhǔn)確描述、抽象和歸類。方便擴展新的異常類型。在編程中方便選取引發(fā)正確的異常類型,也方便按類型來捕獲處理異常。圖15.2是定義在exception和vstdexcept中的一個異常類型架構(gòu)。在頭文件exception中定義了基類exception和一組函數(shù),在vstdexcept中定義了一組派生類,表示各類具體的異常。標(biāo)準(zhǔn)模板庫STL中的部分函數(shù)就利用了這個架構(gòu)。下面簡單介

9、紹各種異常類型。類exception是所有異常類的基類,其公共成員如下:classexceptionpublic:exception()throw();/缺省構(gòu)造函數(shù)exception(constexception&rhs)throw();/拷貝構(gòu)造函數(shù)exception&operator=(constexception&rhs)throw();/賦值操作函數(shù)virtualexception()throw();/虛析構(gòu)函數(shù)virtualconstchar*what()constthrow();/虛函數(shù);注意到每個函數(shù)原型末尾都有“throw()”,稱為函數(shù)的異常規(guī)范(exception-spe

10、cification),括號中為空說明該函數(shù)中不會引發(fā)任何異常出來。如果一個函數(shù)在執(zhí)行時可能引發(fā)某種異常類型,就應(yīng)該在“thow(異常類型表)”中說明,以告知調(diào)用方。每個異常對象都至少包含一個字符串,來說明異常發(fā)生的原因或出錯性質(zhì),稱為出錯信息。因此大多派生異常類都提供含字符串形參的構(gòu)造函數(shù)。例如:classinvalid_argument:publiclogic_errorpublic:invalid_argument(conststring&what_arg);/構(gòu)造函數(shù);一般地,派生類繼承基類的虛函數(shù)what(),返回出錯信息。如果需要的話,派生類可以改寫這個虛函數(shù),以提供更多信息。類l

11、ogic_error表示邏輯錯誤的異常類型,此類錯誤是在特定代碼執(zhí)行之前就違背了某些前置條件,例如,數(shù)據(jù)越界out_of_range,函數(shù)調(diào)用實參無效invalid_argument等,也包括特定領(lǐng)域相關(guān)的錯誤domain_error。讀者可自行擴展新類型。例如,訪問數(shù)據(jù)的下標(biāo)越界可作為out_of_range的派生類。一些邏輯錯誤意味著編程有誤,一般通過改進(jìn)編程能避免。類runtime_error表示運行期錯誤,在程序執(zhí)行期間才能檢測的錯誤。例如算術(shù)運算可能導(dǎo)致上溢出overflow_error、下溢出underflow_error、數(shù)值越界range_error等。讀者可自行擴展新的類型,

12、如空指針錯誤可作為runtime_error的派生類。一些運行期錯誤有一定偶然性,與執(zhí)行環(huán)境有關(guān),如內(nèi)存不足、打開文件失敗等,此類錯誤并不能通過改進(jìn)自身編程來消除。一個異常類所包含的信息越多,對于此類錯誤的檢測和處理就越有利。例如,要說明下標(biāo)越界錯誤,就應(yīng)該說明該下標(biāo)的當(dāng)前值是多少,可能的話,還應(yīng)說明合理的下標(biāo)范圍。這需要添加新的數(shù)據(jù)成員以及相應(yīng)的成員函數(shù)。例如:classIndex_out_of_range:publicout_of_rangeconstintindex;public:Index_out_of_range(intindex1,conststring&what_arg):ind

13、ex(index1),out_of_range(what_arg)intgetIndex()constreturnindex;再如,要說明讀取文件到特定位置時發(fā)生數(shù)據(jù)錯誤,就應(yīng)該說明文件名、出錯位置、所讀到的數(shù)據(jù)等信息。后面將詳細(xì)介紹這些派生類的設(shè)計。大多數(shù)異常派生類都很簡短,關(guān)鍵是對異常的識別和命名。C+異常分為兩類:有命名的和未命名的。有命名的異常是有類型的,基本類型(如int)或字符串(char*)或自定義類型。未命名的異常是在運行時刻某種底層錯誤引起的,例如,整數(shù)相除時除數(shù)為0,通過掛空指針或掛空引用來訪問對象,破壞當(dāng)前函數(shù)的堆棧使函數(shù)返回到錯誤地址等。未命名異常雖然也能被捕獲,但不能

14、提供確切的出錯信息。建立異常類型架構(gòu)本質(zhì)上就是對各種異常情形的識別與命名,對于異常處理具有重要作用。15.3異常處理語句C+語言的異常處理語句包括引發(fā)異常語句throw和捕獲處理語句try-catch。語句引發(fā)異常語句的語法格式為:throw表達(dá)式;其中,關(guān)鍵字throw表示要引發(fā)一個異常到當(dāng)前作用域之外。表達(dá)式值的類型作為異常事件的類型,并將表達(dá)式的值傳給捕獲處理該類型異常的程序。表達(dá)式的值可能是一個基本類型的值,也可能是一個對象。如果要引發(fā)一個對象,對象類應(yīng)該事先設(shè)計好。一個類表示了一種異常事件,應(yīng)描述該類異常發(fā)生的原因、語境以及可能的處理方法等。如果在一個函數(shù)編程中發(fā)現(xiàn)了自己不能處理的錯

15、誤情形,就可使用throw語句引發(fā)一個異常,將它引發(fā)到當(dāng)前作用域之外。如果當(dāng)前作用域是一個函數(shù),就將異常傳遞給函數(shù)的調(diào)用方,讓調(diào)用方來處理。throw與return相似,表達(dá)式也相似,都會中止后面代碼的執(zhí)行。throw語句執(zhí)行將控制流轉(zhuǎn)到異常捕獲語句處理。這將導(dǎo)致throw語句下面相鄰語句不能執(zhí)行,而且會自動回收當(dāng)前作用域中的局部變量。例如:throwindex;/弓I發(fā)一個int異常,index是一個int變量throwindexoutofrange;/弓I發(fā)一個constchar*異常throwinvalid_argument(denominatoriszero);/弓發(fā)invalid_a

16、rgument異常最后一個throw語句執(zhí)行過程是,先創(chuàng)建一個invalid_argument對象,然后再將該對象弓發(fā)到當(dāng)前作用域之外。throw與return的含義不同。一個函數(shù)的返回值表示正常執(zhí)行的結(jié)果,要作為顯式說明的函數(shù)規(guī)范。throw語句雖然也能終止當(dāng)前函數(shù)的執(zhí)行,但表示不正常的執(zhí)行結(jié)果。一個函數(shù)只有一種返回類型,但可能弓發(fā)多種類型的異常。為了說明一個函數(shù)可能引發(fā)哪些類型的異常,可用異常規(guī)范(exceptionspecification)來說明,就是“throw(異常類型表)”。例如下面是一個求商函數(shù):doublequotient(intnumrator,intdenominator

17、)throw(invalid_argument)if(denominator=0)throwinvalid_argument(denominatoriszero);returndouble(numrator)/denominator;該函數(shù)的第一個形參除以第二個形參,返回商作為結(jié)果。該函數(shù)的原型中包含了異常規(guī)范:throw(invalidargument),說明該函數(shù)的調(diào)用可能引發(fā)invalid_argument異常。函數(shù)體中檢查第二個形參(即除數(shù)),如果除數(shù)為0,就弓發(fā)該異常。盡管異常規(guī)范目前還起不到語法檢驗的作用,但起碼能告知函數(shù)的調(diào)用方注意捕獲哪些類型的異常,而不是僅僅等待函數(shù)的返回值。

18、異常對函數(shù)設(shè)計具有重要作用。傳統(tǒng)的C函數(shù)設(shè)計不用異常。如果函數(shù)有多種結(jié)果,返回類型只能有一個,就要添加形參來表示其它結(jié)果。添加的形參往往是指針類型,在調(diào)用時要提供變量地址作為實參,在調(diào)用返回之后再判斷得到什么結(jié)果。例如,一個求商函數(shù)可能設(shè)計如下:doublequotient(intnumrator,intdenominator,int*isValid)if(denominator=0)*isValid=0;/用0表示無效除數(shù)return0;*isValid=1;/用1表示有效除數(shù)returndouble(numrator)/denominator;上面函數(shù)中商作為返回值,添加了一個引用形參來表

19、示除數(shù)是否有效。另一種可行的C函數(shù)設(shè)計是返回一個int值,0表示無效除數(shù),1表示有效除數(shù),而將商作為形參,如下所示:intquotient(intnumrator,intdenominator,double*result)if(denominator=0)return0;*result=double(numrator)/denominator;return1;可以看出,傳統(tǒng)的C函數(shù)設(shè)計把除數(shù)為0作為一種特殊情形,用if語句加以判斷處理,需要更多的函數(shù)形參,導(dǎo)致函數(shù)定義復(fù)雜化。另一方面,調(diào)用方在調(diào)用函數(shù)返回之后,就要立即用一個if語句來判斷返回值是否為1,如為1,商才有效,如不為1,商無效。這對

20、調(diào)用方有一種強迫性,必須立即做出判斷,這將導(dǎo)致程序邏輯復(fù)雜化,而且難以清晰表達(dá)正常流程。異常是C+提供的一種新概念,表示了偏離正常流程的小概率事件。異常不應(yīng)該使正常流程的描述復(fù)雜化,也不應(yīng)該讓調(diào)用方忽視可能發(fā)生的異常。調(diào)用方可以選擇在適當(dāng)?shù)牡胤郊胁东@處理多種異常,就要用到try-catch語句。使用throw語句,應(yīng)注意以下要點:根據(jù)當(dāng)前異常情形,應(yīng)選擇更準(zhǔn)確、更具體的異常類型來引發(fā),而避免引發(fā)抽象的類型。例如,如果在new申請內(nèi)存之后,如果發(fā)現(xiàn)返回空指針,此時應(yīng)引發(fā)OutOfMemory類型的異常,而不是NullPointer異常,也不是更抽象的runtime_error或者excepti

21、on。準(zhǔn)確具體的異常信息對于調(diào)用方的處理非常重要,否則就可能導(dǎo)致誤解。如果一個函數(shù)中使用throw語句引發(fā)異常到函數(shù)之外,應(yīng)該在函數(shù)原型中用異常規(guī)范準(zhǔn)確描述,即“throw(異常類型表)”,使調(diào)用方知道可能引發(fā)的異常類型,提醒調(diào)用方不要忽視。雖然throw語句可以在函數(shù)中任何地方執(zhí)行,但應(yīng)盡可能避免在構(gòu)造函數(shù)、析構(gòu)函數(shù)中使用throw語句,因為這將導(dǎo)致對象的構(gòu)建和撤銷過程中出現(xiàn)底層內(nèi)存錯誤,可能會導(dǎo)致程序在捕獲到異常之前就被終止。后面15.8節(jié)將分析其原因。一般來說,異常發(fā)生總是有條件的,往往在一條if語句檢測到某個假設(shè)條件不成立時,才用throw語句引發(fā)異常,以阻止下面代碼執(zhí)行。在一個函數(shù)中

22、無條件引發(fā)異常,只有一個理由,就是不想讓其它函數(shù)調(diào)用,例如,一些實體類的拷貝構(gòu)造函數(shù)和賦值操作函數(shù)如果不想被調(diào)用,就將這些函數(shù)設(shè)為私有,同時用一條throw語句避免本類其它函數(shù)執(zhí)行。千萬不要認(rèn)為,只要我的編程中沒有throw語句就不會引發(fā)異常,沒有異常就是可靠的。你可以暫時忽略異常,但當(dāng)假設(shè)條件不滿足,異??倳l(fā)生。當(dāng)異常發(fā)生時你就不知道在何處出現(xiàn)異常,也不知道什么原因?qū)е庐惓?,更不知道如何處理能使程序繼續(xù)執(zhí)行。語句捕獲處理異常的語句是try-catch語句,一條try-catch語句由一個try子句(一條復(fù)合語句)和多個catch子句組成。一個catch子句包括一個異常類型及變量和一個異常處

23、理器(一條復(fù)合語句)。語法格式如下:try可能引發(fā)異常的語句序列;/受保護(hù)代碼catch(異常類型1異常變量1)處理代碼1;/異常處理器1catch(異常類型2異常變量2)處理代碼2;/異常處理器2.catch(.)處理代碼;/異常處理器其中,關(guān)鍵字try之后的一個復(fù)合語句稱為try子句。這個復(fù)合語句中的代碼被稱為受保護(hù)代碼,包含多條語句。受保護(hù)代碼描述正常的執(zhí)行流程,但這些語句的執(zhí)行卻可能引發(fā)異常。如果執(zhí)行沒有發(fā)生異常,try-catch語句就正常結(jié)束,開始執(zhí)行其下面語句。如果引發(fā)了某種類型的異常,就按catch子句順序逐個匹配異常類型,捕獲并處理該異常。如果異常被捕獲,而且處理過程中未引發(fā)

24、新的異常,try-catch語句就正常結(jié)束。如果異常未被捕獲,該異常就被引發(fā)到外層作用域。圖15.3表示了try-catch語句的組成結(jié)構(gòu)。圖語句的組成結(jié)構(gòu)一條語句執(zhí)行引發(fā)異常,有以下3種可能的原因:1、該語句是throw語句。2、調(diào)用函數(shù)引發(fā)了異常。3、表達(dá)式執(zhí)行引發(fā)了未命名的異常,如整數(shù)除數(shù)為0、掛空訪問等例如下面try-catch語句,調(diào)用了前面介紹的求商函數(shù)quotient。tryTOC o 1-5 h zresult=quotient(n1,n2);/AcoutThequotientisresult;/B/.catch(invalid_argumentex)/Ccoutinvalid

25、_argument:ex.what();/DA行調(diào)用quotient函數(shù),如果沒有引發(fā)異常,就執(zhí)行B行,然后try-catch語句就執(zhí)行完畢。如果A行引發(fā)了某種異常,B行就不執(zhí)行,從C行開始匹配異常類型,因為A行函數(shù)調(diào)用可能引發(fā)的異常類型正式catch子句要捕獲的異常類型invalid_argument,故此該異常對象就替代了ex形參,之后再執(zhí)行后面的一個復(fù)合語句,D行調(diào)用異常對象ex的成員函數(shù)得到錯誤信息,然后打印出來。try-catch語句執(zhí)行完畢。無論是否發(fā)生異常,這個try-catch語句都能執(zhí)行完畢,下面語句都能執(zhí)行。異常是按其類型進(jìn)行捕獲處理的。一個catch子句僅捕獲一類異常。一

26、個catch子句由一個異常類型及變量和一個異常處理器(一條復(fù)合語句)構(gòu)成。異常類型及變量指明要捕獲的異常的類型,以及接受異常對象的變量。例如catch(invalid_argumentex),要捕獲的異常類型為invalid_argument,如果真的捕獲到該類異常,那么變量ex就持有這個異常對象,這個對象就是前面用throw語句引發(fā)出來的。有一種特殊的catch子句,就是catch(.),該子句能匹配任何類型的異常,包括未命名的異常,不過異常對象或值不能被變量捕獲,故此不能提供確切的錯誤信息。在多個catch子句中,這種catch子句應(yīng)該排在最后。在執(zhí)行try子句中的受保護(hù)代碼時,如果引發(fā)一

27、個異常,系統(tǒng)就到catch子句中尋找處理該異常類型的入口。這種尋找過程稱為異常類型匹配。按如下步驟進(jìn)行:由throw語句引發(fā)異常事件之后,系統(tǒng)依次檢查catch子句以尋找相匹配的處理異常事件入口。如果某個catch子句的異常類型說明與被引發(fā)出來的異常事件類型相一致,該異常就被捕獲,然后執(zhí)行該子句的異常處理器代碼。如果有多個catch子句的異常類型相匹配,按照前后次序只執(zhí)行第一個匹配的異常處理代碼。因此較具體的派生類異常應(yīng)該在匹配在前,以提供最具體詳細(xì)的信息,而較抽象的基類異常應(yīng)該排在后面。若沒有找到任何相匹配的catch子句,該異常就被傳遞到外層作用域。如果外層作用域是函數(shù),就傳遞到函數(shù)的調(diào)用

28、方。一個異常的生命期從創(chuàng)建、初始化之后,被throw引發(fā)出來,然后被某個catch子句捕獲,其生命期就結(jié)束了。一個異常從引發(fā)出來到被捕獲,可能穿越多層作用域或函數(shù)調(diào)用。如果到main函數(shù)都未被捕獲,將導(dǎo)致程序被迫終止。從圖15.3中可以看出,try-catch語句的執(zhí)行結(jié)果有兩個:正常和異常。表15.1分析了try-catch語句的4種具體情形。表語句執(zhí)行結(jié)果序號結(jié)果具體情形1正常完畢受保護(hù)代碼未引發(fā)異常2正常完畢受保護(hù)代碼引發(fā)了異常,但異常被某個catch子句捕獲3異常退出受保護(hù)代碼引發(fā)了異常,但未被catch子句捕獲4異常退出受保護(hù)代碼引發(fā)了異常,而且被某個catch子句捕獲,但在異常處理

29、器中又引發(fā)了新的異常,或者用“throw;”語句把剛捕獲的異常又重新引發(fā)出來分析下面try-catch語句的可能結(jié)果:tryresult=quotient(n1,n2);coutThequotientisresult;/.catch(invalid_argumentex)coutinvalid_argument:ex.what();catch(logic_errorex)coutlogic_error:ex.what();catch(exceptionex)coutexception:ex.what();catch(.)coutsomeunexpectedexception;上面try子句中調(diào)

30、用了可能引發(fā)異常的函數(shù)quotient。這個try語句包含了4個catch子句,這4個catch子句的次序是較具體的派生類放在前面,較抽象的基類放在后面。最后一個catch子句可匹配捕獲任意類型的異常,但因得不到異常對象,故此不能提供更多信息。在一次執(zhí)行時,如果引發(fā)異常,只能有一個catch子句捕獲處理該異常。由于最后一個catch子句能捕獲所有類型的異常,而且所有的異常處理器代碼中都不會引發(fā)異常,因此該try-catch語句的執(zhí)行結(jié)果是表中第1種或者第2種情形。對于try-catch語句的理解和應(yīng)用,應(yīng)注意以下幾點。try子句中的代碼,稱為受保護(hù)代碼,實際上是受到下面若干catch子句的保護(hù)

31、,使得try子句代碼可以放心去描述正常處理流程,而無需每執(zhí)行一步都要用if語句來判斷是否發(fā)生異常情形。并非try子句都可能引發(fā)異常,也并非catch子句要捕獲try子句所引發(fā)的所有異常,當(dāng)前函數(shù)只需捕獲自己能處理的異常。多個catch子句之間,不允許基類異常在前、派生類在后,否則將出現(xiàn)語法警告,這使得列在后面的派生類捕獲不到異常,而排在前面的基類先捕獲到了。try-catch語句僅適合處理異常,并不能將其作為正常流程控制。例.子3例15-1控制流程測試。#includevoidtestExcept(inti)tryif(i=1)throwcatchmewheni=1;/Aif(i=2)TOC

32、o 1-5 h zthrowi;/Bif(i=0)intd=(i+1)/i;/Ccoutdendl;/Dcouti=i;catch(inti)/Ecoutcatchanint:i;catch(char*ex)/Fcoutcatchastring:ex;catch(.)/Gcoutcatchanexceptionunknown;cout的下標(biāo)越界異常。標(biāo)準(zhǔn)模板庫STL提供的向量vectorvT是支持元素隨機訪問的一種常用容器,它有兩種隨機訪問形式:operator和at(),后者可引發(fā)out_of_range異常。#include#includeusingnamespacestd;voidmai

33、n()tryvectorvec(4);/Ainti=0;for(i=0;i4;i+)veci=i+1;for(i=0;i=4;i+)coutveci;coutendl;for(i=0;i=4;i+)coutvec.at(i)coutendl;catch(out_of_rangeex)coutoutofcatch(.)/B/Cnoexception/Dthrowexceptionwheni=4range:ex.what()endl;coutunexpectedn執(zhí)行程序,輸出如下:1234-336860191234outofrange:invalidvectorsubscript上面程序測試兩種

34、按下標(biāo)隨機訪問元素的成員函數(shù)。A行先創(chuàng)建了一個向量,包含4個int元素。B行對這4個元素初始化。C行調(diào)用operator來訪問元素,輸出第1行,當(dāng)下標(biāo)越界時,并沒有引發(fā)任何異常,只是讀取的vec4元素的值是隨機值。D行調(diào)用at(intindex)來訪問元素,輸出第2行。當(dāng)下標(biāo)越界時,引發(fā)了out_of_range異常,而不會按非法下標(biāo)讀取值。例15-3除數(shù)為0的異常。在整數(shù)除法中,如果除數(shù)為0就引發(fā)底層未命名異常,因此有必要在除法執(zhí)行之前判斷除數(shù)是否為0,如果除數(shù)為0就引發(fā)一個命名的異常來通知調(diào)用方。編程如下:#include#includeusingnamespacestd;doublequ

35、otient(intnumrator,intdenominator)throw(invalid_argument)if(denominator=0)throwinvalid_argument(denominatoriszero);/Areturndouble(numrator)/denominator;voidmain()intn1,n2;doubleresult;coutn1n2)tryresult=quotient(n1,n2);coutThequotientisresult;TOC o 1-5 h zcatch(invalid_argumentex)/Bcoutinvalid_argum

36、ent:ex.what();catch(logic_errorex)/Ccoutlogic_error:ex.what();catch(exceptionex)/Dcoutexception:ex.what();catch(.)/Ecoutsomeunexpectedexception;cout和vexception中分別提供了terminate()函數(shù),前者是老版本,作為全局函數(shù),后者是新版本,定義在std命名空間之中。在發(fā)生下面情形之一時將自動執(zhí)行terminate()函數(shù):1、引發(fā)異常最終未能捕獲。2、析構(gòu)函數(shù)在系統(tǒng)堆棧釋放時引發(fā)了異常。3、在引發(fā)某個異常之后系統(tǒng)堆棧遭破壞。缺省的ter

37、minate函數(shù)將調(diào)用abort函數(shù),但abort函數(shù)不執(zhí)行清理而簡單終止程序,因此常常需要自行定義一個函數(shù),作為terminate函數(shù)調(diào)用的函數(shù),這要先準(zhǔn)備一個無參且無返回的函數(shù)f,然后調(diào)用set_terminate(f),將函數(shù)f作為終止處理器。例15-4terminate函數(shù)的例子。#include#includeusingnamespacestd;voidterm_func()/Acoutterm_func()wascalledbyterminate().n;/.cleanuptasksperformedhere/Ifthisfunctiondoesnotexit,abortiscal

38、led.exit(-1);voidmain()inti=10,j=0,result;set_terminate(term_func);/Btryif(j=0)throwDividebyzero!;/Celseresult=i/j;catch(int)coutCaughtanintegerexception.n;cout中的相關(guān)定義如下:typedefvoid(*terminate_handler)();/函數(shù)指針類型,終止處理器terminate_handlerset_terminate(terminate_handlerph)throw();voidterminate();頭一行說明了一種函

39、數(shù)指針的類型名,第二行說明了一個函數(shù)set_terminate,將一個函數(shù)ph說明為新的終止處理器。最后一行是異常處理器函數(shù),缺省將調(diào)用abort函數(shù)。C行引發(fā)的異常類型為constchar*,顯然不能被下面的catch子句捕獲,該異常將導(dǎo)致程序終止,將執(zhí)行terminate函數(shù),因為B行設(shè)置了新的終止處理函數(shù)term_func,那么新的函數(shù)得到執(zhí)行。通常情況下,設(shè)置終止函數(shù)的目的是釋放資源,然后調(diào)用exit函數(shù)來終止程序。標(biāo)準(zhǔn)C+還支持意外處理器unexpectedhandler,但VC+6版本并不支持。15.擴5展新的異常類型雖然前面圖15.2給出了一個異常類型架構(gòu),但經(jīng)常需要擴展自己的異

40、常類型。例如,雖然out_of_range類能用于說明下標(biāo)越界,但未說明發(fā)生異常的下標(biāo)究竟值是什么。再如,前面例子中除數(shù)為0的異常使用了invalid_argument類,而實際上除數(shù)為0可能有多種情形,而不一定都作為函數(shù)實參,因此有必要自行定義除數(shù)為0的異常類。圖15.4給出了一組擴展的異常類型。擴展異常類主要是以logic_error和runtime_error為基類來定義派生類。下面構(gòu)造了一個頭文件exceptions.h,包含了一組常用的異常類。#ifndefEXCEPTIONS#defineEXCEPTIONS#include#includeusingnamespacestd;/下標(biāo)

41、越界,記錄下標(biāo)classIndex_out_of_range:publicout_of_rangeconstintindex;public:Index_out_of_range(intindex1,conststring&what_arg):index(index1),out_of_range(what_arg)intgetIndex()constreturnindex;/除數(shù)為0classDivideByZero:publicruntime_errorpublic:DivideByZero(conststring&what_arg):runtime_error(what_arg);/空指針c

42、lassNullPointer:publicruntime_errorpublic:NullPointer(conststring&what_arg):runtime_error(what_arg);/無可用內(nèi)存classOutOfMemory:publicruntime_errorpublic:OutOfMemory(conststring&what_arg):runtime_error(what_arg);/一般IO異常的基類classIOException:publicruntime_errorpublic:IOException(conststring&what_arg):runtim

43、e_error(what_arg);/打開文件失敗,記錄文件名/可能是要讀的文件不存在,也可能是要寫的文件不能創(chuàng)建classOpenFileException:publicIOExceptionpublic:OpenFileException(conststring&filename):IOException(filename);/讀取文件到特定位置時轉(zhuǎn)換失敗,記錄文件出錯位置/出錯位置可以是文本文件的數(shù)據(jù)位置,第n項出錯/也可以是二進(jìn)制文件的字節(jié)位置,第n個字節(jié)出錯classReadFileFail:publicIOExceptionconstlongerrPos;public:ReadFi

44、leFail(longpos,conststring&what_arg):errPos(pos),IOException(what_arg)constlonggetErrPos()constreturnerrPos;#endif讀者可自行擴展合適的派生類,以適合軟件開發(fā)的具體需要。下面部分例子要使用這些異常類型。15.異6常類型的應(yīng)用利用擴展的異常類型,就可對許多已有程序進(jìn)行改進(jìn)。例如前面矩陣類模板TMatrixvT中,有一個公有成員函數(shù)elemAt如下:templateT&TMatrix:elemAt(intr,intc)/按下標(biāo)訪問元素if(r=row)throwr;/行下標(biāo)越界,引發(fā)in

45、t異常if(c=col)throwc;/列下標(biāo)越界,引發(fā)int異常returndprc;原先是引發(fā)int類型異常,這容易與其它異?;煜,F(xiàn)在就可以使用更明確的Index_out_of_range類型的異常。上面2條throw語句就可以分別改為:throwIndex_out_of_range(r,rowindexinelemAt(int,int);throwIndex_out_of_range(c,colindexinelemAt(int,int);TMatrix模板中另一個公有成員函數(shù)operator()(intr,intc)調(diào)用了elemAt函數(shù),所以operator()(intr,intc

46、)函數(shù)也會引發(fā)下標(biāo)越界異常。這些函數(shù)都沒有顯式說明異常規(guī)范:throw(Index_out_of_range)這是由于VC+6沒有對異常規(guī)范進(jìn)行語法檢查(Java語言要求明確的異常規(guī)范,否則語法編譯出錯)。不過規(guī)范的設(shè)計應(yīng)該顯式說明每個函數(shù)的異常規(guī)范,以提示調(diào)用方可能引發(fā)哪些異常,避免遺忘捕獲處理。例15-5設(shè)計一個函數(shù)從一個文本文件中讀取多個浮點數(shù),放入一個向量vector中,顯示各元素,給出元素的個數(shù),并按升序排序。要讀取的文本文件包含任意多的浮點數(shù),用分隔符分開,例如:11.27.83.4這個例子將演示多種異常類型的引發(fā)和處理。編程如下:#include#include#include#

47、includeexceptions.husingnamespacestd;voidgetVectorFromFile(char*filename,vector&vfs)throw(NullPointer,OpenFileException,ReadFileFail)if(filename=NULL)throwNullPointer(filenameisnull);:ifstreamifs(filename,ios:in|ios:nocreate);if(!ifs)stringmsg=openfile:;msg+=filename;msg+=failforread;throwOpenFileEx

48、ception(msg);floatf;while(!ifs.eof()ifsf;if(ifs.fail()stringmsg=readfilefail:;msg+=filename;throwReadFileFail(vfs.size(),msg);vfs.push_back(f);ifs.close();return;voidmain()trycharfilename200;coutfilename;vectorvf;getVectorFromFile(filename,vf);cout元素個數(shù):vf.size():endl;for(inti=0;ivf.size();i+)coutvf.

49、at(i);coutendl;sort(vf.begin(),vf.end();coutaftersortedn;for(i=0;ivf.size();i+)coutvf.at(i);coutendl;catch(NullPointerex)coutex.what()endl;catch(OpenFileExceptionex)coutex.what()endl;catch(ReadFileFailex)coutex.what()atex.getErrPos()itemn;catch(out_of_rangeex)coutindexoutex.what()endl;catch(exceptio

50、nex)coutex.what()endl;catch(.)coutexceptionunknown引用作為結(jié)果。該函數(shù)可能引發(fā)3種命名異常,用異常規(guī)范throw說明,以提示調(diào)用方。該函數(shù)直接引發(fā)3種類型的異常:1、檢查形參指針是否為空,可能引發(fā)NullPointer異常;2、打開文件,可能引發(fā)OpenFileException異常,保存了出錯的文件名;3、讀浮點數(shù),可能引發(fā)ReadFileFail異常,保存了文件讀錯的位置。在函數(shù)getVectorFromFile執(zhí)行過程中只要引發(fā)任何一種異常,就不能得到結(jié)果。主函數(shù)中使用try-catch語句來完成計算并捕獲處理各種異常。先輸入一個文件名,

51、并說明一個vectorvfloat變量,再調(diào)用函數(shù)getVectorFromFile來得到結(jié)果,下面就是顯示、排序、再顯示。其中在調(diào)用at(i)函數(shù)時可能引發(fā)out_of_range異常。執(zhí)行程序,在第1行輸入一個文件名array.txt,輸出如下:inputfilenametoread:array.txt元素個數(shù):12:aftersorted讀者可自行改變輸入或改變文本文件,引入各種錯誤,看程序運行是否能正確判斷各種異常。例如:用NULL值來調(diào)用函數(shù),看是否導(dǎo)致NullPointerException。輸入錯誤的文件名是否會導(dǎo)致OpenFileException。把某個浮點數(shù)的字符該為字符,

52、看是否導(dǎo)致ReadFileFail。try子句中的代碼描述了正常執(zhí)行的邏輯,而各種異常的捕獲處理都用catch子句描述。這樣就能將正常流程與異常處理分割開,不僅提高了程序的可讀性和可維護(hù)性,而且增強了應(yīng)對多種錯誤的能力,提高了編程可靠性。15.函7數(shù)設(shè)計中的異常處理在函數(shù)設(shè)計中何時要用到異常?有以下3個原則:1、遇到小概率事件,應(yīng)考慮使用異常。一種小概率事件往往就是一種異常情形,例如,函數(shù)的指針形參在調(diào)用時卻得到了空指針實參。再例如,要輸入一個浮點數(shù),但實際輸入錯誤。小概率事件也意味著在可靠性要求不高的前提下可以推遲處理、甚至忽略。前面很多例子都有這樣一個前提,即小概率事件不會發(fā)生。反之,如果

53、不是小概率事件,就不適合用異常。例如,讀文件到文件尾eof判斷,就不適合將讀到文件尾作為一種異常來處理,它不是小概率事件,因為每一次讀取都應(yīng)判斷是否到達(dá)文件尾。2、遇到某種情形,根據(jù)當(dāng)前信息不能確定應(yīng)該如何處理,應(yīng)考慮用異常來通知調(diào)用方處理。例如,一個函數(shù)從文本文件中讀浮點數(shù)序列,文件名由形參提供,假如按調(diào)用方提供的文件名打開文件失敗,應(yīng)如何處理?此時合理的辦法就是告訴調(diào)用方,這個文件名打開失敗了,由調(diào)用方來決定是換一個文件名,還是放棄。反之,對于某種情形,如果函數(shù)可以處理而且不違背約定,那么這種情形就不適合作為異常。例如對于堆棧stack操作pop,只有先判斷堆棧不為空,才能彈出pop元素。

54、堆棧為空這種情形不適合作為異常。3、向調(diào)用方報告的某種結(jié)果的描述比較復(fù)雜,就應(yīng)考慮使用異常。傳統(tǒng)的C語言編程常用不同的int值來表示各種錯誤。例如一個函數(shù)從文本文件中讀浮點數(shù)序列,可以讓該函數(shù)返回一個int值,而且約定返回0表示正常,-1表示實參空指針,-2表示打開文件失敗,-3表示讀取數(shù)據(jù)失敗等。但返回-2時,還應(yīng)告知打開失敗的文件名。當(dāng)返回-3時,不僅要告知文件名,還應(yīng)告知導(dǎo)致讀取失敗的具體位置,即第幾個元素讀取失敗,這樣才方便調(diào)用方有效解決問題,此時就需要用異常來詳細(xì)描述。在一個函數(shù)設(shè)計中,要調(diào)用一個可能引發(fā)某種異常的函數(shù)時,有哪些處理方式?當(dāng)前函數(shù)有下面4種選擇:1、捕獲該異常并進(jìn)行處

55、理,使自己的調(diào)用方不需要捕獲處理該異常。2、捕獲該異常,在處理代碼中轉(zhuǎn)換為另一種異常,再引發(fā)出去,讓調(diào)用方來捕獲處理新的異常。3、捕獲該異常,處理(可能是記錄異常發(fā)生),再將捕獲到的異常引發(fā)出去,讓外層調(diào)用方來捕獲處理。在處理代碼中用不帶表達(dá)式的throw語句可以轉(zhuǎn)發(fā)已捕獲到的異常。4、不捕獲該異常,讓外層調(diào)用方來捕獲處理??赡苁菦]有try-catch語句,也可能有try-catch但沒有catch子句能匹配所發(fā)生的異常類型。應(yīng)采取何種處理方式取決于當(dāng)前函數(shù)所承擔(dān)的異常處理的責(zé)任。第1種方式完全承擔(dān)了該種異常處理的責(zé)任,使外層調(diào)用方可以放心調(diào)用而無需關(guān)心會發(fā)生此類異常。第4種方式則完全不承擔(dān)責(zé)

56、任,調(diào)用方必須考慮如何處理間接引發(fā)的異常。第2種和第3種方式介于兩者之間,承擔(dān)了部分責(zé)任,能捕獲處理異常,也能引發(fā)異常。無論采用哪一種方式,函數(shù)的異常規(guī)范應(yīng)告知調(diào)用方可能會引發(fā)哪些類型的異常。這應(yīng)該是函數(shù)約定的一個重要部分。例15-6異常處理流程的例子#include#includeusingnamespacestd;floatgetValue(inti)/Atryif(i0)throwindexisoutofrange;/Bthrowchar*if(i=0)throw3.14f;/Cthrowfloatif(i=1)throwi;/Dthrowintif(i=2)throw2.718;/Et

57、hrowdoublecoutintryblock,i=iendl;catch(intindex)/Fcatchintcoutcatchintexception:indexendl;catch(floatf)coutcatchfloatexception:fendl;throw;/Gthrowfloatcatch(char*msg)coutcatchchar*exception:msgendl;throwexception(msg);/Hthrowexceptioncoutbelowtry-catch,i=iendl;returni+1;voidmain()for(inti=-1;i=3;i+)

58、tryfloatf=getValue(i);coutf=fendl;catch(floatf)coutmain:catchafloatexception:fendl;catch(doubled)coutmain:catchadoubleexception:dendl;catch(exceptionex)coutmain:catchanexception:ex.what()endl;catch(.)coutcatchanexceptionunknownendl;執(zhí)行程序,輸出如下(行號是為了方便解釋而加入的):catchchar*exception:indexisoutofrangemain:c

59、atchanexception:indexisoutofrangecatchfloatexception:3.14main:catchafloatexception:3.14catchintexception:1belowtry-catch,i=1f=2main:catchadoubleexception:2.718intryblock,i=3belowtry-catch,i=3f=4A行定義的函數(shù)沒有顯式給出異常規(guī)范。在函數(shù)體中的try子句中用throw引發(fā)了4類異常,后面的3個catch子句分別捕獲了3類異常,但異常處理器代碼中又引發(fā)了異常。這些異常的引發(fā)、捕獲、再引發(fā)的關(guān)系如下:i=-1

60、;引發(fā)char*異常,被捕獲,再引發(fā)exception異常出來。i=0;引發(fā)float異常,被捕獲,再用throw;轉(zhuǎn)發(fā)float異常出來。i=1;引發(fā)int異常,被捕獲,沒有再引發(fā)其它異常出來。i=2;引發(fā)double異常,沒有被捕獲。這樣可以知道getValue函數(shù)的異常規(guī)范為throw(exception,float,double)。在main函數(shù)中用i=-1到3來調(diào)用該函數(shù),并用try-catch語句來捕獲所有這些異常。輸出情況如下:i=-1,輸出前2行。i=0,輸出第3、4行。i=1,輸出第5、6、7行。i=2,輸出第8行。i=3,無異常,輸出第9、10、11行。能否在捕獲異常之后再

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論