初讀CLR Via C# 之——實值型別和參考型別、裝箱與拆箱

來源:互聯網
上載者:User

實值型別和參考型別、裝箱與拆箱

       本想把這篇博文題目的拆開來說,但是想一想,實值型別和參考型別、裝箱與拆箱又是密不可分的,於是決定還是放在一起來說。

一、  實值型別和參考型別:

在我們剛開始學習寫程式的時候,物件導向的三大概念等都是我們所能熟悉,並且比較好理解的概念,但是,到實值型別和參考型別的時候,我相信有大部分的同仁都曾經迷茫過(包括我^_^)

在我們之前說的基元類型中,無非分為兩大類型,一個就是實值型別,另一個則是參考型別。我們先說一下參考型別,首先,我們需要非常明確的一點就是參考型別是從託管堆上面分配空間的而實值型別是在一個線程堆棧上分配空間的(實值型別變數做為局部變數時,該執行個體將被建立在堆棧上;而如果實值型別變數作為類型的成員變數時,它將作為類型執行個體資料的一部分,同該類型的其他欄位都儲存在託管堆上)。當我們用new關鍵字去構建一個參考型別的時候,new操作符返回給我們的是一個對象資料的記憶體位址,我們也可以理解為一個對象指標。CLR會對我們構建的參考型別對象的成員進行初始化的操作並將參考型別對象中的欄位進行初始化操作為0或者null,還有一點我們必須注意的是,在我們構建完一個參考型別的時候,很可能會增加一次GC(記憶體回收)的操作,就是說,當我們這個參考型別的對象被認為不會再使用的時候,將被記憶體回收,以釋放佔用的記憶體空間(關於記憶體回收,將在以後的博文中進行討論)然而實值型別是在現成堆棧上進行分配的,所以它沒有一個對象指標,我們操作實值型別就是操作實值型別中的資料。並且,實值型別不受GC記憶體回收的制約。我們來舉個例子,並將一步一步的進行分析。代碼如下:

static void Main(string[] args)
{
String str_a = "abc";//在託管堆上分配記憶體
Int32 int_a = 123;//線上程堆棧上分配記憶體
String str_b = str_a;//複製str_a的對象指標
Int32 int_b = int_a;//線上程堆棧上分配記憶體並複製成員
Console.WriteLine(str_a);//列印str_a的值為abc
Console.WriteLine(str_b);//列印str_b的值為abc
//這裡不再列印int_a和int_b的值
str_b = "uio";
int_b = 456;
Console.WriteLine(str_a);//列印str_a的值為abc
Console.WriteLine(str_b);//列印str_b的值為uio
Console.WriteLine(int_a);//列印int_a的值為123
Console.WriteLine(int_b);//列印int_b的值為456
Console.ReadLine();
}

通過以上代碼我們可以看到,當我們分別從託管堆上分配記憶體給str_a的時候str_a儲存的並不是“abc”而是”abc”的記憶體位址,同樣將str_a的值賦給str_b的時候,str_b儲存的並不是”abc”而是從str_a那裡複製到”abc”的記憶體位址,所以,我們第一次列印str_a和str_b的時候,列印出來的結果是一樣的,都是”abc”。然而,當我們把str_b的值更改為”uio”的時候,str_b儲存的記憶體位址就為”uio”的記憶體位址,而str_a沒有做任何改變,所以,第二次列印str_a和str_b的時候,結果為”abc”和“uio”。

       那麼,實值型別是怎麼回事呢?實值型別儲存中,並不包含一個對象的指標,而是它本身的一個欄位,也就是說當int_a賦值給int_b的時候,int_b把int_a的所有成員全部進行複製在一個新的記憶體空間,而int_b的修改操作,並不影響int_a。

       以上為筆者對實值型別和參考型別的理解,有什麼錯誤的地方也請大家指出。

一、  裝箱和拆箱

下面我們來討論下裝箱和拆箱,裝箱和拆箱是指:從實值型別轉換成參考型別的操作,我們稱之為裝箱,反之,從參考型別轉換成實值型別,我們稱之為拆箱操作。

       理論上,這兩句話應該很好理解,實際上其實也不難。我們來看一個例子                 

如下:

static void Main(string[] args)
{
Int32 int_a = 5;
object o = int_a;
int_a = 123;
o = 456;
Console.WriteLine(int_a+","+(Int32)o);
Console.ReadLine();
}

我們來分析下這段代碼執行了幾次裝箱和幾次拆箱。首先,我們給int_a賦值為5,並且把int_a賦值給object類型,這個時候就發生了一次裝箱操作,就是說把int_a的值5,分配相應的記憶體複製到託管堆上,並把值為5的記憶體位址指標返回給object類型,這是一次裝箱操作,在裝箱操作的時候,int_a還線上程堆棧上並且值為5。Int_a=123隻是單純的更改了原來值。o=456這裡面又進行了一次裝箱操作,就是說int類型456裝箱成參考型別(操作與o=int_a一樣),並把o的指標引用更改為456的指標引用。之前123將等待記憶體回收。接下來,看列印輸出的方法。在這裡面,我們其實傳入的參數都為String類型,所以在這裡int_a也執行了裝箱操作,因為與String類型的“,”進行串連操作。還有一個裝箱操作就是在(Int32)o這裡面,我們將o強制類型轉換是一個拆箱操作(參考型別轉換成實值型別為拆箱操作),這也是整個Main方法中的唯一一次拆箱操作,那麼在轉換成實值型別之後,又與String類型的”,”進行串連操作,在這裡就又執行了一次裝箱操作。最後返回給WriteLine方法的是一個字串。列印出來為“123,456”所以上述代碼進行了4次裝箱操作和1次拆箱操作。為了更清晰的表現給大家,直接上IL代碼的圖:

 

裝箱操作我已經使用方框標識出來,拆箱操作我已經使用橫線標識出來。

       對於裝箱與拆箱操作,上述代碼可以看出,為了提高上段代碼的效率,我們沒有必要在執行WriteLine方法的時候對o進行一次拆箱操作。至於為什麼這麼寫只是為了更清晰的示範裝箱與拆箱操作在編譯為IL代碼的時候是如何表示的。

       我想這篇博文也到此結束了,可能沒有很深入的討論到實值型別與參考型別、裝箱和拆箱操作,只是點到為止吧,更深層次的東西,也希望大家能夠自己進行一個研究。必要的時候看一下IL代碼,會瞭解到很多我們之前不知道的東西。如有錯誤、問題也請各位看官留言告訴我。謝謝!

      

 

       在這裡再次為論壇做個宣傳 www.175m.com,希望大家可以在論壇裡面一起建設、討論該論壇(希望部落格園的大哥們,不要以為我在挖牆角,我只是在為我們程式員的大家庭做貢獻)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.