《CLR Via C#》 學習心得之三 基元類型、引用類型和值類型


1,何謂基元類型

總所周知,c#中有簡化的語法來操縱常用的數據類型,比如可以直接這樣寫:int a=0;string str="abc";等等,相比起System Int32 a=new System.Int32()來的簡單很多吧。那么編譯器直接支持的數據類型稱為基元類型,基元類型直接映射到Framework類庫中存在的類型。

常見的有:

 
C#基元類型 FCL類型 說明
byte System.Byte 無符號8位值
int System.Int32 有符號32位值
string System.String 一個字符數組
float System.Single IEEE32位浮點值
bool System.Boolean 一個true/false值

 

從另一個角度看,可以想象c#編譯器自動假定在所有源代碼文件中添加了以下using指令

using byte=System.Byte;
using int=System.int32;
using string=System.String;
using bool=System.Boolean;
private static void PrimitiveDemo() {
//初始化以下四個變量
int a = new int();
int b = 0;
System.Int32 c = new System.Int32();
Int32 d = 0;

//四個變量全部包含值為0
Console.WriteLine("a = {0}, b = {1}, c = {2}, d = {3}",
new Object[] { a, b, c, d });

//所有變量賦值為5
a = b = c = d = 5;
//全部顯示為5
Console.WriteLine("a = {0}, b = {1}, c = {2}, d = {3}",
new Object[] { a, b, c, d });
}

 

2,checked和unchecked基元類型操作

讓c#編譯器控制溢出的一個辦法是使用/checked+編譯器開關。如下代碼:

Byte b = 100;
b = checked((Byte) (b + 200));//拋出OverflowException異常

3,引用類型和值類型

雖然FCL中的大多數類型都是引用類型,但程序用的最多還是值類型。引用類型總是從托管堆上分配的,c#的new操作符會返回對象的內存地址—也就是指向對象數據的內存地址。使用引用類型時,有以下幾點:

1,內存必須從托管堆上分配。

2,堆上分配的每個對象都有一些額外的成員,這些成員必須初始化。

3,對象中的其他字節總是為零。

4,從托管堆上分配一個對象時,可能強制執行一次垃圾收集操作。

如果所有類型都是引用類型,應用程序的性能將顯著下降。所以“值類型”也適當的出現了,值類型的實例不受垃圾回收器的控制,因此,值類型的使用緩解了托管堆中的壓力,並減少了一個應用程序在其生存期內需要進行的垃圾回收次數。常見的值類型有:System.Int32結構、System.Boolean結構、System.Decimal結構、枚舉等。

private static class ReferenceVsValue {
      // 引用類型(由於使用了'class')
      private class SomeRef { public Int32 x; }
      // 值類型 (由於使用了 'struct')
      private struct SomeVal { public Int32 x; }
      public static void Go() {
         SomeRef r1 = new SomeRef();   // 在堆上分配
         SomeVal v1 = new SomeVal();   // 在棧上分配
         r1.x = 5;                     // 提領指針
         v1.x = 5;                     // 在棧上修改
         Console.WriteLine(r1.x);      // 顯示"5"
         Console.WriteLine(v1.x);      // 同樣顯示"5"

         SomeRef r2 = r1;              // 只復制引用(指針)
         SomeVal 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"
      }
   }

4,值類型的裝箱和拆箱

為了將一個值類型轉換成一個引用類型,要使用一個名為裝箱的機制。總結了幾點:

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

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

3,返回對象的地址。現在,這個地址是對一個對象的引用,值類型現在是一個引用類型。

同理,拆箱,第一步是獲取已裝箱對象中的各個字段的地址,第二步是將這些字段的值從堆中復制到基於棧的值類型實例中。拆箱不是直接將裝箱過程倒過來,拆箱的代價比裝箱低得多。拆箱其實就是獲取一個指針的過程,該指針指向包含在一個對象中的原始值類型(數據類型)。事實上,指針指向的是已裝箱實例中的未裝箱的部分。所以和裝箱不同,拆箱不要求在內存中復制任何字節,知道這個重要區別之后,還應知道一個重點在於,往往會緊挨着拆箱操作發生一次字段的復制操作。

 private static class Boxing {
      public static void Go() {
         ArrayList a = new ArrayList();
         Point p;            //分配一個Point (not in the heap).
         for (Int32 i = 0; i < 10; i++) {
            p.x = p.y = i;   // 初始化值類型中的成員
            a.Add(p);        // 對值類型進行裝箱,並將引用添加到ArrayList中
         }
      }

      // 聲明了一個值類型.
      private struct Point { public Int32 x, y;  }

      public static void Main2() {
         Int32 x = 5;
         Object o = x;         
         Int16 y = (Int16)o;   //拋出InvalidCastException異常
      }

      public static void Main3() {
         Int32 x = 5;
         Object o = x;                 //對x進行裝箱; o引用已裝箱的對象
         Int16 y = (Int16)(Int32)o;    //先拆箱,然后進行轉型
      }

      public static void Main4() {
         Point p;
         p.x = p.y = 1;
         Object o = p;   // 對p進行裝箱; o引用已裝箱的對象instance
         p = (Point)o;   //對o進行拆箱,將字段從已裝箱的實例復制到棧變量中
      }

      public static void Main5() {
         Point p;
         p.x = p.y = 1;
         Object o = p; 
         p = (Point)o; 
         p.x = 2;       
         o = p;          
      }

      public static void Main6() {
         Int32 v = 5;            // 創建一個未裝箱的值類型變量
         Object o = v;            // o引用一個已裝箱的、包含值為5的int32
         v = 123;                 // 將未裝箱的值修改為123
         Console.WriteLine(v + ", " + (Int32)o);    // 顯示"123, 5"
      }

      public static void Main7() {
         Int32 v = 5;                // 創建一個未裝箱的值類型變量
         Object o = v;               // o引用v的已裝箱的版本

         v = 123;                    // 將未裝箱的值類型修改成123
         Console.WriteLine(v);       // 顯示"123"

         v = (Int32)o;               // 拆箱並將o復制到v
         Console.WriteLine(v);       // 顯示"5"
      }
   }

5,Dynamic基元類型

c#是一種類型安全的編譯語言。這意味着所有表達式都解析成某個類型的一個實例,在編譯器生成的代碼中,只會執行這個類型來說有效的操作。但是許多時候,程序員需要一些運行時才會知曉的信息,雖然說可以使用反射來解決,那是在純C#語言寫的應用程序,如果希望是其他語言寫的組件,比如Ruby或者Python等,還有一些COM對像,可能是本地C或者C++實例,這時,c#編譯器就允許將一個表達式的類型標記為Dynamic,還可以將一個表達式的結果放到一個變量中,並將變量的類型標記為Dynamic,然后可以用這個Dynamic表達式調用一個成員,比如字段、屬性/索引器、方法、委托以及一元/二元/轉換操作符。

經過整理,對於更基礎的知識理解更深刻點。


免責聲明!

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



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