5.1 編程語言的基元類型
阿新 • • 發佈:2017-06-22
屬性 long 測試 byte 發生 代碼 arr dsi 算數運算
- 編譯器直接支持的數據類型成為基元類型(primitive type)。基元類型直接映射到 Framework類庫(FCL)中存在的類型。
int a = 0; // Most convenient syntax
System.Int32 a = 0; // Convenient syntax
int a = new int(); // Inconvenient syntax
System.Int32 a = new System.Int32(); // Most inconvenient syntax
上述4行代碼都能正確編譯,並且生成相同的IL。
作者不支持使用 FCL 類型名稱,原因如下:
- 開發人員會糾結使用 string 還是 String,string 實際上是直接映射到 System.String 。不知道 int 是指 Int32 還是 Int64。int 映射到 System.Int32,與操作系統無關。
- C# long 映射島 System.Int64,但在其他編程語言中,long 可能映射到 Int16 或 Int32。(C++/CLI 將long 視為Int32)許多語言不將long 作為關鍵字。
- FCL 的許多方法都將類型名作為方法名的一部分,如ReadBoolean,ReadInt32,ReadSingle 等。
- 只是用C#的許多程序員忘掉還可以用其他語言寫面向CLR的代碼。如 Array的 GetLongLength 返回 Int64 的值,在C#中為long,但是在其他語言中(C++/CLI)中卻不是。
//第3條示例
BinaryReader br = new BinaryReader
(...);float val = br.ReadSingle(); // OK, but feels unnatural
Single val = br.ReadSingle(); // OK and feels good
- 不同類型,並且沒有繼承關系,正常情況下是不能直接轉換的。但是 Int32 就可以隱式轉換為 Int64。 因為C#編譯器非常熟悉基元類型,會在編譯代碼時應用自己的特殊規則。示例:
Int32 i = 5; // Implicit cast from Int32 to Int32
Int64 l = i; // Implicit cast from Int32 to Int64
Single
s = i; // Implicit cast from Int32 to SingleByte b = (Byte) i; // Explicit cast from Int32 to Byte
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"
- 如果表達式由字面值構成,編譯器在編譯時就能完成表達式的求值。
Boolean found = false; // Generated code sets found to 0
Int32 x = 100 + 20 + 3; // Generated code sets x to 123
String s = "a " + "bc"; // Generated code sets s to "a bc"
checked 和 unchecked 基元類型操作
- 基元類型執行的許多算數運算都可能造成溢出:
Byte b = 100;
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 操作符來控制代碼指定區域的溢出檢查。
Uint32 invalid = unchecked((Uint32) (-1)); // OK
Byte b = 100;
b = checked((Byte) (b + 200)); // OverflowException is thrown
b = (Byte) checked(b + 200); // b contains 44; no OverflowException
- 最後一個例子, b + 200 首先會轉換為32位值, 所以不會被檢測為溢出。
- C# 支持checked 和 unchecked 語句,它們語句塊內的表達式都進行/不進行溢出檢查。
checked
{ // Start of checked block
Byte b = 100;
b += 200; // This expression is checked for overflow.
} // End of checked block
- checked 操作符和 checked 語句的唯一作用是決定生成哪個版本的加、減、乘、除和數據轉換IL指令,所以在 checked 操作符和語句中調用方法,不會對該方法造成任何影響。
checked
{
// Assume SomeMethod tries to load 400 into a Byte.
SomeMethod(400);
// SomeMethod might or might not throw an OverflowException.
// It would if SomeMethod were compiled with checked instructions.
}
- 一些建議:
- 盡量使用有符號數(如 Int32,Int64)而不是無符號數,這允許編譯器檢測更多的上溢/下溢錯誤。許多類庫(Array 和 String 的Length 屬性)被硬編碼為返回有符號值,這樣可以減少類型轉換。無符號數不符合CLS(公共語言規範)。
- 在寫代碼時,不希望溢出的部分放到 checked 塊中,同時捕獲 OverflowException,並做相應處理。
- 寫代碼時,將允許發生溢出的代碼顯示的放到 unchecked 塊中,如計算校驗和。
- 對於沒有使用 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 測試:
static void CheckUncheckTest()
{
byte a = 100;
a = (byte)( a + 200 );
a = 100;
checked
{
a = (byte)( a + 200 ); //拋出異常
}
}
- Vs 對 checked 編譯選項開啟關閉設置:屬性->生成->高級->檢查運算上溢/下溢
5.1 編程語言的基元類型