亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

基元類型、引用類型、值類型、裝箱和拆箱

2019-11-14 13:49:46
字體:
來源:轉載
供稿:網友

1.基元類型

有些數據類型我們平常寫代碼經常會用到,例如:int,string等,例如下面我們定義一個整數:

int a =0;

我們也可以用下面的寫法定義:

System.Int32  a = new System.Int32();

以上兩種寫法的結果都是一樣的,為什么會這樣呢?,大家肯定知道是編譯器做的優化;本人比較喜歡第一種寫法,因為這種語法不僅增強了代碼的可讀性,生成的IL代碼跟System.Int32的IL代碼是完全一樣的。那什么是基元類型呢?就是編譯器能直接支持的數據類型稱為基元類型(PRimitive type).基元類型直接映射到.NET框架類庫(FCL)中存在的類型。會不會很難理解?例如:c#中的int直接映射到了System.Int32類型。再看下面的代碼,雖然寫的方式不一樣,但是它們生成的IL代碼是完全一致的。只要是符合公共語言規范(CLS)的類型,不管那種語言都有提供了類似的基元類型。但是,不符合CLS的類型語言就不一定支持。

int a = 0;System.Int32 a = 0;int a = new int();System.Int32 a =new System.Int32();

但是CLR via C#的作者是不推薦這種簡潔的寫法,下面就把他不推薦的理由照抄過來,大家可以作為參考:

1.許多開發人員糾結于是用String還是string。由于c#的string(一個關鍵字)直接映射到System.String(一個FCL類型),所以兩者沒有區別,都可以使用。類似,一些人開發人員說應用程序在32位操作系統上運行,int代表32位整數;在64位系統上運行,int代表64位整數。這個說法完全錯誤。c#的int始終映射到System.Int32,所以不管在什么操作系統上運行,代表的都是32位整數。如果程序員習慣在代碼中使用Int32,像這樣的誤解就沒有了。

2.c#的long映射到Sytem.Int64,但在其他編程語言中,Long可能映射到Int16或Int32.例如,c++/CLI就將long視為Int32.習慣于用一種語言寫程序的人在看用另一種語言寫的源代碼時,很容易錯誤理解代碼意圖。事實上,大多數語言甚至不將long當作關鍵字,根本不編譯使用了它的代碼。

3.FCL的許多方法都將類型名作為方法名的一部分。例如,BinaryReader類型的方法包括ReadBoolean,ReadInt32,ReadSingle等;而System.Convert類型的方法包括ToBoolean,ToInt32,ToSingle等。以下代碼雖然語法沒問題,但包含float的那一行顯得很別扭,無法一下子判斷該行的正確性:

BinaryReader br = new BinaryReader(...);float val = br.ReadSingle();    //正確,但感覺別扭Singleval = br.ReadSingle();    //正確,感覺自然

4.平時只用c#的許多程序員逐漸忘了還可以用其他語言寫面向CLR的代碼,“c#主義”逐漸入侵類庫代碼。例如,Microsoft的FCL幾乎是完全用c#寫的,FCL團隊向庫中引入了像Array的GetLongLength這樣的方法。該方法返回Int64值。這種值在c#確實是long,但在其他語言中不是。另一個例子是System.Linq.Enumerable的LongCount方法。

以上就是作者不推薦基元類型的原因,以至于CLR via c#里面所描述的類型都是FCL的類型名稱。不過我自己倒覺得使用基元類型看起來比用FCL類型順眼,所以還是自己的習慣吧,至少兩種類型生成的IL代碼都是一樣的,也沒有說用這種性能好,用那種性能不好的說法。在這里引用了博友的圖片,圖代表了C#的基元類型與對應的FCL類型

02150019-9c02a771e78d463eb1f88e6c47c[2]

 

最后說下類型轉換,首先,編譯器能執行基元類型之前的隱式或顯示轉換,只有在轉換安全的時候,c#就允許隱式轉型。反之,就要求顯示轉型。

 

2.引用類型和值類型

CLR提供了2種類型,引用類型和值類型。雖然FCL大多都是引用類型,但是開發人員用的最多還是值類型。引用類型總是從托管堆分配,使用new關鍵字返回指向對象數據的內存地址。設想下如果所有類型都是引用類型,那么性能肯定會明顯下降。當你要使用int值時,都需要進行一次內存的分配,性能是會受多么大的影響。所以CLR提供了“值類型”的輕量級類型。值類型的實例一般在棧上分配。值類型的實例變量不包含指向實例的指針,而是變量本身包含了實例本身的字段。由于本身包含了實例的字段,所以不需要提領指針,一定程度上緩解了托管堆的壓力,也減少了程序生存期內垃圾回收的次數。

