(CLR via C#閱讀筆記) 基元類型(primitive type): 基元類型也不做過多的解釋,舉個例子即可清晰的辨別 在java里曾使用過Sting s="java"; 定義字元串,然後就會覺得很詫異,為啥是大寫開頭,我寫C#,一直都是 string ,int ,double,float等 ...
=========(CLR via C#閱讀筆記)========
基元類型(primitive type):
基元類型也不做過多的解釋,舉個例子即可清晰的辨別
在java里曾使用過Sting s="java"; 定義字元串,然後就會覺得很詫異,為啥是大寫開頭,我寫C#,一直都是 string ,int ,double,float等等小寫開頭,這時候,來瞭解基元類型方可解惑。
1 int a=0;
2 System.Int32 a=0;
3 int a=new int();
4 System.Int32 a=new System.Int32();
以上定義int類型的語法,都是能正確編譯與運行的,其中第一種最方便簡潔,那麼這種方便簡潔的int,string,byte..decimal,object等等都稱作C#的基元類型,對應FCL類型則為System.Int32(其它依次類推,註意是大寫開頭)。
以上,基元類型和FCL類型效果完全一致。
基元類型轉換可能造成精度或數量級的丟失:
然後下麵談到,基元類型中的一些轉換問題:
1 int i=5; //System.Int32
2 long j=i; //System.Int64
上述代碼能夠正確的完成隱式轉換,也支持一些字面值的轉換。
但是將代碼調換一下:
1 1 longi=5; //System.Int64
2 2 int j=i; //System.Int32
這個時候是不安全的轉換,因為long 可以容納更長的數字,如果long對象的值超過了int,那麼會發生丟失精度或數量級,這個時候必須使用顯示轉換。
但是即使做了顯示轉換,也無法避免誤差的產生:
對數據轉換造成誤差做了一個小小的測試:
得出結論,在超過int(-2,147,483,648 到 2,147,483,647)範圍後,int會從頭在依次計算,比如比範圍多1,就會從最大值回到開頭,多2,就會往後再數一個
而浮點型,不論是float還是double都是直接截取整數位不會進行四捨五入(有些語言不是截取)。
checkd和uncheckd基元類操作
作用:
一些元算可能會有溢出發生,例如
1 byte b=100; //無符號八位值,範圍0-255
2 b=(byte)(b+200); //溢出得到值 (100+200)-255-1=44
如果,你覺得這種溢出是非法的不可接受的,那麼可以加上checked:
幾種常見用法:
checked語句對指定的代碼塊進行檢查:
1 //unchecked寫法亦然,但是表示不對溢出進行檢查 2 checked 3 { 4 byte b=100; //無符號八位值,範圍0-255 5 b=(byte)(b+200); //溢出得到值 (100+200)-255-1=44 6 }
需要註意的是,checked操作符僅僅對塊裡面進行的運算表達式生效,如果你放一個方法進去,是不會有任何效果的。
使用checked操作符:
1 b=checked((byte)(b+200));
2 b=(byte)checked(b+200); //兩種寫法都可以
這時候,再發生溢出的時候會拋出System.OverflowException: 算術運算導致溢出。
當然在極少數時候,異常是允許的,甚至是希望的,比如:計算哈希值或者校驗和。
checked和unchecked到底是啥?
引申:CLR提供了一些特殊的IL指令,允許編譯器選擇它認為最恰當的行為。CLR有一個add指令,作用是將兩個值相加,但不執行溢出的檢查。還有一個add.ovf指令,作用也是將兩個值相加,但會發生溢出時拋出System.OverflowException,當然減乘除也有對應方法,分別是sub/sub.ovf,mul/mul.ovf和conv/conv.ovf。而上面說到的checked和unchecked則是C#層面上提供的編譯器開關,自然用來指定編譯器下對應的指令(上面提到的IL指令)。
需要註意的是,使用了/checked+(/check-)編譯器開關,會使代碼執行變得稍慢,因為CLR會進行對應的語句檢查。
如何有效的避免對基元類型的不良操作和編碼:
- 儘量使用有符合數值類型,這允許編譯器檢測更多的上溢/下溢錯誤(有符號意味著區分正負,無符號是沒有負數的),也會減少可能的強制類型轉換,另外無符號數值不符合CLS(Common Language Standard)。
- 如果代碼可能發生你不希望的溢出(比如錯誤的輸入造成的),就把這些代碼放到checked塊中,並捕捉異常。
- 將允許溢出的代碼塊放到unchecked塊中
- 沒有上述檢查操作符的,意味著溢出是bug
- 開發環境最好,打開編譯器的/checked+開關(生成-高級),儘可能暴露問題,發佈時應使用/checked-開關,確保代碼更快的運行(當然如果能夠對檢查要求嚴格,又能接受帶來的性能損失也可以打開開發來編譯,可以防止應用程式在包含已損壞的數據的前提下運行)
特別的地方:
- System.Decimal雖然在C#中是基元類型,但是在CLR中並不是,意味著沒有對應的IL指令,它使用int,float,double,uint等數組來表示範圍內的大小,如圖:
- 可以分析出:1.操作符“+”,“-”...對於decimal其實是無效的,但是我們不僅可以對他進行操作符的基本運算,也可以用提供的Add...等方法進行運算,因為它內部其實是對操作符進行了重載,且在運算不安全時是會自動拋出OverFlowException的異常的。2.checked 和 unchecked無效,它無需cheked操作符來檢查拋出異常,也無法unchecked阻止異常拋出。
- System.Numerics.BigInteger類型類似Decimal,也使用數組來表示任意大的數,但是它如何計算都不會溢出,只有在記憶體不足以改變數組大小時才會拋出異常OutMemoryException。
上述有更為詳細的demo,效果如下:
Github地址:
https://github.com/JOJOJOFran/CLR-Via-C--TEST/tree/master/Design_Type/Primitive%20Type
有啥問題歡迎指正!