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

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

圖3-2 創(chuàng)建3.1的項(xiàng)目
(2)編寫字符串引用類型的代碼,如下所示:
01 static void Main(string[] args) 02 { 03 string yourname; //字符串引用類型變量 04 Console.WriteLine("請(qǐng)輸入您的昵稱!"); 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) //公共靜態(tài)字符函數(shù) 12 { 13 string newstr; //字符串引用類型變量 14 newstr = "歡迎" + yourname + "使用C# 4.0開發(fā)平臺(tái)!"; 15 return newstr; 16 }
【代碼解析】第11~15行創(chuàng)建公共靜態(tài)字符函數(shù)NewStr()。
【運(yùn)行效果】代碼編寫完成之后,按“F5”鍵或者單擊工具欄中的“啟動(dòng)調(diào)試”按鈕,當(dāng)用戶輸入“FREEMEN”后,按“Enter”鍵,顯示結(jié)構(gòu)如圖3-3所示。

圖3-3 運(yùn)行結(jié)果
引用類型事實(shí)上保存一個(gè)指向它引用的對(duì)象的內(nèi)存地址。上面的代碼段中有兩個(gè)字符串變量(yourname和newstr)引用了同一個(gè)string對(duì)象。改變某一個(gè)引用指向的對(duì)象的屬性同時(shí)也會(huì)影響所有其他指向這個(gè)對(duì)象的引用。當(dāng)字符串newstr發(fā)生變化時(shí),字符串yourname也跟著發(fā)生變化。產(chǎn)生這種行為的原因是由于string對(duì)象是恒定的,也就是說(shuō),一旦一個(gè)string對(duì)象被創(chuàng)建,它的值就不能再修改,所以當(dāng)改變一個(gè)字符串變量的值的時(shí)候,僅僅是新創(chuàng)建了一個(gè)包含修改內(nèi)容的新的string對(duì)象。
3.2.2 引用類型賦值
引用類型賦值時(shí),將會(huì)產(chǎn)生一個(gè)對(duì)該堆上同一個(gè)對(duì)象的新引用。下面將以引用類型中的class類型為例,實(shí)現(xiàn)引用類型的賦值及原始值的覆蓋。
【實(shí)例3-2】本節(jié)將創(chuàng)建一個(gè)控制臺(tái)應(yīng)用程序,實(shí)現(xiàn)引用類型的賦值及原始值的覆蓋。
(1)創(chuàng)建控制臺(tái)應(yīng)用程序啟動(dòng)Visual Studio 2010,選擇“文件”→“新建”菜單命令,在“項(xiàng)目”選項(xiàng)卡中選擇“控制臺(tái)應(yīng)用程序”,創(chuàng)建一個(gè)名稱為“3.2”的控制臺(tái)應(yīng)用程序。
(2)編寫Person類代碼,并在Program類中實(shí)現(xiàn)引用類型的賦值,代碼如下:
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) //數(shù)組對(duì)象操作方法 18 { 19 for (int i = 0; i < list.Length; i++) //使用循環(huán)操作 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); //實(shí)例化Person 35 } 36 public static void SendAPersonByReference(ref Person p) 37 { 38 //修改age值 39 p.age = 555; 40 p = new Person("張三", 999) ; //實(shí)例化Person 41 } 42 public static void Main() 43 { 44 Console.WriteLine("***** 為Person類設(shè)置一個(gè)新的引用類型值*****"); 45 Person fred = new Person("李四", 12); 46 Console.WriteLine("值被覆蓋前的person對(duì)象是:"); 47 fred.PrintInfo(); 48 SendAPersonByValue(fred); 49 Console.WriteLine("值被覆蓋前的person對(duì)象是:"); 50 fred.PrintInfo(); //年齡的更改會(huì)有效果,但是重新賦值不會(huì)起效 51 Console.WriteLine("\n***** 通過person對(duì)象參考 *****"); 52 Person mel = new Person("王五", 23); 53 Console.WriteLine("值被覆蓋前的person對(duì)象是:"); 54 mel.PrintInfo(); 55 SendAPersonByReference(ref mel); 56 Console.WriteLine("值被覆蓋前的person對(duì)象是:"); 57 mel.PrintInfo(); //被重新賦予另外一個(gè)對(duì)象 58 Console.ReadLine(); 59 } 60 }
【代碼解析】第1~14行創(chuàng)建Person類,在該類中完成年齡的定義、輸出。第17~29行使用數(shù)組對(duì)象操作方法操作Person類。第45行實(shí)例化Person類。
上面代碼完全是對(duì)同一Person對(duì)象的操作,被調(diào)用者可以改變對(duì)象的狀態(tài)數(shù)據(jù)的值和所引用的對(duì)象,可重新賦值。
【運(yùn)行效果】代碼編寫完成之后,按“F5”鍵或者單擊工具欄中的“啟動(dòng)調(diào)試”按鈕,顯示結(jié)果如圖3-4所示。