所以定義的類型儲存在棧中還是托管堆中取決于所屬的類型。比如:String和Object屬于引用類型,其他的基元類型則分配到棧中,下圖詳細地展示了在.NET預置類型中,哪些是值類型,哪些又是引用類型。

7_thumb8

以下代碼說明引用類型和值類型的區別:

//引用類型    class Ref    {        public int x;    }   //值類型    struct Val    {        public int x;    }    static void Demo()    {          Ref r1 = new Ref();            //托管堆分配            Val v1 = new Val();            //棧上分配                r1.x = 5;                //提領指針            v1.x = 5;                //在棧上修改            Console.WriteLine(r1.x);        //顯示5          Console.WriteLine(v1.x);        //同樣顯示5          Ref r2 = r1;                //只復制指針            Val v2 = v1;                //在棧上分配并復制成員            r1.x = 8;                //r1.x和r2.x都會改變            v1.x = 9;                //v1.x會改變,v2.x不會            Console.WriteLine(r1.x);        //顯示8          Console.WriteLine(r2.x);        //顯示8          Console.WriteLine(v1.x);        //顯示9          Console.WriteLine(v2.x);        //顯示5        }

值類型的主要優勢是不作為在托管堆上分配。當然,與引用類型相比,值類型也存在自身的一些局限。下面列出了值類型和引用類型的一些區別。

1.值類型有已裝箱和未裝箱2種表示形式,而引用類型總是處于已裝箱形式。

2.值類型總是從System.ValueType中派生。跟System.Object具有相同的方法。但是ValueType重寫了Equals方法,能在兩個對象的字段值完全匹配前提下返回true,此外也重寫了GetHashCode方法。

3.值類型的所有方法都是不能為抽象的,都是隱式密封的(不可以重寫)。

4.將值類型變量賦給另一個值類型變量,會執行逐字段復制。將引用類型的變量賦給另一個引用類型變量只復制內存地址,所以兩個引用類型的變量都是指向堆同一個對象,所以對一個變量執行操作可能影響到另一個變量引用的對象。

5.由于未裝箱的值類型不在堆中分配,一旦定義了該類型的一個實例方法不在活動,為它們分配的存儲就會被釋放,而不是等著進行垃圾回收。

6.引用類型變量包含堆中對象的引用地址。引用類型的變量創建時默認初始化為null,代表當前不指向任何對象,試圖使用null引用類型變量都會拋出NullReferenceException異常。相反,值類型的所有成員都被初始化為0,訪問值類型不可能拋出NullReferenceException異常。CLR已經為值類型添加了可空標識

 

3.裝箱和拆箱

講到引用類型和值類型,必定要講下裝箱和拆箱了,下面開始講述:

值類型比引用類型“輕”,原因是它們不作為對象在托管堆中分配,不被垃圾回收,也不通過指針進行引用。但許多時候需要獲取對值類型實例的引用。例如下面這個栗子,創建一個ArrayList(這里是為了舉例子而用ArrayList來裝值類型,平常最好不要這樣用,因為FCL已經提供了泛型集合類,List<T>在操作值類型不會進行裝箱和拆箱)來容納一組Point結構,上代碼:

//聲明值類型struct Point {    public int x, y; }static void Main(){    ArrayList arraylist = new ArrayList();    Point p;            //在棧上分配一個point    for (int i = 0; i < 10; i++)    {        p.x = p.y = i;        //初始化成員         arraylist.Add(p);        //對值類型裝箱,將引用添加到ArrayList中     }    Console.ReadLine();}

上面的代碼大家很容易就能看出來,每次循環迭代都初始化一個Point的字段,并將該對象存儲在arraylist中。但思考下ArrayList中究竟存儲了什么?是point結構,還是地址,還是其他的東西?要知道答案,我們來看下ArrayList的Add方法,了解它的參數被定義成什么。代碼如下

 // // 摘要:  //     將對象添加到 System.Collections.ArrayList 的結尾處。 // // 參數:  //   value: //     要添加到 System.Collections.ArrayList 的末尾處的 System.Object。 該值可以為 null。 //// 返回結果:  //     System.Collections.ArrayList 索引,已在此處添加了 value。 // // 異常:  //   System.NotSupportedException: //     System.Collections.ArrayList 為只讀。 - 或 – System.Collections.ArrayList 具有固定大小。 public virtual int Add(object value);

可以看出Add方法獲取的是一個object參數。也就是說,Add獲取對托管堆上的一個對象的引用(或指針)來作為參數。但是point又是值類型的,所以必須轉換成真正的在堆中托管的對象。將值類型轉換成引用類型稱為裝箱。那么裝箱發生了什么呢?

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

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

3.返回對象地址。這時值類型就成了引用類型了。

知道了裝箱之后,我們再來看下拆箱是怎么運行了:

