對值類型和引用類型的全方位認識

   其實每個程序員從一開始接觸程序就與這兩個數據類型建立了永遠也無法斷開的聯繫,本人雖編程不多但是對於這兩個類型的接觸確不少,然而某天某程序員問起我這兩個類型的定義,用法,區別時我就迷茫了,想要表達卻詞窮,只是指着代碼中的定義蒼白的回答這就是值類型,那就是引用類型。

今天查閱了很多資料,理解是五花八門,看過這些文章之後我也把C++,C#,java中引用類型的問題徹底搞暈了(C++與C#中用指針概念來理解比較容易,然而java中不存在指針概念某些文章還冠以指針思想來理解,使得我思維很混亂),對於棧與堆的存儲機制也徹底迷茫了。於是我決定還是依照自己的理解來總結下吧,本身理解並不到位,文章必會有所偏差,還望大家指正,謝謝。

正題開始之前我想說明一點,值類型與引用類型概念看似簡單,但是卻沒有人能很好的形象的表述,這個概念對於程序員而言很基礎很重要,希望我能和大家一起慢慢累積經驗然後昇華對此的理解。閒話至此。

一,基本概念:學習任何知識首先要知道它的基本概念,對於這兩個概念書本上是這麼定義的。

1,          值類型:存放在棧內的一個變量中。即是在棧中分配內存空間,直接存儲所包含的值,其值就代表數據本身。值類型的數據具有較快的存取速度。 包含基本數據類型。(int,bool,double,float等)

2,          引用類型:存儲於堆中。即是在堆中分配內存空間,棧中所存儲的引用不直接存儲所包含的值,而是指向所要存儲的值,其值代表的是所指向的地址。當訪問一個具有引用類型的數據時,需要到棧中檢查變量的內容,該變量引用堆中的一個實際數據。引用類型的數據比值類型的數據具有更大的存儲規模和較低的訪問速度。 包含:object和string。

個人認爲這兩個定義總結的很精闢,短短幾句話就將兩種類型的本質與在內存中的存儲機制表述的很完整,唯一的缺點就是不好理解,所以下面我需要慢慢來理解。對於這兩個值在內存中的存儲,我認爲用圖示表達會更直觀。

參照圖示我們執行以下代碼:

Public void method(){

int  a=100;

String s=new String();

}

