[No0000146]深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing)理解堆与栈3/4

前言

虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC)。另外,了解内存管理可以帮助我们理解在每一个程序中定义的每一个变量是怎样工作的。

简介

这一节我们将介绍引用类型变量在堆中存储时会产生的问题,同时介绍怎么样使用克隆接口ICloneable去修复这种问题。

复制不仅仅是复制

为了更清晰的阐述这个问题,让我们测试一下在堆中存储值类型变量和引用类型变量时会产生的不同情况。

值类型测试

首先,我们看一下值类型。下面是一个类和一个结构类型(值类型),Dude类包含一个Name元素和两个Shoe元素。我们有一个CopyDude()方法用来复制生成新Dude。

[csharp] view plain copy

  1. public struct Shoe{
  2. public string Color;
  3. }
  4. public class Dude
  5. {
  6. public string Name;
  7. public Shoe RightShoe;
  8. public Shoe LeftShoe;
  9. public Dude CopyDude()
  10. {
  11. Dude newPerson = new Dude();
  12. newPerson.Name = Name;
  13. newPerson.LeftShoe = LeftShoe;
  14. newPerson.RightShoe = RightShoe;
  15. return newPerson;
  16. }
  17. public override string ToString()
  18. {
  19. return (Name + " : Dude!, I have a " + RightShoe.Color  +
  20. " shoe on my right foot, and a " +
  21. LeftShoe.Color + " on my left foot.");
  22. }
  23. }

Dude类是一个复杂类型,因为值 类型结构Shoe是它的成员, 它们都将存储在堆中。

当我们执行下面的方法时:

[csharp] view plain copy

  1. public static void Main()
  2. {
  3. Class1 pgm = new Class1();
  4. Dude Bill = new Dude();
  5. Bill.Name = "Bill";
  6. Bill.LeftShoe = new Shoe();
  7. Bill.RightShoe = new Shoe();
  8. Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue";
  9. Dude Ted =  Bill.CopyDude();
  10. Ted.Name = "Ted";
  11. Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";
  12. Console.WriteLine(Bill.ToString());
  13. Console.WriteLine(Ted.ToString());
  14. }

我们得到了期望的结果:

[plain] view plain copy

  1. Bill : Dude!, I have a Blue shoe on my right foot, and a Blue on my left foot.
  2. Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot.

如果我们把Shoe换成引用类型呢?

引用类型测试

当我们把Shoe改成引用类型时,问题就产生了。

[csharp] view plain copy

  1. public class Shoe{
  2. public string Color;
  3. }

执行同样上面的Main()方法,结果改变了,如下:

[plain] view plain copy

  1. Bill : Dude!, I have a Red shoe on my right foot, and a Red on my left foot
  2. Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot

这并不是我们期望的结果。很明显,出错了!看下面的图解:

因为现在Shoe是引用类型而不是值类型,当我们进行复制时仅是复制了指针,我们并没有复制指针真正对应的对象。这就需要我们做一些额外的工作使引用类型Shoe像值类型一样工作。

很幸运,我们有一个接口可以帮我们实现:ICloneable。当Dude类实现它时,我们会声明一个Clone()方法用来产生新的Dude复制类。(译外话:复制类及其成员跟原始类不产生任何重叠,即我们所说的深复制)   看下面代码:

[csharp] view plain copy

  1. ICloneable consists of one method: Clone()
  2. public object Clone()
  3. {
  4. }
  5. Here‘s how we‘ll implement it in the Shoe class:
  6. public class Shoe : ICloneable
  7. {
  8. public string Color;
  9. #region ICloneable Members
  10. public object Clone()
  11. {
  12. Shoe newShoe = new Shoe();
  13. newShoe.Color = Color.Clone() as string;
  14. return newShoe;
  15. }
  16. #endregion
  17. }

在Clone()方法里,我们创建了一个新的Shoe,克隆所有引用类型变量,复制所有值类型变量,最后返回新的对象Shoe。有些既有类已经实现了ICloneable,我们直接使用即可,如String。因此,我们直接使用Color.Clone()。因为Clone()返回object对象,我们需要进行一下类型转换。

下一步,我们在CopyDude()方法里,用克隆Clone()代替复制:

[csharp] view plain copy

  1. public Dude CopyDude()
  2. {
  3. Dude newPerson = new Dude();
  4. newPerson.Name = Name;
  5. newPerson.LeftShoe = LeftShoe.Clone() as Shoe;
  6. newPerson.RightShoe = RightShoe.Clone() as Shoe;
  7. return newPerson;
  8. }

