C#值類型、引用類型、指針類型

計算機內存的分區(qū)如下:

一個內存條

我們的內存就像一個倉庫,這個倉庫由成千上萬個大小相等的房間構成,一個房間就是一個字節(jié)(byte),每個房間都有一個唯一的“房間號”,就是該字節(jié)的“地址”,地址一般以16進制無符號整數(shù)表示,比如電腦藍屏時出現(xiàn)的“0x0000007b”就是一個內存地址

64位操作系統(tǒng)的內存地址轉化為二進制后是一個8byte的無符號整數(shù),32位則是4byte(這也是32位操作系統(tǒng)的內存上限是4G的原因),下文默認討論32位操作系統(tǒng)

我們將這些房間分為五個區(qū),每個區(qū)的功能,顧名思義:代碼區(qū)就是放代碼的地方,常量區(qū)放常量,靜態(tài)區(qū)放靜態(tài)變量,至于其他貨物,都存放在棧區(qū)和堆區(qū)。

棧區(qū)比堆區(qū)要小一些,用來儲存小型貨物、以及堆區(qū)各個“套間”(n個連續(xù)的房間組成一個套間)的鑰匙;堆區(qū)則大很多,用來存儲大型貨物

介紹完了內存結構,接下來讓我們看看內存是如何存儲變量的,C#的變量有三種類型,我們依次介紹:

【CASE1 值類型】

我們看語句:

int a=10;

系統(tǒng)會先處理賦值號左邊的定義語句:

首先在棧上開辟4byte的連續(xù)空間,比如說300,301,302,303這四個房間,組成一個套間。(為了方便,不再使用“0x……”這種十六進制格式來表示地址,就用300來表示地址,下同)然后,系統(tǒng)會在一個叫“符號表”的小本本里記下“a——300號4連房間里的那個貨物

!注意:“變量a”不是指300號房間,而是里面的貨物。300是個常數(shù),而a在不斷變化

系統(tǒng)再處理賦值語句:

首先判斷賦值號右邊的貨物是什么類型,10是整型,并且4連房里放得下,ok沒問題,那就把他塞進剛開的套間a里

值類型a與b

現(xiàn)在我們傳遞一下值:

int b=a;

和上面類似,系統(tǒng)會先看賦值號左邊的定義語句:

首先在內存的棧上開辟4byte的連續(xù)空間,比如說400,401,402,403這四個房間,組成了一個套間。然后在“符號表”里記下“b——400號4連房間里的那個int型貨物

系統(tǒng)再看賦值語句:

首先判斷賦值號右邊是什么類型,a是個什么東西?去符號表里查一下,哦~是個int類型,與左邊一致,ok沒問題,那就把貨物a復制一份,塞到400號4連房間里

在這個case里,b和a指代的都是貨物,也就是數(shù)據(jù)

結論:

因為b是a的一個副本,所以對變量b進行的任何修改,都不會影響到變量a

【CASE2 引用類型】

假設我們有這樣一個類:

class Person{int age;bool sex;}

我們把他實例化:

Person c=new Person();

系統(tǒng)會先看賦值號左邊的定義語句:

首先在棧上開辟4byte連續(xù)空間,比如說500,501,502,503這4個房間,用來存放實例的地址,然后在“符號表”里記下“c——500號4連房里的那把Person型鑰匙

系統(tǒng)再看賦值號右邊的實例化語句:

因為int age占4byte,bool sex占1byte,所以在堆上開辟5byte連續(xù)空間,比如說2000,2001,2002,2003,2004這五個房間,作為一個套間,用來存放這個實例的各個字段,(嚴格來講不止5byte,因為還要存一些其他東西,比如引用計數(shù);方法不放在這里,方法在代碼區(qū)),并且按照構造方法賦初值。因為我們調用的是無參構造函數(shù),所以每個字段初始值就是默認值,age=0,sex=false

系統(tǒng)再處理賦值號:

等號兩邊類型匹配,ok,將“2000號5連房”的大門,配一把鑰匙,把鑰匙放入500號4連房

引用類型c與d

現(xiàn)在我們傳遞一下值:

Person d=c;

系統(tǒng)會先看賦值號左邊的定義語句:

首先在棧上開辟4byte連續(xù)空間,比如說首地址叫600,用來存放Person類實例的地址,然后在“符號表”里記下“d——600號4連房里的那把Person型鑰匙

系統(tǒng)再看賦值號右邊:

c是何物?查一下符號表,得到一把Person類的鑰匙,與等號左邊匹配,所以該賦值過程合法

系統(tǒng)再處理賦值號:

把房間c里的貨物——“那把鑰匙”,又配了一把,塞到了房間d里

在這個case里,c和d指代的不是貨物(數(shù)據(jù)),而是堆上那個實例的套間的鑰匙 (引用);

