C#的值類型和引用類型解析

一、引用類型與值類型

值類型:直接存儲其值,派生自System.ValueType,部署在棧上。值類型不包含null,值類型在聲明後,不管是否已經賦值,編譯器會爲其分配內存。值類型通常在線程棧上分配(靜態分配)。
引用類型:存儲其值的引用,派生自Object類,部署在堆上。引用類型可以使用null,當聲明引用類型時,只會在棧上分配一小片內存,用於存放一個地址。當進行實例化的時候(new)會在堆上分配空間,並會把堆上空間的地址存儲到聲明時開闢的那個棧上的空間中(動態分配)



二、常見的值類型引用類型

值類型:byte,sbyte,short,int,long,float,double,decimal,char,uint,ushort,ulong,bool ,枚舉類型,用戶定義的結構體struct

引用類型:class、delegate、dynamic、interface、object(Object)、string(String)、內插字符串(這個樣子的:$"Name = {name}, hours = {hours:hh}")。


特殊情況:

(1)數組。數組是引用類型,數組的對象在棧上。其每一個元素均分配在託管堆上。如果是值類型數組(int,float....)那麼會在堆上對數組自動進行初始化。如果是引用類型數組,那麼不會初始化任何元素。此時數組爲null。

(2)嵌套類型:如果聲明一個類,該類中包含值類型的字段,同時包含值類型的局部變量,那麼它同樣是引用類型。但是值類型字段和值類型局部變量位置是不同的。

例如:    

class A

    {

        int a;      //值類型字段

        public A()

        {

            int b;      //值類型局部變量

        }

}

字段跟隨實例存儲,所以值類型字段a存儲在託管堆上。但是這裏的b是局部變量,它是值類型,它在棧上。


三、注意事項

1.值類型測試

using System;

namespace ValueAndReference

{

    class Program

    {

        static void Main(string[] args)

        {

            int VT1, VT2;       //值類型變量VT1,VT2

            VT1 = 5;

            VT2 = VT1;

            VT1 = 6;

            Console.WriteLine("VT1 is " + VT1 +"  VT2 is " + VT2);      //傳遞的只是值

        }

    }

}

這個沒啥可注意的,很正常。

 

2.引用類型測試(class)

using System;

namespace ValueAndReference

{

    //引用類型定義

    class ReferenceType

    {

        public int field;      //定義字段

        public ReferenceType(int v)       //構造函數,初始化field

        {

            field = v;

        }

    }

 

    class Program

    {

   

        static void Main(string[] args)

        {

            ReferenceType RT1 = new ReferenceType(5);        //定義並實例化引用類型對象RT1,初始字段值爲5

            ReferenceType RT2 = RT1;

            Console.WriteLine("RT1 is " + RT1.field +",RT2 is " + RT2.field);        //輸出RT1、RT2字段值,均爲5

            RT2 = new ReferenceType(6);     //實例化RT2,並初始字段值爲6

            Console.WriteLine("RT1 is " + RT1.field +",RT2 is " + RT2.field);        //輸出RT1字段值爲5、RT2字段值爲6

 

            ReferenceType RT3 = new ReferenceType(5);        //定義並實例化引用類型對象RT3,初始字段值爲5

            ReferenceType RT4 = RT3;

            Console.WriteLine("RT3 is "+RT3.field+",RT4 is "+RT4.field);        //輸出RT3、RT4字段值,均爲5

            RT4.field = 6;  //更改RT3的字段值爲6

            Console.WriteLine("RT3 is " + RT3.field +",RT4 is " + RT4.field);        //輸出RT3、RT4字段值,均爲6

        }

    }

}

這個比較需要注意,RT1的field字段初始爲5,然後RT1賦值給RT2,由於RT2是引用變量,那麼修改RT2就是修改RT1,可是爲什麼RT1在後邊沒變化呢?這是因爲這種情況下RT1和RT2指向的不是一個堆的地址,具體可以看下邊的圖。RT3和RT4是正常的情況,修改了RT4的值,RT3同時跟着變化。關於這兩種情況其實很簡單,而且這個很好的反應了引用類型的性質。爲什麼第一種情況不行呢?是因爲第一種情況改變了引用對象本身,重新進行了實例化,那麼就是重新聲明瞭一個新的實例。而第二種情況改變的是對象的屬性,兩個對象還是對應的同一個實例。看下面兩幅圖:

第一種情況:


第一次實例化RT2時拷貝的RT1,此時RT1、RT2是一個對象。接下來的實例化是對RT2本身的改變,並且在這裏是會重新分配RT2地址的。所以這個RT2是一個全新的實例,與RT1沒有任何聯繫。所以它再怎麼改字段的值也不會在影響RT1的字段。


第二種情況:


這裏修改的只是字段,所以修改後並沒有產生新的實例,那麼RT1、RT2對應的還是一個實例。修改任何一個的字段值,另外一個會同時變化。

以上這些情況同樣適用於方法中的參數,如果在方法中重新實例了對象,那麼原始的對象在方法結束時不會做出任何改變,如果僅是改變對象實例的字段,那麼沒有任何問題,可以修改。如果想要在方法中對對象的實例本身做修改,使用ref傳遞參數,這個可以做到。


3.引用類型測試(string)

using System;

using System.Runtime.InteropServices;

namespace ValueAndReference

{

    class Program

    {

        static void Main(string[] args)

        {

            string STR1 ="aaaaa";

            string STR2 = STR1;

            Console.WriteLine("STR1 is " + STR1+",STR2 is "+STR2);     //輸出STT1、STR2

            STR2 = "bbbbb";

            Console.WriteLine("STR1 is " + STR1 +",STR2 is " + STR2);  //輸出STT1、STR2

        }

    }

}

最後在說下這個string(String),在這裏STR1並沒有隨着STR2的變化而變化,這麼看起來它確實像值類型。但實際上它確實是引用類型。爲什麼出現這種情況那,這是因爲string做了運算符重載這樣看起來是string更像是字符串。可以把它這個賦值理解爲new,這樣就和上邊的class一樣了,所以這種情況是正常的。









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