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、大多數情況下,目標型別只是含有一些資料,或者以資料為主。