程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++類對象的復制-拷貝構造函數(深拷貝,淺拷貝)

C++類對象的復制-拷貝構造函數(深拷貝,淺拷貝)

編輯:C++入門知識
C++類對象的復制-拷貝構造函數(深拷貝,淺拷貝),進一步理解類成員的操作!

在學習這一章內容前我們已經學習過了類的構造函數和析構函數的相關知識,對於普通類型的對象來說,他們之間的復制是很簡單的,例如:

int a = 10;
int b =a;

自己定義的類的對象同樣是對象,誰也不能阻止我們用以下的方式進行復制,例如:


//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必著名出處和作者

#include <iostream>
usingnamespacestd;

classTest
{
public:
Test(inttemp)
{
p1=temp;
}
protected:
intp1;

};

voidmain()
{
Test a(99);
Test b=a;
}


普通對象和類對象同為對象,他們之間的特性有相似之處也有不同之處,類對象內部存在成員變量,而普通對象是沒有的,當同樣的復制方法發生在不同的對象上的時候,那麼系統對他們進行的操作也是不一樣的,就類對象而言,相同類型的類對象是通過拷貝構造函數來完成整個復制過程的,在上面的代碼中,我們並沒有看到拷貝構造函數,同樣完成了復制工作,這又是為什麼呢?因為當一個類沒有自定義的拷貝構造函數的時候系統會自動提供一個默認的拷貝構造函數,來完成復制工作。

下面,我們為了說明情況,就普通情況而言(以上面的代碼為例),我們來自己定義一個與系統默認拷貝構造函數一樣的拷貝構造函數,看看它的內部是如何工作的!

代碼如下:


//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必著名出處和作者

#include <iostream>
usingnamespacestd;

classTest
{
public:
Test(inttemp)
{
p1=temp;
}
Test(Test &c_t)//這裡就是自定義的拷貝構造函數
{
cout<<"進入copy構造函數"<p1=c_t.p1;//這句如果去掉就不能完成復制工作了,此句復制過程的核心語句
}
public:
intp1;
};

voidmain()
{
Test a(99);
Test b=a;
cout<cin.get();
}



上面代碼中的Test(Test &c_t)就是我們自定義的拷貝構造函數,拷貝構造函數的名稱必須與類名稱一致,函數的形式參數是本類型的一個引用變量,且必須是引用

當用一個已經初始化過了的自定義類類型對象去初始化另一個新構造的對象的時候,拷貝構造函數就會被自動調用,如果你沒有自定義拷貝構造函數的時候系統將會提供給一個默認的拷貝構造函數來完成這個過程,上面代碼的復制核心語句就是通過Test(Test &c_t)拷貝構造函數內的p1=c_t.p1;語句完成的。如果取掉這句代碼,那麼b對象的p1屬性將得到一個未知的隨機值;

下面我們來討論一下關於淺拷貝和深拷貝的問題。

就上面的代碼情況而言,很多人會問到,既然系統會自動提供一個默認的拷貝構造函數來處理復制,那麼我們沒有意義要去自定義拷貝構造函數呀,對,就普通情況而言這的確是沒有必要的,但在某寫狀況下,類體內的成員是需要開辟動態開辟堆內存的,如果我們不自定義拷貝構造函數而讓系統自己處理,那麼就會導致堆內存的所屬權產生混亂,試想一下,已經開辟的一端堆地址原來是屬於對象a的,由於復制過程發生,b對象取得是a已經開辟的堆地址,一旦程序產生析構,釋放堆的時候,計算機是不可能清楚這段地址是真正屬於誰的,當連續發生兩次析構的時候就出現了運行錯誤。

為了更詳細的說明問題,請看如下的代碼。


//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必著名出處和作者

#include <iostream>
usingnamespacestd;

classInternet
{
public:
Internet(char*name,char*address)
{
cout<<"載入構造函數"<strcpy(Internet::name,name);
strcpy(Internet::address,address);
cname=newchar[strlen(name)+1];
if(cname!=NULL)
{
strcpy(Internet::cname,name);
}
}
Internet(Internet &temp)
{
cout<<"載入COPY構造函數"<strcpy(Internet::name,temp.name);
strcpy(Internet::address,temp.address);
cname=newchar[strlen(name)+1];//這裡注意,深拷貝的體現!
if(cname!=NULL)
{
strcpy(Internet::cname,name);
}
}
~Internet()
{
cout<<"載入析構函數!";
delete[] cname;
cin.get();
}
voidshow();
protected:
charname[20];
charaddress[30];
char*cname;
};
voidInternet::show()
{
cout<voidtest(Internet ts)
{
cout<<"載入test函數"<}
voidmain()
{
Internet a("中國軟件開發實驗室","www.cndev-lab.com");
Internet b =a;
b.show();
test(b);
}



上面代碼就演示了深拷貝的問題,對對象b的cname屬性采取了新開辟內存的方式避免了內存歸屬不清所導致析構釋放空間時候的錯誤,最後我必須提一下,對於上面的程序我的解釋並不多,就是希望讀者本身運行程序觀察變化,進而深刻理解。

深拷貝和淺拷貝的定義可以簡單理解成:如果一個類擁有資源(堆,或者是其它系統資源),當這個類的對象發生復制過程的時候,這個過程就可以叫做深拷貝,反之對象存在資源但復制過程並未復制資源的情況視為淺拷貝


淺拷貝資源後在釋放資源的時候會產生資源歸屬不清的情況導致程序運行出錯,這點尤其需要注意!

以前我們的教程中討論過函數返回對象產生臨時變量的問題,接下來我們來看一下在函數中返回自定義類型對象是否也遵循此規則產生臨時對象!

先運行下列代碼:



//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必著名出處和作者

#include <iostream>
usingnamespacestd;

classInternet
{
public:
Internet()
{

};
Internet(char*name,char*address)
{
cout<<"載入構造函數"<strcpy(Internet::name,name);
strcpy(Internet::address,address);
}
Internet(Internet &temp)
{
cout<<"載入COPY構造函數"<strcpy(Internet::name,temp.name);
strcpy(Internet::address,temp.address);
cin.get

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved