裝箱和拆箱-值類型和引用類型的區別


一、概述

在C#中,數據根據變量的類型以兩種方式中的一種存儲在一個變量中。變量的類型分為兩種:引用類型和值類型,這也是CLR支持的兩種類型。

 

二、定義

1.引用類型:

分配在堆上的類型稱為引用類型。

解析:一個可以稱為”類“的類型都是引用類型。 引用類型總是從托管堆上分配的,常用的語法就是New XX(). C#的new 操作符會返回對象的指針 - 也就是指向對象數據的內存地址的一個引用。引用類型的傳遞其實傳遞的是對象的指針(string類型比較特殊),所以在特定的場景下性能是高於值類型的。一個引用類型在創建時默認為null,也就是說當前變量不指向一個有效的對象,也就是我們常遇到的異常“未將對象引用設置到對象的實例”。

2.值類型:

值類型一般在線程棧上分配。

 

三、區別

我們總圖然后詳細分析。

 

1.值類型的數據存儲在內存的棧中,內存分配是自動釋放,在GC的控制之外,不會對GC造成壓力,所以值類型存取速度快;引用類型的數據存儲在內存的堆中,在.NET中會有GC來釋放,而內存單元中只存放堆中對象的地址,在.NET中會有GC來釋放所以存取速度慢。我們可以這么理解,值類型就是現金,要用直接用;引用類型是存折,要用還得先去銀行取現。

 當然,值類型雖然存取速度快,但也不能卵用,舉個例子:我自定義一個struct 類型作為一個方法的參數會發生什么呢?每次調用都會發生全字段的賦值,這是不可接受的,這也是典型的值類型勿用場景。

2.值類型表示實際數據,引用類型表示指向存儲在內存堆中的數據的指針或引用。

3.值類型繼承自System.ValueType,引用類型繼承自System.Object。

4.值類型總是包含一個值,而引用類型可以是null。

 

四、封箱和拆箱

封箱(boxing)是把值類型轉換為引用類型(System.Object)。拆箱(unboxing)是相反的轉換過程。

封箱的過程:

1.在托管堆中分配好內存,分配的內存量是值類型的各個字段需要的內存量加上托管堆上所以對象的兩個額外成員(類型對象指針,同步塊索引)需要的內存量。

2.值類型的字段復制到新分配的堆內存中。

3.返回對象的地址,這個地址就是這個對象的引用。

 

 從圖可知,對象 o 存的是地址引用,指向的是堆上的值,這個值的類型和變量 i 一樣,也是 int 類型,值(123)也就是從棧上變量 i復制過來的一個副本值而已。(所以裝箱就是在堆上分配好內存,再復制棧上的值,再將堆的地址引用返回到棧上)

拆箱的過程:

1.獲取已經裝箱的值類型實例的指針。

2.把獲取到的值復制到棧。

所以裝箱是比較耗費性能的,還有可能引發一次GC操作,而拆箱只是一個獲取指針的過程耗費資源要比裝箱小的多。注意:一個對象拆箱之后只能還原為原先未裝箱之前的類型,例如:你不能把int32類型裝箱后還原為int16類型。

引用類型和值類型區別實例:

1.我們首先定義一個方法,用來處理數據。

 

 

 

 

 2.在控制器中先定義兩個數據類型,賦值都為0。調用上面的類,看看會輸出什么。

 

 

 

 

 前台頁面(簡單實例,就直接用session了,平時可不要這么寫)

 

 

 

 

 輸出頁面:

 

 

 

 

 這究竟是怎么回事呢,為什么有Ref的會改變呢?下面我們詳細分析下:

 

首先,我們先要理解ref是什么,對於Class類型使用 ref,是為了保持引用的地址是一致的。所以在使用引用參數時,必須在方法的聲明和調用中都使用ref修飾符。

如果還不清楚,就跟着代碼走一遍吧!

1.在控制器中先打一個斷點,我們可以在局部變量中看到他們的初始值都為0.

 

 

 

 

 

2.轉到方法Test2中,在還沒有開始修改引用類型的值的時候,str的值還是0.

 

 

 

 

 3.在走過方法Test2后,我們看到引用類型str的值變為了"666".

 

 

 4.走出這個方法,我們看看,str1的會因此改變嗎?

 

 

 !!!str1居然沒有改變,他居然還是0!!

 

 

 4.同樣操作,我們在RefStringTest2方法后看看會怎么樣。

 

 

NEXT》》》進入方法,先不執行賦值操作

 

 

NEXT》》》走過賦值操作

 

 

 NEXT》》》走出方法,去看看str2的值改變了嗎

 

 

 可以看到,str2的值已經變成了“666”。

同樣的操作,我們走過剩下兩個方法,可以看到

 

 可以看出,只有str2和int2的值改變了,也就是只有使用了Ref的值會被修改。但這是為什么呢?我們再一探究竟,Come!!

 

 

     傳遞引用參數的時候傳遞的是一個地址的值,在RefStringTest2方法內,傳入str1的地址會被str給修改(所以我們要用ref來保證a和str的地址一致,這樣當str的值改變時,str1也會改變,因為他們指向的是同一個地址,也就是同一個值!!),所以輸出了666.  如果這里沒有ref,那么,傳入的參數地址,(也就是str1的地址)會被str修改成其他地址,在StringTest2方法內部,修改的是str的地址指向的值,出了方法,str1地址指向的值並沒有改變!!

    而傳遞值類型參數的時候傳遞的是一個真實的值,他沒有地址,在IntTest方法內,由於沒有使用ref,形參int1的值把這個值“0”拷貝一份,然后把拷貝后的值傳遞到了方法內部,所以,在方法內改變的只是拷貝的值,方法結束后int1的值還是0. 而使用ref的int2,保證參數value和傳入的int2的內存位置一致,這樣,同一內存位置的值才會被修改!!!

 

從堆棧地址可以更直觀得看出:

 

 

 

 

 來,,,對比着看一下

 

 

 發現了什么,看得出來,str和str1的地址並不一樣!!既然地址都不一樣,那你給“str = "666";和我str1有啥關系??我str1的值還是0啊大哥!!

 

來,再去RefStringTest2方法看看,看看ref的作用

進入方法前,看好地址!!Look!!

 

走過方法后,Look!!

 

 

 看出了什么???是不是str2和str的地址居然一樣!!!地址一樣,指向的值自然是同一個。

OK,解釋完畢了。(上面地址其實我也不清楚是堆還是棧地址,😓汗,但相同不相同還是看得出來的,等我請教大佬再補上!!)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM