基元型別 引用型別和值型別

2022-01-16 00:21:14 字數 4357 閱讀 6139

編譯器直接支援的資料型別稱為基元型別(primitive type)。基元型別直接對映到framework類庫(fcl)中存在的型別。

fcl型別在c#中對應的基元型別:

c#基元型別

fcl型別

是否符合cls

說明

sbyte

system.sbyte

否 有符號8位值

byte

system.byte

是 無符號8位值

short

system.int16

是 有符號16位值

ushort

system.uint16

否 無符號16位值

int

system.int32

是 有符號32位值

uint

system.uint32

否 無符號32位值

long

system.int64

是 有符號64位值

ulong

system.uint64

否 無符號64位值

char

system.char

是 16位unicode字元

float

system.single

是 ieee32位浮點值

double

system.double

是 ieee64位浮點值

bool

system.boolean

是 true/false值

decimal

system.decimal

是 128位高精度浮點值,常用於不容許捨入誤差的金融計算

string

system.string

是 字元陣列

object

system.object

是 所有型別的基型別

dynamic

system.object

是 對於clr,dynamic和object完全一致。但c#編譯器允許使用簡單的語法讓dynamic變數參與動態排程

從某個角度來說,可以認為c#編譯器自動為所有源**檔案新增using指令為型別建立別名。

using int = system.int32;
說到這裡應該就可以理解string和string的關係了,c#的string(關鍵字)直接對映到system.string(型別),所以兩者實際上沒有區別。

對基元型別執行算術運算時可能造成溢位。不同語言處理溢位的方式不同,c/c++不將溢位視為錯誤,允許值回滾;microsoft visual basic則將溢位視為錯誤,並在檢測到溢位時丟擲異常。而c#允許程式設計師自己決定如何處理溢位,溢位檢查預設關閉。下面介紹兩種方式開啟或關閉溢位檢查:

特定區域控制溢位檢查

c#通過checked和unchecked操作符在**的特定區域進行溢位檢查。

byte b = 100;

b = checked((byte)(b + 200)); //丟擲overflowexception異常

c#也支援checked和unchecked語句塊,對語句塊中的所有語句都進行或不進行溢位檢查

checked

全域性性控制溢位檢查

只需開啟編譯器的/checked+開關,系統就會對沒有顯示標記checked或unchecked的**進行溢位檢查(當然這種情況下應用程式執行起來會慢一些)。要在visual studio中更改checked設定,開啟專案屬性,按照以下步驟即可找到開關:properties|build|advanced|check for arithmetic overflow/underflow

##引用型別和值型別

記憶體必須從託管堆分配。

堆上分配的每個物件都有一些額外成員(型別物件及同步快索引),這些成員必須初始化。

從託管堆分配物件時,可能強制執行一次垃圾**。

當然,也不是所有的情況下值型別都可以提高程式效能,甚至在某些特定的情況下會對效能造成損害。例如當實參以值型別方式傳遞或方法返回乙個值型別時,例項中的字段會複製到呼叫者分配的記憶體中,若例項較大,便會對應用程式的效能造成影響。

許多時候,都需要獲取對值型別例項的引用。首先,我們來看一段**:

arraylist arr = new arraylist();

for (int i = 0; i < 10; i++)

這段**很簡單,迴圈10次向arraylist中新增int型別變數i。現在我們考慮一下,arraylist中儲存的究竟是什麼?要搞清楚這個問題,先看一下add方法的源**:

public virtual int add(object value)
注意這裡的引數型別是object型別,也就是說add方法獲取對託管堆上的乙個物件的引用來作為引數。

所以,為了使**正常工作,int值型別必須轉換成託管堆中的物件,而且要獲得該物件的引用。將值型別轉換為引用型別要使用裝箱操作。以下,是對值型別的例項進行裝箱操作的過程:

在託管堆上分配記憶體。包括值型別各字段所需的記憶體量,還要加上型別物件指標和同步塊索引的記憶體量。

將值型別的字段複製到新分配的堆記憶體。

返回物件位址。

這時我們再回到開頭的**,c#編譯器檢測到向要求引用型別的方法傳遞值型別,所以自動生成**對物件進行裝箱操作。int值型別例項i的字段複製到新分配的int物件中,並將位址返回給add方法。

int.tostring()方法不進行裝箱操作

以下是官方文件中對裝箱的描述,int.tostring()方法顯然不滿足裝箱的條件。

裝箱用於在垃圾**堆中儲存值型別。 裝箱是值型別到 object 型別或到此值型別所實現的任何介面型別的隱式轉換。 對值型別裝箱會在堆中分配乙個物件例項,並將該值複製到新的物件中。 ——裝箱和取消裝箱

了解了裝箱操作後,接著談談拆箱,假設要獲取arraylist中的第乙個元素。

int x = (int)arr[0];
獲取已裝箱的int物件的位址。這個過程稱為拆箱

將字段包含的值複製到執行緒棧的值型別例項中。

注意:對物件進行拆箱時,只能轉型為最初未裝箱的值型別

int32 x = 5;

object o = x;

//int16 y = (int16)o;

int16 y = (int16)(int32)o; //ok

##dynamic基元型別

在大多數情況下,dynamic型別與object型別的行為類似。如果操作包括dynamic型別的表示式,那麼不會通過編譯器對該操作進行解析或型別檢查。編譯器將有關操作資訊打包在一起,之後這些資訊會用於在執行**估操作。在此過程中,dynamic型別的變數會編譯為object型別的變數。因此,dynamic型別只在編譯時存在,在執行時則不存在。

請看以下示例**:

dynamic d;

int x = 5;

d = x + x;

console.writeline(d); //10

string s = "5";

d = s + s;

console.writeline(d); //"55"

注意,所有的表示式都能隱式轉型為dynamic,因為所有表示式最終都生成從object派生的型別。正常情況下,編譯器不允許將表示式從object型別隱式轉換為其他型別,但編譯器允許使用隱式轉型將表示式從dynamic轉型為其他型別

object o = 10;

//int i = o; //error:cannot implicitly convert type 'object' to 'int'.

int i = (int)o; //ok

dynamic d = 10;

int j = d; //ok

CLR 基元型別 引用型別和值型別

前言 今天重新看了下關於clr基元型別的東西,覺得還是有必要將其記錄下來,畢竟這是理解clr成功 之路上的重要一步,希望你也和我一樣。基元型別 編譯器直接支援的資料型別稱之為基元型別,針對那些程式設計師自定義的型別而言。所有基元型別 直接對映到fcl framework class library ...

第五章 基元型別引用型別和值型別

checked開啟時,如果發生溢位會丟擲異常,unchecked則不會排除異常。編譯器預設是關閉溢位檢查的unchecked。若要開啟溢位檢查,使用 checked 在vs的專案屬性中也可設定開啟與否。也可以給一段 新增這樣的標記。如果這段 中呼叫了另外乙個方法,這個方法是不受這個標記控制的。sys...

第5章 基元型別 引用型別與值型別 (2)

所有的值型別都繼承自system.valuetype,而system.valuetype繼承自system.object。它重寫了system.object中的equals方法和gethashcode方法。當定義自己的值型別時我們也應重寫equals方法和gethashcode方法,為它們提供乙個顯...