- ASP.NET 3.5程序設計與項目實踐
- 張聯鋒 陳文臣主編
- 4667字
- 2018-12-27 18:44:53
2.2 基本語法
語法是一種程序語言最基本的定義規范,只有按照語法給出的規則才能編寫出正確的程序。C#程序基本語法包括:數據類型的種類,變量與常量的聲明和使用以及語句的基本組成表達式和運算符。
2.2.1 數據類型
C#的數據類型包括值類型、引用類型和指針類型。指針類型是不安全類型,一般不推薦使用。
1.值類型
值類型包括簡單類型(如字符型、浮點型和整數類等)、枚舉類型和結構類型。所有的值類型都隱含地聲明了一個公共的無參數的構造函數,這個構造函數返回一個初始為零的值類型的實例。例如,對于字符型,默認值是“\x0000”;對于float,默認值是0.0F。
(1) 簡單類型:它是C#預先定義的結構類型,簡單類型用關鍵字定義,這些關鍵字僅僅是在System命名空間里預定義的結構類型的化名,比如關鍵字int對應System.Int32。簡單類型包括如表2-1所示數據類型。
表2-1 簡單數據類型

(2) 集合類型:它是C#中一種輕量級的值類型,用來表達一組特定的值的集合行為,以enum關鍵字進行聲明。
(3) 結構類型:它是用來封裝小型的相關變量組,把它們封裝成一個實體來統一使用,以struct關鍵字進行聲明。
2.引用類型
引用類型包括類類型、對象類型、字符串類型、接口類型、委托類型和數組類型等。引用類型與值類型的不同之處是值類型的變量值直接包含數據,而引用類型的變量把它們的引用存儲在對象中。類類型、對象類型和數組類型在后面的章節有詳細介紹。
(1) 字符串類型:直接從object中繼承而來的密封類。String類型的值可以寫成字符串文字的形式。例如:"123"、"hello world"是字符串類型。
(2) 接口類型:一個接口聲明一個只有抽象成員的引用類型,接口僅僅存在方法標志,但沒有執行代碼,以關鍵字interface進行聲明。
(3) 委托類型:委托引用一種靜態的方法或對象實例,引用該對象的實例方法,與C/C++中的指針類似,以關鍵字delegate進行聲明。
作者心得:
在C#中,所有數據類型都是基于基本對象Object來實現,因此它們之間在允許的范圍內是可以相互轉化的。這就是裝箱和拆箱,在2.2.4節會進行詳細介紹。
2.2.2 變量和常量
1.變量
所謂變量,就是在程序的運行過程中其值可以被改變的量,變量的類型可以是任何一種C#的數據類型。所有值類型的變量具有實際存在于內存中的值,也就是說當將一個值賦給變量時就是在執行對該值的拷貝。變量的定義格式為:
變量數據類型 變量名(標識符);
或者
變量數據類型 變量名(標識符) =變量值;
標識符就是變量名,變量的標志。
其中,第一個定義只是聲明了一個變量,并沒有對變量進行賦值,此時變量使用默認值。第二個聲明定義變量的同時對變量進行了初始化,變量值應該和變量數據類型一致。例如:
int a=10; \\ 聲明了一個整數類型的變量a,并對其賦值為10 double b,c; \\ 兩個double類型的變量 int d=100,e=200; \\ 定義了兩個整數類型的變量,并對變量進行了賦值 double f=a+b+c+d+e; \\ 把前面定義的變量相加,然后賦給一個double類型的變量
作者心得:
當幾個不同數值類型的變量進行運算時,低精度的變量會自動轉化為高精度的變量類型。
2.常量
所謂常量,就是在程序的運行過程中其值不能被改變的量。常量的類型也可以是任何一種C#的數據類型。常量的定義格式為:
const 常量數據類型 常量名(標識符)=常量值;
其中,const關鍵字表示聲明一個常量,“常量名”就是標識符,用于唯一的標識該常量。常量名要有代表意義,不能過于簡練或者復雜。
“常量值”的類型要和常量數據類型一致,如果定義的是字符串型,“常量值”就應該是字符串類型,否則會發生錯誤。例如:
const double PI=3.1415926; // 定義了一個double類型的常量 const string VERSION = "Visual Studio 2008"; //定義了一個字符串型的常量
作者心得:
常量一旦定義,用戶在后面的代碼中如果試圖改變常量的值,編譯器會發現這個錯誤導致代碼無法編譯通過。
2.2.3 表達式和運算符
1.表達式
表達式是可以運算的代碼片段,表達式可以包括運算符、方法調用等,表達式是程序語句的基本組成部分,例如:
int num = 5; //定義一個整型變量num,并對其賦值 string str = “你好,世界!”; //定義一個字符串變量,并對其賦值
2.運算符
運算符是數據運算的術語和符號,它接受一個或多個稱為操作數的表達式作為輸入并返回值。C#中的運算符非常多,從操作數上劃分運算符大致分為以下3類。
● 一元運算符:處理一個操作數,只有幾個一元運算符。
● 二元運算符:處理兩個操作數,大多數運算符都是二元運算符。
● 三元運算符:處理三個操作數,只有一個三元運算符。
從功能上劃分,運算符主要分為:算術運算符,賦值運算符,關系運算符,條件運算符,位運算符和邏輯運算符。
例如:
i ++; //一元運算,變量i自動加1 num = 2 + 3; //二元運算,變量num等于2加3的和 result = a > b ? 100 : -10//三元運算,條件運算符,根據條件的真假來決定運算的正確性
表達式中的運算符按照運算符優先級的特定順序計算,表2-2按優先順序列出常用運算符的優先級別,在表2-2中,上面的比下面的具有較高的優先級別。
表2-2 常用運算符