再次执行主方法Main():

[csharp] view plain copy

  1. public static void Main()
  2. {
  3. Class1 pgm = new Class1();
  4. Dude Bill = new Dude();
  5. Bill.Name = "Bill";
  6. Bill.LeftShoe = new Shoe();
  7. Bill.RightShoe = new Shoe();
  8. Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue";
  9. Dude Ted =  Bill.CopyDude();
  10. Ted.Name = "Ted";
  11. Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";
  12. Console.WriteLine(Bill.ToString());
  13. Console.WriteLine(Ted.ToString());
  14. }

我们得到了期望的结果:

[plain] view plain copy

  1. Bill : Dude!, I have a Blue shoe on my right foot, and a Blue on my left foot
  2. Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot

下面是图解:

整理我们的代码

在实践中,我们是希望克隆引用类型并复制值类型的。这会让你回避很多不易察觉的错误,就像上面演示的一样。这种错误有时不易被调试出来,会让你很头疼。

因此,为了减轻头疼,让我们更进一步清理上面的代码。我们让Dude类实现IConeable代替使用CopyDude()方法:

[csharp] view plain copy

  1. public class Dude: ICloneable
  2. {
  3. public string Name;
  4. public Shoe RightShoe;
  5. public Shoe LeftShoe;
  6. public override string ToString()
  7. {
  8. return (Name + " : Dude!, I have a " + RightShoe.Color  +
  9. " shoe on my right foot, and a " +
  10. LeftShoe.Color + " on my left foot.");
  11. }
  12. #region ICloneable Members
  13. public object Clone()
  14. {
  15. Dude newPerson = new Dude();
  16. newPerson.Name = Name.Clone() as string;
  17. newPerson.LeftShoe = LeftShoe.Clone() as Shoe;
  18. newPerson.RightShoe = RightShoe.Clone() as Shoe;
  19. return newPerson;
  20. }
  21. #endregion
  22. }

在主方法Main()使用Dude.Clone():

[csharp] view plain copy

  1. public static void Main()
  2. {
  3. Class1 pgm = new Class1();
  4. Dude Bill = new Dude();
  5. Bill.Name = "Bill";
  6. Bill.LeftShoe = new Shoe();
  7. Bill.RightShoe = new Shoe();
  8. Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue";
  9. Dude Ted =  Bill.Clone() as Dude;
  10. Ted.Name = "Ted";
  11. Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";
  12. Console.WriteLine(Bill.ToString());
  13. Console.WriteLine(Ted.ToString());
  14. }

最后得到期望的结果:

[plain] view plain copy

  1. Bill : Dude!, I have a Blue shoe on my right foot, and a Blue on my left foot.
  2. Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot.

特殊引用类型String

在C#中有趣的是,当System.String 使用操作符“=”时,实际上是进行了克隆(深复制)。你不必担心你只是在操作一个指针,它会在内存中创建一个新的对象。但是,你一定要注意内存的占用问题(译外话:比如为什么在一定情况下我们使用StringBuilder代替String+String+String+String...前者速度稍慢初始化耗多点内存但在大字符串操作上节省内存,后者速度稍快初始化简单但在大字符串操作上耗内存)。如果我们回头去看上面的图解中,你会发现Stirng类型在图中并不是一个针指向另一个内存对象,而是为了尽可能的简单,把它当成值类型来演示了。

总结

在实际工作中,当我们需要复制引用类型变量时,我们最好让它实现ICloneable接口。这样可以让引用类型模仿值类型的使用,从而防止意外的错误产生。你可以看到,慎重得理不同的类型非常重要,因为值类型和引用类型在内存中的分配是不同的。

原文地址:https://www.cnblogs.com/Chary/p/No0000146.html

时间: 2024-10-15 07:40:44

[No0000146]深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing)理解堆与栈3/4的相关文章

C# Heap(ing) Vs Stack(ing) in .NET [C# 堆和栈的使用以及垃圾回收原理]

最近在<C#Corner>上看到了一篇关于.NET内存管理以及垃圾回收的文章,虽说是英文的内容,但还是硬着头皮读了下来.发现并不是我原本想象中的那么枯燥,因为语言通俗而且还有很多图片示意,感觉让我又对"堆"和"栈"以及垃圾回收机制有了更加深刻的理解和认知,记录下来提醒自己尽量书写优质的代码,而不是只管实现功能,不管性能优劣去蛮干.  [文章出自: http://www.c-sharpcorner.com/article/c-sharp-heaping-v

