zoukankan      html  css  js  c++  java
  • Chapter5 基元类型、引用类型和值类型

    1.1 编程语言的基元类型

    下面给个C#基元类型与对应的FCL类型
    表格:

    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,和object完全一致


    讨论点:使用FCL类型还是用基元类型
    C#语言规范称:从风格上说,最好使用关键字(基元类型)。
    作者赞同:我情愿使用FCL类型名称,完全避免使用基元类型名称。
    作者的理由:
    1.例如:有些程序员认为,int在32位的机器上代表32位整数,在64位的机器上代表64位整数。其实呢这是跟操作系统无关的,int始终映射到System.Int32,如果使用Int32一目了然,就不会产生那样的误解了。
    2.例如:在C#中,long映射到System.Int64,而在C++中long视为一个Int32,甚至大多数语言都不将long看作一个关键字。
    --其实我也是赞同作者的观点。
    checked和unchecked基元类型操作
    都是溢出给逼得,控制溢出的一个办法使用/checked+编译器开关,如果发生溢出,抛出OverflowException异常
    除了全局性的打开和关闭溢出检查,C#提供了checked和unchecked来对特定代码溢出检查,类似于try catch块。
    作者建议:
    1.尽量使用有符号数值类型代替无符号数值类型(无符号数值类型是CLS不相容的)
    2.如果可能会发生不希望的溢出,把这些可能发生的代码放在checked块中

    1.2 引用类型和值类型

    CLR支持的两种类型:引用类型和值类型
    在使用引用类型时必须注意到一下问题:
    1.内存必须从托管堆中分配
    2.在堆上分配的每个对象都有些额外的成员,这些成员必须初始化
    3.对象的其他字节(为字段而设)总是设为零
    4.从托管堆上分配一个对象时,可能强制执行一次垃圾回收操作

    一下演示值类型与引用类型的区别:

    //引用类型
    class SomeRef { public Int32 x; }
    //值类型
    struct SomeVal { public Int32 x;}
    static void ValueTypeDemo()
    {
    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不变
    }
    

    1.3 值类型的装箱与拆箱

    装箱与拆箱的概念太一般了,总之值类型与引用类型之间互相转换就会引发装箱与拆箱行为,而这个过程对性能有负面影响
    例如:

    void Method()
    {
    Int32 i=0;
    Object o=i;
    Int32 j=(Int32)o;
    }	

    理解一下上述代码在内存中是如何变化的
    如图:

    解释装箱操作时内部发生的事情:
    1.在托管对中分配好内存,分配的内存量=值类型各个字段需要的内存量+托管堆中所有对象都有的两个额外成员(类型对象指针和同步索引块)需要的内存量
    2.值类型的字段复制到新分配的堆内存。
    3.返回对象的地址,现在这个地址是对一个对象的引用,值类型现在是个引用类型。
    解释拆箱操作时内部发生的事情:
    1.获取以装箱对象的各个字段的地址(如果“对已装箱值类型实例引用”的变量为null,就会抛出NullRefrenceException异常)
    2.将这些字段包含的值从堆中复制到基于栈的值类型实例中

    未装箱的值类型是比引用类型更“轻”的类型,归于以下两个原因:
    1.它们不再托管堆上分配。
    2.它们没有堆上的每个对象都有的额外成员,也就是类型对象指针与同步索引块。

    通过以下例子来验证对值类型、装箱和拆箱的理解程度

    //Point是个值类型
    internal struct Point
    {
    private Int32 m_x,m_y;
    public Point(Int32 x,Int32 y)
    {
    m_x=x;
    m_y=y;
    }
    public void Change(Int32 x,Int32 y)
    {
    m_x=x;
    m_y=y;
    }
    public override string ToString()
    {
    return String.Format("({0},{1})", m_x, m_y);
    }
    }
    
    static void Main(string[] args)
    {
    Point p = new Point(1, 1);
    //调用WriteLine之前会对p进行装箱,WriteLine会在已装箱的Point上调用ToString
    Console.WriteLine(p);//显示(1,1)
    //该方法将P在栈上的m_x和m_y字段的值都该为2
    p.Change(2, 2);
    //再次对p进行装箱,WriteLine会在已装箱的Point上调用ToString
    Console.WriteLine(p);//显示(2,2)
    
    //第三次装箱,将已装箱的p对象的引用赋予o
    Object o = p;
    Console.WriteLine(o);//显示(2,2)
    
    /*我们希望通过Change方法来更新已装箱的Point对象中的字段
    * 首先转型为一个Point,就是对o拆箱,
    * 并将已装箱的Point中的字段复制到线程栈上的一个临时Point中
    * 这个临时的Point的m_x和m_y字段会编程3和3
    * 但已装箱的Point不受这个影响,所以会再次显示(2,2)
    */
    ((Point)o).Change(3, 3);
    Console.WriteLine(o);//显示(2,2)
    }
    现在的问题所在是,我们不能更改已装箱值类型中的字段,不过我们可以使用接口来欺骗C#
    //接口定义了一个Change方法
    internal interface IChangeBoxedPoint
    {
    void Change(Int32 x, Int32 y);
    }
    //Point是个值类型
    internal struct Point:IChangeBoxedPoint
    {
    private Int32 m_x,m_y;
    public Point(Int32 x,Int32 y)
    {
    m_x=x;
    m_y=y;
    }
    public void Change(Int32 x,Int32 y)
    {
    m_x=x;
    m_y=y;
    }
    public override string ToString()
    {
    return String.Format("({0},{1})", m_x, m_y);
    }
    }	
    //接口方法如何修改一个已装箱的值类型中的字段
    static void Main(string[] args)
    {
    
    //对p进行装箱,更改它已装箱的对象,然后丢弃它
    ((IChangeBoxedPoint)p).Change(4, 4);
    Console.WriteLine(p); //显示(2,2)
    //更改已装箱的对象,并显示
    ((IChangeBoxedPoint)o).Change(5, 5);
    Console.WriteLine(o);//显示(5,5)
    }
    

    1.4对象哈希码

    能将任何实例放到一个哈希表集合中
    System.Object提供了虚方法GetHashCode,它能获取任意对象的Int32哈希码
    如果一个类型重写了Equals方法,那么还应重写GetHashCode方法


  • 相关阅读:
    python解决线性规划问题
    python解决多变量最优化问题
    python处理单变量优化
    java枚举类常用方法
    java实现十大排序算法
    python解方程
    python科学计算包
    python做微积分
    php定界符<<<EOF讲解
    一键安装LNMP/LAMP
  • 原文地址:https://www.cnblogs.com/hailiang2013/p/2859941.html
Copyright © 2011-2022 走看看