深入理解类型

什么是值类型和引用类型

  • 值类型在线程栈分配空间,引用类型在托管堆分配空间

    值类型与引用类型的区别

  • 两类型的数据存储位置不同
  • 在引用类型中嵌套值类型时,或者在值类型装箱的情况下,值类型的实例就会被分配到托管堆上
  • 值类型继承自ValueType,ValueType又继承自System.Object;而引用类型则直接继承于System.Object;
  • 值类型的内存不受GC控制,作用域结束时,值类型会被操作系统自行释放
  • 若值类型为密封的(sealed),你将不能把值类型作为其他任何类型的基类;而引用类型则一般具有继承性,这里是指接口和类;
  • 值类型不能为null值,它会被默认初始化为该值类型的默认值;而引用类型默认会初始化为null值,表示不指向托管堆中的任何地址;对值为null的引用类型的任何操作都会引发NullRefernceException异常
  • 由于值类型变量包含其实际数据,因此默认情况下,值类型之间的参数传递不会影响变量本身;而引用类型变量保存的是数据的引用地址,它们作为参数传递时,参数会发生改变,从而影响引用类型变量的值

几种类型嵌套情况

  • 引用类型中嵌套定义值类型
    • 如果类的字段类型是值类型,它将作为引用类型实例的一部分,被分配到托管堆中,但那些作为局部变量(如下代码中的c变量)的值类型,则仍然会被分配到线程堆栈中.
    • 代码示例
    //引用类型嵌套定义值类型的情况
    public class NestedValueTypeInRef
    {
        //valuetype作为引用类型的一部分被分配到托管堆上
        private int valuetype = 3;
        public void method()
        {
            //C被分配到线程堆栈上
            char c = 'c';
        }
    }
    class Program
    {
    
        static void Main(string[] args)
        {
    
            NestedValueTypeInRef typeInRef = new NestedValueTypeInRef();
        }
    }
    • 示例图
    引用嵌套值类型.png
  • 值类型中嵌套定义引用类型
    • 堆栈上将保存该引用类型的引用,而实际的数据则依然保存在托管堆中
    • 代码示例
     public class TestClass
    {
        public int x;
        public int y;
    }
    class Program
    {
        //值类型嵌套定义引用类型的情况
        public struct NestedRefTypeInValue
        {
            //结构体字段,注意,结构中的字段不能被初始化
            private TestClass classinValuetype;
    
            //结构体中的构造函数,注意,结构体中不能定义无参的构造函数
    
            public NestedRefTypeInValue(TestClass t)
            {
                if (t==null)
                {
                    throw new ArgumentNullException("t");
                }
                classinValuetype = t;
                classinValuetype.x = 3;
                classinValuetype.x = 5;
    
            }
        }
        static void Main(string[] args)
        {
            //值类型变量
            NestedRefTypeInValue typeInValue = new NestedRefTypeInValue(new TestClass());
        }
    }
    • 示例图
    值类型嵌套引用类型.png

两大类型间的转换------装箱与拆箱

  • 值类型转换为引用类型称为装箱,引用类型转换为值类型称为拆箱

  • 隐式转换;由低级别类型向高级别类型的转换过程(例如子类隐式转换为父类)
  • 显式转换(也称为强制类型转换);这种转换可能会导致精度损失或者出现运行时异常;
    • 转换格式
    (type)(变量,或函数);
  • 通过isas运算符进行安全的类型转换;

  • 示例从内存角度对装箱,拆箱进行深入分析
    • 代码示例
    int i=3;
    //装箱
    object o=i;
    //拆箱
    int y=(int)o;
    • 装箱步骤:
      • 1,内存分配:在托管堆中分配好内存空间以存放复制的实际数据;
      • 2,完成实际数据的复制:将值类型实例的实际数据复制到新分配的内存中
      • 3,地址返回:将托管堆中的对象地址返回给引用类型的变量
    • 装箱示例图
      装箱过程.png
    • 拆箱步骤:
      • 1,检查实例:首先检查要进行拆箱操作的引用类型变量是否为null,如果为null则抛出异常;反之则检查变量是否和拆箱后的类型是同一类型,若为否,会导致InvalidCasetException异常;
      • 2,地址返回:返回已装箱的实际数据部分的地址
      • 3,数据复制:将托管堆中的实际数据复制到栈中
    • 拆箱示例图
      装箱过程.png

      参数传递问题剖析

      参数可分为形参和实参两种.形参指的是被调用方法中的参数,也就是方法中定义的参数;实参指的是调用方法时,传递给对应参数的值;
      ```
      static void Main(string[] args)
      {
      int addNum=1;
      //addNum 就是实参
      Add(addNum);
      }
      //addnum就是形参
      private static void Add(int addnum)
      {
      ........
      }

``- 值类型参数的按值传递 - 传递的是该值类型实例的一个副本,因此方法中对参数的改变不会影响到实参 - 引用类型参数的按值传递 - 当传递的参数是引用类型时,传递和操作的目标是指向对象的地址,而传递的内容是对象地址的复制.由于地址指向的是实参的值,方法对地址进行操作时,实际上操作了地址所指向的值,所以调用方法后原来实参的值就会被修改 -string具有不可变性,因此不会改变; 这是一特殊情况 - 值类型,引用类型参数的按引用传递 - 使用ref,out`关键字来实现参数的传递都是引用传递

猜你喜欢

转载自www.cnblogs.com/youMo/p/10360404.html