堆 和 栈的 区别(经典) 转载一篇理解堆和栈区别的好文章

转载地址:http://www.cnblogs.com/Kevin_z/archive/2010/03/05/1679031.html 此文章虽然是面向C/C++程序员写得,但是对咱们Java程序员还是很有帮助的. 堆和栈的区别 一.预备知识—程序的内存分配 一个由C/C++编译的程序占用的内存分为以下几个部分 1.栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等.其 操作方式类似于数据结构中的栈. 2.堆区(heap) — 一般由程序员分配释放, 若程序员不释放,

【转】iOS:堆(heap)和栈(stack)的理解--简介

Objective-C的对象在内存中是以堆的方式分配空间的,并且堆内存是由你释放的,即release 栈由编译器管理自动释放的,在方法中(函数体)定义的变量通常是在栈内,因此如果你的变量要跨函数的话就需要将其定义为成员变量. 1.栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量等值.其操作方式类似于数据结构中的栈. 2.堆区(heap):一般由程序员分配释放,若程序员不释放,则可能会引起内存泄漏.注堆和数据结构中的堆栈不一样,其类是与链表. 操作系统iOS 中应用程序使用的计

a堆内存与栈内存异同(Java Heap Memory vs Stack Memory Difference)

--reference Java Heap Memory vs Stack Memory Difference 在数据结构中,堆和栈可以说是两种最基础的数据结构,而Java中的栈内存空间和堆内存空间有什么异同,以及和数据结构中的堆栈有何关系? 一.Java 堆存储空间 堆内存(堆存储空间)会在Java运行时分配给对象(Object)或者JRE的类.只要我们创建了一个对象,那么在堆中肯定会分配一块存储空间给这个对象.而我们熟知的Java垃圾回收就是在堆存储空间上进行的,用以释放那些没有任何引用指向

stm32 堆和栈(stm32 Heap &amp; Stack)

关于堆和栈已经是程序员的一个月经话题,大部分有是基于os层来聊的. 那么,在赤裸裸的单片机下的堆和栈是什么样的分布呢?以下是网摘: 刚接手STM32时,你只编写一个 int main() { while(1); } BUILD://Program Size: Code=340 RO-data=252 RW-data=0 ZI-data=1632 编译后,就会发现这么个程序已用了1600多的RAM,要是在51单片机上,会心疼死了,这1600多的RAM跑哪儿去了, 分析map,你会发现是堆和栈占用的

iOS:堆(heap)和栈(stack)的理解

Object-c的对象在内存中是以堆的方式分配内存空间的,并且堆内存是由你释放的,即release 1.栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量等值.其操作方式类似于数据结构中的栈. 2.堆区(heap):一般由程序员分配释放,若程序员不释放,则可能会引起内存泄漏.注堆和数据结构中的堆栈不一样,其类是与链表. 操作系统iOS 中应用程序使用的计算机内存不是统一分配空间,运行代码使用的空间在三个不同的内存区域,分成三个段:"text segment ",&qu

Java栈(stack)与堆(heap)的区别

栈与堆都是Java用来在RAM(自动存取存储器,这里简单理解为内存,详见百度百科)中存放数据的地方.与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆. Java的堆是一个运行时数据区,类的对象从中分配空间.这些对象通过new.newarray.anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放.堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走

java 中的内存分为四个部分:stack(栈),heap(堆),data segment

http://www.qdmm.com/BookReader/113167,54166719.aspx http://www.qdmm.com/BookReader/113167,54166867.aspx http://www.qdmm.com/BookReader/113167,54166868.aspx http://www.qdmm.com/BookReader/113167,54166869.aspx http://www.qdmm.com/BookReader/113167,5416

Java Stack栈和Heap堆的区别

首先分清楚Stack,Heap的中文翻译:Stack—栈,Heap—堆. 在中文里,Stack可以翻译为“堆栈”,所以我直接查找了计算机术语里面堆和栈开头的词语: 堆存储: heapstorage 堆存储分配: heapstorage allocation 堆存储管理: heap storage management 栈编址: stack addressing 栈变换:stack transformation 栈存储器:stack memory 栈单元: stack cell 接着,总结在Jav