摘要:[C#][C# IN DEPTH筆記][004] 數值型別與參考型別
註1:將使用C#ID簡稱來代表『C# IN DEPTH 中文版』書名。
註2:引用書中語句時,將會註明頁數。
「」※◎『』:【】↑↓←→↖↗↙↘㊣☆★□■⊕●○
實質型別(Value Type): 即變數的記憶體空間內所存放的資料是實際的值,例如:int a = 5;
變數a的內容便是數值5;又如:char w = 'x';,則變數w的內容為 x 這個字元。
【實體內容式儲存在宣告的地方。】
※ 每個實值型別都有隱含預設建構函式 (Constructor),來初始化此種型別的預設值。
如需實值型別預設值的詳細資訊,請參閱預設值表。
參考型別(Reference Type): 即變數的記憶體空間內所存放的資料是記憶體位址,就是紀錄
實際內容所在地的記憶體位置第一個開頭地方。因此,當要讀取變數內容時,就是先取得
實際位置空間開始的記憶體位置,所以當參考到這個位址時,就能到所在地取得資料。
例如:int[] ary = new int[] {5,4,3}; ,其中變數ary所記錄的便是陣列[3,4,5]實際存放位置的
開頭記憶體位址;因此,若要取得第2個元素的值時,只要取得陣列的起始位址,再加上
第n個 * 型別所佔大小的位址,即可取得該元素位址,也就是說第2個元數的位置是 ary + (2 * sizeof(int)),
便可以取得值為3,意義同等於 ary[2]。
打破迷思
C#ID(P.51):迷思#1 「結構是屬於輕量級的類別」
在某些情況下,數值型別的效能會比參考型別來的好且有效率,因為數值型別不需要進行
垃圾回收(garbage collection),也不需要對型別做驗證和判斷。但某些情況下,參考型別
的效能會比實質型別來的好,例如:變數的傳遞。傳遞實質型別的變數資料是以複製一份
一模一樣的資料,再將這個副本資料傳遞給處理的函式,即傳值方式(Call / Pass by value),故當
資料量比較大時,便顯得相當耗時;而傳遞參考型別的變數時,一樣是複製一份完整的變
數內容到處理的函式變數中,但是差別在於複製的內容是該實體物件所在的記憶體位址,
因為免去複製其他變數內容資料,所以相對比較有效率。
C#ID(P.52):迷思#2 「參考型別是存在於堆積,數值型別是存在於堆疊」
參考型別的實體物件是建立在堆積(heap)上的,而實質型別大都是存在堆疊(stack)中,
但是當實質型別變數被其他物件存取時(例如,存取某類別中的int變數、或是long ...等),
就會將資料存放在堆積中,只有在區域變數(方法內宣告的變數)以及方法上的參數,
會存放在堆疊中。
C#ID(P.52):迷思#3 「C#預設傳遞物件的方式為傳址」
方法的參數是傳值(pass by value)而非傳址(pass by refrence),例如下列程式
void Main() { StringBuilder caller; AppendHi(caller); caller.Append(" I'm test."); } void AppendHi (StringBuilder builder) { builder.Append("Hi!"); }
在Main函式中, 會將區域變數caller傳入AppendHi函式,
caller是一個參考物件,因此caller所儲存的是實際物件的位址,
當caller傳入到AppendHi函式時,會將caller的內容複製一份到builder變數中,
而這個動作是傳值而非傳址,而利用傳值的好處在於可以避免掉一些副作用(side effects),
亦即雖然表面上操作起來與傳址無異,但是當AppendHi函式中的builder,
若不小心被指派為null時,並不會影響到caller的內容,只會影響到該變數所儲存的內容而已。
※不管透過何種方式,物件是絕對不會被當作參數傳遞的。
若參數傳遞想要以傳址的方式進行時,則必須在變數前明確使用關鍵字 ref 來表示,
因此,上例AppendHi函式若要以傳址方式接收變數時,則可以將函式宣告改成下列:
void AppendHi (ref StringBuilder builder)
此時,可以將builder想做是變數caller的別名,在變數的操作上傳值與傳址是相同的,
但是在操作上要特別注意的就是若對變數builder做任何變更時,則會一起影響到變數caller,
所以若是將builder指派為null時,則caller的內容同樣會變成null。
參考資料:
C# 語言規格:5.1.5 參考參數、
C# 程式設計手冊:傳遞參考型別的參數、傳遞參數、資料型別、
簡單就是美 :: { 簡單其實很不簡單 }