數據的類型定義了存儲數據需要的記憶體大小及組成該類型的數據成員。類型還決定了對象在記憶體中的存儲位置——棧或堆。 類型被分為兩種:值類型和引用類型,這兩種類型的對象在記憶體中的存儲方式不同。 值類型只需要一段單獨的記憶體,用於存儲實際的數據。 引用類型需要兩段記憶體。 第一段存儲實際的數據,它總是位於堆中。 ...
數據的類型定義了存儲數據需要的記憶體大小及組成該類型的數據成員。類型還決定了對象在記憶體中的存儲位置——棧或堆。
類型被分為兩種:值類型和引用類型,這兩種類型的對象在記憶體中的存儲方式不同。
- 值類型只需要一段單獨的記憶體,用於存儲實際的數據。
- 引用類型需要兩段記憶體。
- 第一段存儲實際的數據,它總是位於堆中。
- 第二段是一個引用,指向數據在堆中的存放位置。
下圖展示了每種類型的單個數據項是如何存儲的。對於值類型,數據存放在棧里。對於引用類型,實際數據存放在堆里而引用存放在棧里。
值類型
所有值類型都隱式派生自 System.ValueType
,下表顯示 C# 值類型:
值類型 | 類別 | 類型尾碼 |
---|---|---|
bool |
Boolean |
|
byte |
無符號、數字、整型 | |
char |
無符號、數字、整型 | |
decimal |
數字、浮點 | M 或 m |
double |
數字、浮點 | D 或 d |
enum |
枚舉 | |
float |
數字、浮點 | F 或 f |
int |
帶符號、數字、整型 | |
long |
帶符號、數字、整型 | L 或 l |
sbyte |
帶符號、數字、整型 | |
short |
帶符號、數字、整型 | |
struct |
用戶定義的結構 | |
uint |
無符號、數字、整型 | U 或 u |
ulong |
無符號、數字、整型 | UL 、Ul 、uL 、ul 、LU 、Lu 、lU 或 lu |
ushort |
無符號、數字、整型 |
值類型直接包含值,換言之,變數引用的位置就是記憶體中實際存儲值的位置。
因此,將一個值賦給變數 1,再將變數 1 賦給變數 2,會在變數 2 的位置創建值的拷貝,而不是引用變數 1 的位置。
這進一步造成更改變數 1 的值不會影響變數 2 的值。
下圖對此進行了演示。number1
引用記憶體中的特定位置,該位置包含值 42
。將 number1
的值賦給 number2
之後,兩個變數都包含值 42
。但修改其中任何一個值都不會影響另一個值。
類似地,將值類型的實例傳給 Console. WriteLine()
這樣的方法也會生成記憶體拷貝。在方法內部對參數值進行的任何修改都不會影響調用函數中的原始值。
引用類型
引用類型的變數存儲對數據存儲位置的引用,而不是直接存儲數據。要去那個位置才能找到真正的數據。所以為了訪問數據,“運行時”1 要先從變數中讀取記憶體位置,再“跳轉”到包含數據的記憶體位置。
為引用類型的變數分配實際數據的記憶體區域稱為堆(heap)。
由於引用類型只拷貝對數據的引用,所以兩個不同的變數可引用相同的數據。因此,對一個變數執行的操作會影響另一個變數所引用的對象。無論賦值還是方法調用都會如此。因此,如果在方法內部更改引用類型的數據,方法執行完成之後,將看到更改後的結果。
總結
一個類型要麼是值類型,要麼是引用類型。區別在於數據存儲的方式:對於值類型,數據存放在棧里。對於引用類型,實際數據存放在堆里而引用存放在棧里。
引用類型的變數存儲對其數據(對象)的引用,而值類型的變數直接包含其數據。 對於引用類型,兩個變數可引用同一對象;因此,對一個變數執行的操作會影響另一個變數所引用的對象。 對於值類型,每個變數都具有其自己的數據副本,對一個變數執行的操作不會影響另一個變數。