C++深复制(深拷贝)、浅复制(浅拷贝)和复制构造函数(拷贝构造函数)详解+实例
轉(zhuǎn)載出處:https://blog.csdn.net/sxhelijian/article/details/23209967
對(duì)象的復(fù)制
? 對(duì)于普通類(lèi)型的對(duì)象來(lái)說(shuō),它們之間的復(fù)制是很簡(jiǎn)單的,例如:
int a=88; int b=a; double f=3.12; double d(f);而類(lèi)對(duì)象與普通對(duì)象不同,類(lèi)對(duì)象內(nèi)部結(jié)構(gòu)一般較為復(fù)雜,存在各種數(shù)據(jù)成員。下面看一個(gè)類(lèi)對(duì)象復(fù)制的簡(jiǎn)單例子。
#include <iostream> using namespace std; class Test { private:int a,b; public:Test(int x, int y) //提供的形式參數(shù),是為了給數(shù)據(jù)成員直接初始化的{a=x;b=y;}Test(const Test& C) //復(fù)制構(gòu)造函數(shù),提供一個(gè)同類(lèi)型對(duì)象作為參數(shù){a=C.a;b=C.b;}void show (){cout<<a<<" "<<b<<endl;} };int main() {Test a(100,10); //執(zhí)行構(gòu)造函數(shù)Test::Test(int x, int y)Test b(a); //執(zhí)行構(gòu)造函數(shù)Test::Test(const Test& C)Test c=a; //也執(zhí)行構(gòu)造函數(shù)Test::Test(const Test& C)b.show();c.show();return 0; }運(yùn)行程序,屏幕輸出兩行100 ? 10。
從以上代碼的運(yùn)行結(jié)果可以看出,系統(tǒng)在聲明對(duì)象b和c時(shí),完成了由對(duì)象a的復(fù)制。
復(fù)制構(gòu)造函數(shù)
就類(lèi)對(duì)象而言,相同類(lèi)型的類(lèi)對(duì)象是通過(guò)復(fù)制構(gòu)造函數(shù)來(lái)完成整個(gè)復(fù)制過(guò)程的。
上例中的Test::Test(const?Test&?C),就是我們自定義的復(fù)制構(gòu)造函數(shù)。
可見(jiàn),復(fù)制構(gòu)造函數(shù)是一種特殊的構(gòu)造函數(shù),函數(shù)的名稱(chēng)必須和類(lèi)名稱(chēng)一致,它的唯一的一個(gè)參數(shù)是本類(lèi)型的一個(gè)引用變量,該參數(shù)是const類(lèi)型,通常拷貝構(gòu)造函數(shù)的參數(shù)是某個(gè)對(duì)象的引用名,用來(lái)約束作為參數(shù)的對(duì)象在構(gòu)造新對(duì)象中是不能被改變的。
略一歸納:類(lèi)X的復(fù)制構(gòu)造函數(shù)的形式為X(X&?x)。
當(dāng)用一個(gè)已初始化過(guò)了的自定義類(lèi)類(lèi)型對(duì)象去初始化另一個(gè)新構(gòu)造的對(duì)象的時(shí)候,復(fù)制構(gòu)造函數(shù)就會(huì)被自動(dòng)調(diào)用。也就是說(shuō),當(dāng)類(lèi)的對(duì)象需要復(fù)制時(shí),復(fù)制構(gòu)造函數(shù)將會(huì)被調(diào)用。以下情況都會(huì)調(diào)用復(fù)制構(gòu)造函數(shù):
?
- 一個(gè)對(duì)象以值傳遞的方式傳入函數(shù)體
- 一個(gè)對(duì)象以值傳遞的方式從函數(shù)返回
- 一個(gè)對(duì)象需要通過(guò)另外一個(gè)對(duì)象進(jìn)行初始化。
?
如果在類(lèi)中沒(méi)有顯式地聲明一個(gè)復(fù)制構(gòu)造函數(shù),那么,編譯器將會(huì)自動(dòng)生成一個(gè)默認(rèn)的復(fù)制構(gòu)造函數(shù),該構(gòu)造函數(shù)完成對(duì)象之間的淺復(fù)制,后面將進(jìn)行說(shuō)明。
自定義復(fù)制構(gòu)造函數(shù)是一種良好的編程風(fēng)格,它可以阻止編譯器形成默認(rèn)的復(fù)制構(gòu)造函數(shù),提高源碼效率。
?
?淺復(fù)制和深復(fù)制
? ? ? ?深拷貝意味著拷貝了資源和指針,而淺拷貝只是拷貝了指針,沒(méi)有拷貝資源。?這樣使得兩個(gè)指針指向同一份資源,造成對(duì)同一份析構(gòu)兩次,程序崩潰。
所謂淺復(fù)制,如同上面出現(xiàn)過(guò)的構(gòu)造函數(shù)中處理的一樣,直接為數(shù)據(jù)成員賦值即可。在很多情況下,這是可以的。創(chuàng)建新的對(duì)象,要為對(duì)象的數(shù)據(jù)成員分配存儲(chǔ)空間,直接賦值就將值保存在相應(yīng)的空間中。
然而,這種淺復(fù)制,卻并不能通行天下,下面的程序中,淺復(fù)制帶來(lái)了問(wèn)題。
#include <iostream> #include <cstring> using namespace std; class Test { private:int a;char *str; public:Test(int b, char *s){a=b;strcpy(str,s); //肇事地點(diǎn),但不是禍端}Test(const Test& C){a=C.a;strcpy(str,C.str);}void show (){cout<<a<<","<<str<<endl;} };int main() {Test a(100,"hello");Test b(a);a.show();b.show();return 0; }程序運(yùn)行中,會(huì)彈出一個(gè)窗口:程序的執(zhí)行意外停止了。面對(duì)這個(gè)窗口,我們應(yīng)該有感覺(jué),這和使用指針不當(dāng)有關(guān)系。
我們從main函數(shù)看起。
當(dāng)程序執(zhí)行到第28行Test?a(100,"hello");時(shí),對(duì)象a的數(shù)據(jù)成員a獲得實(shí)際參數(shù)100的值,而數(shù)據(jù)成員str,即指針,是個(gè)隨機(jī)地址值(指針的值,非指針指向的值)!在程序中,試圖通過(guò)strcpy(str,s);將形式參數(shù)s指向的字符串"hello",復(fù)制到str所指向的那個(gè)位置,而那個(gè)位置,其地址并不是經(jīng)過(guò)系統(tǒng)分配來(lái)的,這是個(gè)危險(xiǎn)的操作。在這里,str這樣未經(jīng)過(guò)分配的地址(指針),被稱(chēng)為“野指針”。
在執(zhí)行第29行Test?b(a);時(shí),同樣的事情還要發(fā)生。
str這樣的野指針是多么的霸道!在有的系統(tǒng)里,這樣的行為是被接受的,可以想到其有多危險(xiǎn)。有些系統(tǒng)中,不允許這樣的事情發(fā)生的。于是,上面的程序在codeblock中調(diào)試會(huì)出錯(cuò)。這個(gè)錯(cuò)來(lái)的好。
解決這樣的問(wèn)題的方法,就是在構(gòu)造函數(shù)中,要為指針類(lèi)型的成員,分配專(zhuān)門(mén)的空間。以這條規(guī)則構(gòu)建的復(fù)制,稱(chēng)作為深復(fù)制!
上面的程序,改寫(xiě)為:
#include <iostream> #include <cstring> using namespace std; class Test { private:int a;char *str; public:Test(int b, char *s){a=b;str=new char[strlen(s)+1]; //分配str指向的空間,其長(zhǎng)度根據(jù)s指向的字符串定。為何加1?字符串結(jié)束要用\0strcpy(str,s); //前一程序的肇事地點(diǎn),禍端已經(jīng)由上一句摘除}Test(const Test& C){a=C.a;str=new char[strlen(C.str)+1]; //同上,這樣str就不是野指針了strcpy(str,C.str);}~Test(){delete []str;}void show (){cout<<a<<","<<str<<endl;} };int main() {Test a(100,"hello");Test b(a);a.show();b.show();return 0; }好了,a和b對(duì)象的str成員,明確地給分配了空間,他們?cè)俨皇且爸羔樍恕R驗(yàn)槊鞔_地分配了空間,析構(gòu)函數(shù)中要釋放對(duì)應(yīng)的空間。我們不能用野指針,當(dāng)然,也不能對(duì)象要撤銷(xiāo)了,還占著空間不放,做事不能這樣不厚道。
?
深復(fù)制就體現(xiàn)在第13和第19行分配指針指向的空間,這段空間的地址,也將是指針的值(分清指針的值和指針指向的值)。
再一個(gè)深復(fù)制的例子
下面再給一個(gè)例子,類(lèi)A的數(shù)據(jù)成員可以保存len個(gè)整型數(shù)據(jù)。類(lèi)中的數(shù)據(jù)成員arrayAddr是指向整型的指針,可以作為一個(gè)一元數(shù)組的起始地址。這個(gè)類(lèi)有指針數(shù)據(jù)成員,構(gòu)造函數(shù)的定義中,必須要采用深復(fù)制的方法,第16行體現(xiàn)了這一點(diǎn)。另外,析構(gòu)函數(shù)中完成了對(duì)分配的空間的釋放
#include<iostream> using namespace std; class A { private:int *arrayAddr;//保存一個(gè)有l(wèi)en個(gè)整型元素的數(shù)組的首地址int len; //記錄動(dòng)態(tài)數(shù)組的長(zhǎng)度 public:A(int *a, int n);~A();int sum(); }; A::A(int *a, int n) {len=n;arrayAddr=new int[n]; //為指針數(shù)據(jù)成員分配空間,注意,沒(méi)有上面例子中加1那回事for(int i=0; i<n; i++) //逐個(gè)地將a指向的值逐個(gè)地復(fù)制過(guò)來(lái){arrayAddr[i]=a[i];} } //析構(gòu)函數(shù)的類(lèi)外定義,釋放指針型數(shù)據(jù)a所指向的空間 A::~A() {delete [] arrayAddr; } int A::sum() //獲得a指向的數(shù)組中下標(biāo)為i的元素的值 {int s=0;for(int i=0; i<len; i++) //逐個(gè)地將a指向的值逐個(gè)地復(fù)制過(guò)來(lái){s+=arrayAddr[i];}return s; }int main(){int b[10]= {75, 99, 90, 93, 38, 15, 5, 7, 52, 4};A r1(b,10);cout<<"和:"<<r1.sum()<<endl;int c[15] = {18,68,10,52,3,19,12,100,56,96,95,97,1,4,93};A r2(c,15);cout<<"和:"<<r2.sum()<<endl;return 0; }?
總結(jié)
以上是生活随笔為你收集整理的C++深复制(深拷贝)、浅复制(浅拷贝)和复制构造函数(拷贝构造函数)详解+实例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C语言求:1到100之间的所有素数之和
- 下一篇: 类的构造函数和析构函数详解