Keep on going never give up.

Let's Go

C# 学习笔记(52)深入理解值类型和引用类型

C#Lonely2019-05-31 15:19:35108次0条

什么是值类型和引用类型?

C#中的类型可以分为两种——值类型引用类型。那么什么是值类型和引用类型呢?值类型主要包括简单类型、枚举类型、和结构体类型等;引用类型主要包括类类型、接口类型、委托类型、数组类型和字符串类型等。

为了让大家对C#中的类型有一个清晰的认识,下表列出了C#中的基本类型并进行了说明。

类别 说明
值类型 简单类型 有符号整型:int、long、short、sbyte
无符号整型:uint、ulong、ushort、byte
字符串类型:char
浮点型:float、double和高精度小数类型decimal
布尔类型:bool
枚举类型 枚举类型:enum
结构体类型 结构体类型:struct
引用类型 类类型 字符串类型:string
类类型:Console类和自定义的类类型
数组类型 一维数组和多维数组
接口类型 由interface关键字定义的类型
委托类型 由delegate关键字定义的类型

值类型与引用类型的区别

值类型与引用类型的区别在于实际数据的存储位置:值类型的变量和实际数据都存储在栈中,变量保存的内容就是实例数据本身;而引用类型则只有变量存储在栈中,变量存储着实际数据的内存地址,实际数据存储在与内存地址相对应的堆中。

对于栈和堆,你可以将它们理解为内存中存储数据的两种结构,它们存放的数据内容以及存放数据的位置都不一样(一个是在栈上,一个是在堆上)。不同的分配位置导致了不同的管理机制,值类型的管理由操作系统负责,而引用类型的管理则由垃圾回收器(Garbage Collection,GC)负责。管理主要指对内存空间进行分配和释放。之所以需要这些操作,是因为内存大小毕竟有限,值类型和引用类型存储在内存中,自然会消耗可用内存,如果不及时释放掉不再需要的内存,就会导致程序占用的资源越来越多,使可用空间枯竭。

下面通过一个例子来解释值类型与引用类型在内存分配方面的区别,具体的演示代码如下所示:

class Program
{
    static void Main(string[] args)
    {
        // num是值类型
        int num = 10;
        // str是引用类型
        string str = "ABCD";

    }
}

在以上代码中,我们分别定义了一个值类型和一个引用类型,下图演示了它们在内存中的分布情况。

image.png

在程序中,每个变量都有其栈地址,并且不同变量的栈地址不同。所以上面程序中的num和str变量在栈中占用了不同的位置,从上图可以发现,不管是值类型变量还是引用类型变量,变量本身都是存储在栈中的,因为变量存储在栈中的,因为变量只是实例数据的一个引用,即可理解为一个地址。


问:值类型实例就一定会被分配到线程栈上吗?

答:值类型实例不一定总会被分配到线程栈上,在引用类型中嵌套值类型时,或者在值类型装箱的情况下,值类型的实例就会被分配到托管堆中。

嵌套结构包括值类型中嵌套定义了引用类型引用类型中嵌套定义了值类型两种情况。


1、引用类型嵌套定义值类型

如果类的字段类型是值类型,它将作为引用类型实例的一部分,被分配到托管堆中。但那些作为局部变量(例如下列代码中的d变量)的值类型,则仍然会被分配到线程栈中。下面通过一个例子来解释引用类型中嵌套定义值类型情况下的内存分布情况,具体代码如下所示:

class Program
{
    static void Main(string[] args)
    {
        Test t = new Test();
        t.Temp();           
    }
}

public class Test
{
    // num作为引用类型的一部分被分配到托管堆上
    private int num = 10;

    public void Temp()
    {
        // d被分配到线程栈上
        double d = 3.14;
    }

}

以上代码的内存分配情况如下图所示:

image.png

2、值类型中嵌套定义引用类型

值类型嵌套定义引用类型时,栈上将保存该引用类型的引用,而实际的数据则依然保存在托管堆中。相关代码如下所示:

class Program
{
    static void Main(string[] args)
    {
        //值类型变量
        Temp temp = new Temp(new TestClass());
    }
}

public class TestClass
{
    public int x;
    public int y;
}

//值类型嵌套定义引用类型的情况
public struct Temp
{
    //结构体字段,注意:结构体中字段不能被初始化
    private TestClass testClass;

    //结构体的构造函数,注意:结构体中不能显式定义无参的构造函数
    public Temp(TestClass t)
    {
        if (t == null)
            throw new ArgumentNullException("t");
        testClass = t;
        testClass.x = 10;
        testClass.y = 20;
    }

}

以上代码中的内存分配情况如下图所示:

image.png

从以上分析可以得出这样的结论:值类型实例总会被分配到它声明的地方,声明的是局部变量时,将被分配到栈上,而声明为引用类型成员时,则被分配到托管堆上;而引用类型实例总是分配到托管堆上。

上面只分析了值类型与引用类型在内存分布方面的区别,除此之外,二者还存在其他几个方面的区别,现总结如下:

● 值类型继承自ValueType,ValueType又继承自System.Object;而引用类型则直接继承于System.Object;

● 值类型的内存不受GC(垃圾回收器)控制,作用域结束时,值类型会被操作系统自行释放,从而减少了托管堆的压力;而引用类型的内存管理则由GC来完成。所以与引用类型相比,值类型在性能方面更具优势。

● 若值类型是密封的(sealed),你将不能把值类型作为其他任何类型的基类;而引用类型则一般具有继承性,这里指的是类和接口。

● 值类型不能为null值,它会被默认初始化为该类型的默认值;而引用类型在默认情况下会初始化为null值,表示不会指向托管堆中的任何地址。对值为null的引用类型的任何操作都会引发NullReferenceException异常。

● 由于值类型变量包含其实际数据,因此在默认情况下,值类型之间的参数传递不会影响变量本身;而引用类型变量保存的是数据的引用地址,它们作为参数传递时,参数会发生改变,从而影响引用类型变量的值。


暗锚,解决锚点偏移

文章评论

    嘿,来试试登录吧!