對鑰匙d打開的房間里的貨物進行的任何修改,都會影響到鑰匙c打開的房間的貨物,因為他們指向同一堆貨物

結論:

對變量d指向的實例進行的任何修改,都會影響到變量c指向的實例,因為他們指向同一個實例

這就是引用類型和值類型的本質區(qū)別。

這時,細心的同學可能會問:“在case1值類型里,你說的是‘對變量b進行的任何修改,都不會影響到變量a’ ,怎么到了case2引用類型這兒,就不提‘變量本身’了呢?卻要討論‘指向的實例’?”

沒錯!盲生你發(fā)現(xiàn)了華點!

別急,先聽我講完第三種類型——指針

【CASE3 指針類型】

在C#里,指針是一種特殊的類型,既非值類型又非引用類型

比如:

int*  e=&a; 

先處理賦值號左邊:

系統(tǒng)在棧上開辟4byte連續(xù)空間,比如首地址是700,用來存放地址,e的類型是“int*”型,表示它是個指向int的指針型變量。然后在“符號表”里記下“e——700號4連房里那個地址

再處理賦值號右邊:

a還是case1的int a,房間號是300,值是10,&是“取址運算符”,&a就是a的地址,就是300

最后處理賦值號:

將“房間號300”這一號碼牌,作為一個貨物儲存在700號房

與值類型和引用類型不同,這次存的貨物既不是數(shù)據(jù)本身,也不是鑰匙,而是一個“號碼牌”

指針變量e與f

現(xiàn)在我們傳遞一下值:

int*  f=e;

系統(tǒng)在棧上開辟4byte連續(xù)空間,比如首地址是800,那么就在“符號表”里記下“f——800號4連房里那個int型地址”,然后把e,也就是“房間號300”,作為貨物,復制一份儲存在800號房

如果我們想通過e或f來修改a的值,就必須使用“*”號——“取值運算符”,將地址轉化為對應的變量。比如:

*e=20;

這時a的值就是20了

結論:

在這個case里,e和f指代的都是“號碼牌”,也就是地址

因為f是e的一個副本,所以對變量f進行的任何修改,都不會影響到變量e

*e和*f指代的都是a,對變量f指向的變量進行的任何修改,都會影響到變量e指向的變量,因為他們指向同一個變量a

【DISCUSSION1】三種類型的區(qū)別

為什么唯獨在CASE2引用類型里,不提‘變量本身’了呢?卻要討論‘指向的實例’?

原因有二:

其一,多數(shù)情況下,我們所關注的那個有價值的數(shù)據(jù),是堆上的那個實例,而非他的地址。就好比我們來倉庫取快遞,我們關心的是貨物本身,而非那把鑰匙,除非你是倉庫管理員

其二,即使我們想關注那個地址,我們也關注不了:

因為,C#限制我們對引用型變量d中所存儲的“地址”進行任何顯式的讀寫和計算,這也是引用型和指針型的根本區(qū)別

同樣是存放地址,引用型變量d代表著一把鑰匙,而指針型變量f代表著一個房間號,鑰匙和房間號的區(qū)別如下

①一把鑰匙一旦配好,不可以“再加工”以匹配其他房間,除非重新配一把;一個房間號,只要拿橡皮擦掉重新寫,就可以代表另一個房間

比如

d+=1;

是不是d就指向下一個房間了呢?比如從2000號房改為指向2001號房?

事實上,這種操作是非法的。在這個語句中,d代表的是d所指向的Person實例,而非地址,而讓一個實例去+1,沒有任何意義(除非用戶在Person類里對加法重載,即使重載了,d依然指代實例而非地址)。但是

f+=1;

就是合法的。指針類型變量可以對‘地址的值’進行直接讀寫”,+1表示地址增加一個“單位長度”,f指向int型,單位長度是4(byte),f+1就是加4(byte),指向304號房

指針運算有時可以很方便,比如計算數(shù)組某索引的地址,但是如果運用不當,就有可能指向意料之外的位置,如果那個位置已經(jīng)存儲了一些數(shù)據(jù),冒然讀寫可能會導致程序崩潰;引用類型杜絕了地址運算,也就避免了這種錯誤

必須指出的是,d=null; 、d=c; 和 d=new Person(); 這三種語句沒有直接對地址的值進行操作,相當于銷毀鑰匙或者重新配了一把鑰匙,是合法的

②一把鑰匙使用的時候,直接就可以打開門鎖;而一個房間號在使用的時候,需要先借助取值運算符“*”配一把鑰匙,才可以打開指定的房間

*e=30;//使a的值變?yōu)?0

而引用類型無需加“*”,使用時直接指向對象,也就是說我們在讀寫引用型變量時,讀寫的是堆上那個對象而非對象的地址

