最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

第 18 講:程序結(jié)構(gòu)(四):異常結(jié)構(gòu)

2021-04-02 11:40 作者:SunnieShine  | 我要投稿

異常體系是 C# 里最重要也是非常必要的控制執(zhí)行邏輯的一種體系。

和一般的教材不同,異常可能會(huì)用到非常多的超綱的東西,所以它們都只能放在以后來(lái)講。但是,由于這里我們把異常結(jié)構(gòu)作為一種控制流程,因此放在這里給大家做介紹。另一方面,為了避免超綱的內(nèi)容,我們可能會(huì)穿插一些超綱內(nèi)容到這里,簡(jiǎn)單做一個(gè)介紹;但具體詳情的話,我們就不得不放在以后介紹。

Part 1 異常的概念

異常(Exception),是將所有不期望程序遇到,但確實(shí)遇到了,但又為了避免程序產(chǎn)生嚴(yán)重崩潰問(wèn)題的一種東西。在一些別的編程語(yǔ)言里,一旦程序出錯(cuò),就會(huì)直接閃退。在 C# 里,我們擁有控制這種防止閃退的機(jī)制,來(lái)避免很多問(wèn)題。

先來(lái)看一個(gè)簡(jiǎn)單的例子。

顯然,數(shù)學(xué)知識(shí)就告訴了我們,我們無(wú)法使用 a / b,因?yàn)?b 此時(shí)是為 0 的(而 0 不能作為除數(shù))。如果我們將其寫(xiě)入程序里執(zhí)行:

你就會(huì)在運(yùn)行的時(shí)候產(chǎn)生錯(cuò)誤。在控制臺(tái)程序里,我們會(huì)看到類似這樣的信息:

而在 VS 里,我們會(huì)直接看到這個(gè)東西:

這個(gè)所謂的“Unhandled exception”直譯過(guò)來(lái)就是“未處理的異?!?。換句話說(shuō),異常是交給我們靈活處理的,為了避免程序崩潰、閃退。而這里的顯示信息,是程序崩潰的時(shí)候,提示給開(kāi)發(fā)人員看的。這種東西只要開(kāi)發(fā)人員一看,它就知道了問(wèn)題究竟出在具體的哪個(gè)位置上。比如下面的文字“at ...”,就告訴你了錯(cuò)誤是出在這個(gè)地方;而最后的“l(fā)ine 10”就是告訴你,錯(cuò)誤的代碼是在文件的第 10 行上。

這里顯示的 System.DivideByZeroException 是整個(gè)異常錯(cuò)誤而產(chǎn)生的一個(gè)數(shù)據(jù)封裝。這個(gè)東西就被稱為異常。顯示的 DivideByZeroException 是這個(gè)異常的類型。所有的錯(cuò)誤都會(huì)使用異常來(lái)表達(dá),而異常的類型專門表示出現(xiàn)的錯(cuò)誤,到底具體是什么。從這個(gè)異常的類型上直接看,就可以看出 divide by zero 是除以 0 的意思,而 exception 是這里“異?!边@個(gè)單詞的英文。以此可見(jiàn),異常的類型全部以“Exception”單詞結(jié)尾,而前面的單詞拼湊起來(lái),直譯出來(lái)就可以大體知道異常到底是指示錯(cuò)誤的問(wèn)題是什么。

“Exception”這個(gè)單詞原始的意思是“例外”。在程序設(shè)計(jì)里,如果翻譯成例外,有時(shí)候不是很好理解;當(dāng)然,你可以理解成“不屬于正常程序執(zhí)行行為的例外情況”。

這樣一來(lái),所有程序崩潰的具體緣由就使用異常變得更加體系化了。接下來(lái)我們就來(lái)說(shuō)一下,異常的捕獲和處理。

Part 2 try-catch 語(yǔ)句

2-1 示例 1

思考一下問(wèn)題。假如,我們要做一個(gè)除法計(jì)算器,除法的被除數(shù)和除數(shù)由用戶輸入(Console.ReadLine 讀?。?。

可是,我們輸入的內(nèi)容是程序無(wú)法預(yù)期控制的。比如用戶輸入一個(gè)字母 a、輸入一個(gè)減號(hào)、甚至是別的什么東西, C# 都是不可能在輸入的時(shí)候就知道這里必須是要求“非 0 的整數(shù)數(shù)據(jù)”的。因此,這個(gè)程序就會(huì)在某處產(chǎn)生異常。

此時(shí),我們?cè)囍斎?a 和 3,結(jié)果顯然會(huì)出錯(cuò),且報(bào)錯(cuò)信息出在第 4 行:

事實(shí)上,由于 a 自身就已經(jīng)不是一個(gè)數(shù)字,因此還沒(méi)等到 3 的輸入,程序就報(bào)錯(cuò)了。記住這里的 FormatException 異常類型。這個(gè)類型在稍后我們會(huì)用到。

而另一方面,我們?yōu)榱吮苊廨斎脲e(cuò)誤而產(chǎn)生程序異常的信息提示,我們可以這么改裝一下代碼:

代碼稍微臃腫了一點(diǎn),由于 ab 輸入的過(guò)程是完全一樣的,所以我們就只講一個(gè)。請(qǐng)看到第 3 行到第 15 行代碼。

首先,我們用上了死循環(huán)。死循環(huán)的作用,我們可以嘗試看下里面的語(yǔ)句來(lái)確定。里面嵌套了一個(gè) try-catch 語(yǔ)句。這個(gè)語(yǔ)句的意思是,我們嘗試去做 try 下面的大括號(hào)里的操作。如果這段代碼一旦出現(xiàn)錯(cuò)誤,必然就會(huì)產(chǎn)生異常。此時(shí),我們需要在 catch 后追加異常的類型,來(lái)表示這里我們到底需要捕獲(Catch)什么類型的異常。異常一旦被捕獲,程序就擁有了“復(fù)活”的權(quán)利,因?yàn)槌绦虻漠惓1徊东@后,程序就不會(huì)閃退了。

接著,我們?cè)?catch 的大括號(hào)里寫(xiě)上“這個(gè)異常類型的異常產(chǎn)生后,我們應(yīng)該怎么做”。從代碼里可以看出,第 11 行就是出錯(cuò)的時(shí)候應(yīng)該做的事情:輸出一行文字給用戶。文字雖然寫(xiě)的是英文,但是實(shí)際上很好翻譯:“現(xiàn)在這個(gè)輸入是不行的。請(qǐng)重新輸出這個(gè)數(shù)字?!?。而從文字可以看出,一旦輸入失敗,程序是不會(huì)退出的,而是執(zhí)行死循環(huán),讓你重新輸入一個(gè)數(shù)據(jù)進(jìn)去。

而我們?cè)?try 下面的大括號(hào)里加上了 break 語(yǔ)句。這句話加在這里很突兀,但是這么去理解就好:假設(shè)我們第 6 行代碼沒(méi)有出現(xiàn)異常,就說(shuō)明我們的輸入是正常無(wú)誤的。那么既然是沒(méi)有問(wèn)題的話,我們就不用在死循環(huán)里反復(fù)重新輸入數(shù)據(jù)了,這就是死循環(huán)和 break 語(yǔ)句一起而發(fā)揮的作用。

此時(shí),我們?cè)俅屋斎?a 字母,程序就會(huì)提示你輸入的數(shù)據(jù)不對(duì),然后要求你重新輸入,直到數(shù)據(jù)是一個(gè)整數(shù)為止。這避免了我們前文提到的輸入數(shù)據(jù)隨便導(dǎo)致的程序閃退崩潰的問(wèn)題。

另外,請(qǐng)一定注意,break 語(yǔ)句就只給 switch 和循環(huán)語(yǔ)句提供流程控制的服務(wù),因此這里的 try-catch 語(yǔ)句里使用了 break 語(yǔ)句,但它的跳轉(zhuǎn)依舊是跟外層的 while (true) 有關(guān)系,而跟 try-catch 本身沒(méi)有關(guān)系。

而且,trycatch 的大括號(hào)是不可省略的。它和 if、for 這類語(yǔ)句不同:if 等語(yǔ)句,當(dāng)大括號(hào)里只包含一個(gè)獨(dú)立的語(yǔ)句的時(shí)候,是可以不寫(xiě)大括號(hào)的;但是 trycatch 不可以省略大括號(hào)。

不過(guò),程序還有一處問(wèn)題。如果 b 是 0 怎么辦?我們可以這么改寫(xiě)輸出語(yǔ)句:

