- 從零開始學C#
- 劉亮亮等編著
- 415字
- 2018-12-27 12:54:36
第3章C#中的引用類型和值類型
數據類型是.NET最基本的構成元素,數據類型是該平臺下的C#開發語言的基本組成。每一個變量都要求定義為一個特定的類型,并且要求存儲在變量中的值只能是這種類型的值。變量既能保存值類型,也可以保存引用類型,還可以是指針。其中引用類型和值類型是.NET中最基本的數據類型。
3.1 引用類型和值類型簡介
CLR(公共語言運行時)為.NET平臺提供了一個運行環境,CLR支持兩種類型:引用類型和值類型。在.NET中的變量的類型通常是引用類型或者值類型。其中值類型包括:基元類型、枚舉和結構。引用類型包括:類、字符串、標準模塊、接口、數組和委托。CLR中的數據類型示意圖如圖3-1所示。

圖3-1 CLR類型結構
3.2 引用類型
C#預定義的引用類型包括object和string兩種類型。而用戶定義的引用類型可以是接口類型、類類型和委托類型。引用類型實例總是從托管堆上分配,引用類型實例內存的回收通過垃圾收集器。
3.2.1 引用類型內存分配
引用類型實例分配在托管堆上,變量保存了實例數據的內存引用。引用類型可以是自描述類型、指針類型或接口類型。而自描述類型進一步細分成數組和類類型。類類型則可以是用戶定義的類、裝箱的值類型和委托。通常聲明為以下類型:class、interface、delegate、object、string及其他的自定義引用類型時,該變量即為引用類型。
在C#中有以下一些常用引用類型。
? 數組:(派生于System.Array);
? 指針:Indicator(指針);
? 接口:interface(接口)。
用戶使用定義的以下類型。
? 類:class(派生于System.Object);
? 委托:delegate(派生于System.Delegate)。
? object(System.Object的別名);
? 字符串:string(System.String的別名)。
【實例3-1】本例將創建一個控制臺應用程序,當用戶輸入自己的名字后,顯示“歡迎**使用C# 4.0開發平臺!”。
(1)創建控制臺應用程序啟動Visual Studio 2010,選擇“文件”→“新建”命令,在“項目”選項卡中選擇“控制臺應用程序”,創建一個名稱為“3.1”的控制臺應用程序,如圖3-2所示。