例如:
int num = 4 + 5 * 2 + 6 / 2 //數學運算,先算乘除后算加減,結果是17
作者心得:
僅僅依靠優先級來安排數據的運算順序是可靠的,大部分情況下考慮使用()來進行強制優先級,凡是用()括起來的比其他運算符都有高的優先級。
范例2.2
Program.cs
在該程序中定義兩個雙精度變量,通過控制臺分別輸入他們的值,然后分別對這兩個變量做一系列數學運算。
代碼路徑:ShiLi2-2\Program.cs
1 double firstNumber,secondNumber; //定義兩個雙精度變量 2 Console.WriteLine("請輸入一個數字:"); 3 //將用戶輸入的第一個數字轉換成double類型,并存儲在firstNumber中 4 firstNumber=Convert.ToDouble(Console.ReadLine()); 5 Console.WriteLine("請輸入第二個數字"); 6 //將用戶輸入的第二個數字轉換成double類型,并存儲在secondNumber中 7 secondNumber=Convert.ToDouble(Console.ReadLine()); 8 Console.WriteLine("{0}+{1}={2}",firstNumber,secondNumber,firstNumber+ 9 secondNumber); //求和 10 Console.WriteLine("{0}-{1}={2}", firstNumber, secondNumber, firstNumber - 11 secondNumber); //求差 12 Console.WriteLine("{0}*{1}={2}", firstNumber, secondNumber, firstNumber * 13 secondNumber); //求積 14 Console.WriteLine("{0}/{1}={2}", firstNumber, secondNumber, firstNumber / 15 secondNumber); //求商 16 Console.WriteLine("{0}%{1}={2}", firstNumber, secondNumber, firstNumber % 17 secondNumber); //求余 18 Console.ReadKey();
運行以上代碼輸出結果是:
請輸入一個數字:
10
請輸入第二個數字
6
10+6=16
10-6=4
10*6=60
10/6=1.66666666666667
10%6=4
2.2.4 裝箱和拆箱
裝箱和取消裝箱使值類型能夠被視為對象。對值類型裝箱將把該值類型打包到Object引用類型的一個實例中。這使得值類型可以存儲于垃圾回收堆中。取消裝箱將從對象中提取值類型,取消裝箱又經常被稱作“拆箱”。例如:
int i = 123; //定義一個值類型變量 object o=(object)i; // 裝箱 o = 123; //對裝箱后的對象操作 i=(int)o; //取消裝箱
從裝箱和取消裝箱的定義可以看出,這兩種行為主要用于進行值類型和引用類型之間的相互轉化的情況。
作者心得:
相對于簡單的賦值而言,裝箱和拆箱過程需要進行大量的計算。對值類型進行裝箱時,必須分配并構造一個全新的對象。此外,拆箱所需的強制轉換也需要進行大量的計算。因此,讀者在進行裝箱和拆箱操作時應該考慮到該操作對性能的影響。
2.2.5 泛型
C#中的泛型類似C++的模板,它在一定程度上能夠提高應用程序的效率。使用泛型可以定義類型安全的數據結構,而無需使用具體實際的數據類型。通過使用泛型,能夠將數據類型參數化,以此完成代碼重用的目標。
2.2.5.1 使用系統的泛型類
通常情況下,泛型常見于集合應用中。在System.Collections.Generic名稱空間中,包含了一些基于泛型的容器類,例如System.Collections.Generic.Stack、System. Collections.Generic. Dictionary、System.Collections.Generic.List和System.Collections. Generic.Queue等,這些類庫可以在集合中實現泛型。
創建泛型的格式:
1) 類名 <Type> 變量名 =new 類名 <Type>(); 2) List<String>l2=new ArrayList();
第1行是泛型創建的格式,使用泛型類必須指定實際的類型,并在<>角括號中指定實際的類型。第2行根據格式創建了String類型的泛型類的集合類型l2,這意味著l2支能存儲String類型的數據。
泛型的的使用如下面的代碼:
1) List<String>l=new ArrayList(); 2) l.add("小明"); 3) l.add("小王"); 4) String s=l.get(0)
第1行,使用泛型List<String> l=new ArrayList()創建ArrayList的對象l,然后第2、3行使用ArrayList類的add方法,傳入二個String類型的參數:小明和小王。這里只能傳泛型中定義的類型,否則報錯。所以第四行使用ArrayList類的方法get取得元素時就不需要類型轉換了,因為所有的元素類型只能是String類型。
C#泛型類在編譯時,先生成中間代碼IL,通用類型T只是一個占位符。在實例化類時,根據用戶指定的數據類型代替T并由即時編譯器(JIT)生成本地代碼,這個本地代碼中已經使用了實際的數據類型,等同于用實際類型寫的類。我們把為所有類型參數提供參數的泛型類型稱為封閉構造泛型類型,簡稱封閉類。不同的封閉類的本地代碼是不一樣的。按照這個原理,可以這樣認為:泛型類的不同封閉類是不同的數據類型。
2.2.5.2 創建泛型
除了使用系統的泛型類之外,讀者可以編寫自己的泛型類。下面我們來介紹泛型類和普通類的區別。
1.靜態構造函數
靜態構造函數的規則:只能有一個,且不能有參數,它只能被.NET運行時自動調用,而不能人工調用。
泛型中的靜態構造函數的原理和非泛型類是一樣的,只需把泛型中的不同的封閉類理解為不同的類即可。以下兩種情況可激發靜態的構造函數:
● 特定的封閉類第一次被實例化。
● 特定封閉類中任一靜態成員變量被調用。
2.靜態成員變量
在C#1.0中,類的靜態成員變量在不同的類實例間是共享的,并且它是通過類名訪問的。C#2.0中由于引進了泛型,導致靜態成員變量的機制出現了一些變化:靜態成員變量在相同封閉類間共享,不同的封閉類間不共享。
這也非常容易理解,因為不同的封閉類雖然有相同的類名稱,但由于分別傳入了不同的數據類型,他們是完全不同的類,比如:
Stack<int> a = new Stack<int>(); Stack<int> b = new Stack<int>(); Stack<long> c = new Stack<long>();
類實例a和b是同一類型,它們之間共享靜態成員變量,但類實例c卻是和a、b完全不同的類型,所以不能和a、b共享靜態成員變量。
3.數據類型的約束
在編寫泛型類時,一般情況下,通用數據類型T是不能適應所有類型的。但如何才能限制調用者傳入的數據類型呢?這就需要對傳入的數據類型進行約束,約束的方式是指定T的祖先,即繼承的接口或類。因為C#的單根繼承性,所以約束可以有多個接口,但最多只能有一個類,并且類必須在接口之前。
由于通用類型T是從object繼承來的,所以他在類Node的編寫中只能調用object類的方法,這給程序的編寫造成了困難。比如你的類設計只需要支持兩種數據類型int和string,并且在類中需要對T類型的變量比較大小,但這些卻無法實現,因為object是沒有比較大小的方法的。為了解決這個問題,只需對T進行IComparable約束,這時在類Node里就可以對T的實例執行CompareTo方法了。這個問題可以擴展到其他用戶自定義的數據類型。
如果在類Node里需要對T重新進行實例化該怎么辦呢?因為類Node中不知道類T到底有哪些構造函數。為了解決這個問題,需要用到new約束,需要注意的是,new約束只能是無參數的,所以也要求相應的類Stack必須有一個無參構造函數,否則編譯失敗。
4.泛型的使用
上面我們了解了泛型的基本概念和創建泛型的方法,下面我們通過一個具體的例子來演示如何使用泛型。
范例2.3
ShiLi2-10\Program.cs
本實例使用泛型類List實現學生信息錄入的功能,在控制臺根據提示輸入學生的學號、姓名和年齡,確認錄入信息后,顯示該學生的信息。
代碼路徑:ShiLi2-10\Program.cs
1. class Program 2. { 3. public class Student 4. { 5. private int id; 6. private String name; 7. private int age; 8. public Student(){} 9. public Student(int id, String name, int age) 10. { 11. this.id=id; 12. this.name=name; 13. this.age=age; 14. } 15. public void print(List<Student>stud) 16. { 17. foreach(Student s in stud) 18. { 19. Console.WriteLine("學生的學號是:"+s.id); 20. Console.WriteLine("學生的姓名是:"+s.name); 21. Console.WriteLine("學生的年齡是:"+s.age); 22. } 23. } 24. static void Main(string[]args) 25. { 26. List<Student>stud=new List<Student>(); 27. do 28. { 29. Console.WriteLine("請輸入學生學號:"); 30. int id=Convert.ToInt32(Console.ReadLine()); 31. Console.WriteLine("請輸入學生姓名:"); 32. string name=Console.ReadLine(); 33. Console.WriteLine("請輸入學生年齡:"); 34. int age=Convert.ToInt32(Console.ReadLine()); 35. Console.WriteLine("請輸入學生家庭住址:"); 36. Student s=new Student(id, name, age); 37. stud.Add(s); 38. Console.WriteLine("是否錄入學生信息?Y/N"); 39. }while(Console.ReadLine().ToLower()=="y"); 40. Student studnet=new Student(); 41. studnet.print(stud); 42. } 43. } 44. }
程序說明:第3行自定義封裝了學生類,第5行到第7行定義了3個私有字段表示學生的學號、姓名和年齡。第8行定義不帶參數的構造函數,第9行到第13行在構造函數內對三個字段進行初始化。第10行,編寫一種方法print通過foreach語句循環打印學生的信息。
第26行使用泛型初始化泛型集合對象stud,對象類型規定為我們定義的Student類型。第29行到第35行獲得輸入的數據。第37行調用stud對象的Add方法將學生信息添加到泛型集合中。第41行調用Student對象的print方法打印學生信息。
以上代碼運行后會出現如圖2-1所示的結果。

圖2-1 運行程序后的結果
作者心得:
泛型的編程模式可以大大提高代碼的開發效率,雖然其概念比較復雜且難以理解,但讀者只要按照上面的示例代碼編寫自己通用的類型即可。泛型其實就是提供一種模板,而在實際應用中,只要按照這個模板編碼即可。
- R語言經典實例(原書第2版)
- Building a Game with Unity and Blender
- C語言程序設計
- HTML5+CSS3網站設計教程
- Building Mapping Applications with QGIS
- 零基礎入門學習Python
- Java編程技術與項目實戰(第2版)
- Hands-On Functional Programming with TypeScript
- MATLAB for Machine Learning
- Processing創意編程指南
- R語言:邁向大數據之路(加強版)
- 貫通Tomcat開發
- Application Development with Swift
- Tableau Dashboard Cookbook
- Getting Started with SQL Server 2014 Administration