在體驗C#的銳利之前,關乎語言基本知識的掌握是必不可少的一環。由於C#基本語言很多源自C/C++,在這裡對那些和C/C++類似的地方僅作簡單介紹,我們將體驗專注於那些區別於傳統C/C++的關鍵的語言基礎知識。
數據類型
C#語言的數據類型主要分為兩類:值類型和引用類型。另外一種數據類型"指針"是為unsafe上下文編程專門設定的,其中unsafe上下文指對代碼進行unsafe標示以滿足利用指針對內存直接進行操作要求的C#非托管代碼,這些代碼將失去Microsoft.NET平台的垃圾收集等CLR性質,我們放在"COM互操作 非托管編程與異常處理"專題裡闡述。值類型的變量本身包含他們的數據,而引用類型的變量包含的是指向包含數據的內存塊的引用或者叫句柄。從下面這幅圖中可以清晰地看出兩者的差別:
引用類型帶來的可能的問題便是當多個變量引用同樣的內存塊時,對任何一個引用變量的修改都會導致該對象的值的改變。null值表示引用類型沒有對任何實際地址進行引用。
值類型可分為結構類型和枚舉類型。結構類型包括簡單類型和用戶自定義結構類型。枚舉類型和用戶自定義結構類型我們將在"第九講 結構,枚舉,數組與字符串"專題裡詳細闡述。簡單類型又可分為布爾類型和數值類型。C#語言中布爾類型嚴格與數值類型區分,只有true和false兩種取值,不存在像C/C++裡那樣和其他類型之間的轉換。數值類型包括整值,浮點和decimal三種類型。整值類型有sbyte,byte,short,ushort,int,uint,long,ulong,char共九種。除了char類型外,其他8種兩兩一組分別為有符號和無符號兩種。浮點值有float和double兩種。decimal主要用於金融,貨幣等對精度要求比較高的計算環境。下表是對這些簡單類型的一個詳細的描述:
簡單類型
描 述
示 例
sbyte 8-bit 有符號整數 sbyte val = 12; short 16-bit 有符號整數 short val = 12; int 32-bit有符號整數 int val = 12; long 64-bit有符號整數 long val1 = 12; long val2 = 34L; byte 8-bit無符號整數 byte val1 = 12; byte val2 = 34U; ushort 16-bit 無符號整數 ushort val1 = 12; ushort val2 = 34U; uint 32-bit 無符號整數 uint val1 = 12; uint val2 = 34U; ulong 64-bit 無符號整數 ulong val1 = 12; ulong val2 = 34U; ulong val3 = 56L; ulong val4 = 78UL; float 32-bit單精度浮點數 float val = 1.23F; double 64-bit雙精度浮點數 double val1 = 1.23; double val2 = 4.56D; l 布爾類型 bool val1 = true; bool val2 = false; char 字符類型 ,Unicode 編碼 char val = 'h'; decimal 28個有效數字的128-bit十進制類型 decimal val = 1.23M;引用類型共分四種類型:類,接口,數組,委派。類除了我們可以定義自己的類型外,又包括兩個比較特殊的類型object和string。object是C#中所有類型(包括所有的值類型和引用類型)的繼承的根類。string類型是一個密封類型(不能被繼承),其實例表示Unicode字符串,它和數組類型我們將放在"第九講 結構,枚舉,數組與字符串"中詳述。接口類型定義一個方法的合同,我們將在"第七講 接口 繼承與多態"中講述。委派類型是一個指向靜態或實例方法的簽名,類似於C/C++中的函數指針,將在"第八講 委派與事件"中講述。實際上我們將從後面的專題中看到這些類型都是類的某種形式的包裝。
每種數據類型都有對應的缺省值。數值類型的缺省值為0或0.0,其中char的缺省為'\x0000'。布爾類型的缺省值為false。枚舉類型的缺省值為0。結構類型的缺省值是將它所有的值類型的域設置為對應值類型的缺省值,將其引用類型的域設置為null。所有引用類型的缺省值為null。
不同類型的數據之間可以轉換,C#的類型轉換有隱含轉換,明晰轉換,標准轉換,自定義轉換共四種方式。隱含轉換與明晰轉換和C++裡一樣,數據從"小類型"到"大類型"的轉換時為隱含轉換,從"大類型"到"小類型"的轉換為明晰轉換,明晰轉換需要如"(Type)data"一般的括號轉換操作符。標准轉換和自定義轉換是針對系統內建轉換和用戶定義的轉換而言的,兩者都是對類或結構這樣的自定義類型而言的。
變量與常量
變量表示存儲位置,變量必須有確定的數據類型。C#的類型安全的含義之一就是確保變量的存儲位置容納著合適的類型。可以將C#中的變量分為靜態變量,實例變量,傳值參數,引用參數,輸出參數,數組參數和本地變量共七種。本地變量則是在方法體內的臨時變量。
靜態變量和實例變量主要是針對類或結構內的數據成員(又叫域)而言的。靜態變量在它寄存的類或結構類型被裝載後得到存儲空間,如果沒有對它進行初始化賦值,靜態變量的初始值將是它的類型所持有的缺省值。實例變量在它的類實例被創建後獲得存儲空間,如果沒有經過初始化賦值,它的初始值與靜態變量的定義相同。兩者更詳細的說明我們放在"第六講 域 方法 屬性與索引器"專題裡。
傳值參數,引用參數,輸出參數,數組參數主要針對方法的參數類型而言的。簡單的講傳值參數是對變量的值的一種傳遞,方法內對變量的改變在方法體外不起作用。對於傳值參數本身是引用型的變量稍有不同,方法內對該引用(句柄)變量指向的數據成員即實際內存塊的改變將在方法體外仍然保留改變,但對於引用(句柄)本身的改變不起作用。引用參數是對變量的句柄的一種傳遞,方法內對該變量的任何改變都將在方法體外保留。輸出參數是C#專門為有多個返回值的方法而量身定做的,它類似於引用變量,但可以在進入方法體之前不進行初始化,而其他的參數在進入方法體內C#都要求明確的初始化。數組參數是為傳遞大量的數組元素而專門設計的,它從本質上講是一種引用型變量的傳值參數。它們更詳細的闡述我們也放在"第六講 域 方法 屬性與索引器"專題裡。
本地變量嚴格的講是在C#的塊語句,for語句,switch語句,using語句內聲明的變量,它的生命周期嚴格地被限制在這些語句塊內部。
常量在編譯時便確定它的值,在整個程序中也不許修改。常量聲明的同時必須賦值。由於它的編譯時確定值的特性,引用類型可能的值只能為string和null(除string外,引用類型的構建器必須在運行時才能確定引用類型的值)。
操作符與表達式
C#保留了C++所有的操作符,其中指針操作符(*和->)與引用操作符(&)需要有unsafe的上下文。C#擯棄了范圍辨析操作符(::),一律改為單點操作符(.)。我們不再闡述那些保留的C++的操作符,這裡主要介紹C#引入的具有特殊意義的幾個操作符:as,is,new, typeof,sizeof,stackalloc。
as操作符用於執行兼容類型之間的轉換,當轉換失敗時,as 操作符結果為null。is 操作符用於檢查對象的運行時類型是否與給定類型兼容,當表達式非null且可以轉化為指定類型時,is操作符結果為true,否則為false。as和is操作符是基於同樣的類型鑒別和轉換而設計的,兩者有相似的應用場合。實際上expression as type相當於expression is type ? (type)expression : (type)null。
作為操作符的new用於在堆上創建對象和調用構造函數,值得注意的是值類型對象(例如結構)是在堆棧上創建的,而引用類型對象(例如類)是在堆上創建的。new也用於修飾符,用於隱藏基類成員的繼承成員。為隱藏繼承的成員,使用相同名稱在派生類中聲明該成員並用 new 修飾符修改它。typeof 運算符用於獲得某一類型的 System.Type 對象,我們將在"第十講 特征與映射"裡結合Microsoft.NET的類型系統對它作詳細的闡述。sizeof 運算符用於獲得值類型(不適用於引用類型)的大小(以字節為單位)。stackalloc用於在堆棧上分配內存塊, 僅在局部變量的初始值設定項中有效,類似於C/C++語言的_alloca。sizeof和statckalloc都由於涉及內存的直接操作而需要unsafe上下文。
C#裡的某些操作符可以像C++裡那樣被重載。操作符重載使得自定義類型(類或結構)可以用簡單的操作符來方便的表達某些常用的操作。
為完成一個計算結果的一系列操作符和操作數的組合稱為表達式。和C++一樣,C#的表達式可以分為賦值表達式和布爾表達式兩種,C#沒有引入新的表達式形式,我們對此不再贅述。
命名空間與語句
C#采用命名空間(namespace)來組織程序。命名空間可以嵌套。using指示符可以用來簡化命名空間類型的引用。using指示符有兩種用法。"using System;"語句可以使我們用簡短的類型名"Console"來代替類型"System.Console"。"using Output = System.Console;"語句可以使我們用別名"Output"來代替類型"System.Console"。命名空間的引入大大簡化了C#程序的組織方式。
C#語句可以分為標號語句,聲明語句,塊語句,空語句,表達式語句,選擇語句,反復語句,跳轉語句,try語句,checked/unchecked語句,lock語句,using語句。
標號語句主要為goto跳轉設計,C#不允許跨方法的跳轉,但允許小規模的方法內的跳轉。聲明語句可以同時進行初始化賦值,對象的實例化聲明需要new關鍵字。塊語句采用"{"和"}"定義語句塊,主要是界定局部變量的作用范圍。空語句在C#中用分號";"表示,沒有執行語義。表達式語句通過表達式構成語句。
選擇語句有if語句和switch語句兩種,與C++別無二致。反復語句除了while,do,for三種循環結構外引入了foreach語句用於遍歷集合中所有的元素,但這需要特定的接口支持,我們在後面的章節裡對之作詳細闡述。
跳轉語句有break,continue,goto,return,throw五種語句,前四種與C++裡的語義相同,throw語句與後面的try語句我們將在"第十一講 COM互操作 非托管編程與異常處理"闡述。
checked/unchecked語句主要用於數值運算中溢出檢查的上下文。lock語句主要用於線程信號量的鎖控制。using語句主要用於片斷資源管理。這些我們在後續章節裡都會有具體的涉及。