圖3-4 運(yùn)行結(jié)果
3.3 值類型
C#中值類型實(shí)例通常分配在線程的堆棧上,并且不包含任何指向?qū)嵗龜?shù)據(jù)的指針,因?yàn)樽兞勘旧砭桶似鋵?shí)例數(shù)據(jù)。在托管代碼中,類型決定了類型實(shí)例的分配位置,而使用類型的開發(fā)人員對(duì)此沒有控制權(quán)。值類型實(shí)例不受垃圾收集器的控制。
3.3.1 值類型內(nèi)存分配
值類型主要包括簡(jiǎn)單類型、結(jié)構(gòu)體類型和枚舉類型等。通常聲明為以下類型:int、char、float、long、bool、double、struct、enum、short、byte、decimal、sbyte、uint、ulong、ushort等時(shí),該變量即為值類型。C#中的主要值類型如表3-1所示。
表3-1 值類型表

需要說(shuō)明,C#的所有值類型均隱式派生自System.ValueType,每種值類型均有一個(gè)隱式的默認(rèn)構(gòu)造函數(shù)來(lái)初始化該類型的默認(rèn)值。例如:
int i = new int(); //等價(jià)于: Int32 i = new Int32(); //等價(jià)于: int i = 0; //等價(jià)于: Int32 i = 0;
【實(shí)例3-3】本例將創(chuàng)建一個(gè)控制臺(tái)應(yīng)用程序,實(shí)現(xiàn)值類型的操作。
(1)創(chuàng)建控制臺(tái)應(yīng)用程序。
啟動(dòng)Visual Studio 2010,選擇“文件”→“新建”菜單命令,在“項(xiàng)目”選項(xiàng)卡中選擇“控制臺(tái)應(yīng)用程序”,創(chuàng)建一個(gè)名稱為“3.3”的控制臺(tái)應(yīng)用程序。
(2)編寫值類型操作其本身的代碼,代碼如下:
01 static void Main(string[] args) 02 { 03 Console.WriteLine("所設(shè)置的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() //靜態(tài)函數(shù)返回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行靜態(tài)函數(shù)ReturnValue()返回int值。第14行實(shí)例化MyInt類,并為該類的MyValue賦值。
【運(yùn)行效果】代碼編寫完成之后,按“F5”鍵或者單擊工具欄中的“啟動(dòng)調(diào)試”按鈕,顯示結(jié)果如圖3-5所示。

圖3-5 運(yùn)行結(jié)果
3.3.2 值類型賦值
值類型包括數(shù)值類型、枚舉和結(jié)構(gòu),它們都分配在棧上,一旦離開定義的作用域,立即就會(huì)被從內(nèi)存中刪除。當(dāng)一個(gè)值類型賦值給另一個(gè)值類型的時(shí)候,默認(rèn)情況下完成的是一個(gè)成員到另一個(gè)成員的復(fù)制。就數(shù)值和布爾型而言,唯一要復(fù)制的就是變量本身的值。
值類型賦值的時(shí)候,是復(fù)制各個(gè)值到賦值目標(biāo),實(shí)際上各自在棧中都有存在,對(duì)一個(gè)值的操作不會(huì)影響另一個(gè)。
【實(shí)例3-4】本例將創(chuàng)建一個(gè)控制臺(tái)應(yīng)用程序,來(lái)說(shuō)明值類型賦值的時(shí)候,是復(fù)制各個(gè)值到賦值目標(biāo),實(shí)際上各自在棧中都有存在,對(duì)一個(gè)值的操作不會(huì)影響另一個(gè)。
(1)創(chuàng)建控制臺(tái)應(yīng)用程序。
啟動(dòng)Visual Studio 2010,選擇“文件”→“新建”菜單命令,在“項(xiàng)目”選項(xiàng)卡中選擇“控制臺(tái)應(yīng)用程序”,創(chuàng)建一個(gè)名稱為“3.4”的控制臺(tái)應(yīng)用程序。
(2)代碼如下所示:
01 //定義一個(gè)結(jié)構(gòu) 02 struct MyPoint 03 { 04 public int x, y; 05 } 06 // 類將作為結(jié)構(gòu)使用 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改成類類型,此處會(huì)顯示900。 50 Console.WriteLine("p1.x = {0}", p1.x) 51 Console.WriteLine("p2.x = {0}", p2.x);//900 52 Console.WriteLine(); 53 // 創(chuàng)建第一個(gè)MyRectangle. 54 Console.WriteLine("-> Creating r1"); 55 MyRectangle r1 = new MyRectangle("This is my first rect"); 56 // 將一個(gè)新的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行為結(jié)構(gòu)MyPoint定義兩個(gè)變量,供后面調(diào)用。
【運(yùn)行效果】代碼編寫完成之后,按“F5”鍵或者單擊工具欄中的“啟動(dòng)調(diào)試”按鈕,顯示結(jié)果如圖3-6所示。

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

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

圖3-7 調(diào)用層次
為了查看某個(gè)函數(shù)的詳細(xì)信息可以在圖3-7中單擊查看調(diào)用層次,它會(huì)顯示一個(gè)窗體,詳細(xì)顯示對(duì)應(yīng)函數(shù)的信息,如圖3-8所示。

圖3-8 函數(shù)信息圖
在層次結(jié)構(gòu)中選擇窗口函數(shù),調(diào)用它會(huì)顯示參數(shù)和函數(shù)調(diào)用的位置的詳細(xì)信息,如圖3-9所示。

圖3-9 函數(shù)詳細(xì)信息
3.6 小結(jié)
本章講解了C#中的數(shù)據(jù)類型,C#中每一種類型要么是值類型,要么是引用類型。所以每個(gè)對(duì)象要么是值類型的實(shí)例,要么是引用類型的實(shí)例。值類型的實(shí)例通常是在線程棧上分配的(靜態(tài)分配)。引用類型的對(duì)象總是在進(jìn)程堆中分配(動(dòng)態(tài)分配)的。
3.7 練習(xí)
一、填空題
1.CLR的全稱是( )。
2.常見的值類型包括( )。
3.常見的引用類型包括( )。
4.值類型存放的位置( )。
5.引用類型存放的位置( )。
二、簡(jiǎn)答題
1.簡(jiǎn)述C#中常見的數(shù)據(jù)類型。
2.說(shuō)說(shuō)值類型和引用類型的區(qū)別。
三、上機(jī)題
嘗試使用C#創(chuàng)建一個(gè)控制臺(tái)應(yīng)用程序,輸入一個(gè)自然數(shù),顯示結(jié)果在輸入的自然數(shù)上加10,運(yùn)行效果要求與如圖2-3所示一致。
- DevOps:軟件架構(gòu)師行動(dòng)指南
- Facebook Application Development with Graph API Cookbook
- Learning C# by Developing Games with Unity 2020
- Mastering Adobe Captivate 2017(Fourth Edition)
- Machine Learning with R Cookbook(Second Edition)
- 信息安全技術(shù)
- 人人都是網(wǎng)站分析師:從分析師的視角理解網(wǎng)站和解讀數(shù)據(jù)
- R的極客理想:工具篇
- Access 2016數(shù)據(jù)庫(kù)管
- GitHub入門與實(shí)踐
- HTML5游戲開發(fā)實(shí)戰(zhàn)
- 微信公眾平臺(tái)開發(fā)最佳實(shí)踐
- Instant JRebel
- 匯編語(yǔ)言程序設(shè)計(jì)教程
- Learning Android Application Development