C++中復(fù)制構(gòu)造函數(shù)與重載賦值操作符的深入分析
我們現(xiàn)在著重來分析一下為什么復(fù)制構(gòu)造函數(shù)與重載賦值操作符在沒有定義的情況下,編譯器會為我們生成一個,這說明這兩個函數(shù)是一個類必不可少的部分。由此可知如果一個類沒有定義任何的東西,編譯器也會幫助我們生成下面的4個函數(shù):1、一個構(gòu)造函數(shù),也就是所謂的類名比如classname(),這是在沒有定義構(gòu)造函數(shù)時,編譯器會自動生成的。2、析構(gòu)函數(shù),這個前面已經(jīng)提到了。3、復(fù)制構(gòu)造函數(shù)。4、重載賦值操作符。
為什么復(fù)制構(gòu)造函數(shù)和重載賦值操作符如此重要呢?
首先介紹一下復(fù)制構(gòu)造函數(shù)與重載賦值操作符的聲明形式,這兩個函數(shù)的參數(shù)是固定的,是不能改變的。假設(shè)存在一個類Base。那么它的這兩個函數(shù)聲明分別如下所示:
class Base
{
public:
//構(gòu)造函數(shù)
Base();
//復(fù)制構(gòu)造函數(shù)
Base(const Base &);
//重載賦值操作符
Base &operator=(const Base &);
//析構(gòu)函數(shù)
~Base();
...
private:
...
};
面的形式可以知道,復(fù)制構(gòu)造函數(shù)與重載賦值操作符的參數(shù)是一致的,都是該類的const引用類型。為什么不能重載呢?這些都是存在的問題。我覺得要搞清楚這些問題,首先我們就不能只能片面的去理解,而是應(yīng)該對C++的面向?qū)ο缶幊逃辛艘粋€較好的理解以后再來分析這個問題。
么是引用,很多書上都會說是為了避免復(fù)制,這是其中的一個原因,如果說只是這個原因,完全可以只用指針就可以了,對于系統(tǒng)而言,不在乎多幾個指針的存儲空間。
首先說明一下在C++中的繼承問題,一般而言我們的繼承主要是指公共繼承,也就說如下的派生類定義所示:
class Derived : public Base
{
public:
...
private:
...
}
公共繼承而言,基類的公共部分成為了派生類的公共部分,私有部分成為了派生類的私有部分。也就是說派生類中包含基類的部分。也就是說在實現(xiàn)派生類的過程中,基類的公用接口會被派生類直接繼承。而基類的私有成員也作為派生類的私有成員。但是對于其他的繼承類型,我就不去討論啦。這時派生類的成員可以訪問到基類的私有成員。也就是說相當(dāng)于派生類是在基類的基礎(chǔ)上增加了自己的特色。
需要介紹一個問題就是采用派生類對象的引用初始化基類的引用。多態(tài)性的動態(tài)綁定中存在兩個條件:1、必須是virtual函數(shù)(虛函數(shù)),2、必須是通過基類的引用或者基類的指針進(jìn)行成員虛函數(shù)的調(diào)用。
只有上面兩個條件滿足才能發(fā)生動態(tài)調(diào)用問題。
由于派生類中存在基類的成員,也就相當(dāng)于一個派生類對象中包含了一個基類對象(我不知道這樣是否合理),所以可以采用一個基類引用來綁定一個派生類對象。引用實質(zhì)上是針對一塊內(nèi)存區(qū)域,引用是一個標(biāo)號,是這塊內(nèi)存區(qū)域的一個名字,一個引用與一塊內(nèi)存區(qū)域綁定,因為派生對象中存在基類部分,可以認(rèn)為派生對象的區(qū)域中存在基類對象,這時可用基類的引用來表明這塊內(nèi)存區(qū)域,即采用一個基類的別名來表示(綁定)這段內(nèi)存區(qū)域,派生對象的地址(這段內(nèi)存)以及內(nèi)容都沒有發(fā)生改變,也沒有重現(xiàn)創(chuàng)造出一個新的對象,基類的引用還是指向這個派生對象。對于指針的分析方式相似。因此可以采用基類的引用綁定派生類對象。
但是如何實現(xiàn)派生類對象到基類的轉(zhuǎn)換呢?
這時候的轉(zhuǎn)換與前面的綁定存在很大的差別,因為這是重新分配一個基類對象,而不再是引用問題,不再是綁定問題,是依據(jù)一個派生類對象生成一個新的基類對象。因為派生類對象中存在一個基類對象基本的信息,完全可以生成一個基類對象,完全將此過程看作是一個初始化或者賦值的問題。也就是采用派生類創(chuàng)建一個新的對象或者賦值一個對象。
從上面的分析我們可以采用下面的形式來實現(xiàn):
Base(const Derived &);
Base &operator=(const Derived &);
是在基類函數(shù)中采用構(gòu)造函數(shù)基于派生類來重載一系列的構(gòu)造函數(shù),但是這也存在一個問題,如果存在很多派生類,這時候就要重載很多構(gòu)造函數(shù),這肯定不是我們需要的。
這時候是否發(fā)現(xiàn)與前面的問題關(guān)聯(lián)起來了?
這時候我們發(fā)現(xiàn)對于一個類而言,為什么復(fù)制構(gòu)造函數(shù)和重載賦值操作符這么重要了。因為這兩個函數(shù)都是接受一個基類的引用,根據(jù)前面的分析我們知道一個基類引用完全可以綁定一個派生類的對象,而派生類對象中又包含了一個基類對象的基本信息。我們能夠?qū)崿F(xiàn)一個從一個派生對象到基類的構(gòu)造過程。
我們用一個基類引用綁定一個派生對象,然后采用基類引用對基類成員進(jìn)行訪問,完成了一個基類對象基本要素的填充操作,相當(dāng)于完成了基類對象的創(chuàng)建,也就是構(gòu)造問題。這樣也就能完成由派生類對象到基類對象的構(gòu)造過程。
至于為什么是一個const的引用,我認(rèn)為主要是因為不去修改派生類對象和擴大能夠接受的參數(shù)情況,由const引用可以綁定不同類型的對象特性,說明const引用會擴大接受實參的能力。
現(xiàn)在我可以總結(jié)起來說了,因為在復(fù)制構(gòu)造函數(shù)中,C++中的基類引用可以綁定一個派生類的對象,如果在允許訪問的情況下,采用基類引用可以訪問基類的成員以及派生類的其他成員,采用引用可以復(fù)制派生類對象中基類成員的值到新創(chuàng)建的基類成員中,完成一個基類成員數(shù)據(jù)的填充操作,這時候一個完整的基類對象就創(chuàng)建完成了。
重載賦值操作符則是發(fā)生在使用一個派生對象來賦值一個基類對象時,這時候也是const基類引用綁定一個派生類對象,然后復(fù)制對應(yīng)的基類成員到基類對象對于的成員中,完成一個基類對象成員的更新操作。
來說,復(fù)制構(gòu)造函數(shù)不僅僅實現(xiàn)了同類型之間的初始化操作,同時也完成了采用一個派生類對象初始化一個基類對象的操作,重載賦值操作符實現(xiàn)了同類型之間的賦值操作,也完成了采用派生類對象賦值基類對象的操作。如果沒有這兩個函數(shù)的存在,也就不能完成派生類到基類的賦值和初始化操作。這也是為什么一定會存在這兩個函數(shù)的原因。
評論