語句
什么是語句
- 語句是描述某個類型或讓程序執行某動作的源代碼指令。
- 語句主要有3類
- 聲明語句 聲明類型或變量
- 嵌入語句 執行動作或管理控制流
- 標簽語句 控制跳轉
例:語句示例
int x=19; //簡單聲明 int z; //簡單聲明 { //塊 int y=20; //簡單聲明 z=x+y; //嵌入語句 top:y=30; //標簽語句 ... { //嵌套塊 ... } //結束嵌套塊 } //結束外部塊
塊在語法上算作單條嵌入語句。任何語法上需要一個嵌入語句的地方,都可以使用塊。
空語句僅由一個分號組成。當程序邏輯不需要任何動作時使用。
if(x<y) ; else z=a+b;
控制流語句
C#提供與現代編程語言相同的控制流結構。
- 條件執行依據一個條件執行或跳過一個代碼片段。條件執行語句如下:
- if
- if…else
- switch
- 循環語句重復執行一個代碼片段。循環語句如下:
- while
- do
- for
- foreach
- 跳轉語句把控制流從一個代碼片段改變到另一個代碼片段中的指定語句。跳轉語句如下:
- break
- continue
- return
- goto
- throw
條件執行和循環結構(foreach)需要一個測試表達式或條件以決定程序應當在哪里繼續執行。
與C和C++不同,測試表達式必須返回bool型值。數字在C#中沒有布爾意義。
if語句
if語句實現按條件執行。if語句的語法如下所示。
- TestExpr必須計算成bool型值
- 如果TestExpr求值為true,執行Statement
- 如果求值為false,則跳過Statement
if(TestExpr) Statement


例:if語句示例
//With a simple statement if(x<10) z=x-1;//簡單語句不需要大括號 //With a block if(x>=20) { //塊需要大括號 x-=5; y=x+z; } int x=5; if(x) //錯:表達式必須是bool型,而不是int型 { ... }
if…else語句
if…else語句實現雙路分支。if…else語句的語法如下。
- 如果TestExpr求值為true,執行Statement1
- 如果求值為flase,執行Statement2
if(TestExpr) Statement1 else Statement2

例:if…else示例
if(x<=10) z=x-1; //簡單語句 else { //多條語句組成的語句塊 x-=5; y=x+z; }
if…else或if語句都可以嵌套。如果你在閱讀嵌套if…else語句代碼時,要找出else屬於哪個if,有個簡單的規則。每個else都屬於離它最近的前一條沒有相關else字句的if語句。
while循環
while是一種簡單循環結構,其測試表達式在循環的頂部執行。while循環語法如下。
- 首先對TestExpr求值
- 如果TestExpr求值為false,將繼續執行在while循環結尾之后的語句
- 當TestExpr求值為true時,執行Statement,並且再次對TestExpr求值。每次TestExpr求值為true時,Statement都要在執行一次。循環在TestExpr求值為false時結束
while(TestExpr) Statement

例:while循環示例
int x=3; while(x>0) { Console.WriteLine("x:{0}",x); x--; } Console.WriteLine("Out of loop");


do循環
do循環是一種簡單循環結構,其測試表達式在循環的底部執行。do循環語法如下。
- 首先,執行Statement
- 然后,對TestExpr求值
- 如果TestExpr返回true,那么再次執行Statement
- 每次TestExpr返回true,都將再次執行Statement
- 當TestExpr返回false時,控制傳遞到循環結構結尾之后的那條語句
do循環有幾個特征,使它與其他控制流結構相區分。
- 循環體Statement至少執行一次,即使TestExpr初始為false,這是因為在循環底部才會對TestExpr求值
- 在測試表達式的關閉圓括號之后需要一個分號
do Statement while(TestExpr); //結束do循環

例:do循環示例
int x=0; do Console.WriteLine("x is {0}",x++); while(x<3);


for循環
只要測試表達式在循環體頂端計算時返回true,for循環結構就會執行循環體。for循環語法如下。
- 在for循環的開始,執行一次Initializer
- 然后對TestExpr求值
- 如果它返回true,執行Statement,接着是IterationExpr
- 然后控制回到循環頂端,再次對TestExpr求值
- 只要TestExpr返回true,Statement和IterationExpr都將被執行
- 一旦TestExpr返回false,就繼續執行Statement之后的語句
for(Initializer;TestExpr;IterationExpr) Statement
語句中的一些部分是可選的,其他部分是必需的。
- Initializer、TestExpr和IterationExpr都是可選的。它們位置可以空着。如果TestExpr位置是空的,那么測試被假定返回true。因此程序要避免進入無限循環,必須有某種其他退出該語句的方法
- 作為字段分隔符,兩個分號是必需的,即使其他部分都省略了


