有一種陳述式,你在每種程式語言控制流程陳述式中都可以找到。在這一章中,我介紹 C# 的控制陳述式,它們分為兩個主要部分:
如果你是 C 或 C++ 程式設計師,很多資訊會讓你感到似曾相識;但是,你必須知道它們還存在著一些差別。
當運用選擇陳述式時,你定義了一個控制陳述式,它的值控制了哪個陳述式被執行。在 C# 中用到兩個選擇陳述式:
if 陳述式switch 陳述式最先且最常用到的陳述式是 if 陳述式。內含陳述式是否被執行取決於布林運算式:
if (布林運算式) 內含陳述式
當然,也可以有 else 分支,當布林運算式的值為假時,該分支就被執行:
if (布林運算式) 內含陳述式 else 內含陳述式
在執行某些陳述式之前就檢查一個非零長字串的例子:
if (0 != strTest.Length)
{
}
這是一個布林運算式。(!= 表示不等於。)但是,如果你來自 C 或者 C++,可能會習慣於撰寫像這樣的程式碼:
if (strTest.Length)
{
}
這在 C# 中不再工作,因為 if 陳述式僅允許布林(bool)資料類型的結果,而字串的 Length 屬性物件回傳一個整數(integer)。編譯器將出現以下錯誤訊息:
error CS0029: Cannot implicitly convert type int to bool (不能隱含地轉換類型 int 為 bool。)
上邊是你必須改變的習慣,而下邊將不會再在 if 陳述式中出現指派錯誤:
if (nMyValue = 5) ...
正確的程式碼應為:
if (nMyValue == 5) ...
因為相等比較由 == 實行,就象在 C 和 C++ 中一樣。看以下有用的比較運算子(但並不是所有的資料類型都有效):
== —— 如果兩個值相同,回傳真。!= —— 如果兩個值不同,回傳假。<, <=, >, >= —— 如果滿足了關係(小於、小於或等於、大於、大於或等於),回傳真。每個運算子是通過多載運算子被執行的,而且這種執行對資料類型有規定。如果你比較兩個不同的類型,對於編譯器,必須存在著一個隱含的轉換,以便自動地建立必要的程式碼。但是,你可以執行一個顯式的類型轉換。
清單 6.1 中的程式碼示範了 if 陳述式的一些不同的使用場合,同時也示範了如何使用字串資料類型。這個程式的主要思想是,確定傳遞給應用程式的第一個參數是否以大寫字母、小寫字母或者數字開始。
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.Parse(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 行的第一個 if 程式碼段檢測參數陣列是否只有一個字串。如果不滿足條件,程式就在螢幕上顯示用法資訊,並終止執行。
可以採取多種方法從一個字串中提取出單個字元——既可像第 13 行那樣利用字元索引,也可以使用 Char 類別的靜態 Parse 方法,它回傳字串的第一個字元。
第 16~20 行的 if 陳述式區塊使用一個巢狀的 if 陳述式區塊檢查大寫字母。用邏輯「與」運算子(&&)可以勝任小寫字母的檢測,而最後通過使用 Char 類別的靜態函式 IsDigit,就可以完成對數字的檢測。
除了「&&」運算子之外,還有另一個條件邏輯運算子,它就是代表「或」的「||」。兩個邏輯運算子都是「短路」式的。對於「&&」運算子,意味著如果條件「與」運算式的第一個結果回傳一個假值,剩下的條件「與」運算式就不會再被求值了。相對應,「||」運算子當第一個真條件滿足時,它就「短路」了。
我想讓大家理解的是,要減少計算時間,你應該把最有可能使求值「短路」的運算式放在前面。同樣你應該清楚,計算 if 陳述式中的某些值會存在著潛在的危險。
if (1 == 1 || (5 == (strLength = str.Length)))
{
Console.WriteLine(strLength);
}
當然,這是一個極其誇張的例子,但它說明了這樣的觀點:第一條陳述式求值為真,那麼第二條陳述式就不會被執行,它使變數 strLength 維持原值。給大家一個忠告:決不要在具有條件邏輯運算子的 if 陳述式中指派值。
和 if 陳述式相比,switch 陳述式有一個控制運算式,而且內含陳述式按它們所關聯的控制運算式的常數執行。
switch (控制運算式)
{
case 常數運算式:
內含陳述式
default:
內含陳述式
}
控制運算式所允許的資料類型為:sbyte, byte, short, ushort, uint, long, ulong, char, string, 或者列舉類型。只要使其他不同資料類型能隱含轉換成上述的任何類型,用它作為控制運算式也很不錯。
switch 陳述式接以下順序執行:
case 標籤後的常數運算式符合控制陳述式所求出的值,內含陳述式被執行。default 標籤內的內含陳述式被執行。case 標籤,且沒有 default 標籤,控制轉向 switch 程式碼段的結束端。在繼續更詳細地探討 switch 陳述式之前,請看清單 6.2,它示範用 switch 陳述式來顯示一個月的天數(忽略閏年)。
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 程式設計師,這看起來非常相似,因為它不使用 break 陳述式。因此,存在著一個更具生命力的重要差別。你必須加上一個 break 陳述式(或一個不同的跳躍陳述式),因為編譯器會提醒,不允許貫穿到下一部分。
何謂貫穿?在 C(和 C++)中,忽略 break 並且按以下撰寫程式碼是完全合法的:
nVar = 1
switch (nVar)
{
case 1:
DoSomething();
case 2:
DoMore();
}
在這個例子中,在執行了第一個 case 陳述式的程式碼後,將直接執行到其他的 case 標籤的程式碼,直到一個 break 陳述式退出 switch 程式碼段為止。雖然有時這是一個強大的功能,但它更經常地產生難於發現的缺陷。
可如果你想執行其他的 case 標籤的程式碼,那怎麼辦?有一種辦法,它顯示於清單 6.3 中。
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: }
在這個例子中,通過 Random 類別產生用於控制運算式的值(第 7~9 行)。switch 程式碼段包含兩個對 switch 陳述式有效的跳躍陳述式。
goto case 標籤:跳躍到所說明的標籤goto default:跳躍到 default 標籤有了這兩個跳躍陳述式,你可以建立同 C 一樣的功能,但是,貫穿不再是自動的。你必須明確地請求它。
不再使用貫穿功能的更深的含義為:你可任意排列標籤,如把 default 標籤放在其他的所有標籤前面。為了說明它,我建立了一個例子,故意不結束迴圈:
switch (nSomething)
{
default:
case 5:
goto default;
}
我已經保留了其中一個 switch 陳述式功能的討論直至結束——事實上你可以使用字串作為常數運算式。這對於 VB 程式設計師,可能聽起來不象是什麼大的新聞,但來自 C 或 C++ 的程式設計師將會喜歡這個新功能。
現在,一個 switch 陳述式可以如以下所示檢查字串常數了。
string strTest = "Chris";
switch (strTest)
{
case "Chris":
Console.WriteLine("Hello Chris!");
break;
}
當你想重複執行某些陳述式或程式碼段時,依賴迴圈陳述式……