- Visual C# 2008開發技術詳解
- 李容等編著
- 3067字
- 2018-12-27 11:19:35
第二篇 C#語法及面向對象基礎
第2章 C#語法基礎
每種計算機語言都有其基本的語法規則,用來管理內存和處理數據。任何計算機程序都必須遵循其編寫語言的語法規則。C#語言繼承了許多C++的語法特性,簡化了C++語法的諸多復雜性。很多關鍵字和C++中的是一樣的。這對使用過C、C++、Java等語言的讀者來說是個好消息,但是C#語法并不是C++語法的簡單復制,如它對數據類型的管理更為嚴格。
本章主要內容:
● 變量的定義和使用
● 數據類型及其轉換方法
● 常用表達式
● C#基本流程控制語句
● 命名空間介紹
2.1 C#的基本語法
C#在保持C系列語言的優美表示形式的同時,實現了應用程序的快速開發。各語句之間采用分號相隔,各代碼塊使用“{”作為開始,使用“}”作為結束。代碼注釋分兩種情況,一種是行注釋,使用“//”可以實現;另一種是多行注釋,使用“/*”作為注釋行的開始,使用“*/”作為注釋行的結束。不管是行注釋,還是多行注釋,“C#”編譯器都會忽略被注釋的行。
在C#中沒有單獨的頭文件,聲明方法和類型不要求按照特定的順序。在C#中引入了namespace(叫做命名空間,詳細說明請參考后面章節中的介紹)代替C++中的包含文件,可以使用using namespace包含命名空間,這樣程序中便可以使用空間中定義的類、方法等。
下面是對第1章中的程序實例稍加改進之后的代碼。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace FormsTest { public partial class Form1 : Form { public Form1() { InitializeComponent(); //初始化控件 } private void button1_Click(object sender, EventArgs e) // button1的 //單擊事件 { //MessageBox.Show("你單擊了左邊的button1按鈕。"); } private void button2_Click(object sender, EventArgs e) // button2的 //單擊事件 { MessageBox.Show("你單擊了右邊的button2按鈕。"); //彈出消息框 } } }
可以看到語句MessageBox.Show("你單擊了左邊的button1按鈕。")被“//”注釋了。但是這并不會產生語法錯誤,因為C#中的方法可以沒有命令語句,該段代碼展示了C#的大部分基本語法。為方便開發和維護,代碼的編寫應該遵循縮進原則,具體格式如上面代碼所示,當然VS2008工具具備自動縮進功能。
2.2 變量
變量,對應著內存中的一塊存儲位置。簡單地說,變量是計算機存儲于內存中其值可改變的量。這里的“可改變”是指程序運行期間的可改變。
2.2.1 變量的聲明
在C#中變量也是對象,變量也有其方法和屬性等特征。在程序中需要首先聲明變量,然后才可以使用。可以按照如下方式命名變量。
<變量類型> <變量名>;
其中,<變量類型>有int、float、string、byte、long等。變量名的命名需要遵循以下原則。
● 第一個字符只能是字母(包括漢字)或下畫線。其余字符可以為字母(包括漢字)、數字和下畫線的組合。
● 變量名不能和C#的關鍵字或庫函數名相同。如Main、public、int、class等。
下面的變量命名是合法的。
int x; string p2p; bool _start; float p_p;
而下面的代碼則不合法。
int Main; //與庫函數同名 string 2pp; //以數字開始 bool ! start; //有標點符號 float _; //只有下畫線
另外,聲明變量還需要有一定的意義,最好名字能表示其用途。如需要定義一個bool型變量,表示是否啟動了線程,則_start是一個很好的變量名。
2.2.2 變量的賦值
在C#中對變量的賦值是非常容易的事。只需要在要賦值的變量名后邊加上“=”和所賦的值即可。如下所示代碼是給本節中定義的幾個變量賦值。
X=20; p2p=”Hello, C#! ”; _start=true; p_p=3.14159;
當然,也可以在定義變量時賦值。同時也可以利用已賦值變量給變量賦值,具體方法如下所示。
int x=20; string p2p=”Hello, C#! ”; bool _start=true; float p_p=3.14159; float r_r=p_p //將已賦值變量p_p的值賦給r_r string p3p= p_p.ToString() //將已賦值變量的p_p.ToString()方法返回值賦給p3p double Sum = 0, a, b, c;
上面代碼的最后一行同時聲明了4個double型變量。其中在聲明的同時Sum被賦初始值0,這在C#中是允許的。
程序中有時需要將變量的值輸出。其方法與在C++中略有不同,下面是有關變量定義、賦值和輸出的程序VarTest,代碼如下所示。
using System; using System.Collections.Generic; using System.Text; namespace VarTest { class Program { static void Main(string[] args) { string str; double Sum = 0, a, b, c; a = 20; b = 30; c = 40; Sum = a + b + c; //求和 str="a+b+c="; //設置字符串值 Console.Write(str); //輸出字符串 Console.WriteLine("{0}", Sum); //輸出a、b、c的和 Console.ReadKey(); } } }
運行程序,得到如圖2-1所示的結果。

圖2-1 程序運行結果
其中定義了4個double型變量Sum、a、b、c,并分別賦值,求和后再賦值給Sum變量。注意,str為字符串變量,只能存儲字符串。所有字符串都要用雙引號括起來。上述“a+b+c”實際上是一串字符,后面將詳細介紹。
Console.Write(str); Console.WriteLine("{0}", Sum);
這兩行代碼實現了寫入標準輸出流,簡單地說就是在控制臺窗口中顯示。Console.WriteLine用到了兩個參數的方法,第一個參數“{0}”表示輸出其后邊變量列表的第0個變量值。之所以叫變量列表,是因為該方法還可以同時輸出多個變量。如將上面兩行代碼用下面一行代碼代替,形式如下所示。
Console.WriteLine("{0}+{1}+{2}={3}", a, b, c, Sum);
其輸出結果如圖2-2所示。

圖2-2 程序運行結果
2.2.3 簡單數據類型
細心的讀者可能會發現,上面所介紹的變量有多種類型。其實在計算機中,為了便于管理和有效利用內存,把變量分為了多種類型,如整數型、浮點數類型、字符類型等。在C#中,所有數據類型都是從System.Object繼承而來的,也即是任何數據類型的值都可以賦值給Object類型變量。在C#中可以將數據類型分為值類型和引用類型。其中值類型又可以分為簡單類型和struct類型。前面介紹的數據類型如int、double等數據類型就叫簡單數據類型。本節只介紹簡單類型,表2-1列出了C#中的簡單數據類型。
表2-1 C#中的簡單數據類型

從表2-1中可以看出,簡單數據類型又可以分為整數類型、布爾類型、浮點類型、字符類型、字符串類型和decimal類型。
表2-1中前8種數據類型就是就是整數類型,即byte、sbyte、ushort、short、uint、int、ulong、long。
表2-1中的bool類型用來表示邏輯“真”和“假”,這在程序的邏輯判斷中經常使用。在程序中用“true”和“false”表示邏輯“真”和“假”。
2.2.4 使用簡單數據類型
程序BoolTest演示了布爾類型的用法,代碼如下所示(鑒于篇幅原因,只給出了部分代碼)。
static void Main(string[] args) { bool flagT, flagF; flagT = true; //初始化變量為true flagF = false; //初始化變量為false Console.WriteLine("The value of flagT is {0}", flagT); //輸出變量值 Console.WriteLine("The value of flagF is {0}", flagF); //輸出變量值 Console.ReadKey(); }
運行程序,效果如圖2-3所示。

圖2-3 程序運行結果
代碼第一行是變量聲明,聲明了兩個bool型變量。第二行、第三行分別對其賦值。隨后的代碼輸出兩個變量的值,bool型變量只能取“true”或“false”,若將整型數據或字符串等數據賦給它則會出錯。
說明:定義了一種數據類型變量后,該變量的表示范圍就已經確定了。如果出現表示范圍以外的數據則會產生所謂的“溢出”,這樣會導致程序發生錯誤。實際編程中應該謹慎選取數據類型。
表2-1中的double和float稱為浮點類型。浮點類型和整數類型不同,它可以表示小數位。其中double型數據可以有15到16位小數,而float類型數據有7位小數。它們都有一個缺點,就是無法精確表示很多數(一般情況的精度要求足夠了)。decimal可以彌補這個缺點,它可以表示到小數點后28位以后。decimal(十進制)類型數據常用于對數據精度要求很高的場合,如銀行、復雜的科學計算等。
程序DecimalTest對比了decimal和double類型的精度,代碼如下所示。
static void Main(string[] args) { //定義變量 decimal x; double y; x = 3.14159265358979323m; x = x / 3; y = (double)x; //輸出變量 Console.WriteLine("x={0}", x); Console.WriteLine("y={0}", y); Console.ReadKey(); }
運行程序,輸出結果如圖2-4所示。

圖2-4 程序運行結果
程序第一、二行分別定義了decimal型變量x和double型變量y。第三行給x賦值圓周率小數點后18位的數值。x對自身除以3之后,第5行代碼將decimal強制轉換后賦值給double型變量y,最后輸出兩變量的值。輸出結果中,decimal類型變量x的精度達到27位。而double類型變量y的精度只達到了12位,便將后面的數據舍棄了。
表2-1中的char表示字符類型,用于存儲字符,長度為一個字節;string是字符串類型,用于存儲字符串。字符串的長度可以為0,也可以無限長。
下面程序代碼演示了它們的部分屬性,程序名為StrChTest。
static void Main(string[] args) { char c; string str, s; c=' I' ; str="you and "; s = " are friends"; str = str + c; //字符串相加 Console.WriteLine("str={0}", str); //輸出字符串 str = str + s; //字符串相加 Console.WriteLine("str={0}", str); //輸出字符串 str = Convert.ToString(c); //將字符轉換為字符串 Console.WriteLine("str={0}", str); //輸出字符串 Console.ReadKey(); }
運行程序,結果如圖2-5所示。

圖2-5 程序運行結果
第一、二行分別定義了一個字符變量c和兩個字符串變量str和s。字符需用單引號括起來,而字符串用雙引號括起來。對它們賦值完畢后是如下語句。
str = str + c;
該語句將字符和字符串相加后的結果賦值給原來字符串。這在C#這是允許的。從圖2-5中可以看出輸出第一行str的值為“you and I”。接下來是將兩字符串相加,代碼如下所示。
str = str + s;
從輸出結果知道,后一字符串“are friends”接到了前一個字符串的末尾。如圖2-5中第二行輸出。
最后是類型轉換,代碼如下所示。
str = Convert.ToString(c);
字符不可以隱式轉換為字符串,所以有必要使用類型轉換語句。如圖2-5中第三行輸出。變量str只有一個字符,但是它仍然被當做字符串。最后,字符和字符串類型還支持包括漢字在內的所有Unicode編碼字符。
2.2.5 使用struct創建結構類型
前面講到,struct(結構)類型是一種復雜數據類型,是因為它可以包含簡單數據類型。不但如此,它還可以包含包括自身在內的其他結構類型,以及方法、屬性、索引器等。struct類型也是一種值類型,它通過值而不是通過引用方式傳遞參數。這是因為struct是值類型,而類是引用類型。這也是它與類最重要的區別。struct類型常用來封裝小型變量組。如矩形的坐標、大地方位信息等。
2.2.6 結構類型例程
下面的代碼顯示了一個結構類型的一般定義方法。
<保護級別> struct <結構名> { <保護級別> <類型> <結構成員名稱> }
此處<保護級別>有private和public。結構可以實現接口,卻無法繼承另外一個結構。正因為如此,結構成員不能被聲明為protected。
下面是一段聲明結構類型的示例代碼。
public struct point { public int x; public int y; }
上面代碼定義了一個點的坐標,其中結構名為point。成員變量是兩個整形變量x和y。其<保護級別>設置為public,讀者只需知道這是為了讓其他代碼可以訪問。
定義了結構類型就可以在程序中使用該類型了,這些類型就像簡單數據類型如int、double等,可以用來聲明具體的變量。可以用如下方法聲明。
<結構名> <結構變量名>;
如“point P; ”,聲明了一個point類型的結構變量P。該結構變量即包含了一個整型變量x和一個整型變量y。可以通過P.x和P.y訪問。在C#中用“.”表示對象的層次關系。
同樣結構類型還可以包含結構類型,代碼如下所示。
public struct Rectangle { //包含point結構 public point topleft; public point topright; public point buttomleft; public point buttomright; //包含變量 public bool used; }
該結構定義包含了4個point結構類型,和一個bool類型。其結構變量的聲明和上述聲明方法一致。如“Rectangle rec; ”,同樣可以使用如rec.tpoleft.x、rec.tpoleft.y訪問所包含結構中的對象。
需要說明的是,在結構類型中不能包含其自身。下面的代碼將會導致循環,從而產生錯誤。
public struct Rectangle { public Rectangle rec; public bool used; }
下面的代碼演示了使用struct定義結構類型的具體方法。
//程序StructTest using System; using System.Collections.Generic; using System.Text; namespace StructTest { //定義point結構 public struct point { public int x; public int y; } //定義Rectangle結構 public struct Rectangle { //包含point結構 public point topleft; public point topright; public point buttomleft; public point buttomright; public bool used; } class Program { static void Main(string[] args) { //初始化Rectangle結構變量 Rectangle rec, rc; rec.topleft.x= 200; rec.topleft.y = 200; rec.topright.x = 300; rec.topright.y = 200; rec.buttomleft.x = 200; rec.buttomleft.y = 300; rec.buttomright.x = 300; rec.buttomright.y = 300; rec.used = true; rc = rec; //輸出結構實例中的變量值 Console.WriteLine("rc.topleft.x={0}", rc.topleft.x); Console.WriteLine("rc.topleft.y={0}", rc.topleft.y); Console.WriteLine("rc.topright.x={0}", rc.topright.x); Console.WriteLine("rc.topright.y={0}", rc.topright.y); Console.WriteLine("rc.topright.x={0}", rc.buttomleft.x); Console.WriteLine("rc.topright.y={0}", rc.buttomleft.y); Console.WriteLine("rc.buttomright.x={0}", rc.buttomright.x); Console.WriteLine("rc.buttomright.y={0}", rc.buttomright.y); Console.WriteLine("rc.used={0}", rc.used); Console.ReadKey(); } } }
程序首先定義了兩個struct結構類型,其中一個是point類型,另一個是Rectangle類型。Point中定義了兩個整型成員變量x和y。Rectangle中定義了矩形的4個point結構類型的頂點,以及一個bool類型。完成類型定義之后就可以在程序中使用所定義的類型了。代碼首先定義兩個Rectangle結構類型rec和rc,接下來對其中的rec中的結構成員進行初始化,代碼如下。
rc = rec;
表示將結構變量rec的值賦給rc。因為它們具有相同的類型結構,在內存中相當于將rec的成員值復制給rc的對應成員。從圖2-6的顯示結構也可以看出,它們的成員值完全相同。

圖2-6 程序運行結果
復雜變量的類型中,還有數組類型,詳細介紹請參考第5章的數組處理。接下來將介紹有關類型轉換問題。
2.2.7 定義結構的構造函數
構造函數是一種特殊的成員函數,主要用來初始化對象,在對象生成時該函數就自動執行。構造函數要求與類型名稱相同且沒有返回值。
下面定義了一個結構的構造函數實例,程序名為ConstructTest。
using System; using System.Collections.Generic; using System.Text; namespace ConstructTest { class Program { public struct point { public int x; public int y; public point(int x, int y) //定義構造函數 { this.x = x; //對成員賦值 this.y = y; } } static void Main(string[] args) { point P1 = new point(); //使用默認值初始化 point P2 = new point(32, 45); //使用自定義構造函數初始化 Console.WriteLine("P1.x={0}", P1.x); Console.WriteLine("P1.y={0}", P1.y); Console.WriteLine("P2.x={0}", P2.x); Console.WriteLine("P2.y={0}", P2.y); Console.ReadKey(); } } }
程序中的如下代碼行是用來定義結構point,包含兩個整型成員變量和一個構造函數,該函數帶有兩個參數,用來初始化前面定義的兩個成員變量。
public struct point { public int x; public int y; public point(int x, int y) //定義構造函數 { this.x = x; //對成員賦值 this.y = y; } }
其中如下的代碼,
this.x = x; this.y = y;
使用了this指針,此處讀者只需知道它表示當前類。上述兩行語句中的this.x和this.y表示了獲取當前結構中的成員變量x和y。并將參數列表中的x和y值分別賦給它們。
接下來在Main函數中的開始兩行語句,代碼如下。
point P1 = new point(); //使用默認值初始化 point P2 = new point(32, 45); //使用自定義構造函數初始化
用來實例化類,得到兩個相應對象P1和P2。此處的“new”就是用來進行類的實例化。其中第一個對象采用了默認值初始化,第二個對象采用了自定義的構造函數初始化。運行程序,得到如圖2-7所示運行結果。

圖2-7 程序運行結果
從結果可以看出,第一個對象P1的成員全部為0。第二給對象P2的成員變量值正是所預設的值,分別是32和45。
2.2.8 類型轉換
程序中經常會遇到需要將某個類變量的值轉換為另外一個類型。如需要顯示某個整型變量,則需要將其轉換為字符串。類型轉換分為以下幾種情況。
● 根據轉換方式的不同,可以分為隱式(Implicit)轉換和顯式(Explicit)轉換。
● 根據源類型和目標類型之間的關系進行劃分,又可分為變換(Conversion)、投射(Cast)和裝箱/拆箱(Boxing/Unboxing)。
2.2.9 隱式轉換
隱式轉換,通常將低類型向高類型轉換。如將int型變量轉換為float型變量。這類轉換可以保證轉換前后的值不發生變化。一般情況下,系統可以自動實現隱式轉換。如下面的程序IntToDoubleTest所示。
static void Main(string[] args) { double k; int i = 5; k = i; Console.WriteLine("k={0}", k); //實現隱式轉換,并輸出轉換結果 Console.ReadKey(); }
上述代碼中定義了一個double類型(高類型)變量k和一個int類型(低類型)變量i。如下代碼行將賦值給k,由于類型不同,系統自動實現了隱式轉換。
k = i;
代碼運行結果如圖2-8所示。表2-2列出了可以實現隱式轉換的類型。

圖2-8 程序運行結果
表2-2 可以實現隱式轉換的類型列表

從表2-2中可以看出,不存在向char類型的隱式轉換。如整型不可以自動轉換為char類型。但是在顯示轉換中是存在的,這將在后面章節中作介紹。另外,也不支持浮點類型向decimal類型轉換。將上述程序改為如下形式。
static void Main(string[] args) { double k=5; int i; i = k; Console.WriteLine("i={0}", i); //不能實現隱式轉換 Console.ReadKey(); }
運行程序,錯誤提示如圖2-9所示。

圖2-9 類型轉換錯誤提示
錯誤提示需要進行強制轉換或顯式轉換。將上述代碼修改如下。
static void Main(string[] args) { double k=5; int i; i =(int)k; //強制轉換類型(可能丟失數據) Console.WriteLine("i={0}", i); Console.ReadKey(); }
則可編譯通過,該方法強迫數據從一種類型轉換到另一種類型,這即是顯示轉換。
2.2.10 顯式轉換
兩種類型之間沒有任何關系的數據類型,是不能相互轉換的。表2-3列出了可以進行顯式轉換數值類型。
表2-3 可以實現顯式轉換的數值類型

上述轉換也可使用System.Convert方法進行顯式轉換,方法如下。
static void Main(string[] args) { double k=5; int i; i =Convert. ToInt32(k); //使用函數進行類型轉換 Console.WriteLine("i={0}", i); Console.ReadKey(); }
Convert.ToInt32(k)的作用是將變量k的值轉換為等效的32位有符號整數。System.Convert類為類型轉換提供了一整套方法,前提是這些轉換能夠被編譯器所支持。它提供一種與語言無關的方法進行類型轉換,并且可用于公共語言運行庫支持的所有語言。Convert用于執行收縮轉換和不相關數據類型之間的轉換。例如,支持從DateTime類型轉換為string類型、從string類型轉換為數字類型,以及從string類型轉換為bool類型等。
顯式轉換需要明確制定需要轉換的類型。轉換前后可能有誤差,所以有的地方又叫強制轉換。表2-4列出了有關Convert的一些類型轉換方法說明。
表2-4 Convert的一些轉換方法

由于顯式轉換是從高類型向低類型轉換,轉換前后將不可避免存在誤差,甚至出現不能成功轉換(編譯器會提示)。將上述代碼修改如下。
static void Main(string[] args) { double k = -3.1415926535; uint ui; ui = Convert.ToUInt32(k); //轉換為Uint32類型 Console.WriteLine("ui={0}", ui); }
系統將出現如圖2-10所示OverflowException異常。然而,將上述代碼改為如下形式。

圖2-10 類型轉換異常
double k = 3.1415926535; uint ui; ui = Convert.ToUInt32(k); Console.WriteLine("ui={0}", ui);
則系統的編譯通過,同時得到如圖2-11所示的運行結果。

圖2.11 程序運行結果
轉換的結果是double變量k的所有小數位被四舍五入掉了。上述兩段代碼說明,雖然有的類型之間可以實現轉換,卻是需要一定的條件的。如其中第一段發生異常的代碼,就是因為Convert.ToUInt32(k)方法只能將變量k轉換為無符號整型;而k是有符號的(負數),所以發生了異常。
2.2.11 根據參與類型轉換的劃分
根據參與類型轉換的源類型和目標類型的關系不同進行劃分,又可以將類型轉換分為變換、投射和裝箱/拆箱。
變換是最常見的一種類型轉換,通常用于簡單的值類型之間的轉換。前面介紹的隱式轉換和顯式轉換都是屬于這種類型的轉換。
當源類型和目標類型之間具有直接或間接的繼承關系時,進行的類型轉換屬于投射。它分為兩種情況。第一種是將子孫類型的對象轉換為祖先類型,叫做向上投射(Upcast);第二種是將祖先類型的對象轉換為子孫類型,叫做向下投射(Downcast)。
其中,向上投射可以使用隱式轉換,而向下投射須使用顯式轉換。因為具有繼承關系的兩個類中,子類將擁有父類中的所有成員。因此當子類對象向父類轉換時,可以確保不會產生成員丟失的情況。而當父類對象向子類轉換時,不一定能確保父類對象擁有子類中定義的成員。
進行類型轉換時,在源類型和目標類型中,如果一個是值類型而另一個是引用類型時,則會發生裝箱/拆箱轉換。其中,從值類型到引用類型的轉換稱為裝箱,而從引用類型到值類型轉換則稱為拆箱。執行裝箱操作時,程序會在堆中新創建一個對象(視為“箱子”)。然后將值類型的值復制到這個新創建的對象中。拆箱時,則是將對象中的值復制到棧上。
2.3 常量
有時候需要在程序中將某個量固定下來,使之保持不變。程序中的圓周率π,它是不需要改變的,同時也不希望程序運行期間被誤改。這是就用到了常量這個概念。常量在程序運行期間,其值是不能改變的。在C#語言中,常量可以分為兩種類型。一種是靜態常量(Compile-time constant),另一種是動態常量(Runtime constant)。其中,前者使用“const”關鍵字來定義,后者使用“readonly”關鍵字來定義。
2.3.1 靜態常量
可以使用如下方式定義靜態常量。
<訪問權限 > const <變量類型>
其中“訪問權限”有public、private、protected等(具體含有將在第3章有關章節介紹)。使用“const”修飾的“變量類型”通常是值類型。如果是引用類型,則只能在初始化時為其賦予null。下面是一個名為ConstTest的用于定義常量的示例。
public const double pi = 3.14159265;
上面代碼定義了一個公有整型常量pi。應該注意的是,靜態常量在定義時需要對其初始化。
public const double piL = 3.14; //定義靜態常量 static void Main(string[] args) { const double piH = 3.14159265; //定義靜態常量 piH = 3.15; Console.WriteLine("piL={0}", piL); //輸出靜態常量 Console.WriteLine("piH={0}", piH); //輸出靜態常量 Console.ReadKey(); }
程序中定義了一個const成員變量piL和一個const變量piH。運行結果如圖2-12所示。

圖2-12 程序運行結果
將上述代碼改為如下所示的代碼。
const double piH = 3.14159265; piH = 3.15; Console.WriteLine("piL={0}", piL); Console.WriteLine("piH={0}", piH); Console.ReadKey();
則系統出現錯誤提示:“賦值號左邊必須是變量、屬性或索引器。”
這說明const常量在程序運行過程中,其值是不允許改變的。其次,const常量還有一個優點,就是在定義const常量時是不占用內存的。因為在編譯程序時產生的中間預言代碼中,將所有用到這種常量的地方,都會使用其實際值給予替換。
2.3.2 動態常量
動態常量(也叫只讀常量)使用“readonly”關鍵字來定義。相對于靜態常量,動態常量要靈活得多,它的定義方式如下。
<訪問權限 > readonly <變量類型>
“訪問權限”同樣有public、private、和protected。只讀常量的“變量類型”可以是值類型,也可以是引用類型。
下面是兩個只讀常量的聲明示例。
public readonly System.Text.StringBuilder Sb = new StringBuilder(); private readonly double pi = 3.14159265;
之所以稱為動態變量,是因為系統要為“readonly”所定義的動態常量分配內存空間。它和類中其他成員一樣擁有獨立的內存空間,這和靜態常量是不同的。此外,動態常量除了可以在定義的時候設定常量值外,也可以在類的構造函數中設定。由于動態常量相當于類中的成員,因此使用靜態常量所受到的類型限制,在動態常量的情況下就不存在了。也可以使用“readonly”去定義任何類型的常量。
2.3.3 使用動態常量
下面代碼程序名為ReadOnlyTest,演示動態常量的一些設置。
using System; using System.Collections.Generic; using System.Text; namespace ReadOnlyTest { //定義結構point public struct point { public int x; public int y; //定義結構point的構造函數 public point(int x, int y) { this.x = x; this.y = y; } } class Program { //定義動態常量 private static readonly double pi = 3.14159265; //使用構造函數定義動態常量 public static readonly point P=new point (12,26); static void Main(string[] args) { //輸出對比結果 Console.WriteLine("pi={0}", pi); Console.WriteLine("P.x={0}", P.x); Console.WriteLine("P.y={0}", P.y); Console.ReadKey(); } } }
程序中首先定義了帶有自定義構造函數的結構point(結構的構造函數的定義方法,請參考前面的章節)。
public struct point { public int x; public int y; public point(int x, int y) { this.x = x; this.y = y; } }
該構造函數帶有兩個整型變量x和y。用于將參數列表中傳下來的參數,分別賦值給結構的成員變量x和y。后面實例化該對象時便是調用了該函數。代碼如下所示。
private static readonly double pi = 3.14159265; public static readonly point P=new point (12,26);
這兩行中用到了“static”關鍵字,此關鍵字是一個靜態方法,用來定義的對象可以直接調用。需要注意的是,動態常量不能也沒有必要使用“static”關鍵字修飾。
其中,第一行代碼定義了一個靜態雙精度浮點型常量pi。第二行使用自定義的構造函數,實例化一個point類型的結構。整個程序的最后幾行是常量值的輸出。運行程序,結果如圖2-13所示。

圖2-13 程序運行結果
上述程序實現了在結構的構造函數種進行初始化,達到了代碼重用的效果。
表2-5總結了靜態常量和動態常量的異同點。
表2-5 靜態常量和動態常量對比

2.4 表達式
C#語言中的表達式與數學中的表達式基本類似,都有嚴格的格式。在C#中表達式用于幫助數據處理,如加、減、取反和邏輯判斷等。任何程序中基本都離不開表達式的使用。通常給一個變量賦值用的符號“=”是一個賦值運算符,它與變量和值構成一個表達式。表達式描述了對數據進行處理的順序和操作。由運算符和運算量組成,其中運算量(又叫操作數)通常指的是變量。下面內容主要介紹運算符。
2.4.1 數學運算符
和數學中的運算符對應,在C#中有加、減、乘、除和求余5種基本的數學運算符。除此之外還有兩個符號運算符,它們是正號“+”和負號“-”。表2-6列出了它們的基本用法和意義。
表2-6 數學運算符

2.4.2 普通數學運算符
下面程序MathOperatorTest演示了上述運算符的使用和意義。
static void Main(string[] args) { double a; int b, c; a = 0; b = 25; c = 2; a = b + c; Console.WriteLine("a={0}", a); a = b - c; Console.WriteLine("a={0}", a); a = b * c; Console.WriteLine("a={0}", a); a =(double)b / (double)c; Console.WriteLine("a={0}", a); a = b % c; //求余 Console.WriteLine("a={0}", a); a = +a; //取符號為“+” Console.WriteLine("a={0}", a); a = -a; //取符號為“-” Console.WriteLine("a={0}", a); Console.ReadKey(); }
上面代碼運用了所有的數學運算符,需要說明的是下面的代碼。
a =(double)b / (double)c;
這里用到了類型轉換,是由于C#中的整數相除,仍然是整數。也就是說,如果將上述代碼修改如下。
a =b / c;
則a的值為12,而不是12.5,所以在進行相除時一定要注意變量類型。運行程序,得到如圖2-14所示的結果。

圖2-14 程序運行結果
可以看出,一元運算符“-”實現了變量的符號取反運算;而運算符“+”對變量的數值并沒有影響。但是,這并不表示“+”運算符沒有用處,示例代碼如下所示。
string str1 , str2 , str3; str1=”abcdefg”; str2=”higklmn”; str1=str2 + str3; //字符串相加 Console.WriteLine("str1={0}”, str1);
上面代碼運行后將會輸出“abcdefghigklmn”。
也就是“+”運算符實現了兩個字符串的連接,而其他數學運算符則不能實現對字符串的操作。后面的章節還會介紹“+”運算符的其他應用,如運算符重載等。
2.4.3 自加和自減運算符
自加運算符用“++”表示,自減運算符用“--”表示。它們在C#中分別都有兩種使用方式,同時這兩種方式的意義也是不一樣的。表2-7列出了它們的用法和意義。
表2-7 自加和自減運算符

自加和自減運算符采用“自右至左”的結合方向,它們只能對整型變量進行運算,而不能是表達式或常數,如下面語句是錯誤的。
6++; (a+b)--;
程序AutoTest演示了自加和自減運算符的使用和意義,代碼如下。
static void Main(string[] args) { int a, b; a = 0; b = 5; a = b++; //自加運算,先賦值加后 Console.WriteLine("a={0}", a); Console.WriteLine("b={0}", b); a = b--; //自減運算,先賦值加減 Console.WriteLine("a={0}", a); Console.WriteLine("b={0}", b); a = ++b; //自加運算,先加后賦值 Console.WriteLine("a={0}", a); Console.WriteLine("b={0}", b); a = --b; //自減運算,先減后賦值 Console.WriteLine("a={0}", a); Console.WriteLine("b={0}", b); Console.ReadKey(); }
程序運行結果如圖2-15所示。

圖2-15 程序運行結果
2.4.4 賦值運算符
前面已經多次使用到了“=”賦值運算符。其實,在C#中還有很多類似的運算符,正確地使用它們可以提高程序開發效率。表2-8列出了C#中的賦值運算符及其解釋。
表2-8 C#中的賦值運算符

下面程序AssignmentTest演示了上述賦值運算符的使用。
static void Main(string[] args) { byte a , b; a = 9; b = 3; a += b; //相加 Console.WriteLine("a={0}", a); a -= b; //相減 Console.WriteLine("a={0}", a); a *= b; //相乘 Console.WriteLine("a={0}", a); a /= b; //相除 Console.WriteLine("a={0}", a); a <<= b; //左移 Console.WriteLine("a={0}", a); a >>= b; //右移 Console.WriteLine("a={0}", a); a &= b; //求位與運算 Console.WriteLine("a={0}", a); a ^= b; //求位異或運算 Console.WriteLine("a={0}", a); a —= b; //求位或運算 Console.WriteLine("a={0}", a); Console.ReadKey(); }
代碼首先定義了兩個字節型(無符號8位整數)變量數a和b,其中a=9,二進制表示為a=00001001。b=3,其二進制表示為b=00000011。運行程序,結果如圖2-16所示。

圖2-16 程序運行結果
前4行分別是加、減、乘、除的結果,第5行中a=72。語法如下所示。
a <<= b;
將a左移了3位(b=3),此時的a=9,即a=00001001。將其向左移后變成a=01001000也即a=72。在移動時將右邊空出的位填0。
同理,下面的代碼。
a >>= b;
將a的值右移3位,等于將a的值還原。
接下來的代碼。
a &= b;
用于將a和b按位求與運算。此時a=00001001, b=00000011。所以得到最后的a=00000001即a=1。其余兩個運算符道理相同,在此不再贅述。
2.4.5 比較運算符
比較運算符通常用于確定變量之間的關系,如大于、小于等。它是程序作出邏輯判斷的基礎。表2-9列出了C#中的一些常用的比較運算符及其解釋。
表2-9 C#中的比較運算符

static void Main(string[] args) { int a, b; bool bt, bf; bt = true; bf = false; a = 5; b = 8; if (a == b) //判斷是否相等 { Console.WriteLine("a等于b");
下面的程序CompareTest說明了上述比較運算符的使用和意義。
} else { Console.WriteLine("a不等于b"); } //輸出類型判斷結果 Console.WriteLine(a is int); //輸出邏輯與運算結果 Console.WriteLine(bt && bf); Console.ReadKey(); }
程序中用到了“if else”分支語句,該語句的意義正如其英文意義。當“if”的括號中的表達式值為“true”時,程序進入該代碼塊,否則進入“else”代碼塊運行。程序運行結果如圖2-17所示。

圖2-17 程序運行結果
2.4.6 運算符的優先級
上面介紹的很多運算符都可以相互組合為表達式,如(a=b)+c*d、x%y+z等。這為復雜問題的解決提供了方便,但同時帶來了需要考慮的運算符的優先級問題。表2-10列出了C#中各種運算符的優先級順序。
表2-10 C#中的比較運算符

其中的“? :”為條件運算符,該運算符使用形式如下。
c? e1:e2
當“c”為真時程序執行e1語句,反之則執行e2語句。該語句也是C#中唯一的三元運算符。
注意:編程應該以“可靠性高于效率”的原則進行。如將費解的地方分解處理,或者使用“()”將沒有把握的地方括起來,或者對代碼加注釋等。這樣使程序容易讓人讀懂,便于后期的檢查和維護。
2.4.7 命名空間
在.NET Framework中包含了一個由4000多個類組成的基礎類庫,這就是.NET Framewok基礎類庫。基礎類庫中的這些類被組織成為命名空間。為從字節流的輸入和輸出到數據庫操作、到文件操作、到Windows窗體控件和ASP.NET控件,所涉及的所有內容提供多種有用的功能。通常的C#應用程序都離不開.NET Framework類庫支持。
其實,在前面的每個程序中都用到了命名空間。有System.、System.IO、System.Text等。以第2.3.2節的程序為例,它們的使用方式如下。
using System; using System.Collections.Generic; using System.Text;
包含命名空間用到了“using”關鍵字,告知系統“本程序需要用到這個命名空間,請提供該命名空間中的所有需要的服務”。這樣便可以在自己的程序(命名空間)中使用上述命名空間中的類。
當然,也可以自定義命名空間。實際上C#中的幾乎所有類都放在特定的命名空間中,包括用戶自己編寫的程序。
下面的示例程序,整個命名空間以“namespace ReadOnlyTest”開始。“ReadOnlyTest”便是自定義的程序名,也是自定義的命名空間名。整個代碼塊包含在“namespace ReadOnlyTest”所在的大括號內,其中是系統和用戶定義的類、結構及其成員,包括point結構和program類。
namespace ReadOnlyTest { //定義結構 public struct point { public int x; public int y; //定義結構的構造函數 public point(int x, int y) { this.x = x; this.y = y; } } class Program { //定義動態常量 private static readonly double pi = 3.14159265; //使用構造函數定義動態常量 public static readonly point P=new point (12,26); static void Main(string[] args) { //輸出結果 Console.WriteLine("pi={0}", pi); Console.WriteLine("P.x={0}", P.x); Console.WriteLine("P.y={0}", P.y); Console.ReadKey(); } } }
之所以在程序中引入命名空間,是由于該方式可以有效解決類的重名問題。比如在開發大型軟件時A開發組定義了classA類,然而B開發小組也定義了一個classA類。這樣在最后組織到一起進行編譯時編譯器便會提示兩個類重名。而引入命名空間之后,可以將A開發組的命名空間命名為namespaceA,將B開發組的命名空間命名為namespaceB。這樣由于命名空間的不同便可以使用相同的類名了,這在C#中是完全可行的。只是如果需要訪問classA,需要使用完全的路徑名,如namespaceA.classA、namespaceB.classA,這樣編譯器才知道到底訪問哪個空間中的類。當然也可以使用“using”關鍵字將它們包含進程序,但是仍然需要限定命名空間。
細心的讀者可能會發現有的命名空間會非常長,如System.Security.Cryptography.X509 Certificates。如果每次都要限定命名空間,將會是一件非常煩瑣的事情。讓人欣慰的是,C#提供了一種“別名”機制,可以使用該機制定義一個需要引用的命名空間別名,定義方法如下。
using <別名> =<命名空間>
定義了別名之后就可以利用該別名訪問對于命名空間中的類了。這與訪問實際的命名空間是一樣的,代碼如下。
using SSCX= System.Security.Cryptography.X509Certificates; ...... SSCX.X509Store.Equals=true; ......
2.4.8 嵌套命名空間
從命名空間的引用可以看出,命名空間中可以繼續定義命名空間。這就是命名空間的嵌套,定義方式如下。
namespace NameA { public class classA { ...... funA() { ...... } ...... } namespace NameB { public class classB { ...... funB() { ...... } ...... } } ...... }
上面代碼定義了一個命名空間NameA,其中又定義了一個類classA和一個命名空間NameB。其中類classA中又定義了一個方法funA()。命名空間NameB中又定義了一個方法funB()。如果該名稱空間的外部空間程序要訪問funA(),則可以通過全名NameA.classA.funA()訪問;相應的訪問funB()可以通過全名NameA.NameB.classB.funB()訪問。
上面介紹的是在一個文件中定義命名空間,C#中還有可以分開定義同一個命名空間,并不要求一定在同一個文件中。
2.5 流程控制
從功能上劃分,語句可以分為兩類。一類是用于描述計算的操作運算語句,如數學運算、賦值運算、方法調用語句等;另一類是用于控制這些語句執行順序的語句,如選擇語句、循環控制語句等。這類語句叫做流程控制語句。
可以將流程控制語句分為分支語句、循環語句和跳轉語句等幾個大類。本節主要介紹這幾類語句。
2.5.1 分支語句
程序中經常會出現很多的運算結果,這些結果又對應著多種可能的執行分支語句。分支語句便是用于選擇和執行程序中的這些分支語句。C#分支語句主要包括三元運算符、if語句和switch語句。
2.5.2 三元運算符
三元運算符在前面已經有所介紹,其格式如下。
c? e1:e2
其分支方法是,當“c”為真時程序執行e1語句,反之則執行e2語句。下面的程序TriTest演示了三元運算符的具體應用。
static void Main(string[] args) { const double noon=12; string resultStr; //獲取系統當前時間的小時部分 double now = System.DateTime.Now.Hour; Console.WriteLine("time={0}", now); //判斷變量大小,用以確定是上午或下午 resultStr=(now< noon)? "現在是上午":"現在是下午"; Console.WriteLine("{0}", resultStr); Console.ReadKey(); }
程序首先定義了三個變量,其中兩個double型變量用于存儲時間,另一個string型變量用于顯示結果。
double now = System.DateTime.Now.Hour;
用來獲取系統當前時間的小時部分,并把結果賦值給變量now。
resultStr=(now<time)? "現在是上午":"現在是下午";
該語句是關鍵,判斷現在時間和設定的時間的大小,用以確定是上午還是下午。結果返回給變量resultStr,最后是顯示結果。運行程序得到如圖2-18所示結果。

圖2-18 程序運行結果
三元運算符,也可以實現嵌套使用。嵌套格式如下所示。
expr0? expr1:expr2;
其中表達式expr1和expr2都可以嵌套。如下面的代碼。
expr0? (expr00? expr11: expr22):expr2;
為了便于區分使用了括號,但實際中可以不使用。
上面程序中只能判斷是上午還是下午,如果還需要判斷是下午還是晚上,則可以將上述代碼改為如下形式。
static void Main(string[] args) { const double noon=12; const double eveningTime = 19; string resultStr; //獲取系統當前時間的小時部分 double now = System.DateTime.Now.Hour; Console.WriteLine("time={0}", now); //判斷變量大小,用以確定是上午或下午或晚上 resultStr=(now<noon)? "現在是上午":(now<eveningTime)? "現在是下午":"現在是晚上"; Console.WriteLine("{0}", resultStr); Console.ReadKey(); }
將系統設置為12點以后,19點以前的時間。然后運行程序,結果如圖2-19所示。

圖2-19 程序運行結果
2.5.3 if語句
與三元運算符具有同樣功能的還有if語句。所不同的是if語句沒有返回值。if語句有多種形式,前面用到的有if…else是其中比較簡單的一種。除此之外還有if…else if…else,或者僅僅一個單獨的if語句,但是沒有單獨的else語句。
對if…else語句,其表達形式如下。
if(expr) { ...... } else { ...... }
expr可以是任何表達式或方法的返回結果,結果的類型必須是bool型。這和三元運算符是一致的。當返回為“true”時,執行if代碼塊中的語句。否則,執行else代碼塊中的語句。
if…else屬于二選一執行。也有多選一執行的用法,這就是if…else if…else。其表達的形式如下。
if(expr1) { ...... } else if(expr2) { ...... } else { }
expr1和expr2的意義和上面是一樣的。當expr1返回“true”時,執行if代碼塊;否則判斷expr2的返回,如果是“true”則執行expr2代碼塊中語句;否則執行else代碼塊中的語句。當然這種格式并不限于三選一,還可以有更多的選擇分支。只需要多加“else if”語句便可。
2.5.4 使用if語句
下面的程序IfElseTest演示了如何使用if語句。
using System; using System.Collections.Generic; using System.Text; namespace IfElseTest { class Program { static void Main(string[] args) { Console.WriteLine("請選擇你目前的開發工作:"); Console.WriteLine("1.Windows桌面應用程序"); Console.WriteLine("2.Web應用程序"); Console.WriteLine("3.Web服務"); Console.Write("請輸入你的選擇:"); //讀取用戶的輸入字符 string choice=Console.ReadLine(); //判斷用戶的輸入 if (choice=="1") { Console.WriteLine("你目前的開發工作是:1.Windows桌面應用程序"); } else if (choice == "2") { Console.WriteLine("你目前的開發工作是:2.Web應用程序"); } else if (choice == "3") { Console.WriteLine("你目前的開發工作是:3.Web服務"); } else { Console.WriteLine("對不起你選擇錯誤,下次請輸入1-3之間的整數"); } Console.ReadKey(); } } }
整個程序主要是完成對用戶輸入的判斷。其中下面的代碼在控制臺上列出用戶選項,并提示用戶輸入。
Console.WriteLine("請選擇你目前的開發工作:"); Console.WriteLine("1.Windows桌面應用程序"); Console.WriteLine("2.Web應用程序"); Console.WriteLine("3.Web服務"); Console.Write("請輸入你的選擇:");
下面定義了一個字符串變量,用于接收用戶輸入的字符。
string choice=Console.ReadLine();
接下來判斷用戶的輸入是1~3中的哪一個數字字符,并輸出對應項。如果輸入不在1~3之間,則輸出錯誤提示。運行代碼,輸入一個數字,如“1”,運行結果如圖2-20所示。

圖2-20 程序運行結果
2.5.5 程序流程
如圖2-21所示為上節示例整個程序的流程圖。

圖2-21 程序流程圖
相比而言,三元運算符的開發效率更高,if語句更為直觀,其代碼易于理解和維護。但是從開發效率的角度來看,if語句稍遜一籌。
2.5.6 switch語句
if語句在解決程序分支問題上提供了一個很好的解決方案。但是,如果選項很多,則需要很多的else if語句,這樣會造成代碼的不易理解和維護。在C#中還提供了一種專門解決多分支問題的語句,這就是switch語句。
“switch”在英語中的意思是“開關”,所以又叫開關語句。該語句可以一次將測試變量與多個可能值進行比較,而不是一次只能測試一個可能值。該語句的基本結構如下所示。
switch(expr) { case value1: statement1; break; case value2: statement2; break; ...... case valueN: statementN; break; default: defaultStatement; break; }
expr可以是任何整數類型或字符串,或者返回整數類型、字符串類型的表達式、方法等。value1~valueN是expr的可能取值。如果expr的值為其中之一則執行其對應的statement。如果所有value都不滿足,則執行default語句對應的defaultStatement語句。break語句表示跳出整個switch語句。
2.5.7 使用switch語句
將第2.5.4節中的例子改為使用switch語句控制分支,程序名為SwitchTest,代碼如下所示。
using System; using System.Collections.Generic; using System.Text; namespace SwitchTest { class Program { static void Main(string[] args) { Console.WriteLine("請選擇你目前的開發工作:"); Console.WriteLine("1.Windows桌面應用程序"); Console.WriteLine("2.Web應用程序"); Console.WriteLine("3.Web服務"); Console.Write("請輸入你的選擇:"); //讀取用戶的輸入字符 string choice=Console.ReadLine(); //判斷用戶的輸入 switch(choice) { case "1": Console.WriteLine("你目前的開發工作是:1.Windows桌面應用程序"); break; //跳出循環 case "2": Console.WriteLine("你目前的開發工作是:2.Web應用程序"); break; //跳出循環 case "3": Console.WriteLine("你目前的開發工作是:3.Web服務"); break; //跳出循環 default: Console.WriteLine("對不起你選擇錯誤,下次請輸入1-3之間的整數"); break; //跳出循環 } Console.ReadKey(); } } }
程序使用switch語句將if語句部分替換了。需要注意的是每個case部分的可能變量值類型應使用雙引號括起來。因為choice是一個字符串變量。運行程序并輸入一個數字,如“2”,運行結果如圖2-22所示。

圖2-22 程序運行結果
2.5.8 goto語句
和C、C++一樣,C#也支持goto語句。該語句是一種用于流程無條件轉移的語句。使用該語句的前提是需要在程序中加入標簽。下面是goto語句的使用格式。
...... goto <LabelName>; ...... <LabelName>: ......
其中<LabelName>是標簽名。程序執行到goto語句處便會忽略goto和<LabelName>之間的語句,而轉向<LabelName>后面執行。
前面的程序SwitchTest中有一個需要改進的地方,就是當用戶輸入的不是1~3之間的數字時,程序便自動退出了。使用goto語句可以做以下改進,改進后的程序名為GotoTest,代碼如下所示。
static void Main(string[] args) { Console.WriteLine("請選擇你目前的開發工作:"); Console.WriteLine("1.Windows桌面應用程序"); Console.WriteLine("2.Web應用程序"); Console.WriteLine("3.Web服務"); //設置標簽 Label: Console.Write("請輸入你的選擇:"); //讀取用戶的輸入字符 string choice=Console.ReadLine(); //判斷用戶的輸入 switch(choice) { case "1": Console.WriteLine("你目前的開發工作是:1.Windows桌面應用程序"); break; //跳出循環 case "2": Console.WriteLine("你目前的開發工作是:2.Web應用程序"); break; //跳出循環 case "3": Console.WriteLine("你目前的開發工作是:3.Web服務"); break; //跳出循環 default: Console.WriteLine("對不起,你選擇錯誤,下次請輸入1-3之間的整數"); goto Label; //轉向Label處執行 break; //跳出循環 } Console.ReadKey(); }
在程序中設置了標簽和goto語句,當用戶輸入非法字符時,程序將執行default語句。輸出錯誤信息提示后,便遇到goto語句轉向Label后邊的語句,提示用戶重新輸入。
運行程序,先輸入一個字符,如“y”,程序提示錯誤信息;再輸入一個數字,如“3”,運行結果如圖2-23所示。

圖2-23 程序運行結果
說明:雖然goto語句能夠使程序員很方便地控制程序的轉向,但是過多地使用goto語句會造成程序結構的混亂。這會讓人難以讀懂代碼,容易導致后期的軟件維護相當困難,所以建議C#語言的初學者不使用goto語句。
2.5.9 循環語句
循環語句用于解決多次重復性的計算問題,如窮舉問題和迭代問題。該語句充分發揮了計算機的快速計算能力。
在C#中循環語句有do-while循環、while循環、for循環和foreach循環。下面將分別作詳細介紹。
2.5.10 do-while語句
do-while是兩個關鍵字的組合循環,其結構如下。
do { statement; }while(expr);
其中do代碼塊中的statement是要循環處理的語句。這些語句按照順序執行。當執行完畢程序跳出代碼塊執行while語句。該語句首先判斷expr返回值是true還是false,如果是true,則繼續執行do代碼塊中的代碼。一般情況下statement都和expr有一定的關聯。如圖2-24所示為do-while語句執行的流程圖。

圖2-24 do-while語句執行流程
2.5.11 使用do-while語句
下面的程序DoWhileTest中用到了do-while語句,該代碼是對前面例程的改進。
using System; using System.Collections.Generic; using System.Text; namespace DoWhileTest { class Program { static void Main(string[] args) { bool flag; Console.WriteLine("請選擇你目前的開發工作:"); Console.WriteLine("1.Windows桌面應用程序"); Console.WriteLine("2.Web應用程序"); Console.WriteLine("3.Web服務"); do { flag = false; Console.Write("請輸入你的選擇:"); //讀取用戶的輸入字符 string choice = Console.ReadLine(); //判斷用戶的輸入 switch (choice) { case "1": Console.WriteLine("你目前的開發工作是:1.Windows桌面應用程序"); break; case "2": Console.WriteLine("你目前的開發工作是:2.Web應用程序"); break; case "3": Console.WriteLine("你目前的開發工作是:3.Web服務"); break; default: Console.WriteLine("對不起,你選擇錯誤,下次請輸入1-3之間的整數"); flag = true; break; } }while (flag); Console.ReadKey(); } } }
上面的程序使用do-while語句替換了goto語句。實現了使用goto語句同樣的功能。其中,
bool flag;
定義了一個布爾變量,用于測試循環是否結束。do代碼塊中的如下語句,
flag = false;
將flag設置為“false”。這樣當代碼沒有執行到default代碼塊時,程序跳出switch語句便執行while判斷,此時flag為“false”則跳出do-while循環。如果程序執行到default代碼塊,程序將會執行到下面的語句。
flag = true;
該語句將flag的值改變為“true”。這樣在做while判斷時就會繼續返回do代碼塊繼續執行下一輪循環。
運行程序并向控制臺輸入一個字符,如“a”,程序便會提示錯誤信息;再輸入一個數字字符,如“1”,運行結果如圖2-25所示。

圖2-25 程序運行結果
2.5.12 while語句
while循環語句的結構如下。
while(expr) { statement; }
無論從關鍵字看,還是從功能上看,它和do-while語句都非常相似。唯一和do-while語句不同的是do-while語句先執行“statement”語句,然后判斷是否繼續循環;而while循環語句首先對expr的返回值進行判斷,如果為“true”才執行代碼塊中的語句statement。反之,則一次也不執行。如圖2-26所示為while語句執行的流程圖。

圖2-26 while語句執行流程圖
2.5.13 使用while語句
下面的程序WhileTest使用while語句,替換了上面例程中的do-while語句。
using System; using System.Collections.Generic; using System.Text; namespace WhileTest { class Program { static void Main(string[] args) { bool flag; Console.WriteLine("請選擇你目前的開發工作:"); Console.WriteLine("1.Windows桌面應用程序"); Console.WriteLine("2.Web應用程序"); Console.WriteLine("3.Web服務"); flag = true; while (flag) { flag = false; Console.Write("請輸入你的選擇:"); //讀取用戶的輸入字符 string choice = Console.ReadLine(); //判斷用戶的輸入 switch (choice) { case "1": Console.WriteLine("你目前的開發工作是:1.Windows桌面應用程序"); break; case "2": Console.WriteLine("你目前的開發工作是:2.Web應用程序"); break; case "3": Console.WriteLine("你目前的開發工作是:3.Web服務"); break; default: Console.WriteLine("對不起你選擇錯誤,下次請輸入1-3之間的整數"); flag = true; break; } } Console.ReadKey(); } } }
與程序DoWhileTest不同的是,上面代碼首先需要將flag設為“true”。這樣才能通過判斷,執行while代碼塊中的語句。代碼如下所示。
flag = false;
將flag設置為“false”,這樣代碼沒有執行到“default”語句便會跳出循環。如果執行到“default”語句,代碼如下所示。
flag = true;
將flag設置為“true”,從而繼續循環。
運行程序,首先輸入錯誤字符串,如“abc”,程序提示輸入錯誤信息;然后輸入一個數字字符,如“3”。得到如圖2-27所示運行結果。

圖2-27 程序運行結果
2.5.14 for語句
for循環語句與前面介紹的while等語句最大的不同就是,for語句可以指定循環的次數。并且主要是通過循環次數判斷是否要繼續循環。
下面是for語句的使用格式。
for(<初始化計數器>; <繼續循環的條件>; <操作計數器>) { statement; }
<初始化計數器>在整個循環過程中只執行一次。如果<繼續循環的條件>返回的是“true”,那么執行statement語句,并且執行完之后再執行<操作計數器>。否則,如果<繼續循環的條件>返回的是false那么跳出循環。如圖2-28所示為“for”語句流程圖。

圖2-28 for語句執行流程圖
2.5.15 使用for語句
程序ForTest演示了for語句的使用,代碼如下所示。
using System; using System.Collections.Generic; using System.Text; namespace ForTest { class Program { static void Main(string[] args) { int i, j, k; //前5行輸出 for (i = 0; i <=4; i++) //控制行 { for (k = 0; k < i; k++) //輸出空格 { Console.Write(" "); } for (j = 1; j <= 9-2 * i; j++) //控制列 { Console.Write("*"); } Console.WriteLine(); } //后5行輸出 for (i = 4; i >= 0; i--) //控制行 { for (k = 0; k < i; k++) //輸出空格 { Console.Write(" "); } for (j = 1; j <= 9-2 * i; j++) //控制列 { Console.Write("*"); } Console.WriteLine(); } Console.ReadKey(); } } }
運行程序,結果如圖2-29所示。

圖2-29 程序運行結果
運行結果得到一個“沙漏”圖形。在程序中將該圖形分為兩部分輸出,第一部分畫“沙漏”的上半部,代碼如下所示。
//前5行輸出 for (i = 0; i <=4; i++) //控制行 { for (k = 0; k < i; k++) //輸出空格 { Console.Write(" "); } for (j = 1; j <= 9-2 * i; j++) //控制列 { Console.Write("*"); } Console.WriteLine(); }
其中,for語句中又嵌套了兩個for循環。這在C#中是允許的。上半部總共有5行,所以控制行的變量i從0取到4。嵌套的第一個for用于控制輸出空格,第二個for語句用于控制第i行輸出“*”的個數。有興趣的讀者可以自己研究一下,此處不再做細致分析。
接下來的語句是畫“沙漏”下半部分。由于只是將上半部分倒過來,所以直接將第一部分的如下語句,
for (i = 0; i <=4; i++)
換成如下語句即可。
for (i = 4; i >= 0; i--)
2.5.16 foreach循環語句
foreach循環與for循環較為相似。主要用于循環訪問各種存儲數據對象的數據內容,如數組、集合等。下面的程序實例ForeachTest,演示了foreach語句循環訪問數組的方法。
using System; using System.Collections.Generic; using System.Text; namespace ForeachTest { class Program { static void Main(string[] args) { int Val=1; //定義一個二維數組 int[, ] MyArry = new int[2,3] ; //初始化二維數組 for (int i = 0; i < MyArry.GetLength(0); i++) { for (int j = 0; j < MyArry.GetLength(1); j++) { //給數組元素賦值 MyArry[i, j] = Val; Val = Val + Val; } } //訪問并輸出數組內容 foreach (int ind in MyArry) { Console.WriteLine(ind); } Console.ReadKey(); } } }
程序使用到了數組,有關數組的具體介紹請參考第5章相關內容。程序首先定義一個二維數組MyArry,并對其進行初始化。初始化代碼如下。
for (int i = 0; i < MyArry.GetLength(0); i++) { for (int j = 0; j < MyArry.GetLength(1); j++) { MyArry[i, j] = Val; Val = Val + Val; } }
接著使用foreach循環訪問數組中的各元素,代碼如下。
foreach (int ind in MyArry) { Console.WriteLine(ind); }
其中的ind是獲取到的數組元素值。運行程序,結果如圖2-30所示。

圖2-30 程序運行結果
說明:foreach循環語句只能讀取數據,而不能寫入數據。同時不能以索引方式隨機訪問數據。但是foreach循環語句的執行效率比for語句略高。
在循環語句中有時并不需要按照特定的方式循環,而需要在某個情況發生時就跳出循環。這時就可以使用循環中斷語句了。
2.5.17 循環中斷語句
循環中斷語句包括break、continue、return。
其中,break語句前面已經用到過。循環執行過程中遇到該語句,就會跳出循環,不再執行下面的語句。
2.5.18 使用break語句
下面的程序BreakTest說明了break語句的用法。
static void Main(string[] args) { for (int i = 0; i < 5; i++) { //如果i == 3 if (i == 3) { //輸出i的值 Console.WriteLine("i={0}", i); //跳出整個循環 break; Console.WriteLine("Excuted! "); } if (i == 4) { //輸出i的值 Console.WriteLine("i={0}", i); } } Console.ReadKey(); }
輸出結果為i=3。這表明下面的語句,
if (i == 3) { Console.WriteLine("i={0}", i); break; Console.WriteLine("Excuted! "); }
中break后面的如下語句,
Console.WriteLine("Excuted! ");
和如下語句,
if (i == 4) { Console.WriteLine("i={0}", i); }
并沒有被執行到。因為循環遇到break語句便跳出了循環。
2.5.19 使用continue語句
continue語句與break語句略有差異。它用于循環中的某次執行,而繼續下次循環。將上述程序改為如下形式。
static void Main(string[] args) { for (int i = 0; i < 5; i++) { if (i == 3) { Console.WriteLine("i={0}", i); continue; //繼續下一輪循環(如果滿足循環條件) Console.WriteLine("Excuted! "); } if (i == 4) { Console.WriteLine("i={0}", i); } } Console.ReadKey(); }
運行程序,得到如圖2-31所示運行結果。

圖2-31 程序運行結果
結果說明下面的代碼段,
if (i == 3) { Console.WriteLine("i={0}", i); continue; Console.WriteLine("Excuted! "); }
中的如下代碼,
Console.WriteLine("i={0}", i);
和如下代碼,
if (i == 4) { Console.WriteLine("i={0}", i); }
被執行了,而continue語句后的如下代碼,
Console.WriteLine("Excuted! ");
并沒有執行。也就是說,continue語句用來實現終止本次循環,而接著執行下次的循環。
2.5.20 使用return語句
最后來說明return語句,該語句主要用于結束函數的執行,并可以將需要的參量返回。可以用于包括循環語句在內的函數中的任何地方。
將一節的代碼中的continue改為return,代碼如下。
static void Main(string[] args) { for (int i = 0; i < 5; i++) { if (i == 3) { Console.WriteLine("i={0}", i); return; //函數返回,退出當前函數 Console.WriteLine("Excuted! "); } if (i == 4) { Console.WriteLine("i={0}", i); } } Console.ReadKey(); }
程序將在遇到return語句后立即退出整個Main函數,而返回一個void類型。表現在控制臺是程序輸出“i=3”便立即關閉控制臺,這表明整個程序已退出。
2.6 小結
本章介紹了C#中經常用到的變量、常量、表達式,以及流程控制,其中包括它們的定義和使用,并配合了大量實例。
變量部分主要介紹了變量的聲明和賦值,以及C#中簡單數據類型。還對結構做了較詳細的介紹,同時也介紹了程序中常遇到的類型轉換。常量部分主要介紹了靜態常量和動態常量及其區別。表達式部分介紹了常用的運算符,以及其使用方法。同時對命名空間作了大致的介紹。
本章的最后介紹的是流程控制語句,這也是重點介紹之一。包括了分支語句、goto語句、循環語句及循環中斷語句。