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