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();
        }
    }
}

下图为代码执行时的内存分配情况:
这里写图片描述

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