定義代碼如下:
/// <summary> /// 深拷貝接口 /// </summary> interface IDeepCopy { object DeepCopy(); } /// <summary> /// 淺拷貝接口 /// </summary> interface IShallowCopy { object ShallowCopy(); } /// <summary> /// 教室信息 /// </summary> class ClassRoom : IDeepCopy, IShallowCopy { public int RoomID = 1; public string RoomName = "Room1"; public override string ToString() { return "RoomID=" + RoomID + "/tRoomName=" + RoomName; } public object DeepCopy() { ClassRoom r = new ClassRoom(); r.RoomID = this.RoomID; r.RoomName = this.RoomName; return r; } public object ShallowCopy() { //直接使用內(nèi)置的淺拷貝方法返回 return this.MemberwiseClone(); } } class Student : IDeepCopy, IShallowCopy { //為了簡(jiǎn)化,使用public 字段 public string Name; public int Age; //自定義類(lèi)型,假設(shè)每個(gè)Student只擁有一個(gè)ClassRoom public ClassRoom Room = new ClassRoom(); public Student() { } public Student(string name, int age) { this.Name = name; this.Age = age; } public object DeepCopy() { Student s = new Student(); s.Name = this.Name; s.Age = this.Age; s.Room = (ClassRoom)this.Room.DeepCopy(); return s; } public object ShallowCopy() { return this.MemberwiseClone(); } public override string ToString() { return "Name:" + Name + "/tAge:" + Age + "/t" + Room.ToString(); } }pastingpasting測(cè)試代碼:Student s1 = new Student("Vivi", 28); Console.WriteLine("s1=[" + s1 + "]"); Student s2 = (Student)s1.ShallowCopy(); //Student s2 = (Student)s1.DeepCopy(); Console.WriteLine("s2=[" + s2 + "]"); //此處s2和s1內(nèi)容相同 Console.WriteLine("-----------------------------"); //修改s2的內(nèi)容 s2.Name = "tianyue"; s2.Age = 25; s2.Room.RoomID = 2; s2.Room.RoomName = "Room2"; Console.WriteLine("s1=[" + s1 + "]"); Console.WriteLine("s2=[" + s2 + "]"); //再次打印兩個(gè)對(duì)象以比較 Console.ReadLine();運(yùn)行結(jié)果:
a.ShallowCopys1=[Name:Vivi Age:28 RoomID=1 RoomName=Room1]s2=[Name:Vivi Age:28 RoomID=1 RoomName=Room1]-------------------------------------------------------------s1=[Name:Vivi Age:28 RoomID=2 RoomName=Room2]s2=[Name:tianyue Age:25 RoomID=2 RoomName=Room2]b.DeepCopys1=[Name:Vivi Age:28 RoomID=1 RoomName=Room1]s2=[Name:Vivi Age:28 RoomID=1 RoomName=Room1]-----------------------------s1=[Name:Vivi Age:28 RoomID=1 RoomName=Room1]s2=[Name:tianyue Age:25 RoomID=2 RoomName=Room2] 從以上結(jié)果可以看出,深拷貝時(shí)兩個(gè)對(duì)象是完全“分離”的,改變其中一個(gè),不會(huì)影響到另一個(gè)對(duì)象; 淺拷貝時(shí)兩個(gè)對(duì)象并未完全“分離”,改變頂級(jí)對(duì)象的內(nèi)容,不會(huì)對(duì)另一個(gè)對(duì)象產(chǎn)生影響,但改變子對(duì)象的內(nèi)容,則兩個(gè)對(duì)象同時(shí)被改變。 這種差異的產(chǎn)生,即是取決于拷貝子對(duì)象時(shí)復(fù)制內(nèi)存還是復(fù)制指針。 深拷貝為子對(duì)象重新分配了一段內(nèi)存空間,并復(fù)制其中的內(nèi)容;淺拷貝僅僅將指針指向原來(lái)的子對(duì)象。示意圖如下:2.淺拷貝與賦值操作 大多數(shù)面向?qū)ο笳Z(yǔ)言中的賦值操作都是傳遞引用,即改變對(duì)象的指針地址,而并沒(méi)有復(fù)制內(nèi)存,也沒(méi)有做任何復(fù)制操作。 由此可知,淺拷貝與賦值操作的區(qū)別是頂級(jí)對(duì)象的復(fù)制與否。當(dāng)然,也有一些例外情況,比如類(lèi)型定義中重載賦值操作符(assignment Operator),或者某些類(lèi)型約定按值傳遞,就像C#中的結(jié)構(gòu)體和枚舉類(lèi)型。賦值操作示意圖如下:
3.C++拷貝構(gòu)造函數(shù) 與其它面向?qū)ο笳Z(yǔ)言不同,C++允許用戶選擇自定義對(duì)象的傳遞方式:值傳遞和引用傳遞。在值傳遞時(shí)就要使用對(duì)象拷貝,比如說(shuō)按值傳遞參數(shù),編譯 器需要拷貝一個(gè)對(duì)象以避免原對(duì)象在函數(shù)體內(nèi)被破壞。為此,C++提供了拷貝構(gòu)造函數(shù)用來(lái)實(shí)現(xiàn)這種拷貝行為,拷貝構(gòu)造函數(shù)是一種特殊的構(gòu)造函數(shù),用來(lái)完成一 些基于同一類(lèi)的其它對(duì)象的構(gòu)造和初始化。它唯一的參數(shù)是引用類(lèi)型的,而且不可改變,通常的定義為X(const X&)。在拷貝構(gòu)造函數(shù)里,用戶可以定義對(duì)象的拷貝行為是深拷貝還是淺拷貝,如果用戶沒(méi)有實(shí)現(xiàn)自己的拷貝構(gòu)造函數(shù),那么編譯器會(huì)提供一個(gè)默認(rèn)實(shí) 現(xiàn),該實(shí)現(xiàn)使用的是按位拷貝(bitwise copy),也即本文所說(shuō)的淺拷貝。構(gòu)造函數(shù)何時(shí)被調(diào)用呢?通常以下三種情況需要拷貝對(duì)象,此時(shí)拷貝構(gòu)造函數(shù)將會(huì)被調(diào)用。 1.一個(gè)對(duì)象以值傳遞的方式傳入函數(shù)體 2.一個(gè)對(duì)象以值傳遞的方式從函數(shù)返回 3.一個(gè)對(duì)象需要通過(guò)另外一個(gè)對(duì)象進(jìn)行初始化4.C# MemberwiseClone與ICloneable接口 和C++里的拷貝構(gòu)造函數(shù)一樣,C#也為每個(gè)對(duì)象提供了淺拷貝的默認(rèn)實(shí)現(xiàn),不過(guò)C#里沒(méi)有拷貝構(gòu)造函數(shù),而是通過(guò)頂級(jí)類(lèi)型Object里的 MemberwiseClone方法。MemberwiseClone 方法創(chuàng)建一個(gè)淺表副本,方法是創(chuàng)建一個(gè)新對(duì)象,然后將當(dāng)前對(duì)象的非靜態(tài)字段復(fù)制到該新對(duì)象。有沒(méi)有默認(rèn)的深拷貝實(shí)現(xiàn)呢?當(dāng)然是沒(méi)有,因?yàn)樾枰袇⑴c拷貝 的對(duì)象定義自己的深拷貝行為。C++里需要用戶實(shí)現(xiàn)拷貝構(gòu)造函數(shù),重寫(xiě)默認(rèn)的淺拷貝;C#則不同,C#(確切的說(shuō)是.NET Framework,而非C#語(yǔ)言)提供了 ICloneable 接口,包含一個(gè)成員 Clone,它用于支持除 MemberwiseClone 所提供的克隆之外的克隆。C++通過(guò)拷貝構(gòu)造函數(shù)無(wú)法確定子對(duì)象實(shí)現(xiàn)的是深拷貝還是淺拷貝,而C#在“強(qiáng)制”實(shí)現(xiàn)淺拷貝的基礎(chǔ)上,提供 ICloneable 接口由用戶定義深拷貝行為,通過(guò)接口來(lái)強(qiáng)制約束所有參與拷貝的對(duì)象,個(gè)人覺(jué)得,這也算是一小點(diǎn)C#對(duì)C++的改進(jìn)。 5.深拷貝策略與實(shí)現(xiàn) 深拷貝的要點(diǎn)就是確保所有參與拷貝的對(duì)象都要提供自己的深拷貝實(shí)現(xiàn),不管是C++拷貝構(gòu)造函數(shù)還是C#的ICloneable 接口,事實(shí)上都是一種拷貝的約定。有了事先的約定,才能約束實(shí)現(xiàn)上的統(tǒng)一,所以關(guān)鍵在于設(shè)計(jì)。 但偶爾也會(huì)在后期才想到要深拷貝,怎么辦?總不能修改所有之前的實(shí)現(xiàn)吧。有沒(méi)有辦法能夠通過(guò)頂級(jí)類(lèi)而不關(guān)心內(nèi)部的子對(duì)象直接進(jìn)行深拷貝呢?能不 能搞個(gè)萬(wàn)能的深拷貝方法,在想用的時(shí)候立即用,而不考慮前期的設(shè)計(jì)。這樣“大包大攬”的方法,難點(diǎn)在于實(shí)現(xiàn)時(shí)必須自動(dòng)獲取子對(duì)象的信息,分別為子對(duì)象實(shí)現(xiàn) 深拷貝。C++里比較困難,.NET的反射機(jī)制使得實(shí)現(xiàn)容易一些。不過(guò)這樣的方法雖然通用,實(shí)則破壞了封裝,也不符合“每個(gè)類(lèi)對(duì)自己負(fù)責(zé)”的設(shè)計(jì)原則。 基于.NET的反射機(jī)制,以前寫(xiě)了一個(gè)通用的序列化方法,現(xiàn)在可以拿過(guò)來(lái),先序列化,然后再反序列化回來(lái),也即是一個(gè)深拷貝,示例代碼如下:深拷貝示例代碼:
#region ICloneable Members /// <summary> /// 此處的復(fù)制為深拷貝,在實(shí)現(xiàn)上,為了簡(jiǎn)化,采用序列化和反序列化。 /// </summary> /// <returns>深拷貝對(duì)象</returns> public object Clone() { Student stu = new Student(); xmlStorageHelper helper = new XmlStorageHelper(); string strXml = helper.ConvertToString(this); helper.LoadFromString(stu, strXml); //從XML字符串來(lái)賦值 return stu; } #endregion
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注