from: http://blog.csdn.net/lai123wei/article/details/7217365
1.深拷貝與淺拷貝 拷貝即是通常所說的複製(Copy)或複製(Clone),對象的拷貝也就是從現有對象複製一個“一模一樣”的新對象出來。雖然都是複製對象,但是不同的 複製方法,複製出來的新對象卻並非完全一模一樣,對象內部存在著一些差異。通常的拷貝方法有兩種,即深拷貝和淺拷貝,那二者之間有何區別呢?MSDN裡對 IClone介面的Clone方法有這樣的說明:在深層副本中,所有的對象都是重複的;而在淺表副本中,只有頂級對象是重複的,並且頂級以下的對象包含引 用。可以看出,深拷貝和淺拷貝之間的
區別在於是否複製了子物件。這如何理解呢?下面我通過帶有子物件的代碼來驗證二者的區別。首先定義兩個類型:Student和ClassRoom,其中Student類型裡包含ClassRoom,並使這兩個類型都分別實現自訂的深拷貝介面(IDeepCopy)和淺拷貝介面(IShallowCopy)。類圖如下:
定義代碼如下:
View Code /// <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
測試代碼:
View Code 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的反射機制,以前寫了一個通用的序列化方法,現在可以拿過來,先序列化,然後再還原序列化回來,也即是一個深拷貝,範例程式碼如下:深拷貝範例程式碼:
View Code #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