首页 > 代码库 > C++/C#中堆栈、对象内存模型、深浅拷贝、Array.Clone方法
C++/C#中堆栈、对象内存模型、深浅拷贝、Array.Clone方法
转载自:http://blog.csdn.net/jarvischu/article/details/6425534
目录
1. C++/C#中对象内存模型..................................................................................................... 1
1.1. 栈内存与堆内存...................................................................................................... 1
1.1.1. 栈内存........................................................................................................... 1
1.1.2. 堆内存........................................................................................................... 1
1.1.3. 栈内存与堆内存比较................................................................................... 1
1.2. 值类型与引用类型...................................................................................................2
1.3. 内存模型.................................................................................................................. 3
2. 深拷贝和浅拷贝..................................................................................................................4
2.1. 案例1.........................................................................................................................4
2.2. 理解.......................................................................................................................... 5
2.3. 案例2........................................................................................................................ 6
3. System.Array的Clone() ........................................................................................................8
1. C++/C#中对象内存模型
1.1. 栈内存与堆内存栈内存与堆内存
1.1.1. 栈内存
l 由编译器自动分配和释放
l 用于保存一些局部变量、函数的参数等
理解:
l int a;
那么编译器会自动在栈中给变量a分配一个sizeof(int)大小的内存
1.1.2. 堆内存
l 由程序员手动申请和[释放]
在C++中,通过new申请,编译器不会释放,必须通过delete释放。
在C#中,通过new申请,因编译器的GC(垃圾回收机制),程序员省去了delete操作
l char* p = new char[10];//C++
char[] p = new char[10];//C#
首先,编译器分配一个栈内存保存变量p
之后,编译器分配一个堆内存,大小为sizeof(char)*10
最后,将堆内存的首地址存放在p中。
1.1.3. 栈内存与堆内存比较
u 内存分配
栈:后进先出;由编译器分配相应类型大小;分配的大小受限于栈区大小
堆:随意分配;由程序员手动申请指定大小;分配的大小受限于计算机的虚拟内存
u 效率
栈:高
堆:相对栈低
1.2. 值类型与引用类型
u 简单理解
值类型变量保存的是实际数据,引用类型变量保存的实际数据所在的内存地址。
u 存储区别
值类型数据存储在栈内存中,引用类型变量对应的实际数据存储在堆内存(变量本身存储在栈内存中,通常是四个字节,保存着一个地址数值)
u C++类型
值类型:int,float,double,char,enum
引用类型:array,structure,class
u C#类型
值类型:struct,enum
引用类型:class,delegate,array,interface
为什么这里没有int,float等类型了?(写漏了?)
回答这个问题,请看MSDN中的一句话:
Types that you define by using the struct keyword are value types; all the built-in numeric types are structs. (C#)
这里有三点强调一下:
ü struct(结构体)类型是值类型
ü built-in numberic types(内建数值类型,即int,float等)都是结构体,如int对应了System.Int32结构体
ü 具MSDN记载,System.Int32就是int的别名
图 1 C#中类型概览
1.3. 内存模型
图 2 值类型内存模型
图 3 引用类型内存模型(C#为例,C++类似)
2. 深拷贝和浅拷贝
2.1. 案例1
以图3中的Person类为例。(Name 和 Age是Person类的public 属性)
上面的语句执行结果是什么?
这里涉及到深拷贝和浅拷贝,理解了这两个概念后,问题的答案也就出来了。
2.2. 理解
当将对象a赋值给另一个对象b时,就会执行拷贝函数。
深拷贝:将a 的完完全全复制一份,然后赋值给b(Copy Value)(可以理解成副本)
浅拷贝:将a 的引用复制一份,然后赋值给b(Copy Reference)(可以理解成别名)
图4中:
Person b = al;
浅拷贝,它只是将对象a的引用(就是变量a,更确切的说是a中保存的内存地址)赋值给了变量b。这样,a和b就指向了同一块内存。
Person c = new Person(a.Name,a.Age);
深拷贝,它将对象a的引用以及它所指向的内存区的数据都复制了一份,然后赋值给了b。这样,a和b都指向了各自地址不同,但数据相同的内存区。
这样,上面问题的答案也出来了,输出:
//2013-10-23修正
Chu
Jarvis
图 4 深浅拷贝
2.3. 案例2
好了,我们趁热打铁,再看另一种情况。
Country类和Person类的定义如下:
则下面的代码输出结果是什么?
- Country china = new Country(“中国”,960);
- Person a = new Person(“Jarvis”,22,china);
- Person b = a;
- Console.WriteLine(a.MotherLand.CountryName);
- Console.WriteLine(b.MotherLand.CountryName);
- china.CountryName = “共和国”;
- Console.WriteLine(a.MotherLand.CountryName);
- Console.WriteLine(b.MotherLand.CountryName);
这个案例中涉及了两次浅拷贝,
一次是MotherLand = land; ,
一次是Person b = a;。
它体现的内容是:
将一个含有对象类型属性(MotherLand)的对象(a),直接赋值给另一个对象(b)。
那么这个拷贝(很明显是浅拷贝)的过程究竟是怎么样的?内存中数据的状态又是如何的?
答案是:
共和国
共和国
(你答对了么?)
看一下我的图解。
图 5 含对象属性的拷贝
如果将2处的“中国”变成了“共和国”,当然输出结果是两行“共和国”咯
3. System.Array的Clone()方法
为什么要特意讲这个Clone()方法呢?它有什么特别么?
说特别,可能有点特别,但是讲它的主要原因不在于此,而是因为这个方法让我纠结了很久,因为MSDN中,说它是浅拷贝。可是下面的代码输出的结果却与我想的不一样:
- int[] arrOriginal = { 1, 2, 3, 4 };
- int[] arrCopyDirect = arrOriginal; //直接浅拷贝
- int[] arrCopyClone = (int[])arrOriginal.Clone(); //Clone方法
- ShowArray(arrOriginal); //ShowArray方法就是简单的把数组的每个元素都显示出来ShowArray(arrCopyDirect);
- ShowArray(arrCopyClone);
- arrOriginal[0] = 0;
- Console.WriteLine();
- ShowArray(arrOriginal);
- ShowArray(arrCopyDirect);
- ShowArray(arrCopyClone);
输出的结果是:
说明Clone方法是把arrOriginal数组中的元素都另外拷贝了一份赋值给了arrCopyClone数组,所以赋值结束后,改变arrOriginal数组的元素的值(arrOriginal[0] = 0),arrCopyClone就不受影响了(arrCopyClone[0] = 1),这是浅拷贝么?
有点让人疑惑。
但是如果用一个Person类类型的数组而不是int类型的数组去测试时,可以发现的确源数组A赋值给新数组B 后,A中元素如果改变,B也相应的变化了。
这个让我在费了很长时间才理解,这个过程中,我就对上面提到的几个知识点做了进一步理解。
最终我找到了答案,MSDN上的解释是:
A shallow copy of an Array copies only the elements of the Array, whether they are reference types or value types, but it does not copy the objects that the references refer to. The references in the new Array point to the same objects that the references in the original Array point to.
意思就是说:
不管数组中的元素是值类型(Value Types,如int)还是引用类型(Reference Types,如Person),该方法都是将数组的所有元素都拷贝到另一个数组中。
这样的结果就是:如果原数组中的元素都是值类型,这个就相当于深拷贝,如果原数组中的元素是引用类型,那么就相当于浅拷贝。
所以我认为,MSDN上直接把它说成是Shallow Copy还是有待商榷的。
C++/C#中堆栈、对象内存模型、深浅拷贝、Array.Clone方法