C#中結構類型和類類型在語法上非常相似,他們都是一種數據結構,都可以包括數據成員和方法成員。
結構和類的區別:
1、結構是值類型,它在棧中分配空間;而類是引用類型,它在堆中分配空間,棧中保存的只是引用。
2、結構類型直接存儲成員數據,讓其他類的數據位於對中,位於棧中的變量保存的是指向堆中數據對象的引用。
C#中的簡單類型,如int、double、bool等都是結構類型。如果需要的話,甚至可以使用結構類型結合運算符運算重載,再為C#語言創建出一種新的值類型來。
由於結構是值類型,並且直接存儲數據,因此在一個對象的主要成員為數據且數據量不大的情況下,使用結構會帶來更好的性能。
一、聲明結構的語法 - struct關鍵字
public struct AddressBook { //字段、屬性、方法、事件 }
對於類而言,兩個變量指向同一個對象的情況是存在的,因此對這樣兩個變量中的任意一個進行操作,起結果必然會影響另外一個,因為它們指向的是同一個對象。
結構是值類型,,直接包含它自己的數據,每個結構都保存自己的一份數據,修改每一個結構的數據都不會對其他結構的數據造成影響。
二、給結構賦值
如果從結構中創建一個對象,並將該對象賦給某個變量,則該變量包含結構的全部值。復制類型為結構的變量時,將同時復制該結構所持有的所有數據。由於結構不是引用類型,因此結構類型的變量不能被賦予null值。
public class Program { static void Main(string[] args) { PersonStruct p1, p2; //與類一樣,但可以不new p1.Name = "張飛"; p1.MobilePhone = "13553663108"; p1.Birthday = DateTime.Now.AddYears(-10); p2 = p1; //將p1的值賦給p2 //由於是值類型,因此賦值等於將全部值全部復制到p2的棧空間 p2.Name = "關羽"; //然后修改p2的值看是否會影響p1 Console.WriteLine(p1.Name); //輸出 張飛 沒影響 PersonClass p3 = new PersonClass(); p3.Name = "張飛"; p3.MobilePhone = "13553663108"; p3.Birthday = DateTime.Now.AddYears(-10); PersonClass p4 = new PersonClass(); p4 = p3; //將p3的值賦給p4 賦值后,由於是引用類型,因此兩個對象指向的是同一個地址(堆空間) p4.Name = "關羽"; //然后修改p4的值看是否會影響p3 Console.WriteLine(p3.Name); //輸出 關羽 有影響 Console.ReadKey(); } } public class PersonClass { public string Name; public string MobilePhone; public DateTime Birthday; } public struct PersonStruct { public string Name; public string MobilePhone; public DateTime Birthday; }
將一個結構變量賦值給另一個結構變量,就是把數據從一個結構復制到另一個結構。而類則不同,在類的變量之間,復制的是引用,而不是類數據。。因此當數據比較大的時候,這種數據復制機制會帶來較大的開銷。
三、構造函數
結構類型可以有實例構造函數和靜態構造函數,但不能有析構函數。
1、實例構造函數
結構類型都有一個預定義的,沒有參數的構造函數,這點與類是一樣的。此構造函數不允許刪除和重定義,並且這個無參數的構造函數會一直存在,並不會因為定義了其他帶參數的構造函數就消失,這一點和類不同。
注意如果沒有使用new運算符,是不可以使用數據成員的值(除非已顯示地設置了該數據成員的值)和調用函數成員的(除非所有數據成員均已經被賦值)。
四、靜態構造函數
和類一樣,結構類型也可以有靜態構造函數,靜態構造函數用於初始化靜態數據成員。靜態構造函數有如下特點:
1、靜態構造函數不能有訪問修飾符和參數;
2、靜態構造函數不能訪問實例成員;
3、靜態構造函數無法直接進行調用;
結構和類的靜態構造函數的觸發規則不同,類的靜態構造函數是在創建第一個實例或引用任何靜態成員之前自動調用的,而結構的靜態構造函數在以下情況調用:
1、使用顯式聲明的構造函數進行初始化
2、調用結構的方法或訪問結構的靜態數據成員(無論是讀取還是賦值,訪問實例數據成員不會觸發CLR自動調用靜態的構造函數)。
五、結構的多態和可繼承性
結構直接派生自System.ValueType,間接派生自System.Object,但結構是隱式密封的,不能作為基類在派生出其他的結構,也不能從類派生,但可以從接口派生。
結構的特性:
1、結構類型總是隱式密封的,因此在定義結構時不能使用sealed和abstract關鍵字;
2、因為結構不能作為基類,結構的成員不能使用如下訪問修飾符:protected和protected,internal;
3、結構的函數成員不能聲明為abstract和virtual,但是可以使用override關鍵字,用以覆寫它的基類System.ValueType中的方法。
六、結構的裝箱與拆箱
結構是值類型,因此當它被轉換為object類型時,或者它所實現的接口類型的時候,就會執行裝箱操作;同樣,當執行相反操作的時候,就會執行拆箱操作。
七、結構和類的對比
結構 類
數據類型 值類型 引用類型
是否必須使用new運算符實例化 否 是
是否可聲明無參數的構造函數 否 是
數據成員可否在聲明的同時初始化 聲明為const或static可以,數據成員不可以 可以
直接派生自什么類型 System.ValueType 有
是否有析構函數 無 有
可否從類派生 否 可以
可否實現接口 可以 可以
實例化時在棧還是在堆分配內存 棧 堆,棧中保存引用
該類型的變量可否被賦值為null 否 可以
可否定義私有的無參構造函數 否 可以
是否總有一個默認的無參構造函數 是 否
無論結構使用預定義的、無參數的構造函數,還是使用用戶定義的、有參數的構造函數進行初始化,都會初始化結構的數據成員。不過預定義的,無參的會將數值型初始化為默認值,引用類型初始化為null;而用戶自定義的初始化策略對個成員進行初始化。因此結構類型的數據成員不允許在聲明是顯式初始化。
八、性能
因為結構是值類型,因此在為結構分配內存,或者當結構超出了作用域被刪除時,性能會非常好,因為他們將內聯或者保存在堆棧中。當把一個結構類型的變量賦值給另一個結構時,對性能的影響取決於結構的大小,如果結構的數據成員非常多而且復雜,就會造成損失,接下來使用一段代碼來說明這個問題。
結構和類的適用場合分析:
1、當堆棧的空間很有限,且有大量的邏輯對象時,創建類要比創建結構好一些;
2、對於點、矩形和顏色這樣的輕量對象,假如要聲明一個含有許多個顏色對象的數組,則CLR需要為每個對象分配內存,在這種情況下,使用結構的成本較低;
3、在表現抽象和多級別的對象層次時,類是最好的選擇,因為結構不支持繼承。
4、大多數情況下,目標類型只是含有一些數據,或者以數據為主。