當代碼開始執行到第一行時便爲我們定義的i在棧中分配了一塊內存區域,同時將i的值100儲存進去,這便是值類型,比較簡單。然後代碼繼續運行至第二行,代碼僅很短看似很簡單可編譯器卻做了很多工作,首先創建了一個String類型的對象s,此時編譯器在棧上分配了一塊區域用以存放變量的地址,(很多文章中說這裏是創建了一個指針,在C++和C#中這樣便於理解,但是java中並沒有指針的概念,所以我們就說分配了一塊區域用於存放變量的地址,其實目的都是一樣的,因爲指針就是指向變量首地址的)此時便可以看出值類型與引用類型的不同了,執行到現在引用類型並沒有存儲任何值,只是儲存了變量的地址,真正的對象是儲存在堆內存上的,如上圖所示,也就是說聲明String s時並不會給String的實例分配內存,而是分配一個棧變量s(並設置爲null),然後把它指向“堆”。最後進行的便是‘=’的賦值操作了,也就是把剛申請到的內存的地址保存爲s的值。(這段話不知道有沒有把大家繞暈,我寫的有點暈,多體會兩遍理解起來應該沒有太大的問題。)

      對於概念有了大致的瞭解之後繼續後面的問題。

二,賦值操作:

1,         值類型變量的賦值,如下例:

Public void method(){

int a=100;

int b=a;

}

這是一個很簡單的值類型變量的賦值操作,很容易看出,b=100。當我們把一個int值分配給另外一個int值時,需要創建一個完全不同的拷貝。換句話說,你可以改變其中任何一個而不會影響另外一個。這種數據類型被稱爲值類型。此時又深入的理解了值類型的概念,爲了使得理解更透徹我們用圖示再來演示下。

結果一目瞭然。

2,      引用類型變量賦值,如下例:

Public void method(){

String s=new String();

String s2=s;

}

如我們之前所知,我們創建對象的同時分配了一塊棧內存用於存放變量的地址,注意僅是存放的地址而並非真實的值,所以當代碼執行到第二行時就在棧中多了一塊存放變量地址的區域,賦值操作使得兩塊區域同時指向相同的堆內存,所以當我們改變期中一個的時候另一個也會隨之改變,這種數據類型即爲引用類型,同樣以圖示來演示引用類型變量的賦值。

這樣就比較直觀了。

上面的內容相對而言就比較好理解,使我最最頭痛的是接下來的問題。

三,引用類型在函數調用中的參數傳遞:網上針對引用類型傳遞的參數到底是傳值還是傳引用的說法各不相同,有說是傳值,有說是傳址,還有的說根據不同的類型有不同的傳遞方法,學識淺薄的我真的不知道孰對孰錯,以前學習C++時有指針的概念,於是對於參數傳遞便能輕鬆的知道,java裏面沒有了指針概念少去了好多麻煩是肯定,然而在這個問題的理解上卻有了些許不方便,所以下面我就稍稍表達下自己的理解吧,可能並不正確,也是希望大家能指正,千萬不要看我犯錯而置之不理啊。

其實我覺得啊,這個到底是傳值還是傳引用在於自己怎麼理解,比如用以前舉過的例子吧,

Public void method(){

int a=100;

String s=new String();

}

現在若給出這樣一個語句System.out.println(s);則可以看做是傳引用方式,將引用的首地址傳遞進來,編譯器開始編譯就對棧進行查找,找到所對應傳遞的地址後指向對內存中存儲的對象。

但是若給出語句System.out.println(a);則毋庸置疑是傳值。

所以我的理解便是要根據參數的不同類型加以區別。

實在抱歉我不能對此有更好的理解,所以在此貼出幾個網址,裏面有關於這個問題的討論,大家可以進行參閱,我也將繼續研習這個問題,希望能在不久之後有更好的理解。

http://topic.csdn.net/t/20060322/19/4632866.html

四,最後的最後來總結下值類型與引用類型的區別:(這個部分借鑑了網上高手的總結,在此表示感謝。)

1、值類型通常被分配在棧上,它的變量直接包含變量的實例,使用效率比較高。
2、引用類型分配在託管堆上,引用類型的變量通常包含一個指向實例的指針,變量通過該指針來引用實例。
3、值類型繼承自ValueType(注意:而System.ValueType又繼承自System.Object);而引用類型繼承自System.Object。 
4、值類型變量包含其實例數據,每個變量保存了其本身的數據拷貝(副本),因此在默認情況下,值類型的參數傳遞不會影響參數本身;而引用類型變量保存了其數據的引用地址,因此以引用方式進行參數傳遞時會影響到參數本身,因爲兩個變量會引用了內存中的同一塊地址。 
5、值類型有兩種表示:裝箱與拆箱;引用類型只有裝箱一種形式。我會在下節以專門的篇幅來深入討論這個話題。 
6、典型的值類型爲:struct,enum以及大量的內置值類型;而能稱爲類的都可以說是引用類型。 
7、值類型的內存不由GC(垃圾回收,Gabage Collection)控制,作用域結束時,值類型會自行釋放,減少了託管堆的壓力,因此具有性能上的優勢。例如,通常struct比class更高效;而引用類型的內存回收,由GC來完成,微軟甚至建議用戶最好不要自行釋放內存。 
8、值類型是密封的(sealed),因此值類型不能作爲其他任何類型的基類,但是可以單繼承或者多繼承接口;而引用類型一般都有繼承性。 
9、值類型不具有多態性;而引用類型有多態性。 
10、值類型變量不可爲null值,值類型都會自行初始化爲0值;而引用類型變量默認情況下,創建爲null值,表示沒有指向任何託管堆的引用地址。對值爲null的引用類型的任何操作,都會拋出NullReferenceException異常。 
11、值類型有兩種狀態:裝箱和未裝箱,運行庫提供了所有值類型的已裝箱形式;而引用類型通常只有一種形式:裝箱。

  關於這個內容到此結束,寫的時候感覺雜亂無章,錯誤百出,希望大家批評指正。

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