C++ 深拷贝和浅拷贝
對于基本類型的數(shù)據(jù)以及簡單的對象,它們之間的拷貝非常簡單,就是按位復(fù)制內(nèi)存。例如:
class Base{ public:Base(): m_a(0), m_b(0){ }Base(int a, int b): m_a(a), m_b(b){ } private:int m_a;int m_b; };int main(){int a = 10;int b = a; //拷貝Base obj1(10, 20);Base obj2 = obj1; //拷貝return 0; }b 和 obj2 都是以拷貝的方式初始化的,具體來說,就是將 a 和 obj1 所在內(nèi)存中的數(shù)據(jù)按照二進(jìn)制位(Bit)復(fù)制到 b 和 obj2 所在的內(nèi)存,這種默認(rèn)的拷貝行為就是淺拷貝,這和調(diào)用 memcpy() 函數(shù)的效果非常類似。
對于簡單的類,默認(rèn)的拷貝構(gòu)造函數(shù)一般就夠用了,我們也沒有必要再顯式地定義一個(gè)功能類似的拷貝構(gòu)造函數(shù)。但是當(dāng)類持有其它資源時(shí),例如動(dòng)態(tài)分配的內(nèi)存、指向其他數(shù)據(jù)的指針等,默認(rèn)的拷貝構(gòu)造函數(shù)就不能拷貝這些資源了,我們必須顯式地定義拷貝構(gòu)造函數(shù),以完整地拷貝對象的所有數(shù)據(jù)。
我們知道,有些較老的編譯器不支持變長數(shù)組, 這會(huì)給編程帶來不便,自定義 Array 類來實(shí)現(xiàn)變長數(shù)組。
#include <iostream> #include <cstdlib> using namespace std;//變長數(shù)組類 class Array{ public:Array(int len);Array(const Array &arr); //拷貝構(gòu)造函數(shù)~Array(); public:int operator[](int i) const { return m_p[i]; } //獲取元素(讀取)int &operator[](int i){ return m_p[i]; } //獲取元素(寫入)int length() const { return m_len; } private:int m_len;int *m_p; };Array::Array(int len): m_len(len){m_p = (int*)calloc( len, sizeof(int) ); }Array::Array(const Array &arr){ //拷貝構(gòu)造函數(shù)this->m_len = arr.m_len;this->m_p = (int*)calloc( this->m_len, sizeof(int) );memcpy( this->m_p, arr.m_p, m_len * sizeof(int) ); }Array::~Array(){ free(m_p); }//打印數(shù)組元素 void printArray(const Array &arr){int len = arr.length();for(int i=0; i<len; i++){if(i == len-1){cout<<arr[i]<<endl;}else{cout<<arr[i]<<", ";}} }int main(){Array arr1(10);for(int i=0; i<10; i++){arr1[i] = i;}Array arr2 = arr1;arr2[5] = 100;arr2[3] = 29;printArray(arr1);printArray(arr2);return 0; }運(yùn)行結(jié)果:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9 0, 1, 2, 29, 4, 100, 6, 7, 8, 9本例中我們顯式地定義了拷貝構(gòu)造函數(shù),它除了會(huì)將原有對象的所有成員變量拷貝給新對象,還會(huì)為新對象再分配一塊內(nèi)存,并將原有對象所持有的內(nèi)存也拷貝過來。這樣做的結(jié)果是,原有對象和新對象所持有的動(dòng)態(tài)內(nèi)存是相互獨(dú)立的,更改一個(gè)對象的數(shù)據(jù)不會(huì)影響另外一個(gè)對象,本例中我們更改了 arr2 的數(shù)據(jù),就沒有影響 arr1。
這種將對象所持有的其它資源一并拷貝的行為叫做深拷貝,我們必須顯式地定義拷貝構(gòu)造函數(shù)才能達(dá)到深拷貝的目的。
標(biāo)準(zhǔn)模板庫(STL)中的 string、vector、stack、set、map 等也都必須使用深拷貝。
大家如果希望親眼目睹不使用深拷貝的后果,可以將上例中的拷貝構(gòu)造函數(shù)刪除,那么運(yùn)行結(jié)果將變?yōu)?#xff1a;
0, 1, 2, 29, 4, 100, 6, 7, 8, 9 0, 1, 2, 29, 4, 100, 6, 7, 8, 9可以發(fā)現(xiàn),更改 arr2 的數(shù)據(jù)也影響到了 arr1。這是因?yàn)?#xff0c;在創(chuàng)建 arr2 對象時(shí),默認(rèn)拷貝構(gòu)造函數(shù)將 arr1.m_p 直接賦值給了 arr2.m_p,導(dǎo)致 arr2.m_p 和 arr1.m_p 指向了同一塊內(nèi)存,所以會(huì)相互影響。
注意:printArray() 函數(shù)的形參為引用類型,這樣做能夠避免在傳參時(shí)調(diào)用拷貝構(gòu)造函數(shù);又因?yàn)?printArray() 函數(shù)不會(huì)修改任何數(shù)組元素,所以我們添加了 const 限制,以使得語義更加明確。
到底是淺拷貝還是深拷貝
如果一個(gè)類擁有指針類型的成員變量,那么絕大部分情況下就需要深拷貝,因?yàn)橹挥羞@樣,才能將指針指向的內(nèi)容再復(fù)制出一份來,讓原有對象和新生對象相互獨(dú)立,彼此之間不受影響。如果類的成員變量沒有指針,一般淺拷貝足以。
另外一種需要深拷貝的情況就是在創(chuàng)建對象時(shí)進(jìn)行一些預(yù)處理工作,比如統(tǒng)計(jì)創(chuàng)建過的對象的數(shù)目、記錄對象創(chuàng)建的時(shí)間等,請看下面的例子:
#include <iostream> #include <ctime> #include <windows.h> //在Linux和Mac下要換成 unistd.h 頭文件 using namespace std;class Base{ public:Base(int a = 0, int b = 0);Base(const Base &obj); //拷貝構(gòu)造函數(shù) public:int getCount() const { return m_count; }time_t getTime() const { return m_time; } private:int m_a;int m_b;time_t m_time; //對象創(chuàng)建時(shí)間static int m_count; //創(chuàng)建過的對象的數(shù)目 };int Base::m_count = 0;Base::Base(int a, int b): m_a(a), m_b(b){m_count++;m_time = time((time_t*)NULL); }Base::Base(const Base &obj){ //拷貝構(gòu)造函數(shù)this->m_a = obj.m_a;this->m_b = obj.m_b;this->m_count++;this->m_time = time((time_t*)NULL); }int main(){Base obj1(10, 20);cout<<"obj1: count = "<<obj1.getCount()<<", time = "<<obj1.getTime()<<endl;Sleep(3000); //在Linux和Mac下要寫作 sleep(3);Base obj2 = obj1;cout<<"obj2: count = "<<obj2.getCount()<<", time = "<<obj2.getTime()<<endl;return 0; }運(yùn)行結(jié)果:
obj1: count = 1, time = 1488344372 obj2: count = 2, time = 1488344375運(yùn)行程序,先輸出第一行結(jié)果,等待 3 秒后再輸出第二行結(jié)果。Base 類中的 m_time 和 m_count 分別記錄了對象的創(chuàng)建時(shí)間和創(chuàng)建數(shù)目,它們在不同的對象中有不同的值,所以需要在初始化對象的時(shí)候提前處理一下,這樣淺拷貝就不能勝任了,就必須使用深拷貝了。
總結(jié)
以上是生活随笔為你收集整理的C++ 深拷贝和浅拷贝的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java类的属性
- 下一篇: s3c2440移植MQTT