|
第六章 控制語句
有一種語句,你在每種編程語言控制流程語句中都可以找到。在這一章中,我介紹了C#的控制語句,它們分為兩個(gè)主 要部分: 。選擇語句 。循環(huán)語句 如果你是C或C++程序員,很多信息會讓你感到似曾相似;但是,你必須知道它們還存在著一些差別。
6.1 選擇語句 當(dāng)運(yùn)用選擇語句時(shí),你定義了一個(gè)控制語句,它的值控制了哪個(gè)語句被執(zhí)行。在C#中用到兩個(gè)選擇語句: 。if 語句 。switch 語句
6.1.1 if 語句 最先且最常用到的語句是 if 語句。內(nèi)含語句是否被執(zhí)行取決于布爾表達(dá)式: if (布爾表達(dá)式) 內(nèi)含語句 當(dāng)然,也可以有else 分枝,當(dāng)布爾表達(dá)式的值為假時(shí),該分枝就被執(zhí)行: if (布爾表達(dá)式) 內(nèi)含語句 else 內(nèi)含語句 在執(zhí)行某些語句之前就檢查一個(gè)非零長字符串的例子:
if (0 != strTest.Length) { }
這是一個(gè)布爾表達(dá)式。(!=表示不等于。) 但是,如果你來自C或者C++,可能會習(xí)慣于編寫象這樣的代碼: if (strTest.Length) { }
這在C#中不再工作,因?yàn)?if 語句僅允許布爾( bool) 數(shù)據(jù)類型的結(jié)果,而字符串的Length屬性對象返回一個(gè)整 形(integer)。編譯器將出現(xiàn)以下錯(cuò)誤信息: error CS0029: Cannot implicitly convert type 'int' to 'bool' (不能隱式地轉(zhuǎn)換類型 'int' 為 'bool'。)
上邊是你必須改變的習(xí)慣,而下邊將不會再在 if 語句中出現(xiàn)賦值錯(cuò)誤: if (nMyValue = 5) ...
正確的代碼應(yīng)為
if (nMyValue == 5) ...
因?yàn)橄嗟缺容^由==實(shí)行,就象在C和C++中一樣?匆韵掠杏玫膶Ρ炔僮鞣ǖ⒉皇撬械臄(shù)據(jù)類型都有效): == ——如果兩個(gè)值相同,返回真。 != ——如果兩個(gè)值不同,返回假。 <, <=, >, >= —— 如果滿足了關(guān)系(小于、小于或等于、大于、大于或等于),返回真。 每個(gè)操作符是通過重載操作符被執(zhí)行的,而且這種執(zhí)行對數(shù)據(jù)類型有規(guī)定。如果你比較兩個(gè)不同的類型,對于編譯 器,必須存在著一個(gè)隱式的轉(zhuǎn)換,以便自動地創(chuàng)建必要的代碼。但是,你可以執(zhí)行一個(gè)顯式的類型轉(zhuǎn)換。 清單 6.1 中的代碼演示了 if 語句的一些不同的使用場合,同時(shí)也演示了如何使用字符串?dāng)?shù)據(jù)類型。這個(gè)程序的 主要思想是,確定傳遞給應(yīng)用程序的第一個(gè)參數(shù)是否以大寫字母、小寫字母或者數(shù)字開始。
清單 6.1 確定字符的形態(tài)
1: using System; 2: 3: class NestedIfApp 4: { 5: public static int Main(string[] args) 6: { 7: if (args.Length != 1) 8: { 9: Console.WriteLine("Usage: one argument"); 10: return 1; // error level 11: } 12: 13: char chLetter = args[0][0]; 14: 15: if (chLetter >= 'A') 16: if (chLetter <= 'Z') 17: { 18: Console.WriteLine("{0} is uppercase",chLetter); 19: return 0; 20: } 21: 22: chLetter = Char.FromString(args[0]); 23: if (chLetter >= 'a' && chLetter <= 'z') 24: Console.WriteLine("{0} is lowercase",chLetter); 25: 26: if (Char.IsDigit((chLetter = args[0][0]))) 27: Console.WriteLine("{0} is a digit",chLetter); 28: 29: return 0; 30: } 31: }
始于第7行的第一個(gè) if 語段檢測參數(shù)數(shù)組是否只有一個(gè)字符串。如果不滿足條件,程序就在屏幕上顯示用法信息,并 終止運(yùn)行。 可以采取多種方法從一個(gè)字符串中提取出單個(gè)字符——既可象第13行那樣利用字符索引,也可以使用Char類的靜態(tài) FromString 方法,它返回字符串的第一個(gè)字符。 第16~20行的 if 語句塊使用一個(gè)嵌套 的if 語句塊檢查大寫字母。用邏輯“與”操作符(&&)可以勝任小寫字母的 檢測,而最后通過使用Char類的靜態(tài)函數(shù)IsDigit,就可以完成對數(shù)字的檢測。 除了“&&”操作符之外,還有另一個(gè)條件邏輯操作符,它就是代表“或”的“¦¦”。兩個(gè)邏輯操作 符都 是“短路”式的。對于“&&”操作符,意味著如果條件“與”表達(dá)式的第一個(gè)結(jié)果返回一個(gè)假值,余下的條件“與” 表達(dá)式就不會再被求值了。相對應(yīng),“¦¦”操作符當(dāng)?shù)谝粋(gè)真條件滿足時(shí),它就“短路”了。 我想讓大家理解的是,要減少計(jì)算時(shí)間,你應(yīng)該把最有可能使求值“短路”的表達(dá)式放在前面。同樣你應(yīng)該清楚,計(jì) 算 if 語句中的某些值會存在著替在的危險(xiǎn)。
if (1 == 1 ¦¦ (5 == (strLength=str.Length))) { Console.WriteLine(strLength); }
當(dāng)然,這是一個(gè)極其夸張的例子,但它說明了這樣的觀點(diǎn):第一條語句求值為真,那么第二條語句就不會被執(zhí)行,它 使變量strLength維持原值。給大家一個(gè)忠告:決不要在具有條件邏輯操作符的 if 語句中賦值。
6.1.2 switch 語句 和 if 語句相比,switch語句有一個(gè)控制表達(dá)式,而且內(nèi)含語句按它們所關(guān)聯(lián)的控制表達(dá)式的常量運(yùn)行。
switch (控制表達(dá)式) { case 常量表達(dá)式: 內(nèi)含語句 default: 內(nèi)含語句 }
控制表達(dá)式所允許的數(shù)據(jù)類型 為: sbyte, byte, short, ushort, uint, long, ulong, char, string, 或者枚舉類 型。只要使其它不同數(shù)據(jù)類型能隱式轉(zhuǎn)換成上述的任何類型,用它作為控制表達(dá)式也很不錯(cuò)。 switch 語句接以下順序執(zhí)行: 1、控制表達(dá)式求值 2、如果 case 標(biāo)簽后的常量表達(dá)式符合控制語句所求出的值,內(nèi)含語句被執(zhí)行。 3、如果沒有常量表達(dá)式符合控制語句,在default 標(biāo)簽內(nèi)的內(nèi)含語句被執(zhí)行。 4、如果沒有一個(gè)符合case 標(biāo)簽,且沒有default 標(biāo)簽,控制轉(zhuǎn)向switch 語段的結(jié)束端。 在繼續(xù)更詳細(xì)地探討switch語句之前,請看清單 6.2 ,它演示用 switch語句來顯示一個(gè)月的天數(shù)(忽略跨年度) 清單 6.2 使用switch語句顯示一個(gè)月的天數(shù)
1: using System; 2: 3: class FallThrough 4: { 5: public static void Main(string[] args) 6: { 7: if (args.Length != 1) return; 8: 9: int nMonth = Int32.Parse(args[0]); 10: if (nMonth < 1 ¦¦ nMonth > 12) return; 11: int nDays = 0; 12: 13: switch (nMonth) 14: { 15: case 2: nDays = 28; break; 16: case 4: 17: case 6: 18: case 9: 19: case 11: nDays = 30; break; 20: default: nDays = 31; 21: } 22: Console.WriteLine("{0} days in this month",nDays); 23: } 24: }
switch 語段包含于第13~21行。對于C程序員,這看起來非常相似,因?yàn)樗皇褂胋reak語句。因此,存在著一個(gè)更具 生命力的重要差別。你必須加上一個(gè)break語句(或一個(gè)不同的跳轉(zhuǎn)語句),因?yàn)榫幾g器會提醒,不允許直達(dá)下一部分。 何謂直達(dá)?在C(和C++)中,忽略break并且按以下編寫代碼是完全合法的: nVar = 1 switch (nVar) { case 1: DoSomething(); case 2: DoMore(); }
在這個(gè)例子中,在執(zhí)行了第一個(gè)case語句的代碼后,將直接執(zhí)行到其它c(diǎn)ase標(biāo)簽的代碼,直到一個(gè)break語句退出 switch語段為止。盡管有時(shí)這是一個(gè)強(qiáng)大的功能,但它更經(jīng)常地產(chǎn)生難于發(fā)現(xiàn)的缺陷。 可如果你想執(zhí)行其它c(diǎn)ase標(biāo)簽的代碼,那怎么辦? 有一種辦法,它顯示于清單6.3中。
清單 6.3 在swtich語句中使用 goto 標(biāo)簽 和 goto default
1: using System; 2: 3: class SwitchApp 4: { 5: public static void Main() 6: { 7: Random objRandom = new Random(); 8: double dRndNumber = objRandom.NextDouble(); 9: int nRndNumber = (int)(dRndNumber * 10.0); 10: 11: switch (nRndNumber) 12: { 13: case 1: 14: //什么也不做 15: break; 16: case 2: 17: goto case 3; 18: case 3: 19: Console.WriteLine("Handler for 2 and 3"); 20: break; 21: case 4: 22: goto default; 23: // everything beyond a goto will be warned as 24: // unreachable code 25: default: 26: Console.WriteLine("Random number {0}", nRndNumber); 27: } 28: } 29: }
在這個(gè)例子中,通過Random類產(chǎn)生用于控制表達(dá)式的值(第7~9行)。switch語段包含兩個(gè)對switch語句有效的跳轉(zhuǎn) 語句。 goto case 標(biāo)簽:跳轉(zhuǎn)到所說明的標(biāo)簽 goto default: 跳轉(zhuǎn)到 default 標(biāo)簽 有了這兩個(gè)跳轉(zhuǎn)語句,你可以創(chuàng)建同C一樣的功能,但是,直達(dá)不再是自動的。你必須明確地請求它。 不再使用直達(dá)功能的更深的含義為:你可任意排列標(biāo)簽,如把default標(biāo)簽放在其它所有標(biāo)簽的前面。為了說明它,我 創(chuàng)建了一個(gè)例子,故意不結(jié)束循環(huán):
switch (nSomething) { default: case 5: goto default; }
我已經(jīng)保留了其中一個(gè)swich 語句功能的討論直至結(jié)束——事實(shí)上你可以使用字符串作為常量表達(dá)式。這對于VB程序 員,可能聽起來不象是什么大的新聞,但來自C或C++的程序員將會喜歡這個(gè)新功能。 現(xiàn)在,一個(gè) switch 語句可以如以下所示檢查字符串常量了。
string strTest = "Chris"; switch (strTest) { case "Chris": Console.WriteLine("Hello Chris!"); break; }
6.2 循環(huán)語句 當(dāng)你想重復(fù)執(zhí)行某些語句或語段時(shí),依據(jù)當(dāng)前不同的任務(wù),C#提供4個(gè)不同的循環(huán)語句選擇給你使用: 。 for 語句 。foreach 語句 。 while 語句 。do 語句
6.2.1 for 語句 當(dāng)你預(yù)先知道一個(gè)內(nèi)含語句應(yīng)要執(zhí)行多少次時(shí),for 語句特別有用。當(dāng)條件為真時(shí),常規(guī)語法允許重復(fù)地執(zhí)行內(nèi)含語 句(和循環(huán)表達(dá)式): for (初始化;條件;循環(huán)) 內(nèi)含語句 請注意,初始化、條件和循環(huán)都是可選的。如果忽略了條件,你就可以產(chǎn)生一個(gè)死循環(huán),要用到跳轉(zhuǎn)語句(break 或 goto)才能退出。
for (;;) { break; // 由于某些原因 }
另外一個(gè)重點(diǎn)是,你可以同時(shí)加入多條由逗號隔開的語句到for循環(huán)的所有三個(gè)參數(shù)。例如,你可以初始化兩個(gè)變量、 擁有三個(gè)條件語句,并重復(fù)4個(gè)變量。 作為C或C++程序員,你必須了解僅有的一個(gè)變化:條件語句必須為布爾表達(dá)式,就象 if 語句一樣。 清單6.4 包含使用 for 語句的一個(gè)例子。它顯示了如何計(jì)算一個(gè)階乘,比使用遞歸函數(shù)調(diào)用還要快。
清單 6.4 在for 循環(huán)里計(jì)算一個(gè)階乘
1: using System; 2: 3: class Factorial 4: { 5: public static void Main(string[] args) 6: { 7: long nFactorial = 1; 8: long nComputeTo = Int64.Parse(args[0]); 9: 10: long nCurDig = 1; 11: for (nCurDig=1;nCurDig <= nComputeTo; nCurDig++) 12: nFactorial *= nCurDig; 13: 14: Console.WriteLine("{0}! is {1}",nComputeTo, nFactorial); 15: } 16: }
盡管該例子過于拖沓,但它作為如何使用for 語句的一個(gè)開端。首先,我本應(yīng)在初始化內(nèi)部聲明變量nCurDig: for (long nCurDig=1;nCurDig <= nComputeTo; nCurDig++) nFactorial *= nCurDig; 另一種忽略初始化的選擇如下行,因?yàn)榈?0行在for 語句的外部初始化了變量。(記住C#需要初始化變量): for (;nCurDig <= nComputeTo; nCurDig++) nFactorial *= nCurDig; 另一種改變是把++操作符移到內(nèi)含語句中: for ( ;nCurDig <= nComputeTo; ) nFactorial *= nCurDig++; 如果我也想擺脫條件語句,全部要做的是增加一條if 語句,用break 語句中止循環(huán):
for (;;) { if (nCurDig > nComputeTo) break; nFactorial *= nCurDig++; }
除了用于退出for語句的break語句外,你還可以用continue 跳過當(dāng)前循環(huán),并繼續(xù)下一次循環(huán)。 for (;nCurDig <= nComputeTo;) { if (5 == nCurDig) continue; // 這行跳過了余下的代碼 nFactorial *= nCurDig++;
}
6.2.2 foreach 語句 已經(jīng)在Visual Basic 語言中存在了很久的一個(gè)功能是,通過使用For Each 語句收集枚舉。C#通過foreach 語句,也 有一個(gè)用來收集枚舉的命令: foreach(表達(dá)式中的類型標(biāo)識符) 內(nèi)含語句 循環(huán)變量由類型和標(biāo)識符聲明,且表達(dá)式與收集相對應(yīng)。循環(huán)變量代表循環(huán)正在為之運(yùn)行的收集元素。
你應(yīng)該知道不能賦一個(gè)新值給循環(huán)變量,也不能把它當(dāng)作ref 或out 參數(shù)。這樣引用在內(nèi)含語句中被執(zhí)行的代碼。
你如何說出某些類支持foreach 語句? 簡而言之,類必須支持具有 GetEnumerator()名字的方法,而且由其所返回的 結(jié)構(gòu)、類或者接口必須具有public 方法MoveNext() 和public 屬性Current。如果你想知道更多,請閱讀語言參考手冊, 它有很多關(guān)于這個(gè)話題的詳細(xì)內(nèi)容。
對于清單 6.5 中的例子,我恰好偶然選了一個(gè)類,實(shí)現(xiàn)了所有這些需要。我用它來列舉被定義過的所有的環(huán)境變量。
清單 6.5 讀所有的環(huán)境變量
1: using System; 2: using System.Collections; 3: 4: class EnvironmentDumpApp 5: { 6: public static void Main() 7: { 8: IDictionary envvars = Environment.GetEnvironmentVariables(); 9: Console.WriteLine("There are {0} environment variables declared", envvars.Keys.Count); 10: foreach (String strKey in envvars.Keys) 11: { 12: Console.WriteLine("{0} = {1}",strKey, envvars[strKey].ToString()); 13: } 14: } 15: } 對GetEnvironmentVariables的調(diào)用返回一個(gè)IDictionary類型接口,它是由.NET框架中的許多類實(shí)現(xiàn)了的字典接口。 通過 IDictionary 接口,可以訪問兩個(gè)收集:Keys 和 Values。在這個(gè)例子里,我在foreach語句中使用Keys,接著查 找基于當(dāng)前key值的值(第12行)。 當(dāng)使用foreach時(shí),只要注意一個(gè)問題:當(dāng)確定循環(huán)變量的類型時(shí),應(yīng)該格外小心。選擇錯(cuò)誤的類型并沒有受到編譯 器的檢測,但它會在運(yùn)行時(shí)受檢測,且會引發(fā)一個(gè)異常。
6.2.3 while 語句 當(dāng)你想執(zhí)行一個(gè)內(nèi)含語句0次或更多次時(shí),while語句正是你所盼望的:
while (條件) 內(nèi)含語句
條件語句——它也是一個(gè)布爾表達(dá)式 ——控制內(nèi)含語句被執(zhí)行的次數(shù)。你可以使用 break 和continue語句來控制 while語句中的執(zhí)行語句,它的運(yùn)行方式同在for語句中的完全相同。 為了舉例while的用法,清單 6.6 說明如何使用一個(gè) StreamReader類輸出C#源文件到屏幕。
清單 6.6 顯示一個(gè)文件的內(nèi)容
1: using System; 2: using System.IO; 3: 4: class WhileDemoApp 5: { 6: public static void Main() 7: { 8: StreamReader sr = File.OpenText ("whilesample.cs"); 9: String strLine = null; 10: 11: while (null != (strLine = sr.ReadLine())) 12: { 13: Console.WriteLine(strLine); 14: } 15: 16: sr.Close(); 17: } 18: }
代碼打開文件 whilesample.cs, 接著當(dāng)ReadLine 方法返回一個(gè)不等于null的值時(shí),就在屏幕上顯示所讀取的值。注 意,我在while條件語句中用到一個(gè)賦值。如果有更多的用&&和¦¦連接起來的條件語句,我不能保證它們是 否會被執(zhí)行,因?yàn)榇嬖谥岸搪贰钡目赡堋?br> 6.2.4 do 語句 C#最后可利用的循環(huán)語句是do語句。它與while語句十分相似,僅當(dāng)經(jīng)過最初的循環(huán)之后,條件才被驗(yàn)證。
do { 內(nèi)含語句 } while (條件);
do語句保證內(nèi)含語句至少被執(zhí)行過一次,而且只要條件求值等于真,它們繼續(xù)被執(zhí)行。通過使用break語句,你可以迫 使運(yùn)行退出 do 語塊。如果你想跳過這一次循環(huán),使用continue語句。 一個(gè)如何使用do語句的例子顯示在清單 6.7中。它向用戶請求一個(gè)或多個(gè)數(shù)字,并且當(dāng)執(zhí)行程序退出do循環(huán)后計(jì)算平 均值。
清單 6.7 在do 循環(huán)中計(jì)算平均值
1: using System; 2: 3: class ComputeAverageApp 4: { 5: public static void Main() 6: { 7: ComputeAverageApp theApp = new ComputeAverageApp(); 8: theApp.Run(); 9: } 10: 11: public void Run() 12: { 13: double dValue = 0; 14: double dSum = 0; 15: int nNoOfValues = 0; 16: char chContinue = 'y'; 17: string strInput; 18: 19: do 20: { 21: Console.Write("Enter a value: "); 22: strInput = Console.ReadLine(); 23: dValue = Double.Parse(strInput); 24: dSum += dValue; 25: nNoOfValues++; 26: Console.Write("Read another value?"); 27: 28: strInput = Console.ReadLine(); 29: chContinue = Char.FromString(strInput); 30: } 31: while ('y' == chContinue); 32: 33: Console.WriteLine("The average is {0}",dSum / nNoOfValues); 34: } 35: }
在這個(gè)例子里,我在靜態(tài) Main函數(shù)中實(shí)例化 ComputeAverageApp類型的一個(gè)對象。它同樣接著調(diào)用實(shí)例的Run方法, 該方法包含了計(jì)算平均值所有必要的功能。 do 循環(huán)跨越第19~31行。條件是這樣設(shè)定的:分別回答各個(gè)問題 “y”,以決定是否要增加另一個(gè)值。輸入任何其它 字符會引起程序退出 do語塊,且平均值被計(jì)算。 正如你可以從提到的例子看出,do語句和while語句差別不太大——僅有的差別就是條件在什么時(shí)候被求值。
6.3 小結(jié) 這章解釋了如何使用C#中用到的各種選擇和循環(huán)語句。 if 語句在應(yīng)用程序中可能是最為常用的語句。當(dāng)在布爾表達(dá) 式中使用計(jì)算時(shí),編譯器會為你留意。但是,你一定要確保條件語句的短路不會阻止必要代碼的運(yùn)行。 switch 語句——盡管同樣與C語言的相應(yīng)部分相似——但也被改善了。直達(dá)不再被支持,而且你可以使用字符串標(biāo) 簽,對于C程序員,這是一種新的用法。 在這一章的最后部分,我說明如何使用for、foreach、while和do語句。語句完成各種需要,包括執(zhí)行固定次數(shù)的循 環(huán)、列舉收集元素和執(zhí)行基于某些條件的任意次數(shù)的語句。
|