至此,程序就不會(huì)出現(xiàn)前面可能的兩處錯(cuò)誤了。當(dāng)然了,除以 0 會(huì)產(chǎn)生 DivideByZeroException,你甚至可以使用 try-catch 語(yǔ)句來(lái)捕獲這個(gè)異常類型,然后提示錯(cuò)誤信息的文字來(lái)避免程序崩潰:

“The divisor is 0, which isn't allowed in division operation.”這段文字的意思是:“除數(shù)是 0,而這個(gè)數(shù)是不能用在除法操作里的?!薄?span id="s0sssss00s" class="md-plain">

2-2 示例 2

還記得之前提到的溢出嗎?數(shù)據(jù)在溢出的時(shí)候,我們使用 checked 來(lái)控制溢出的時(shí)候產(chǎn)生錯(cuò)誤,以提示溢出錯(cuò)誤。在那篇文章里,展示到了一個(gè)叫做 OverflowException 的異常。這個(gè)異常就是專門指代溢出的。

我們可以改造代碼。比如寫(xiě)一個(gè)加法計(jì)算器,當(dāng)數(shù)據(jù)運(yùn)算超出表示范圍的時(shí)候,提示用戶,輸入數(shù)據(jù)計(jì)算結(jié)果無(wú)效。

因?yàn)樽址恍袑?xiě)不下,我就用了加號(hào)拼接字符串來(lái)將字符串折行。

當(dāng)然,字符串折行還可以使用原義字符串:

注意,折行后,字符串必須頂格書(shū)寫(xiě),因?yàn)樽址锏乃凶址ò崭襁@些字符)也是字符串的一部分,系統(tǒng)是不處理的。

從這個(gè)例子里,我們可以看出,只要算術(shù)出現(xiàn)溢出問(wèn)題,我們就產(chǎn)生異常來(lái)告知用戶數(shù)據(jù)輸入無(wú)效。

Part 3 異常捕獲需要注意的地方

顯然,異??梢员苊獬绦虮罎⒑烷W退,但是我們隨時(shí)隨地去查看問(wèn)題和異常的源頭是什么類型,然后都去捕獲,這樣真的好嗎?怕是不見(jiàn)得。C# 里很多異常類型都是可以通過(guò) catch 來(lái)捕獲掉的,這樣確實(shí)防止了程序崩潰,但很多時(shí)候,程序的閃退可以幫助我們程序員更好、更快地找到問(wèn)題所在。試想一下,異常如果一旦被捕獲,程序就不會(huì)產(chǎn)生閃退。全都捕獲掉的話,就算我們遇到了問(wèn)題,程序也不會(huì)閃退,這就會(huì)造成一個(gè)潛在的、我們無(wú)法發(fā)現(xiàn)或很難發(fā)現(xiàn)到的問(wèn)題:畢竟這樣的問(wèn)題都被捕獲掉了。因此,我們不建議隨時(shí)隨地都使用異常捕獲。

“C# 里很多異常類型都是可以通過(guò) catch 來(lái)捕獲掉的”是想告訴你,C# 里不是所有異常都能捕獲,但這部分的異常類型很少被用到;很有可能 C# 教程把語(yǔ)法全部介紹完畢了之后,這部分無(wú)法捕獲的異常類型也不會(huì)介紹到。因此,你不必?fù)?dān)心遇到它們;但另外一方面,你需要知道的是,確實(shí)存在這種異常類型。

從另外一方面來(lái)看,異常的捕獲是需要一點(diǎn)點(diǎn)性能需求的,這會(huì)耽誤一點(diǎn)點(diǎn)時(shí)間。雖然對(duì)你來(lái)說(shuō),時(shí)間并不夠多,但是對(duì)于程序來(lái)說(shuō),影響是比較大的??赡苣阌脛e的處理過(guò)程和邏輯,執(zhí)行效率會(huì)比異常捕獲好一些,且可以達(dá)到完全一樣的運(yùn)行效果。比如前面我們捕獲除以 0 的異常的問(wèn)題,我們完完全全可以通過(guò)判斷 b == 0 來(lái)過(guò)濾掉除以 0 的情況。異??刂屏鞒淌菑腻e(cuò)誤本身出發(fā)考慮的,而 b == 0 直接是通過(guò)數(shù)據(jù)本身觸發(fā)考慮的。雖然完成的方法不同,但目的是一樣的:提示用戶,0 不能作除數(shù)。但是,后者(b == 0 作為判斷條件)的處理方式就比異常捕獲要好:它直接避免了使用異常機(jī)制。

