香蕉视频app

香蕉视频appKeep on going never give up.

Let's Go

C# 學習筆記(52)深入理解值類型和引用類型

C#Lonely2019-05-31 15:19:35138次0條

什么是值類型和引用類型?

C#中的類型可以分為兩種——值類型引用類型。那么什么是值類型和引用類型呢?值類型主要包括簡單類型、枚舉類型、和結構體類型等;引用類型主要包括類類型、接口類型、委托類型、數組類型和字符串類型等。

為了讓大家對C#中的類型有一個清晰的認識,下表列出了C#中的基本類型并進行了說明。

類別 說明
值類型 簡單類型 有符號整型:int、long、short、sbyte
無符號整型:uint、ulong、ushort、byte
字符串類型:char
浮點型:float、double和高精度小數類型decimal
布爾類型:bool
枚舉類型 枚舉類型:enum
結構體類型 結構體類型:struct
引用類型 類類型 字符串類型:string
類類型:Console類和自定義的類類型
數組類型 一維數組和多維數組
接口類型 由interface關鍵字定義的類型
委托類型 由delegate關鍵字定義的類型

值類型與引用類型的區別

值類型與引用類型的區別在于實際數據的存儲位置:值類型的變量和實際數據都存儲在棧中,變量保存的內容就是實例數據本身;而引用類型則只有變量存儲在棧中,變量存儲著實際數據的內存地址,實際數據存儲在與內存地址相對應的堆中。

對于棧和堆,你可以將它們理解為內存中存儲數據的兩種結構,它們存放的數據內容以及存放數據的位置都不一樣(一個是在棧上,一個是在堆上)。不同的分配位置導致了不同的管理機制,值類型的管理由操作系統負責,而引用類型的管理則由垃圾回收器(Garbage Collection,GC)負責。管理主要指對內存空間進行分配和釋放。之所以需要這些操作,是因為內存大小畢竟有限,值類型和引用類型存儲在內存中,自然會消耗可用內存,如果不及時釋放掉不再需要的內存,就會導致程序占用的資源越來越多,使可用空間枯竭。

下面通過一個例子來解釋值類型與引用類型在內存分配方面的區別,具體的演示代碼如下所示:

class Program
{
    static void Main(string[] args)
    {
        // num是值類型
        int num = 10;
        // str是引用類型
        string str = "ABCD";

    }
}

在以上代碼中,我們分別定義了一個值類型和一個引用類型,下圖演示了它們在內存中的分布情況。

image.png

在程序中,每個變量都有其棧地址,并且不同變量的棧地址不同。所以上面程序中的num和str變量在棧中占用了不同的位置,從上圖可以發現,不管是值類型變量還是引用類型變量,變量本身都是存儲在棧中的,因為變量存儲在棧中的,因為變量只是實例數據的一個引用,即可理解為一個地址。


問:值類型實例就一定會被分配到線程棧上嗎?

答:值類型實例不一定香蕉视频app總會被分配到線程棧上,在引用類型中嵌套值類型時,或者在值類型裝箱的情況下,值類型的實例就會被分配到托管堆中。

嵌套結構包括值類型中嵌套定義了引用類型引用類型中嵌套定義了值類型兩種情況。


1、引用類型嵌套定義值類型

如果類的字段類型是值類型,它將作為引用類型實例的一部分,被分配到托管堆中。但那些作為局部變量(例如下列代碼中的d變量)的值類型,則仍然會被分配到線程棧中。下面通過一個例子來解釋引用類型中嵌套定義值類型情況下的內存分布情況,具體代碼如下所示:

class Program
{
    static void Main(string[] args)
    {
        Test t = new Test();
        t.Temp();           
    }
}

public class Test
{
    // num作為引用類型的一部分被分配到托管堆上
    private int num = 10;

    public void Temp()
    {
        // d被分配到線程棧上
        double d = 3.14;
    }

}

以上代碼的內存分配情況如下圖所示:

image.png

2、值類型中嵌套定義引用類型

值類型嵌套定義引用類型時,棧上將保存該引用類型的引用,而實際的數據則依然保存在托管堆中。相關代碼如下所示:

class Program
{
    static void Main(string[] args)
    {
        //值類型變量
        Temp temp = new Temp(new TestClass());
    }
}

public class TestClass
{
    public int x;
    public int y;
}

//值類型嵌套定義引用類型的情況
public struct Temp
{
    //結構體字段,注意:結構體中字段不能被初始化
    private TestClass testClass;

    //結構體的構造函數,注意:結構體中不能顯式定義無參的構造函數
    public Temp(TestClass t)
    {
        if (t == null)
            throw new ArgumentNullException("t");
        testClass = t;
        testClass.x = 10;
        testClass.y = 20;
    }

}

以上代碼中的內存分配情況如下圖所示:

image.png

從以上分析可以得出這樣的結論:值類型實例總會被分配到它聲明的地方,聲明的是局部變量時,將被分配到棧上,而聲明為引用類型成員時,則被分配到托管堆上;而引用類型實例總是分配到托管堆上。

香蕉视频app 上面只分析了值類型與引用類型在內存分布方面的區別,除此之外,二者還存在其他幾個方面的區別,現總結如下:

● 值類型繼承自ValueType,ValueType又繼承自System.Object;而引用類型則直接繼承于System.Object;

● 值類型的內存不受GC(垃圾回收器)控制,作用域結束時,值類型會被操作系統自行釋放,從而減少了托管堆的壓力;而引用類型的內存管理則由GC來完成。所以與引用類型相比,值類型在性能方面更具優勢。

香蕉视频app ● 若值類型是密封的(sealed),你將不能把值類型作為其他任何類型的基類;而引用類型則一般具有繼承性,這里指的是類和接口。

● 值類型不能為null值,它會被默認初始化為該類型的默認值;而引用類型在默認情況下會初始化為null值,表示不會指向托管堆中的任何地址。對值為null的引用類型的任何操作都會引發NullReferenceException異常。

● 由于值類型變量包含其實際數據,因此在默認情況下,值類型之間的參數傳遞不會影響變量本身;而引用類型變量保存的是數據的引用地址,它們作為參數傳遞時,參數會發生改變,從而影響引用類型變量的值。


暗錨,解決錨點偏移

文章評論

    嘿,來試試登錄吧!