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