拆箱不是直接將裝箱過程倒過來,它其實就是獲取指針的過程,該指針指向包含在一個對象中的原始值類型,然后再進行字段的復制。所以拆箱的代價比裝箱低得多

那么已裝箱值類型實例在拆箱時,內部發生下面這些事情:

1.如果包含“對已裝箱值類型實例的引用”的變量為null,拋出NullReferenceException異常

2.如果引用的對象不是所需值類型的已裝箱實例,拋出InvalidCastException異常。

用代碼來看看裝箱和拆箱的例子

static void Main() {            int val = 5;    //創建未裝箱值類型變量            object obj = val;   //val進行裝箱            val = 123;          //將val值改為123            Console.WriteLine(val + "," + (int)obj);    //顯示123,5 }

大家能從上面代碼看出有多少次裝箱和拆箱嗎?利用ILDasm查看本代碼的IL就能很清楚看出來:

.method private hidebysig static void  Main() cil managed{  .entrypoint  // 代碼大小       47 (0x2f)  .maxstack  3  .locals init ([0] int32 val,           [1] object obj)  IL_0000:  nop  //將5加載到val中  IL_0001:  ldc.i4.5  IL_0002:  stloc.0  //將val進行裝箱,將引用指針存儲到obj中  IL_0003:  ldloc.0  IL_0004:  box        [mscorlib]System.Int32  IL_0009:  stloc.1  //將123加載到val中  IL_000a:  ldc.i4.s   123  IL_000c:  stloc.0  //將val進行裝箱,將指針保留在棧上以進行Concat操作  IL_000d:  ldloc.0  IL_000e:  box        [mscorlib]System.Int32  //將字符串加載到棧上  IL_0013:  ldstr      ","  //對obj進行拆箱,獲取一個指針,指向棧上的Int32字段  IL_0018:  ldloc.1  IL_0019:  unbox.any  [mscorlib]System.Int32  IL_001e:  box        [mscorlib]System.Int32  //調用Concat方法  IL_0023:  call       string [mscorlib]System.String::Concat(object,                                                              object,                                                              object)  //返回字符串  IL_0028:  call       void [mscorlib]System.Console::WriteLine(string)  IL_002d:  nop  //從main返回,終止引用程序  IL_002e:  ret} // end of method Program::Main

上面的IL顯示出三個box和一個unbox,第一次裝箱很明顯就能看出來,但是第二三次裝箱可能有些同學會無法理解,在調用writeline這個方法時返回一個String對象,所以c#編譯器生成代碼來調用String的靜態方法Concat。該方法有幾個重載版本,而在這里調用了一下版本:

public static string Concat(object arg0, object arg1, object arg2);

所以在代碼中val和轉成Int32的obj都會裝箱然后傳給Concat中。大家有興趣可以把上面的代碼改下,輸入結構代碼變成Console.WriteLine(val + "," + obj); 然后再看看IL代碼,你會發現減少了一次裝箱和拆箱而且代碼的大小減少了10字節左右。所以證明額外的裝箱拆箱會在托管堆分配一個額外的對象,然后還要進行垃圾回收,可見過多的裝箱操作會影響到程序的性能和內存的消耗。所以我們盡可能地在自己的代碼中減少裝箱。

如果知道自己的代碼在編譯器中會反復發生裝箱,那最好是用手動方式對其進行裝箱,例如:

static void Main(){            int val=5;            //會進行3次裝箱            Console.WriteLine("{0}{1}{2}",val,val,val);            //手動進行裝箱            object obj=val;            //不會發生裝箱           Console.WriteLine("{0}{1}{2}",obj,obj,obj);}

以上羅列出了基元類型、引用類型和值類型的區別,最后加了裝箱和拆箱的,文碼并茂,希望能給你帶來比較深刻的印象,雖然不夠深,但愿能夠起到拋磚引玉的作用。本文參考了CLR via C#,很推薦大家有空可以閱讀此書。以后我還會在這個系列中多寫些文章,分享給大家。


上一篇:C#計時器

下一篇:事件

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久久久精品国产| 美女福利视频一区| 成人美女免费网站视频| 国产精品高清免费在线观看| 91精品久久久久久久久久久久久| 中文字幕日韩欧美在线| 国产精品中文字幕在线| 亚洲国产精品人人爽夜夜爽| 97视频在线观看播放| 亚洲男人天堂手机在线| xxav国产精品美女主播| 欧洲亚洲免费视频| 午夜免费日韩视频| 岛国av一区二区在线在线观看| 97成人在线视频| 日韩av在线网站| 久久久欧美精品| 欧美乱大交xxxxx| 综合欧美国产视频二区| 欧美亚洲成人免费| 国产在线观看不卡| 国产aⅴ夜夜欢一区二区三区| 国产精品18久久久久久首页狼| 日韩中文字幕精品| 欧美日韩免费看| 狠狠躁夜夜躁人人躁婷婷91| 精品久久久久久中文字幕一区奶水| 中文字幕欧美亚洲| 欧美裸体xxxxx| 欧洲美女免费图片一区| 日韩电影中文 亚洲精品乱码| 亚洲欧美一区二区激情| 欧美国产在线视频| 亚洲成人动漫在线播放| 5566日本婷婷色中文字幕97| 日韩成人黄色av| 国产午夜精品视频免费不卡69堂| 欧美精品第一页在线播放| 欧美成人精品激情在线观看| 97视频在线看| 欧美日韩国产一中文字不卡| 日韩欧美在线免费观看| 亚洲色图国产精品| 亚洲一区二区日本| 国产精品一区二区三区久久久| 精品呦交小u女在线| 亚洲va欧美va国产综合久久| 亚洲第一区第二区| 亚洲一区二区三区在线免费观看| 亚洲欧美日韩国产中文| 91国产视频在线| 91免费高清视频| 久久精品国产精品| 亚洲免费伊人电影在线观看av| 97av在线视频免费播放| 午夜精品久久17c| 国内精品久久久久久中文字幕| 亚洲美女av黄| 日韩欧美国产骚| 午夜精品一区二区三区在线视频| 亚洲视频日韩精品| 欧美成人久久久| 青草青草久热精品视频在线观看| 久久99久国产精品黄毛片入口| xxxxxxxxx欧美| 色多多国产成人永久免费网站| 欧美大尺度电影在线观看| 亚洲日韩中文字幕| 国产精品福利在线观看网址| 欧美国产高跟鞋裸体秀xxxhd| 福利二区91精品bt7086| 亚洲另类xxxx| 97视频免费在线看| 亚洲精品一区久久久久久| 欧美性猛交xxxx乱大交3| 一区二区三区久久精品| 久久久久久国产精品| 97久久超碰福利国产精品…| 在线免费观看羞羞视频一区二区| 国产免费成人av| 韩国19禁主播vip福利视频| 国产精品主播视频| 91精品国产亚洲| 亚洲欧美国产一本综合首页| 日韩国产欧美精品一区二区三区| 日本成人在线视频网址| 亚洲欧洲av一区二区| 成人黄在线观看| 久久97精品久久久久久久不卡| 国产盗摄xxxx视频xxx69| 欧美视频在线看| 欧美极品少妇xxxxⅹ免费视频| 26uuu久久噜噜噜噜| 97成人超碰免| 亚洲午夜精品视频| 亚洲欧洲xxxx| 久久香蕉频线观| 国产热re99久久6国产精品| 国产免费成人av| 国产精品美女网站| 亚洲男人天堂网| 九九久久国产精品| 主播福利视频一区| 超在线视频97| 2019国产精品自在线拍国产不卡| 欧美美最猛性xxxxxx| 欧美大片欧美激情性色a∨久久| 久久成年人免费电影| 亚洲第一视频网| 国产视频亚洲精品| 亚洲国产精品成人一区二区| 国产91成人video| 九色精品美女在线| 91丝袜美腿美女视频网站| 欧美日韩一二三四五区| 播播国产欧美激情| www.日韩视频| 亚洲一区二区在线播放| 2019av中文字幕| 日韩免费不卡av| 成人高清视频观看www| 全亚洲最色的网站在线观看| 欧美视频在线看| 日韩av最新在线观看| 国产香蕉精品视频一区二区三区| 91中文字幕在线| 日韩美女视频免费看| 亚洲自拍偷拍色片视频| 综合网日日天干夜夜久久| 日韩av在线影视| 97久久精品人人澡人人爽缅北| 精品网站999www| 国产精品久久久精品| 国产精品h片在线播放| 91精品国产乱码久久久久久蜜臀| 57pao成人国产永久免费| 中文字幕亚洲精品| 日韩在线视频中文字幕| 国产午夜精品视频| 日韩成人在线视频观看| 久久久免费精品| 日韩综合视频在线观看| 日韩乱码在线视频| 中文字幕一精品亚洲无线一区| 精品成人在线视频| 久久综合免费视频影院| 久久久亚洲国产天美传媒修理工| 国产视频久久久久久久| 欧美做爰性生交视频| 精品国产视频在线| 日韩在线不卡视频| 性欧美亚洲xxxx乳在线观看| 7777精品视频| 国产精品亚洲第一区| 欧美孕妇与黑人孕交| 国产不卡av在线| 国产成人精品在线观看| 欧美精品一区在线播放| 国产69久久精品成人| 国产精品扒开腿做爽爽爽的视频| 日韩激情第一页| 亚洲最大激情中文字幕| 国产综合香蕉五月婷在线| 成人免费大片黄在线播放|