zoukankan      html  css  js  c++  java
  • 值类型

    老生常谈                                                                                                                                                   

    所有的类型可以划分为两类:值类型和引用类型。他们的区别在于复制策略的差异,后者又造成每种类型在内存中的存储位置不同。

    值类型

      值类型直接包含值。换句话说,变量引用的位置就是值在内存中实际存储的位置。因此,将一个变量的值赋值给另一个变量时,会在新变量的位置创建原始变量的值的一个内存副本。所以,更改第一个变量的值是不会影响第二个变量的值。将值类型作为参数传递给方法时,也会生成一个内存副本。

      值类型所需要的内存量会在编译时固定下来,且不会在运行时改变。

    引用类型

      引用类型存储的是对一个内存地址的引用,要去该位置才能找到真正的数据。因此,要访问数据,“运行时”要从变量中读取内存的位置,然后跳转到包含数据的位置。

      引用类型的访问需要一次额外的跳转,所以速度似乎会慢一些。然而,将一个引用类型的变量赋值给另一个引用类型的变量,只会多出地址的一个内存副本(32位处理器只需要一个32位的地址,64位处理器值需要一个64位的地址),它在内存的利用率上更好一些。在数据较大的情况下,由于不需要复制实际数据,所以引用类型比值类型更有效。

      除了string和object,C#基本类型都是值类型。

    一、结构

      定义一个自定义的值类型。

        internal struct Angle
        {
            public Angle(int hours, int minutes, int seconds)
            {
                _hours = hours;
                _minutes = minutes;
                _seconds = seconds;
            }
            public int Hours {
                get { return _hours;}
            }
            private readonly int _hours;
            public int Minutes {
                get { return _minutes; }
            }
            private readonly int _minutes;
            public int Seconds {
                get { return _seconds; }
            }
            private readonly int _seconds;
    
            public Angle Move(int hours, int minutes, int seconds)
            {
                return new Angle(Hours + hours, Minutes + minutes, Seconds + seconds);
            }
        }
    View Code

      虽然语言本身未做要求,但作为一个良好的习惯,我们应该确保值类型是不可变。换句话说,一段实例了一个值类型,那么这个实例就不能修改。如果要修改,应创建一个新的实例(如上面代码Move方法)。

    二、装箱

      值类型转换成引用类型的过程叫装箱(boxing),相反的过程称为拆箱(unboxing)。装箱过程:

      1.在堆中分配好内存。将用于存放值类型的数据及少许额外开销(一个SyncBlock-Index以及方法表指针)。

      2.接着发生一次内存复制动作,栈上值类型数据复制到堆上分配好的位置。

      3.最后,引用类型对象引用得到更新,指向堆上的位置。

      不允许在locak()语句中使用值类型

      lock语句:用于同步代码。实际会编译为System.Threading.Monitor的Enter()和Exit()方法。这两个方法必须成对调用。

            Enter()记录由其唯一的引用型参数传递的一个lock,这样,使用相同的引用调用Exit()的时候,就可以释放该lock。使用值类型的问题在于装箱,每次Enter()和Exit()时,都会在堆上创建一个新值。将一个副本的引用和另一个不同副本的引用进行比较,总会返回false。所以无法将Enter()与对应的Exit()钩到一起。

      避免拆箱

        拆箱指令不包括将数据复制回栈的动作。有些语言可以直接访问堆上的值类型,但在C#中,只有当值类型做为引用类型的一个字段访问时才可能这样。

        由于接口是引用类型,所以通过接口访问已装箱的值时,拆箱和复制是可避免的。

        int number=12;

        object o;

        //boxing

        o=number;

        //避免拆箱

        string text=((IFormattable)o).ToString();

        当调用值类型的接口方法时,实例必须是一个变量,因为方法可能会改变值。由于拆箱会生成一个托管地址,“运行时”就会有一个存储位置和一个变量。结果,运行时值传递接口的托管地址,并不需要拆箱操作。

        除此之外,调用一个struct 的ToString()方法(它重写object的ToString()方法),也不需要执行unboxing。在编译时,显然会调用struct重写的ToString()方法,因为所有值类型都是密封的。

    三、枚举

      枚举的基础类型不能是char.

      枚举之间的类型兼容性

        C#不支持两个不同枚举数组的直接转型,办法是先转型为一个数组,在转型为第二个枚举。

  • 相关阅读:
    使用URLEncoder、URLDecoder进行URL参数的转码与解码
    js中的连等==和全等===
    sass用法
    sass/scss 和 less的区别
    JSON与localStorage的爱恨情仇
    ionic2
    雪碧图
    JavaScript交换两个变量值的七种解决方案
    JavaScript中的[]和{}
    为什么 ++[[]][+[]]+[+[]] = 10?
  • 原文地址:https://www.cnblogs.com/zhangxiaohui/p/4165180.html
Copyright © 2011-2022 走看看