??定義
拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一個(gè)重載,它的本質(zhì)還是構(gòu)造函數(shù),那就意味著,只有在創(chuàng)建對(duì)象的時(shí)候,編譯器才會(huì)自動(dòng)調(diào)用它,那他和普通的構(gòu)造函數(shù)有什么區(qū)別呢?
拷貝構(gòu)造函數(shù),是創(chuàng)建對(duì)象的時(shí)候,用一個(gè)已存在的對(duì)象,去初始化待創(chuàng)建的對(duì)象。簡(jiǎn)單來(lái)說(shuō),就是在我們創(chuàng)建對(duì)象的時(shí)候,希望創(chuàng)建出來(lái)的對(duì)象,和一個(gè)已存在的對(duì)象一模一樣,此時(shí)就應(yīng)該用拷貝構(gòu)造函數(shù),而不是普通的構(gòu)造函數(shù)??截悩?gòu)造函數(shù)有一點(diǎn)類似于克隆技術(shù)。
Data d1(2023, 7, 20);//定義一個(gè)日期類對(duì)象d1
Data d2(d1);//會(huì)去調(diào)用拷貝構(gòu)造函數(shù)
int a = 10;
int b = a;//不會(huì)調(diào)用拷貝構(gòu)造
上面代碼,首先定義了一個(gè)日期類對(duì)象d1
,接著想創(chuàng)建第二個(gè)日期類對(duì)象d2
,并且希望d2
和d1
一模一樣,也就是用d1
去克隆出d2
,d2
相當(dāng)于是d1
的一份拷貝。所以在創(chuàng)建d2
對(duì)象的時(shí)候,參數(shù)列表直接傳遞了d1
。
小Tips:拷貝構(gòu)造函數(shù)是針對(duì)自定義類型的,自定義類型的對(duì)象在拷貝的時(shí)候,C++規(guī)定必須要調(diào)用拷貝構(gòu)造函數(shù)。內(nèi)置類型不涉及拷貝構(gòu)造函數(shù),如上,用a
去創(chuàng)建b
,是由編譯器直接把a
所表示的空間中的內(nèi)容直接拷貝到b
所表示的空間,并不涉及拷貝構(gòu)造函數(shù)。
??拷貝構(gòu)造函數(shù)的錯(cuò)誤寫(xiě)法
有了上面的分析,可能很多朋友會(huì)覺(jué)得,那我直接在類里面再寫(xiě)一個(gè)構(gòu)造函數(shù),把它的形參設(shè)置成日期類對(duì)象,不就行了嘛,于是便得到了下面的代碼:
Data(Data d)//錯(cuò)誤的拷貝構(gòu)造
{
_year = d._year;
_month = d._month;
_day = d._day;
}
是不是覺(jué)得很簡(jiǎn)單?創(chuàng)建d2
對(duì)象的時(shí)候,實(shí)參把d1
傳過(guò)來(lái),然后用d
接收,最后再把d
的所有值賦值給this
指針(當(dāng)前this指針就指向d2
),這一切堪稱完美,但是我想告訴你,這種寫(xiě)法是大錯(cuò)特錯(cuò)的。
??為什么是錯(cuò)的
問(wèn)題出現(xiàn)在傳參,就是實(shí)參d1
傳遞給形參d
的時(shí)候,上面代碼中的形參d
,既不是指針也不是引用,說(shuō)明是值傳遞,值傳遞就意味著,形參d
是實(shí)參d1
的一份拷貝,注意:是拷貝,就是說(shuō),形參d
要和實(shí)參d1
一模一樣,怎么才能讓d
和d1
一摸一樣?調(diào)用拷貝構(gòu)造函數(shù)呀。
形參d
在接收實(shí)參d1
的時(shí)候,又要去調(diào)用拷貝構(gòu)造來(lái)創(chuàng)建d
,這次調(diào)用拷貝構(gòu)造,又會(huì)有一個(gè)形參d
,這個(gè)形參d
又需要調(diào)用拷貝構(gòu)造才能創(chuàng)建,相信到這里,小伙伴們已經(jīng)看出問(wèn)題所在了———無(wú)窮遞歸,形參在接收的時(shí)候,會(huì)無(wú)窮無(wú)盡的去調(diào)用拷貝構(gòu)造函數(shù),就像套娃一樣。
為了避免出現(xiàn)這種無(wú)窮遞歸,編譯器會(huì)自行檢查,如果拷貝構(gòu)造函數(shù)的形參是值傳遞,編譯時(shí)會(huì)直接報(bào)錯(cuò)。
??必須是引用
為了打破上面的魔咒,拷貝構(gòu)造函數(shù)的形參只能有一個(gè),并且必須是類類型對(duì)象的引用。下面才是正確的拷貝構(gòu)造函數(shù):
Data(Data& d)//正確的拷貝構(gòu)造
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Data d1(2023, 7, 20);//定義一個(gè)日期類對(duì)象d1
Data d2(d1);//
此時(shí)創(chuàng)建d2
的時(shí)候,傳遞d1
調(diào)用拷貝構(gòu)造函數(shù),形參d
是一個(gè)日期類的引用,因?yàn)橐脮r(shí)區(qū)別名,意味著d
是d1
的一個(gè)別名,此時(shí)就不會(huì)再去無(wú)窮無(wú)盡的調(diào)用拷貝構(gòu)造啦。
??建議加const
因?yàn)榇嬖谟靡粋€(gè)const
對(duì)象去初始化創(chuàng)建一個(gè)新對(duì)象這種場(chǎng)景,所以建議在拷貝構(gòu)造函數(shù)的形參前面加上const
,此時(shí)普通的對(duì)象能用,const
對(duì)象也能用。
Data(const Data& d)//正確的拷貝構(gòu)造
{
_year = d._year;
_month = d._month;
_day = d._day;
}
const Data d1(2023, 7, 20);//定義一個(gè)日期類對(duì)象d1
Data d2(d1);
??編譯器生成的拷貝構(gòu)造干了什么?
上一節(jié)提到,拷貝構(gòu)造是一種默認(rèn)成員函數(shù),我們不寫(xiě)編譯器會(huì)自動(dòng)生成。編譯器生成的默認(rèn)拷貝構(gòu)造函數(shù),對(duì)內(nèi)置類型按照字節(jié)方式直接拷貝(也叫值拷貝
或淺拷貝
),對(duì)自定義類型是調(diào)用其拷貝構(gòu)造函數(shù)完成拷貝。
class Time//定義時(shí)間類
{
public:
Time()//普通構(gòu)造函數(shù)
{
_hour = 1;
_minute = 1;
_second = 1;
}
Time(const Time& t)//拷貝構(gòu)造函數(shù)
{
_hour = t._hour;
_minute = t._minute;
_second = t._second;
cout << "Time::Time(const Time&)" << endl;
}
private://成員變量
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本類型(內(nèi)置類型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定義類型
Time _t;
};
int main()
{
Date d1;
// 用已經(jīng)存在的d1拷貝構(gòu)造d2,此處會(huì)調(diào)用Date類的拷貝構(gòu)造函數(shù)
// 但Date類并沒(méi)有顯式定義拷貝構(gòu)造函數(shù),則編譯器會(huì)給Date類生成一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù)
Date d2(d1);
return 0;
}
??什么是淺拷貝
上面提到,編譯器生成的拷貝構(gòu)造函數(shù),會(huì)對(duì)內(nèi)置類型完成淺拷貝,淺拷貝就是以字節(jié)的方式,把一個(gè)字節(jié)里的內(nèi)容直接拷貝到另一個(gè)字節(jié)中。
??拷貝構(gòu)造函數(shù)可以不寫(xiě)嘛?
通過(guò)上面的分析可以得出:編譯器自己生成的構(gòu)造函數(shù)對(duì)內(nèi)置類型和自定義類型都做了處理。那是不是意味著我們就可以不寫(xiě)拷貝構(gòu)造函數(shù)了呢?答案是否定的,對(duì)于日期類,我們確實(shí)可以不寫(xiě),用編譯器自己生成的,但是對(duì)于一些需要深拷貝的對(duì)象,構(gòu)造函數(shù)是非寫(xiě)不可的。棧就是一個(gè)典型的需要我們自己寫(xiě)構(gòu)造函數(shù)的例子
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申請(qǐng)空間失敗");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType *_array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2(s1);
return 0;
}
上面定義了一個(gè)棧類Stack
,我們沒(méi)有寫(xiě)它的拷貝構(gòu)造函數(shù),編譯器會(huì)自動(dòng)生成一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù),棧中的成員變量都是內(nèi)置類型,默認(rèn)的拷貝構(gòu)造函數(shù)會(huì)對(duì)這三個(gè)成員變量都完成值拷貝(淺拷貝)。
此時(shí)淺拷貝的問(wèn)題在于:對(duì)象s1
和對(duì)象s2
中的_array
存的是同一塊空間的地址,他倆指向了同一塊空間,當(dāng)程序退出,往s1
或s2
中的任意一個(gè)對(duì)象push
值,另一個(gè)也會(huì)跟著改變。s1
和s2
要銷(xiāo)毀,s2
先銷(xiāo)毀,s2
銷(xiāo)毀時(shí)調(diào)用析構(gòu)函數(shù),已經(jīng)將0X11223344
這塊空間釋放了,但是s1
并不知道,到s1
銷(xiāo)毀的時(shí)候,會(huì)將0X11223344
這塊空間再釋放一次,一塊內(nèi)存空間多次釋放,最終就會(huì)導(dǎo)致程序崩潰。
??深拷貝
通過(guò)上面的分析可以看出,簡(jiǎn)單的淺拷貝不能滿足棧的需求,因此,對(duì)于棧,我們需要自己寫(xiě)一個(gè)拷貝構(gòu)造函數(shù),來(lái)實(shí)現(xiàn)深拷貝,深拷貝就是去堆上重新申請(qǐng)一塊空間,把s1
中_array
指向的空間中的內(nèi)容,拷貝到新申請(qǐng)的空間,再讓s2
中的_array
指向該空間。
//自己寫(xiě)的拷貝構(gòu)造函數(shù),實(shí)現(xiàn)深拷貝
Stack(const Stack& st)
{
DataType* tmp = (DataType*)malloc(sizeof(DataType) * st._capacity);
if (nullptr == tmp)
{
perror("malloc申請(qǐng)空間失敗");
return;
}
memcpy(tmp, st._array, sizeof(DataType) * st._size);
_array = tmp;
_size = st._size;
_capacity = st._capacity;
}
??總結(jié):
類中如果沒(méi)有涉及資源申請(qǐng)時(shí),拷貝構(gòu)造函數(shù)寫(xiě)不寫(xiě)都可以;一旦涉及到資源申請(qǐng)時(shí),拷貝構(gòu)造函數(shù)是一定要寫(xiě)的,否則就是淺拷貝,最終析構(gòu)的時(shí)候,就會(huì)釋放多次,造成程序崩潰。
??拷貝構(gòu)造函數(shù)典型的調(diào)用場(chǎng)景:
- 使用已存在對(duì)象創(chuàng)建新對(duì)象。
- 函數(shù)參數(shù)類型為類類型對(duì)象。
- 函數(shù)返回值為類類型對(duì)象。
class Data
{
public:
Data(int year = 1, int month = 1, int day = 1)
{
cout << "調(diào)用構(gòu)造函數(shù):" << this << endl;
cout << endl;
_year = year;
_month = month;
_day = day;
}
Data(const Data& d)
{
cout << "調(diào)用拷貝構(gòu)造:" << this << endl;
cout << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
~Data()
{
cout << "~Data()" << this << endl;
cout << endl;
}
private:
int _year;
int _month;
int _day;
//可以不用寫(xiě)析構(gòu),因?yàn)槿亲远x類型,并且沒(méi)有動(dòng)態(tài)申請(qǐng)的空間,這三個(gè)成員變量會(huì)隨著對(duì)象生命周期的結(jié)束而自動(dòng)銷(xiāo)毀
};
Data Text(Data x)
{
Data tmp;
return tmp;
}
int main()
{
Data d1(2023, 4, 29);
Text(d1);
return 0;
}
??總結(jié):
自定義類型在傳參的時(shí)候,形參最好用引用來(lái)接收,這樣可以避免調(diào)用拷貝構(gòu)造函數(shù),尤其是深拷貝的時(shí)候,會(huì)大大的提高效率,函數(shù)返回時(shí),如果返回的對(duì)象在函數(shù)棧幀銷(xiāo)毀后還在,最好也用引用返回。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-599611.html
??結(jié)語(yǔ):
?今天的分享到這里就結(jié)束啦!如果覺(jué)得文章還不錯(cuò)的話,可以三連支持一下,您的支持就是春人前進(jìn)的動(dòng)力!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-599611.html
到了這里,關(guān)于【C++雜貨鋪】拷貝構(gòu)造函數(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!