【DISCUSSION2】函數(shù)參數(shù)傳遞

上文一直在討論變量之間值的傳遞,沒有提及函數(shù)調用時參數(shù)的傳遞,事實上,兩者在原理上是一樣的,都是值傳遞,值類型變量傳參時傳遞的是變量的“值”,引用類型和指針類型傳參時傳遞的也是變量的“值”,只不過“值”是個地址

只有一種情況屬于例外:

當函數(shù)參數(shù)使用關鍵字“ref”和“out”時,傳的是地址而非

例如有如下方法:

public void Shopping(ref int money)//ref值類型變量
    {money-=100;}

在調用此方法時:

int cash=1000;
jim.Shopping(ref cash);

傳遞的是實參cash在棧上的地址,比如說是900,那么900號房間里的貨物又獲得一個別名——money,對money的任何修改,都是對cash的修改
即,通過ref/out,值類型的參數(shù)傳遞可以表現(xiàn)地像引用類型一樣

例如有如下方法:

public void CelebrateBirthday(ref Person friend)//為他人慶生,ref引用型變量
    {friend.age+=1;}

在調用此方法時:

Person lucy=new Person();
jim.CelebrateBirthday(ref lucy);

傳遞的是實參lucy在棧上的地址(注意,不是堆上那個實例的地址,而是“引用型變量lucy”的地址),比如說是1000,那么1000這個房間的貨物(一把鑰匙)又獲得一個別名——friend,對friend的任何修改,都是對lucy的修改
雖說傳遞的是地址,而不再是值,過程和剛才不一樣了,但是從結果上講,引用類型使用ref/out來傳參,與不使用ref/out傳參,幾乎沒有區(qū)別。除非你在這個方法里做了一些無聊的事:friend=c; 、friend=null; 或 friend=new Person(); 讓friend指向了別的實例,相當于新配了一把鑰匙,絕大多數(shù)情況下,這種操作沒有意義

指針型傳參我們就不討論了,感興趣的同學可以自己琢磨一下,和引用型類似

事實上,在C#里我們基本用不上指針,C#的引用類型已經(jīng)可以滿足絕大多數(shù)需求,而且更安全,更方便。那我們?yōu)楹芜€要講指針呢?因為有些其他語言使用指針,比如C和C++,為了避免概念混淆,必須要把他們的區(qū)別說一下;而且指針可以更好的幫助我們理解引用

【DISCUSSION3】類型的聲明

我們在定義變量時,為何必須要聲明變量的類型?

int g=259;

為例,259必須要轉化為二進制,即“100000011”,然后把所有空位補0:

00000000 00000000 00000001 00000011

然后把它存到300號四連房里,如下圖:

(其實上文中所有棧的開辟空間方式都是錯的,棧是從內存條最上面開始使用的,逐漸往下開辟空間,地址越來越小)

內存中的變量g

大小端模式是系統(tǒng)儲存數(shù)據(jù)的兩種模式,兩者互為倒序,本文只討論大端

系統(tǒng)會在“符號表”里記下“g——首地址為0xFFFFFFFF的4連房間里的那個貨物”

如果我們不告訴系統(tǒng)g是int型,他怎么知道去0xFFFFFFFF這個地址取出四個byte組成一個數(shù)呢?他完全可以只取一個byte,取出來的貨物是“11”,轉化成十進制是3,這與我們存進去的貨物“259”毫無關系

指針和引用也是同理,如果我們不聲明他們指向的數(shù)據(jù)的類型,只給他們一個首地址,他們根本就不知道該取多少貨

這時有同學可能會問,python從來不需要聲明變量類型,是為什么呢?

python里只有兩種東西,引用(相當于C#引用類型變量)和對象(相當于C#里類的實例)。在python里一切數(shù)據(jù)都是對象,“值類型”數(shù)據(jù)也是對象,所有對象都有一個屬性:“類型”,所以取貨時系統(tǒng)先瞄一眼貨的類型,類型是啥我就取多少

這種模式寫起來方便,但也有代價:傳值或傳參時,如果類型不匹配,編輯器不會報錯,甚至運行了也不一定報錯,這種bug比較隱蔽;而且,如果寫代碼的人不寫注釋,代碼的可讀性極差

【PS】

在定義指針變量時,必須明確他所指向的變量的類型,就是*前面那個數(shù)據(jù)類型
這個“數(shù)據(jù)類型”包括且僅包括:指針類型和值類型(其中struct不能包含引用型字段)

C#值類型包括且僅包括:sbyte、byte、short、ushort、int、uint、long、ulong、float、double、decimal 、bool、char、枚舉、自定義struct
C#引用類型包括且僅包括:類、接口、數(shù)組、委托

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容