1. 程式人生 > >5.1 編程語言的基元類型

5.1 編程語言的基元類型

屬性 long 測試 byte 發生 代碼 arr dsi 算數運算

  • 編譯器直接支持的數據類型成為基元類型(primitive type)。基元類型直接映射到 Framework類庫(FCL)中存在的類型。
  1. int a = 0; // Most convenient syntax
  2. System.Int32 a = 0; // Convenient syntax
  3. int a = new int(); // Inconvenient syntax
  4. System.Int32 a = new System.Int32(); // Most inconvenient syntax
  • 上述4行代碼都能正確編譯,並且生成相同的IL。
    技術分享
    技術分享

  • 作者不支持使用 FCL 類型名稱,原因如下:

    1. 開發人員會糾結使用 string 還是 String,string 實際上是直接映射到 System.String 。不知道 int 是指 Int32 還是 Int64。int 映射到 System.Int32,與操作系統無關。
    2. C# long 映射島 System.Int64,但在其他編程語言中,long 可能映射到 Int16 或 Int32。(C++/CLI 將long 視為Int32)許多語言不將long 作為關鍵字。
    3. FCL 的許多方法都將類型名作為方法名的一部分,如ReadBoolean,ReadInt32,ReadSingle 等。
    4. 只是用C#的許多程序員忘掉還可以用其他語言寫面向CLR的代碼。如 Array的 GetLongLength 返回 Int64 的值,在C#中為long,但是在其他語言中(C++/CLI)中卻不是。
  1. //第3條示例
  2. BinaryReader br = new BinaryReader
    (...);
  3. float val = br.ReadSingle(); // OK, but feels unnatural
  4. Single val = br.ReadSingle(); // OK and feels good
  • 不同類型,並且沒有繼承關系,正常情況下是不能直接轉換的。但是 Int32 就可以隱式轉換為 Int64。 因為C#編譯器非常熟悉基元類型,會在編譯代碼時應用自己的特殊規則。示例:
  1. Int32 i = 5; // Implicit cast from Int32 to Int32
  2. Int64 l = i; // Implicit cast from Int32 to Int64
  3. Single
    s = i; // Implicit cast from Int32 to Single
  4. Byte b = (Byte) i; // Explicit cast from Int32 to Byte
  5. Int16 v = (Int16) s; // Explicit cast from Single to Int16
  • 只有在類型安全的時候(不會發生數據丟失),C#才允許隱式轉換。
  • 不同編譯器可能生成不同代碼來處理轉型。如將6.8的 Single 類型轉換為 Int32 時,有的編譯器可能截斷(向下取整),有的則可能轉換為7(向上取整)。C# 總是對結果截斷(向下取整)
  • 基本類型可以被寫成字面值(literal),字面值為類型本身的實例。所以可以對其使用實例方法:
    Console.WriteLine(123.ToString() + 456.ToString()); // "123456"
  • 如果表達式由字面值構成,編譯器在編譯時就能完成表達式的求值。
  1. Boolean found = false; // Generated code sets found to 0
  2. Int32 x = 100 + 20 + 3; // Generated code sets x to 123
  3. String s = "a " + "bc"; // Generated code sets s to "a bc"

checked 和 unchecked 基元類型操作

  • 基元類型執行的許多算數運算都可能造成溢出:
  1. Byte b = 100;
  2. b = (Byte)(b + 200);
  • 執行上述運算時,第一步把操作數擴大為32位(或64位),計算結果,在存回變量 b 之前必須轉型為 Byte。
  • 不同語言處理移除的方式不同。 C 和 C++ 不將溢出視為操作,允許值回滾(wrap), 而 VB 總是將溢出視為錯誤。
  • CLR 提供了一些特殊的 IL 指令,允許編譯器選擇它認為最恰當的行為。 CLR 的 加、減、乘、除都提供了相關指令:add/add.ovf,sub/sub.ovf,mul/mul.ovf,conv/conv.ovf。帶.ovf表示異常時會拋出 System.OverflowException異常,不帶.ovf 則表示不會執行溢出檢查。
  • C# 默認關閉溢出檢查。
  • C# 編譯器使用 /checked+ 編譯開關。開啟時生成的代碼在執行時會稍微慢一些,因為需要檢查計算結果。
  • C# 提供 checked 和 unchecked 操作符來控制代碼指定區域的溢出檢查。
  1. Uint32 invalid = unchecked((Uint32) (-1)); // OK
  2. Byte b = 100;
  3. b = checked((Byte) (b + 200)); // OverflowException is thrown
  4. b = (Byte) checked(b + 200); // b contains 44; no OverflowException
  • 最後一個例子, b + 200 首先會轉換為32位值, 所以不會被檢測為溢出。
  • C# 支持checked 和 unchecked 語句,它們語句塊內的表達式都進行/不進行溢出檢查。
  1. checked
  2. { // Start of checked block
  3. Byte b = 100;
  4. b += 200; // This expression is checked for overflow.
  5. } // End of checked block
  • checked 操作符和 checked 語句的唯一作用是決定生成哪個版本的加、減、乘、除和數據轉換IL指令,所以在 checked 操作符和語句中調用方法,不會對該方法造成任何影響。
  1. checked
  2. {
  3. // Assume SomeMethod tries to load 400 into a Byte.
  4. SomeMethod(400);
  5. // SomeMethod might or might not throw an OverflowException.
  6. // It would if SomeMethod were compiled with checked instructions.
  7. }
  • 一些建議:
    1. 盡量使用有符號數(如 Int32,Int64)而不是無符號數,這允許編譯器檢測更多的上溢/下溢錯誤。許多類庫(Array 和 String 的Length 屬性)被硬編碼為返回有符號值,這樣可以減少類型轉換。無符號數不符合CLS(公共語言規範)。
    2. 在寫代碼時,不希望溢出的部分放到 checked 塊中,同時捕獲 OverflowException,並做相應處理。
    3. 寫代碼時,將允許發生溢出的代碼顯示的放到 unchecked 塊中,如計算校驗和。
    4. 對於沒有使用 checked 和 unchecked 的代碼,都假定希望在溢出時產生異常,視溢出為bug。
  • 開發程序時,打開編譯開關/checked+,檢測異常,在發布時應用編譯器的/checked-,可以確保運行效率。
  • 如果在效率允許的情況下,開啟/checked+,防止數據損壞。
  • System.Decimal 被許多語言(如 C# 和 VB)認為是基元類型,但是 CLR 不把它當做基元類型。CLR 沒有處理 Decimal 的 IL 指令。編譯器會生成代碼來調用 Decimal 的成員,並通過這些成員來進行運算,這意味著 Decimal 值的處理速度慢於 CLR 基元類型的值。
  • 由於 CLR 沒有處理 Decimal 的 IL 指令,所以checked 和 unchecked 對其無效,如果產生溢出,一定會拋出 OverflowException。


補充

  • 下列操作受溢出檢查的影響:
    • 表達式在整型上使用下列預定義運算符:
      ++ — -(一元) + - * /
    • 整型間的顯式數字轉換。
  • checked unchecked IL 測試:
  1. static void CheckUncheckTest()
  2. {
  3. byte a = 100;
  4. a = (byte)( a + 200 );
  5. a = 100;
  6. checked
  7. {
  8. a = (byte)( a + 200 ); //拋出異常
  9. }
  10. }

技術分享

  • Vs 對 checked 編譯選項開啟關閉設置:屬性->生成->高級->檢查運算上溢/下溢
    技術分享

5.1 編程語言的基元類型