.Net常見面試題整理(一)——值類型和引用類型

原文地址:http://www.cnblogs.com/zhangkai2237/archive/2013/03/17/2964528.html

類型一直是C#中最基本的問題,關於值類型和引用類型,我想每個C#程序員都知道“值類型保存在上,引用類型保存在上”。但是僅僅知道到這裏是完全不夠的,我們需要理解C#中的類型,瞭解爲什麼要有值類型和引用類型以及他們的特徵。

一、值類型和引用類型的概念
 
        值類型的實例是在線程棧上分配的(不能免俗的提起這句話),值類型的變量並沒有一個指向實例的指針,而是變量中已經包含了實例本身的字段。
        相應的引用類型的實例時在託管堆中分配的,返回的是一個指向實例對象的內存地址。
 
        比如我們一個值類型的變量 valType, 他包含一個int的字段a,其值爲5,他在棧和堆中的示意圖爲:
    現有一個引用類型的變量refType,他指向RefType類的一個實例,下圖爲示意圖:
        另外我們都知道基元類型中除了string類型,其他的都是值類型,但是我們大部分人都沒有發現他們之間的區別。只要我們進入各種基元類型的定義中就可以發現:string類型是一個class,而其他的值類型都是struct。翻閱資料發現了微軟在定義值類型和引用類型的區別:
        引用類型包括類和接口,所有的以class和interface修飾的類型都是引用類型;而值類型包括結構和枚舉,所有的結構和枚舉都是值類型。繼續查找資料發現所有的結構都是抽象類型System.ValueType,所有的枚舉都是派生自System.Enum類型的,而System.Enum類型也繼承自System.ValueType類。所以我們可以得出值類型都是繼承自System.ValueType的結論。
 
        值類型還有一個重要的特徵是因爲結構是隱式密封的,所以我們沒辦法由自值類型來派生一個我們想要的類型來。例如我們無法從System.Int32(int)類派生出另外一個類型來。
 
二、爲什麼要有值類型
        FCL中的絕大多數類型都是引用類型,那麼爲什麼要有值類型呢?首先我們回顧下實例化一個引用類型的步驟:
            1. 計算好在託管堆上分配的內存大小,包括該類型實例的所有字段的大小,以及在託管堆中的兩個“額外成員”(《CLR via C#》中的翻譯):類型對象指針和同步塊索引;
            2. 在託管堆中分配指定大小的內存塊;
            3. 初始化對象的“類型對象指針”和“同步塊索引”;
            4、調用類型的構造函數,如果調用的是有參構造函數,則還要向其傳入參數。注意,如果要實例化子類對象,則必須先調用父類的構造函數,一直會追溯到System.Object。這部分具體的可以參看《C#入門經典》相關章節。
 
        如果所有的類型都是引用類型,那麼我們程序的性能將顯著下降。引用類型在性能方面還有一個重大的問題是垃圾回收。當沒有變量指向某個對象時,那麼該對象就會成爲垃圾,就會成爲GC回收的對象,而GC是相當耗費資源的。而使用值類型是不需要垃圾回收的,對象超過了其作用域就會自動銷燬
 
        上面這段解釋了爲什麼我們需要值類型,講解了值類型在性能上的又是,那麼我們爲何還需要引用類型呢?
        
        我們都知道值類型的傳值是要複製整個對象的,而引用類型僅僅是複製指向實例對象的指針。而且值類型不能被繼承,所以值類型適合一些比較輕量和簡單的類型,否則同樣會出現性能問題。
        那麼我們應該什麼時候使用值類型呢:
必須滿足以下所有條件,否則不要定義成值類型
第一,類型具有基元類型的行爲。類型簡單,其中沒有成員會修改類型的任何實例字段。
第二,類型不需要從其他任何類型繼承。
第三,類型不會派生出其他任何類型。
除了滿足以上全部條件,還必須滿足以下條件中的一個。
第一,類型的實例較小(約是16字節或者更小)。
第二,類型實例較大,但不作爲方法的實參傳遞,也不通過方法返回。(這樣即使很大但是不需實參傳遞,不會進行復制的操作)
 
        到這裏我們基本上把值類型和引用類型的一些基本知識瞭解完了,那麼這部分面試官喜歡問什麼問題呢?
值類型和引用類型的區別
                1. 值類型分配在內存棧上,引用類型分配在託管堆上。當一個值類型的變量賦給另一個值類型的變量時,會執行一次逐字段的複製,而一個引用類型的變量賦給另一個引用類型的變量時,僅僅會複製對象的內存地址
                2. 基於上一條,多個引用類型的變量可以同時指向同一個對象,對其中的任何一個變量執行操作都會影響到另一個變量引用的對象。而每個值類型的變量都已經包含了自己的對象,所以對值類型對象的操作不會影響到另一個值類型變量
                3. 值類型包括結構枚舉,他們均間接或直接派生自System.ValueType類;引用類型包括類和接口,他們都派生自System.Object類(這一句是廢話,所有的類型都派生自System.Object類,可說可不說)。
                4. 值類型都是隱式密封的,不能將一個值類型作爲基類來定義一個新的值類型或者引用類型,也因此值類型中不能包括虛方法(不能被繼承,虛方法給誰重寫呢)。
                5、默認情況下,創建一個引用類型的變量時,他會被初始化爲null;而創建一個值類型時,他的所有成員都會被初始化爲0.
                6. 值類型的變量一旦超過了其作用域,爲他分配的內存就會被立即釋放;而引用類型則會在託管堆裏待一段時間,直到垃圾回收器將其回收。
                7. 由於System.ValueType類重寫了Equals方法,所以兩個值類型的Equals方法會在兩個對象的字段完全匹配的情況下返回true;而引用類型的Equals則會在兩個變量引用同一個對象的情況下才返回true。(這一條不重要,不說也無所謂,但是如果被問到自己要有所瞭解)。
 
            數組是值類型還是引用類型?
                我第一次遇到的這個問題的時候並沒有特別注意,但是心想他既然問這樣的問題,應該是引用類型,所以堅定的回答”引用類型“,讓面試官看不出來我是猜的。所以童鞋們以後如果遇到類似的問題,即使是猜的也要理直氣壯,否則即使你答對了,面試官也知道你是猜的,在這個問題上還是會被扣分的。
                數組類型的確是引用類型,可能有部分童鞋不承認,那我們簡單的寫一個驗證方法:
            
       #region 數組的類型
            int[] intArray = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            int[] copyArray = intArray;
            intArray[0] = 9;
            Console.WriteLine(copyArray[0]);
            #endregion

                輸出爲:9.

 
            結構和類的區別
                實際上就是值類型和引用類型的區別,對照着第一題回答就行了。
 
        到這裏,值類型和引用類型還有一個特別重要的點沒有提到,那就是裝箱和拆箱。實際上是一個很簡答的問題,但是如果你不瞭解,在面試時會很難回答。並且在日常的工作中很有可能經常掉入裝箱的陷阱,對程序的性能有較大的影響。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章