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