C#的值类型和引用类型解析

18 篇文章 0 订阅
11 篇文章 1 订阅

一、引用类型与值类型

值类型:直接存储其值,派生自System.ValueType,部署在栈上。值类型不包含null,值类型在声明后,不管是否已经赋值,编译器会为其分配内存。值类型通常在线程栈上分配(静态分配)。
引用类型:存储其值的引用,派生自Object类,部署在堆上。引用类型可以使用null,当声明引用类型时,只会在栈上分配一小片内存,用于存放一个地址。当进行实例化的时候(new)会在堆上分配空间,并会把堆上空间的地址存储到声明时开辟的那个栈上的空间中(动态分配)



二、常见的值类型引用类型

值类型:byte,sbyte,short,int,long,float,double,decimal,char,uint,ushort,ulong,bool ,枚举类型,用户定义的结构体struct

引用类型:class、delegate、dynamic、interface、object(Object)、string(String)、内插字符串(这个样子的:$"Name = {name}, hours = {hours:hh}")。


特殊情况:

(1)数组。数组是引用类型,数组的对象在栈上。其每一个元素均分配在托管堆上。如果是值类型数组(int,float....)那么会在堆上对数组自动进行初始化。如果是引用类型数组,那么不会初始化任何元素。此时数组为null。

(2)嵌套类型:如果声明一个类,该类中包含值类型的字段,同时包含值类型的局部变量,那么它同样是引用类型。但是值类型字段和值类型局部变量位置是不同的。

例如:    

class A

    {

        int a;      //值类型字段

        public A()

        {

            int b;      //值类型局部变量

        }

}

字段跟随实例存储,所以值类型字段a存储在托管堆上。但是这里的b是局部变量,它是值类型,它在栈上。


三、注意事项

1.值类型测试

using System;

namespace ValueAndReference

{

    class Program

    {

        static void Main(string[] args)

        {

            int VT1, VT2;       //值类型变量VT1,VT2

            VT1 = 5;

            VT2 = VT1;

            VT1 = 6;

            Console.WriteLine("VT1 is " + VT1 +"  VT2 is " + VT2);      //传递的只是值

        }

    }

}

这个没啥可注意的,很正常。

 

2.引用类型测试(class)

using System;

namespace ValueAndReference

{

    //引用类型定义

    class ReferenceType

    {

        public int field;      //定义字段

        public ReferenceType(int v)       //构造函数,初始化field

        {

            field = v;

        }

    }

 

    class Program

    {

   

        static void Main(string[] args)

        {

            ReferenceType RT1 = new ReferenceType(5);        //定义并实例化引用类型对象RT1,初始字段值为5

            ReferenceType RT2 = RT1;

            Console.WriteLine("RT1 is " + RT1.field +",RT2 is " + RT2.field);        //输出RT1、RT2字段值,均为5

            RT2 = new ReferenceType(6);     //实例化RT2,并初始字段值为6

            Console.WriteLine("RT1 is " + RT1.field +",RT2 is " + RT2.field);        //输出RT1字段值为5、RT2字段值为6

 

            ReferenceType RT3 = new ReferenceType(5);        //定义并实例化引用类型对象RT3,初始字段值为5

            ReferenceType RT4 = RT3;

            Console.WriteLine("RT3 is "+RT3.field+",RT4 is "+RT4.field);        //输出RT3、RT4字段值,均为5

            RT4.field = 6;  //更改RT3的字段值为6

            Console.WriteLine("RT3 is " + RT3.field +",RT4 is " + RT4.field);        //输出RT3、RT4字段值,均为6

        }

    }

}

这个比较需要注意,RT1的field字段初始为5,然后RT1赋值给RT2,由于RT2是引用变量,那么修改RT2就是修改RT1,可是为什么RT1在后边没变化呢?这是因为这种情况下RT1和RT2指向的不是一个堆的地址,具体可以看下边的图。RT3和RT4是正常的情况,修改了RT4的值,RT3同时跟着变化。关于这两种情况其实很简单,而且这个很好的反应了引用类型的性质。为什么第一种情况不行呢?是因为第一种情况改变了引用对象本身,重新进行了实例化,那么就是重新声明了一个新的实例。而第二种情况改变的是对象的属性,两个对象还是对应的同一个实例。看下面两幅图:

第一种情况:


第一次实例化RT2时拷贝的RT1,此时RT1、RT2是一个对象。接下来的实例化是对RT2本身的改变,并且在这里是会重新分配RT2地址的。所以这个RT2是一个全新的实例,与RT1没有任何联系。所以它再怎么改字段的值也不会在影响RT1的字段。


第二种情况:


这里修改的只是字段,所以修改后并没有产生新的实例,那么RT1、RT2对应的还是一个实例。修改任何一个的字段值,另外一个会同时变化。

以上这些情况同样适用于方法中的参数,如果在方法中重新实例了对象,那么原始的对象在方法结束时不会做出任何改变,如果仅是改变对象实例的字段,那么没有任何问题,可以修改。如果想要在方法中对对象的实例本身做修改,使用ref传递参数,这个可以做到。


3.引用类型测试(string)

using System;

using System.Runtime.InteropServices;

namespace ValueAndReference

{

    class Program

    {

        static void Main(string[] args)

        {

            string STR1 ="aaaaa";

            string STR2 = STR1;

            Console.WriteLine("STR1 is " + STR1+",STR2 is "+STR2);     //输出STT1、STR2

            STR2 = "bbbbb";

            Console.WriteLine("STR1 is " + STR1 +",STR2 is " + STR2);  //输出STT1、STR2

        }

    }

}

最后在说下这个string(String),在这里STR1并没有随着STR2的变化而变化,这么看起来它确实像值类型。但实际上它确实是引用类型。为什么出现这种情况那,这是因为string做了运算符重载这样看起来是string更像是字符串。可以把它这个赋值理解为new,这样就和上边的class一样了,所以这种情况是正常的。









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值