Part 4 異常實(shí)體的使用

在前文里,我們僅僅是捕獲了異常的類型,然后提供對(duì)應(yīng)的策略。但是有些時(shí)候,我們可能會(huì)需要使用異常的一些具體信息,來(lái)幫助程序員修復(fù)問(wèn)題。這個(gè)時(shí)候,我們需要使用異常的實(shí)體。

我們拿這個(gè)例子來(lái)說(shuō)。一旦拋出了異常后,我們?cè)诋惓n愋秃缶o跟一個(gè)變量:如果從理解的角度來(lái)說(shuō),你可以把這個(gè)變量當(dāng)成是這個(gè)異常類型的一個(gè)實(shí)體。當(dāng)異常拋出后,這個(gè)實(shí)體存儲(chǔ)的東西就是整個(gè)異常在產(chǎn)生的時(shí)候,記錄下來(lái)的具體錯(cuò)誤信息(包括錯(cuò)誤的具體文字信息、錯(cuò)誤的相關(guān)類型、錯(cuò)誤發(fā)生在哪里)。

比如這個(gè)例子,我們用了一個(gè)叫 ex 的變量。在 catch 里,我們使用 Console.WriteLine 輸出一行文字到屏幕上,文字里使用到了 ex.Message 這個(gè)寫(xiě)法。緊跟的 .Message 表示獲取這個(gè)實(shí)體里的文本錯(cuò)誤信息的具體內(nèi)容。

當(dāng)然,這個(gè) ex 異常的實(shí)體還包含了很多其它的東西,它們?nèi)慷际窃诋惓3霈F(xiàn)的時(shí)候,記錄下來(lái)的、對(duì)程序員有幫助的錯(cuò)誤信息。不過(guò)這里就不啰嗦了,因?yàn)槲覀冇貌簧纤鼈?;而且有些?nèi)容是超綱的。

Part 5 throw 語(yǔ)句

5-1 throw-new 語(yǔ)句

當(dāng)然,除了我們處理系統(tǒng)產(chǎn)生的異常外,我們還可以自己產(chǎn)生一個(gè)異常。這個(gè)行為叫異常的拋出(Throw)。拋出一個(gè)異常需要了解一個(gè)語(yǔ)句:throw-new 語(yǔ)句。

假設(shè)我們有 5 個(gè)變量 ab、cde,通過(guò)輸入一個(gè) 1 到 5 之間的數(shù)字來(lái)獲取對(duì)應(yīng)變量的數(shù)值,我們的代碼可以這么寫(xiě):

注意 default 部分。當(dāng)輸入的數(shù)據(jù)不是 1 到 5 的話,就會(huì)執(zhí)行 default 部分的內(nèi)容。這里寫(xiě)的是 throw new Exception("The index is invalid."); 這樣一個(gè)語(yǔ)句。

throw 開(kāi)頭的語(yǔ)句就是我們這里說(shuō)的拋出異常的語(yǔ)句。當(dāng)程序員為了調(diào)試程序需要,可以嘗試添加這個(gè)語(yǔ)句來(lái)強(qiáng)制在執(zhí)行到這里的時(shí)候自動(dòng)產(chǎn)生類似前面一些圖片里這樣的嚴(yán)重錯(cuò)誤信息,以幫助程序員了解程序的執(zhí)行流程,找到和解決 bug。

注意寫(xiě)法。throw 后緊跟 new 單詞。這個(gè) new 是一個(gè)關(guān)鍵字,所以不能寫(xiě)成其它的東西。在 new 后跟上你要拋出的異常的類型名稱。比如之前的 FormatException 啊,DivideByZeroException 等等。異常類型名稱是需要你記住一些的;但是我們可以慢慢來(lái),不用一口氣記住很多,因此這里就這兩個(gè)就可以了,再算上這里的 Exception,一共是三個(gè)。Exception 異常是一種“不屬于任何異常類型的異?!?。這種異常類型是當(dāng)系統(tǒng)拋出的異常類型不夠用的時(shí)候(換句話說(shuō),就是系統(tǒng)提供的那些異常類型都不屬于的時(shí)候,這個(gè) Exception 就可以用)。比如這里,我們就可以使用這個(gè)異常,然后跟一個(gè)小括號(hào),里面寫(xiě)上異常的錯(cuò)誤信息(用一般是字符串字面量)就可以了,比如代碼里的“The index is invalid.”(編號(hào)無(wú)效)。

