C# 基元類型 引用類型和值類型

NET的所有類型都是由基類System.Object繼承過來的,包括最常用的基礎類型:int, byte, short,bool等等,就是說所有的事物都是對象。如果申明這些類型得時候都在堆(HEAP)中分配內存,會造成極低的效率!(箇中原因以及關於堆和棧得區別會在另一篇裏單獨得說說!)
.NET如何解決這個問題得了?正是通過將類型分成值型(value)和引用型(regerencetype)。
C#中定義的值類型包括原類型(Sbyte、Byte、Short、Ushort、Int、Uint、Long、Ulong、Char、Float、Double、Bool、Decimal)、枚舉(enum)、結構(struct)。
引用類型包括:類、數組、接口、委託、字符串等。

1. 基元類型
編譯器直接支持的數據類型爲基元類型。基元類型直接映射到Framework類庫(FCL)中存在的類型。例如:C#的int直接映射到System.Int32類型。

static void Main(string[] args)
{
    int a = 0; //最方便的語法
    System.Int32 b = 0; //方便的語法
    int c = new int(); //不方便的語法
    System.Int32 d = new System.Int32(); //最不方便的語法
}

下表列出了FCL類型在C#中都有的基元類型。
這裏寫圖片描述
也可以認爲C#編譯器自動假定所有源代碼文件都添加了以下using指令:
using sbyte = System.SByte;
using byte = System.Byte;
using short = System.Int16;
using int = System.Int32;

check和unchecked基元類型操作
checked和unchecked操作符用於整型算術運算時控制當前環境中的溢出檢查。

下列運算參與了checked和unchecked檢查(操作數均爲整數):
1) 預定義的++和――一元運算符。
2) 預定義的-一元運算符。
3) 預定義的+、-、×、/等二元操作符。
4) 從一種整型到另一種整型的顯示數據轉換。

當上述整型運算產生一個目標類型無法表示的大數時,可以有相應的處理方式:
(一)使用checked
若運算是常量表達式,則產生編譯錯誤:The operation overflows at complie time in checked mode.
若運算是非常量表達式,則運行時會拋出一個溢出異常:OverFlowException異常
(二)使用unchecked
無論運算是否是常量表達式,都沒有編譯錯誤或是運行時異常發生,只是返回值被截掉不符合目標類型的高位。
(三)既未使用checked又未使用unchecked
若運算是常量表達式,默認情況下總是進行溢出檢查,同使用checked一樣,會無法通過編譯。
若運算是非常量表達式,則是否進行溢出檢查,取決於外部因素,包括編譯器狀態、執行環境參數等。

    class Program
    {
        static byte byVal1 = 200;
        static byte byVal2 = 100;

        static void Main(string[] args)
        {
            byte byNum = (Byte)(byVal1 + byVal2); // 賴於編譯器的默認設置,一般是不檢查

            try
            {
                byNum = checked((Byte)(byVal1 * byVal2)); // 運行時拋出OverFlowException異常
            }
            catch (System.Exception ex)
            {
                Console.WriteLine("Throw the exception!");
            }

            byNum = unchecked((Byte)(byVal1 * byVal2)); // 截去高位部分依

        }
    }

    運行結果:
    44
    Throw the exception!
    32

2. 引用類型和值類型
CLR支持兩種類型:引用類型和值類型。雖然FCL的大多數類型都是引用類型,但程序員用的最多的還是值類型。

引用類型總是從託管堆分配,C#的new操作符返回對象內存地址—即指向對象數據的內存地址。
使用引用類型必須留意性能問題。首先要認清以下四個事實。
(1)內存必須從託管堆中分配;
(2)堆上分配的每個對象都有一些額外成員,這些額外的成員必須初始化;
(3)對象中的其他字節(位字段而設)總是設爲0;
(4)從託管堆分配對象時,可能強制執行一次垃圾回收。

值類型的實例一般在線程棧上分配。代表值類型實例的變量中不包含指向實例的指針,相反,變量中包含了實例本身的字段。由於變量已包含實例的字段,所以操作實例中的字段不需要提領指針。值類型的實例不受垃圾回收器的控制。(託管堆對應於垃圾回收。)因此,值類型的使用緩解了託管堆的壓力,並減少了應用程序的生存期內的垃圾回收次數。

在查看類型時,任何稱爲“類”的類型都是引用類型。例如,System.Exception類、System.IO.FileStream類以及System.Random類都是引用類型。相反,所有值類型都稱爲結構和枚舉。例如,System.Int32結構、System.Boolean結構、System.Decimal結構、System.TimeSpan結構、System.DayOfWeek枚舉、System.IO.FileAttributes枚舉以及System.Drawing.FontStyle枚舉都是值類型。

下面的代碼演示了引用類型和值類型的區別:

namespace ConsoleApplicationTest
{
    // 引用類型(因爲“class”)
    class SomeRef { public Int32 x; }

    // 值類型(因爲“struct”)
    struct SomeVal { public Int32 x; }

    class Program
    {
        static void Main(string[] args)
        {
            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

            Console.ReadLine();
        }
    }
}

下圖爲代碼執行時的內存分配情況:
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章