- Initializer只執行一次,在for結構的任何其他部分之前。它常用於聲明和初始化循環中使用的本地變量
- 對TestExpr求值以決定應該執行Statement還是跳過。它必須計算成bool類型的值。如前所述,如果TestExpr為空,將永遠返回true
- IterationExpr在Statement之后並且在返回到循環頂端TestExpr之前立即執行
例:for循環示例
for(int i=0;i<3;i++) { Console.WriteLine("Insile loop. i: {0}",i); } Console.WriteLine("Out of loop");


for語句中變量的作用域
任何聲明在Initializer中的變量只在該for語句的內部可見。
- 這與C和C++不同,C和C++中聲明把變量引入到外圍的塊
- 下面的代碼闡明了這點
這里需要類型來聲明 ↓ for(int i=0;i<10;i++) //變量i在作用域內 Statement; //語句 //在該語句之后,i不再存在 這里仍需要類型,因為前面的變量i已經超出存在范圍 ↓ for(int i=0;i<10;i++) //我們需要定義一個新的i變量 Statement; //因為先前的i變量已經不存在
在循環體內部聲明的變量只能在循環體內部使用。
循環變量常常使用標識符i、j、k。這是早年FORTRAN程序的傳統。在FORTRAN中,以字母I、J、K、L、M、N開頭的標識符默認為INTEGER類型,沒有必要聲明。由於循環變量通常為整型,程序員簡單地使用I作為循環變量的名稱,並把它作為一種約定。這簡短易用,而且不用聲明。如果存在嵌套循環,內層循環變量通常為J。如果還有內層嵌套循環,就用K。
初始化和迭代表達式中的多表達式
初始化表達式和迭代表達式都可以包含多個表達式,只要它們用逗號隔開。
例:下面代碼,初始化表達式中有兩個變量聲明,迭代表達式中有兩個表達式
class Program { static void Main() { const int MaxI=5; for(int i=0,j=10;i<MaxI;i++,j+=10) { Console.WriteLine("{0},{1}",i,j); } } }


switch語句
switch實現多路分支
- switch包含0個或多個分支
- 每個分支以一個或多個分支標簽開始
- 每個分支的末尾必須為break或其他4種跳轉語句
- 跳轉語句包括 break、return、continue、goto、throw
- break語句將執行過程跳轉到switch的尾部
分支標簽按順序求值。如果某標簽與測試表達式的值匹配,就執行該分支,然后跳到switch尾部




分支示例
例:下面代碼通過for循環,執行switch語句5次
for(int x=1;x<6;x++) { switch(x) { case 2: Console.WriteLine("x is {0} -- In Case 2",x); break; case 5: Console.WriteLine("x is {0} -- In Case 5",x); break; default: Console.WriteLine("x is {0} -- In Default case",x); break; } }


switch語句補充
switch語句可以有任意多分支,包括沒有分支。default不是必需的,然而通常認為擁有default是好習慣,因為它可以捕獲潛在錯誤。
例:沒有default
for(int x=1;x<6;x++) { switch(x) { case 5: Console.WriteLine("x is {0} -- In Case 5",x); break; } }

例:只有default
for(int x=1;x<4;x++) { switch(x) { default: Console.WriteLine("x is {0} -- In Default Case",x); break; } }