5-2 throw 實(shí)體 語(yǔ)句

在前文,我們捕獲了異常,并使用了異常信息的實(shí)體的內(nèi)容。當(dāng)我們有時(shí)候不得不再次在 catch 里拋出這個(gè)異常的時(shí)候,我們可以使用 throw 實(shí)體 語(yǔ)句。

這種格式下,我們就會(huì)把捕獲的異常實(shí)體再次拋出來(lái),這種行為稱為異常的重拋出(Re-throw)。

5-3 throw 語(yǔ)句

catch 部分里,我們還可以用上一種特殊的異常拋出語(yǔ)句:throw;。

catch 里寫(xiě)了一句 throw;,這就可以表示原封不動(dòng)地把錯(cuò)誤信息重新拋出來(lái)。你甚至可以不寫(xiě)出 ex 變量,就可以拋出。

在初學(xué)的時(shí)候,throw 實(shí)體;throw; 確實(shí)沒(méi)有明顯的區(qū)別。但實(shí)際上,它們的調(diào)用堆棧(Calling Stack)是不同的。調(diào)用堆棧這一點(diǎn)對(duì)于 C# 非常重要,但因?yàn)閮?nèi)容極為復(fù)雜,我們將這個(gè)超綱內(nèi)容放在以后講。你可以把調(diào)用堆棧理解成做一件事經(jīng)過(guò)多少人的手。throw; 語(yǔ)句會(huì)重新拋出原封不動(dòng)的異常信息,它可以保證拋出的異常,記錄的東西“高保真”:甚至是連哪些人動(dòng)過(guò)這個(gè)物件都記得很清楚;但是 throw 實(shí)體; 語(yǔ)句的其它東西都一樣,就只有調(diào)用堆棧不同:它會(huì)重置堆棧,使得調(diào)用方無(wú)法確認(rèn)(比如說(shuō),如果我要查看誰(shuí)動(dòng)過(guò)我的奶酪,通過(guò) throw 實(shí)體; 就無(wú)法確認(rèn)了)。

總之,我們總是建議你使用 throw; 而不是 throw 實(shí)體;。

Part 6 總結(jié)

前文我們學(xué)到了使用 try-catch 語(yǔ)句來(lái)執(zhí)行程序、捕獲異常,以及重拋出異常。不過(guò)因?yàn)閮?nèi)容講得復(fù)雜,學(xué)得簡(jiǎn)單,所以可能你看一遍也不太明白到底是什么。其實(shí),沒(méi)關(guān)系的:異常的話,按道理說(shuō)是得將了調(diào)用堆棧、講了面向?qū)ο蟮睦^承等等超綱知識(shí)點(diǎn),才可以說(shuō)的東西。但是這么講解有一個(gè)弊端,就是沒(méi)有保持內(nèi)容的系統(tǒng)化。不管怎么說(shuō),教材可能跟我的順序不同,這一點(diǎn)僅作參考。

而且,在 C# 里,異??刂七€有一個(gè)叫做 finally 的控制部分(除了 try、catch 這兩個(gè)控制部分外),但是因?yàn)檫@個(gè)內(nèi)容是超綱的(這涉及到對(duì)象的內(nèi)存釋放,完全是理論知識(shí)),所以我們不能在這里介紹:它會(huì)用到非常后面的知識(shí)點(diǎn)。到時(shí)候我們?cè)僬f(shuō)。

第 18 講:程序結(jié)構(gòu)(四):異常結(jié)構(gòu)的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
襄城县| 普兰县| 建昌县| 平江县| 呈贡县| 康定县| 六盘水市| 黔西县| 漾濞| 葵青区| 防城港市| 凤山市| 巴彦县| 盐津县| 永川市| 凭祥市| 孟津县| 张家港市| 贡嘎县| 紫金县| 平和县| 文登市| 虎林市| 亳州市| 胶州市| 樟树市| 墨竹工卡县| 晋江市| 浦北县| 新余市| 黄陵县| 遵义县| 如皋市| 驻马店市| 即墨市| 青铜峡市| 沂源县| 上高县| 鹤岗市| 石河子市| 芦山县|