C#浅析值类型和引用类型,堆内存和栈内存,浅拷贝与深拷贝

摘要:讲解值类型和引用类型的概念、划分和区别联系,以及其中所用到不同的存储方式——堆内存和栈内存。

编程语言:C#

编程环境:Visual Studio 2019

目录

值类型和引用类型的概念

常见值类型和引用类型

值类型和引用类型的区别与联系

区别

联系

堆内存和栈内存

概念

区别

浅拷贝与深拷贝

概念

实现方法

举例

小结 

每篇寄语


值类型和引用类型的概念

        C#中,类型被分为两种——值类型和引用类型,值类型变量指直接存储数据的变量,它的实例通常实在栈上分配的(静态分配),而引用类型变量持有数据的引用,其数据存储在数据堆中,它的实例在堆中分配(动态分配)。

        光看概念可能比较抽象,下面用一个例子来说明。

int i = 10;//定义值类型——int型
int[] ii = { 1, 2, 3 };//定义引用类型——数组

//分别输出这两个变量
Console.WriteLine(i);
Console.WriteLine(ii);

        执行程序,输出

        可见,对于值类型,直接输出了它的值,而对于引用类型,只是输出了它的引用。因为值类型数据直接存储在栈上,而引用类型只是把引用存储在栈上,其数据存储在堆中,存储方式如图。

 

常见值类型和引用类型

常见值类型和引用类型
值类型 基本数据类型 int、float、double、char、bool等
枚举类型 enum
结构体 struct
引用类型 基类Object、string、自定义类class
接口 interface
数组 各种类型数组

   

值类型和引用类型的区别与联系

区别

  • 在内存中存储方式不同,前文已经详细介绍。
  • 内存回收方式不同,值类型存储在栈上,使用完会立即回收,而引用类型数据存储在堆中,使用完会等待垃圾回收(GC垃圾收集器自动回收)。
  • 值类型在声明时就包含了其所跟随的数据,而引用类型在声明时仅仅是在栈上定义了一个指针,需要new实例化后才会在堆中开辟内存空间。若使用未经实例化的引用类型的变量,会出现空指针异常,因为没有对应的内存空间。举例如下:

  • 对于值类型的赋值,相当于复制一个同值新对象,而对于引用类型的赋值,相当于对原对象的引用,改变其值会对原对象造成影响。(这个过程属于浅拷贝)代码举例如下:

int i = 10;
int j = i;//i赋值给j,相当于复制一个同值新对象
j = 15;
Students student1 = new Students();//实例化学生1 张三 80
student1.Name = "张三";
student1.Score = 80;
Students student2 = student1;//学生1赋值给学生2,相当于student2拥有student1的引用
student2.Name = "李四"; 
student2.Score = 90;

//分别输出值类型i,j,发现i和j互不影响
Console.WriteLine(i);
Console.WriteLine(j);

//分别输出两个学生的信息,发现student1的信息被覆盖为student2
Console.WriteLine(student1.Name);
Console.WriteLine(student1.Score);
Console.WriteLine(student2.Name);
Console.WriteLine(student2.Score);

        运行程序,输出为

        可以看出,值类型和引用类型的赋值代表的意义是不一样的,值类型是复制一个新对象,引用类型是复制一个原对象的引用。

  • 作为函数的参数与返回值时,规则同上,值类型是对变量的复制传入参数或者返回值,而引用类型是对变量的引用复制传入参数或者返回值。 
  • 引用类型可以派生出新的类型,支持多态,而值类型都是密封的,不支持多态。

联系

  • 引用类型可以实现接口(Interface),值类型可以用结构体实现接口。
  • 引用类型直接继承自System.Object类,值类型直接继承Syetem.Object的子类System.ValueTpye类。

堆内存和栈内存

概念

        C#程序在公共语言运行库(CLR)运行的时候,内存从逻辑上划分为两大块,栈和堆。堆又叫托管堆,栈又叫堆栈。

区别

  • 栈空间遵循后进先出原则,当栈顶元素使用完毕后立即释放;堆空间则像一个仓库,需要的时候会自行寻找并调用,需要GC清理。
  • 栈空间比较小,但读取数据快;堆空间比较大,但读取数据慢。

浅拷贝与深拷贝

概念

        对一个对象进行浅拷贝时,若是值类型成员,则会复制一个等同于其本身新对象,若是引用类型成员,则仅复制它的引用;对一个对象进行深拷贝时,会对引用类型成员指向的对象也进行复制。当我们想完完全全地复制一个与原对象没有关系地引用类型对象,两者完全分离,就得用到深拷贝。

实现方法

        对于浅拷贝,将对象直接赋值给一个新对象就属于浅拷贝,或者利用对象的MemberwiseClone()方法实现浅拷贝。对于深拷贝,小编介绍一种较为方便的方法——反序列化法实现深拷贝,代码如下:(注:调用此方法的T需要在前面加入[Serializable]指示其可序列化)

//反序列化深拷贝方法,T表示拷贝对象类,调用此方法需在T前加[Serializable]
public static T DeepCopyByBinary<T>(T obj)
{
    object retval;
    using (MemoryStream ms = new MemoryStream())
    {
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(ms, obj);
        ms.Seek(0, SeekOrigin.Begin);
        retval = bf.Deserialize(ms);
        ms.Close();
    }
    return (T)retval;
}

举例

        直接上代码,其中,student2是对student1的浅拷贝,student3是对student1的深拷贝。

Students student1 = new Students();//实例化学生1 张三 80
student1.Name = "张三";
student1.Score = 80;
Students student2 = student1;//学生1赋值给学生2,相当于浅拷贝
student2.Name = "李四"; 
student2.Score = 90;
Students student3 = DeepCopyByBinary(student1);//对学生1深拷贝,赋值给学生3
student3.Name = "王麻子";
student3.Score = 100;

//分别输出三个学生的信息
Console.WriteLine(student1.Name);
Console.WriteLine(student1.Score);
Console.WriteLine(student2.Name);
Console.WriteLine(student2.Score);
Console.WriteLine(student3.Name);
Console.WriteLine(student3.Score);

        输出结果为:

        可以看出,浅拷贝后,student2仅是对student1的引用;深拷贝后,student3与student1是相互独立的两个对象。

小结 

        理解C#编程中的值类型和引用类型非常重要,这会避免很多BUG的发生;理解.NET下的堆和栈,对于提高程序性能,从底层理解值类型和引用类型非常重要;理解对象的浅拷贝和深拷贝,可以让我们根据复制需要正确选择合适的拷贝方式。

每篇寄语

        认真的态度能成事,能把事情做到精益求精,这是我们成长道路上不能丢弃的秘诀。

猜你喜欢

转载自blog.csdn.net/lucgh/article/details/130362522