分支標簽
case后的分支標簽必須是常量表達式,即必須在編譯時完全獲取運算結果。
和C/C++不同,每個switch段,包括可選的default段,必須以一個跳轉語句結尾。在C#中,不可以執行一個switch段中的代碼然后直接執行接下來的部分。
盡管C#不允許從一個分支到另一個分支的導向,你仍然可以把多個分支標簽附加到任意分支。
switch(x) { case 1: case 2: //可接受的 ... break; case 5: y=x+1; case 6: //因為沒有break,所以不可接受 ... }
跳轉語句
控制流到達跳轉語句時,程序執行被無條件轉移到程序的另一部分。
- break
- continue
- return
- goto
- throw
這一章闡述前4條語句,throw在第11章討論
break語句
前面你已經看到break被用在switch中。它還能被用在下列語句中:
- for
- foreach
- while
- do
break導致執行調出最內層封裝語句(innermost enclosing statement)
例:break+while
int x=0; while(true) { x++; if(x>=3) break; }
continue語句
continue語句導致程序執行轉到下列類型循環的最內層封裝語句的頂端:
- while
- do
- for
- foreach
例:下例for循環在前3次迭代,它遇到continue直接返回循環頂部,錯過WriteLine語句
for(int x=0;x<5;x++) { if(x<3) continue; Console.WriteLine("Value of x is {0}",x); }


標簽語句
標簽語句格式:
Identifier:Statement
標簽語句的執行如果標簽不存在,僅執行Statement部分。
- 給語句增加一個標簽允許控制從代碼其他部分轉移到該語句
- 標簽語句只允許用在塊內部
標簽
標簽有自己的聲明空間,所以標簽的標識符可以與本地變量或參數名重復。
{ int xyz=0; ... xyz:Console.WriteLine("No problem."); }
標簽語句作用域
標簽語句作用域為:
- 它聲明所在的塊
- 任何嵌套在該內部的塊


goto語句
goto語句無條件轉移控制到一個標簽語句。
goto Identifier;
例
bool thingsAreFine; while(true) { thingsAreFine=GetNuclearReactorCondition(); if(thingsAreFine) Console.WriteLine("Things are fine."); else goto NotSoGood; } NotSoGood:Console.WriteLine("We have a problem.");
goto語句必須在標簽語句的作用域內。
- goto語句可以跳到它本身所在塊內的任何標簽語句,或跳出到任何它被嵌套的塊內的標簽語句
- goto語句不能跳入任何嵌套在該語句本身所在塊內部的任何塊
警告:使用goto語句是非常不好的,因為它會導致弱結構化的、難以調試和維護的代碼。EdsgerDijkstra在1968年給Communication of the ACM寫了一封信,標題為“Go To Statement Considerred Harmful”,是對計算機科學非常重要的貢獻。它是最先發表的描述使用goto語句缺陷的文章之一。
goto語句在switch語句內部
goto case ConstantExpression;//跳轉到指定case段 goto default; //跳轉到default段
using語句
有些類型的非托管對象數量有限制或很耗費系統資源。在使用完它們后,盡可能快地釋放它們是非常重要的。using語句有助於簡化該過程並確保這些資源被適當的處置(dispose)。
資源是指一個實現了System.IDisposable接口的類或結構。接口在第15章詳述,但簡而言之,接口就是未實現的函數成員的集合,類和結構可以選擇去實現。IDisposable接口含有單獨一個名稱為Dispose的方法。
使用資源的階段如下圖:
- 分配資源
- 使用資源
- 處置資源
如果在正在使用資源的那部分代碼中產生一個意外的運行時錯誤,那么處置資源的代碼可能得不到執行。

using語句不同於using指令。using指令在第21章闡述。
資源的包裝使用
using語句幫助減少意外的運行時錯誤帶來的潛在問題,它整潔地包裝了資源的使用。
有兩種形式的using語句。第一種形式如下:
- 圓括號內的代碼分配資源
- Statement是使用資源的代碼
- using語句隱式產生處置該資源的代碼
using(ResourceType Identifier=Expression)//分配資源 { Statement//使用資源 }
第二種形式放在異常處理的finally塊中。
- 分配資源
- 把Statement放進try塊
- 創建資源的Dispose方法的調用,並把它放進finally塊


using語句示例
- TextWriter資源打開一個文本文件,並向文件寫入一行
- TextReader資源接着打開相同的文本文件,一行一行讀取並顯式它的內容
- 在兩種情況中,using語句確保調用對象的Dispose方法
- 還要注意Main中using語句和開始兩行的using指令之間的區別
using System; //using指令,不是using語句 using System.IO; //using指令,不是using語句 namespace UsingStatement { class Program { static void Main() { //using語句 using(TextWriter tw=File.CreateText("Lincoln.txt")) { tw.WriteLine("Four score and seven years ago,..."); } //using語句 using(TextReader tr=File.OpenText("Lincoln.txt")) { string InputString; while(null!=(InputString=tr.ReadLine())) { Console.WriteLine(InputString); } } } } }


多個資源和嵌套
using語句還可以用於相同類型的多個資源,資源聲明用逗號隔開。
只有一個類型 資源 資源 ↓ ↓ ↓ using(ResourceType Id1=Expr1,Id2=Expr2,...) { EmbeddedStatement }
例:
class Program { static void Main() { using(TextWriter tw1=File.CreateText("Lincoln.txt"), tw2=File.CreateText("Franklin.txt")) { tw1.WriteLine("Four score and seven years ago,..."); tw2.WriteLine("Early to bed;Early to rise..."); } ... } }
using語句還可以嵌套。
using(TextWriter tw1=File.CreateText("Lincoln.txt")) { tw1.WriteLine("Four score and seven years ago,..."); using(TextWriter tw2=File.CreateText("Franklin.txt")) { tw2.WriteLine("Early to bed;Early to rise..."); } }
using語句的另一種形式
關鍵字 資源 ↓ ↓ using(Expression) { EmbeddedStatement//使用資源 }
這種形式中,資源在using語句前聲明。
TextWriter tw=File.CreateText("Lincoln.txt"); using(tw) { tw.WriteLine("Four score and seven years ago,..."); }
雖然這種形式也能確保對資源的使用結束后總是調用Dispose方法,但它不能防止在using語句已經釋放了它的非托管資源后使用該資源,導致了不一致的狀態。因此它提供了較少的保護,不推薦使用。


其他語句
還有一些語句和語言的特征相關。這些語句在涉及相應特征的章節中闡述。
| 語句 | 描述 | 相關章節 |
|---|---|---|
| checked,unchecked | 控制溢出檢查上下文 | 第16章 |
| foreach | 遍歷一個集合的每個成員 | 第12章和第18章 |
| try、throw、finally | 處理異常 | 第22章 |
| return | 將控制返回到調用函數的成員,而且還能返回一個值 | 第5章 |
| yield | 用於迭代 | 第18章 |