圖3-2 創建3.1的項目
(2)編寫字符串引用類型的代碼,如下所示:
01 static void Main(string[] args) 02 { 03 string yourname; //字符串引用類型變量 04 Console.WriteLine("請輸入您的昵稱!"); 05 string name = Console.ReadLine(); 06 yourname = NewStr(name); 07 Console.WriteLine(yourname); 08 Console.ReadLine(); 09 } 10 11 public static string NewStr(string yourname) //公共靜態字符函數 12 { 13 string newstr; //字符串引用類型變量 14 newstr = "歡迎" + yourname + "使用C# 4.0開發平臺!"; 15 return newstr; 16 }
【代碼解析】第11~15行創建公共靜態字符函數NewStr()。
【運行效果】代碼編寫完成之后,按“F5”鍵或者單擊工具欄中的“啟動調試”按鈕,當用戶輸入“FREEMEN”后,按“Enter”鍵,顯示結構如圖3-3所示。

圖3-3 運行結果
引用類型事實上保存一個指向它引用的對象的內存地址。上面的代碼段中有兩個字符串變量(yourname和newstr)引用了同一個string對象。改變某一個引用指向的對象的屬性同時也會影響所有其他指向這個對象的引用。當字符串newstr發生變化時,字符串yourname也跟著發生變化。產生這種行為的原因是由于string對象是恒定的,也就是說,一旦一個string對象被創建,它的值就不能再修改,所以當改變一個字符串變量的值的時候,僅僅是新創建了一個包含修改內容的新的string對象。
3.2.2 引用類型賦值
引用類型賦值時,將會產生一個對該堆上同一個對象的新引用。下面將以引用類型中的class類型為例,實現引用類型的賦值及原始值的覆蓋。
【實例3-2】本節將創建一個控制臺應用程序,實現引用類型的賦值及原始值的覆蓋。
(1)創建控制臺應用程序啟動Visual Studio 2010,選擇“文件”→“新建”菜單命令,在“項目”選項卡中選擇“控制臺應用程序”,創建一個名稱為“3.2”的控制臺應用程序。
(2)編寫Person類代碼,并在Program類中實現引用類型的賦值,代碼如下:
01 class Person 02 { 03 public string fullName; //聲明引用類型的字符變量 04 public int age; //聲明int變量 05 public Person(string n, int a) //定義Person 06 { 07 fullName = n; 08 age = a; 09 } 10 public void PrintInfo() //輸出 11 { 12 Console.WriteLine("{0} 的年齡是 {1} 歲", fullName, age); 13 } 14 } 15 class Program 16 { 17 public static void ArrayOfObjects(params object[] list) //數組對象操作方法 18 { 19 for (int i = 0; i < list.Length; i++) //使用循環操作 20 { 21 if (list[i] is Person) 22 { 23 ((Person)list[i]).PrintInfo(); 24 } 25 else 26 Console.WriteLine(list[i]); 27 } 28 Console.WriteLine(); 29 } 30 public static void SendAPersonByValue(Person p) 31 { 32 //為age賦值 33 p.age = 99; 34 p = new Person("張三", 999); //實例化Person 35 } 36 public static void SendAPersonByReference(ref Person p) 37 { 38 //修改age值 39 p.age = 555; 40 p = new Person("張三", 999) ; //實例化Person 41 } 42 public static void Main() 43 { 44 Console.WriteLine("***** 為Person類設置一個新的引用類型值*****"); 45 Person fred = new Person("李四", 12); 46 Console.WriteLine("值被覆蓋前的person對象是:"); 47 fred.PrintInfo(); 48 SendAPersonByValue(fred); 49 Console.WriteLine("值被覆蓋前的person對象是:"); 50 fred.PrintInfo(); //年齡的更改會有效果,但是重新賦值不會起效 51 Console.WriteLine("\n***** 通過person對象參考 *****"); 52 Person mel = new Person("王五", 23); 53 Console.WriteLine("值被覆蓋前的person對象是:"); 54 mel.PrintInfo(); 55 SendAPersonByReference(ref mel); 56 Console.WriteLine("值被覆蓋前的person對象是:"); 57 mel.PrintInfo(); //被重新賦予另外一個對象 58 Console.ReadLine(); 59 } 60 }
【代碼解析】第1~14行創建Person類,在該類中完成年齡的定義、輸出。第17~29行使用數組對象操作方法操作Person類。第45行實例化Person類。
上面代碼完全是對同一Person對象的操作,被調用者可以改變對象的狀態數據的值和所引用的對象,可重新賦值。
【運行效果】代碼編寫完成之后,按“F5”鍵或者單擊工具欄中的“啟動調試”按鈕,顯示結果如圖3-4所示。

圖3-4 運行結果
3.3 值類型
C#中值類型實例通常分配在線程的堆棧上,并且不包含任何指向實例數據的指針,因為變量本身就包含了其實例數據。在托管代碼中,類型決定了類型實例的分配位置,而使用類型的開發人員對此沒有控制權。值類型實例不受垃圾收集器的控制。
3.3.1 值類型內存分配
值類型主要包括簡單類型、結構體類型和枚舉類型等。通常聲明為以下類型:int、char、float、long、bool、double、struct、enum、short、byte、decimal、sbyte、uint、ulong、ushort等時,該變量即為值類型。C#中的主要值類型如表3-1所示。
表3-1 值類型表

需要說明,C#的所有值類型均隱式派生自System.ValueType,每種值類型均有一個隱式的默認構造函數來初始化該類型的默認值。例如:
int i = new int(); //等價于: Int32 i = new Int32(); //等價于: int i = 0; //等價于: Int32 i = 0;
【實例3-3】本例將創建一個控制臺應用程序,實現值類型的操作。
(1)創建控制臺應用程序。
啟動Visual Studio 2010,選擇“文件”→“新建”菜單命令,在“項目”選項卡中選擇“控制臺應用程序”,創建一個名稱為“3.3”的控制臺應用程序。
(2)編寫值類型操作其本身的代碼,代碼如下:
01 static void Main(string[] args) 02 { 03 Console.WriteLine("所設置的Int類型的值是:"); 04 Console.WriteLine(ReturnValue()); 05 Console.ReadLine(); 06 } 07 public class MyInt 08 { 09 public int MyValue; //聲明值類型變量MyValue 10 } 11 12 public static int ReturnValue() //靜態函數返回int值 13 { 14 MyInt x = new MyInt(); 15 x.MyValue = 3; 16 MyInt y = new MyInt(); 17 y = x; 18 y.MyValue = 4; 19 return x.MyValue; //返回int值 20 }
【代碼解析】第9行聲明值類型變量MyValue。第12~20行靜態函數ReturnValue()返回int值。第14行實例化MyInt類,并為該類的MyValue賦值。
【運行效果】代碼編寫完成之后,按“F5”鍵或者單擊工具欄中的“啟動調試”按鈕,顯示結果如圖3-5所示。

圖3-5 運行結果
3.3.2 值類型賦值
值類型包括數值類型、枚舉和結構,它們都分配在棧上,一旦離開定義的作用域,立即就會被從內存中刪除。當一個值類型賦值給另一個值類型的時候,默認情況下完成的是一個成員到另一個成員的復制。就數值和布爾型而言,唯一要復制的就是變量本身的值。
值類型賦值的時候,是復制各個值到賦值目標,實際上各自在棧中都有存在,對一個值的操作不會影響另一個。
【實例3-4】本例將創建一個控制臺應用程序,來說明值類型賦值的時候,是復制各個值到賦值目標,實際上各自在棧中都有存在,對一個值的操作不會影響另一個。
(1)創建控制臺應用程序。
啟動Visual Studio 2010,選擇“文件”→“新建”菜單命令,在“項目”選項卡中選擇“控制臺應用程序”,創建一個名稱為“3.4”的控制臺應用程序。
(2)代碼如下所示:
01 //定義一個結構 02 struct MyPoint 03 { 04 public int x, y; 05 } 06 // 類將作為結構使用 07 class ShapeInfo 08 { 09 public string infoString; 10 public ShapeInfo(string info) 11 { infoString = info; } 12 } 13 14 struct MyRectangle 15 { 16 public ShapeInfo rectInfo; 17 public int top, left, bottom, right; 18 public MyRectangle(string info) 19 { 20 rectInfo = new ShapeInfo(info); 21 top = left = 10; 22 bottom = right = 100; 23 } 24 } 25 class ValRefClass 26 { 27 static void Main(string[] args) 28 { 29 Console.WriteLine("***** 值類型/ 引用類型 *****"); 30 //在棧中 31 MyPoint p = new MyPoint(); 32 Console.WriteLine("-> Creating p1"); 33 MyPoint p1 = new MyPoint(); 34 p1.x = 100; 35 p1.y = 100; 36 Console.WriteLine("-> Assigning p2 to p1"); 37 MyPoint p2 = p1; 38 // P1 39 Console.WriteLine("p1.x = {0}", p1.x);//100 40 Console.WriteLine("p1.y = {0}", p1.y);//100 41 // P2. 42 Console.WriteLine("p2.x = {0}", p2.x);//100 43 Console.WriteLine("p2.y = {0}", p2.y);//100 44 // 修改p2.x.不改變p1.x. 45 Console.WriteLine("-> Changing p2.x to 900"); 46 p2.x = 900; 47 //顯示 48 Console.WriteLine("-> Here are the X values again..."); 49 ;//100,如果將MyPoint改成類類型,此處會顯示900。 50 Console.WriteLine("p1.x = {0}", p1.x) 51 Console.WriteLine("p2.x = {0}", p2.x);//900 52 Console.WriteLine(); 53 // 創建第一個MyRectangle. 54 Console.WriteLine("-> Creating r1"); 55 MyRectangle r1 = new MyRectangle("This is my first rect"); 56 // 將一個新的MyRectangle賦值給r1 57 Console.WriteLine("-> Assigning r2 to r1"); 58 MyRectangle r2; 59 r2 = r1; 60 //修改r2的值 61 Console.WriteLine("-> Changing all values of r2"); 62 r2.rectInfo.infoString = "This is new info!"; 63 r2.bottom = 4444; 64 //顯示s 65 Console.WriteLine("-> Values after change:"); 66 Console.WriteLine("-> r1.rectInfo.infoString:{0}", r1.rectInfo.infoString); 67 Console.WriteLine("-> r2.rectInfo.infoString:{0}", r2.rectInfo.infoString); 68 Console.WriteLine("-> r1.bottom: {0}", r1.bottom);//100 69 Console.WriteLine("-> r2.bottom: {0}", r2.bottom);//4444 70 Console.ReadLine(); 71 } 72 }
【代碼解析】第4行為結構MyPoint定義兩個變量,供后面調用。
【運行效果】代碼編寫完成之后,按“F5”鍵或者單擊工具欄中的“啟動調試”按鈕,顯示結果如圖3-6所示。

圖3-6 運行效果
3.4 引用類型和值類型的區別
引用類型和值類型的區別如下。
? 值類型對象有兩種表示:未裝箱形式和裝箱形式,而引用類型總是裝箱形式。
? 當定義自己的值類型時,應該重寫Equals方法和GetHashCode方法。
? 值類型中不可以有任何的抽象方法,不可以引入任何新的虛方法,所有的方法都隱含為sealed方法。
? 當一個引用類型變量被創建時,它被初始化為null。值類型變量總是包含一個符合它的類型的值。
? 當將一個值類型變量賦值給另一個值類型變量,會進行一個字段對字段的復制,而將一個引用類型變量賦值給另一個引用類型變量時,只會復制內存地址。
? 兩個或多個引用類型變量可以指向托管堆中的同一個對象,而每個值類型變量都有一份自己的對象數據副本。
? 值類型實例在內存回收時不可能收到任何通知。
System.Runtime.InteropServices.StructLayout特性用于指示CLR是按指定的順序來存儲類型實例的字段,還是以任何CLR認為合適的順序排列字段。C#編譯器為引用類型選擇的是LayoutKind.Auto方式,而為值類型選擇的是LayoutKind.Sequential方式。
值類型和引用類型的區別如表3-2所示。
表3-2 值類型和引用類型區別表

3.5 C# 4.0中的新特性:查看調用層(View Call Hierarchy)
該特性主要用于查看函數和屬性,在編程過程中遇到不明用途的函數,可在該函數上面單擊鼠標右鍵,該特性會告訴用戶的函數使用分層列表,如圖3-7所示。

圖3-7 調用層次
為了查看某個函數的詳細信息可以在圖3-7中單擊查看調用層次,它會顯示一個窗體,詳細顯示對應函數的信息,如圖3-8所示。

圖3-8 函數信息圖
在層次結構中選擇窗口函數,調用它會顯示參數和函數調用的位置的詳細信息,如圖3-9所示。

圖3-9 函數詳細信息
3.6 小結
本章講解了C#中的數據類型,C#中每一種類型要么是值類型,要么是引用類型。所以每個對象要么是值類型的實例,要么是引用類型的實例。值類型的實例通常是在線程棧上分配的(靜態分配)。引用類型的對象總是在進程堆中分配(動態分配)的。
3.7 練習
一、填空題
1.CLR的全稱是( )。
2.常見的值類型包括( )。
3.常見的引用類型包括( )。
4.值類型存放的位置( )。
5.引用類型存放的位置( )。
二、簡答題
1.簡述C#中常見的數據類型。
2.說說值類型和引用類型的區別。
三、上機題
嘗試使用C#創建一個控制臺應用程序,輸入一個自然數,顯示結果在輸入的自然數上加10,運行效果要求與如圖2-3所示一致。
- 精通Nginx(第2版)
- 數字媒體應用教程
- iOS 9 Game Development Essentials
- SpringMVC+MyBatis快速開發與項目實戰
- Mastering phpMyAdmin 3.4 for Effective MySQL Management
- 深入淺出WPF
- Mastering Python Scripting for System Administrators
- Node.js全程實例
- Windows Embedded CE 6.0程序設計實戰
- 移動增值應用開發技術導論
- Laravel Application Development Blueprints
- Java 9 Programming By Example
- Learning Grunt
- LabVIEW數據采集
- Python 3快速入門與實戰