說(shuō)明:
? <u>不是很清楚的點(diǎn)</u>,用下劃線。
? 解答,用斜體;
? 重點(diǎn),用粗體加粗;
第五章 構(gòu)造、析構(gòu)、拷貝 語(yǔ)意學(xué)
5.1 純虛函數(shù)
-
擁有純虛函數(shù)的類(lèi),為抽象類(lèi),不可能擁有實(shí)例(不可能創(chuàng)造出一個(gè)對(duì)象)。
但若抽象類(lèi)中有數(shù)據(jù)成員,則需要一個(gè)顯式的構(gòu)造函數(shù)去初始化它。
不要把析構(gòu)函數(shù)聲明為 pure(純)。
不要給一個(gè)虛函數(shù)后面加 const。
5.2 “無(wú)繼承”情況下的對(duì)象構(gòu)造
當(dāng)一個(gè)class導(dǎo)入一個(gè)虛函數(shù)時(shí),會(huì)發(fā)生下列事情:
- 每一個(gè)class object多負(fù)擔(dān)一個(gè)vptr;
- 自己定義的構(gòu)造函數(shù)被附加了一些代碼,實(shí)現(xiàn)vptr的初始化;
- 合成拷貝構(gòu)造函數(shù)、賦值構(gòu)造函數(shù),因?yàn)関ptr不能用默認(rèn)的bitwise方式復(fù)制了;
5.3 繼承體系下的對(duì)象構(gòu)造
在繼承下,編譯器會(huì)擴(kuò)充每一個(gè)constructor,擴(kuò)充程度視繼承體系而定。
constructor的調(diào)用伴隨了哪些步驟?
初始化列表(member initialization list)的data members初始化操作會(huì)被放進(jìn)constructor的函數(shù)本身,并以members的聲明順序?yàn)轫樞?/strong>。(如*this->x = 0;)
如果有一個(gè)member并沒(méi)有在初始化列表中,但它在一個(gè)default constructor,那么該default constructor 必須被調(diào)用(手動(dòng))。
在那之前,如果class object有virtual table pointer(s),它(們)必須被設(shè)定初始值,指定適當(dāng)?shù)膙irtual table(s)。
-
在那之前,所有上一層的base class constructors 必須被調(diào)用,以base class 的聲明順序?yàn)轫樞颍ㄅc初始化列表的順序沒(méi)有關(guān)聯(lián))。
如果base class 被列于初始化列表中,那么任何明確指定參數(shù)都應(yīng)該傳遞過(guò)去。
如果base class 沒(méi)有列于初始化列表,那么調(diào)用default constructor。
如果base class 是多重繼承下的第二或后面的base class ,那么this指針必須有所調(diào)整。
-
<u>在那之前,所有 virtual base class constructors 必須被調(diào)用,從左到右,從最深到最淺。</u>
- 如果class 被列于初始化列表中,那么如果有任何明確指定的參數(shù),都應(yīng)該傳遞過(guò)去,若沒(méi)有列于初始化列表中,則調(diào)用default constructor。
- 此外,class中的每一個(gè)virtual base class subobject的偏移量必須在執(zhí)行期可存取。
- 如果class object 是最底層的class,某constructors可能被調(diào)用;某些用以支持這個(gè)行為的機(jī)制必須被放進(jìn)來(lái)。
【注】在那之前,是指在用戶(hù)代碼執(zhí)行前。
其中的虛擬繼承
為了防止重復(fù)對(duì)virtual base class調(diào)用構(gòu)造函數(shù),規(guī)定:只有在繼承體系最深層的object才可以對(duì)virtual父類(lèi)進(jìn)行調(diào)用構(gòu)造初始化。
其中的vptr語(yǔ)意學(xué)
vptr在constructor何時(shí)被初始化?
在base class constructors調(diào)用操作之后,但是在程序員供應(yīng)的碼或是初始化列表中所列的members初始化操作之前。
5.4 對(duì)象復(fù)制語(yǔ)意學(xué)
在復(fù)制操作時(shí),需要一個(gè)面對(duì)自我拷貝的過(guò)濾過(guò)程:
if( this == &rhs) return *this;
當(dāng)設(shè)計(jì)一個(gè)class,并以一個(gè)class object 指定另一個(gè)class object時(shí),有三種選擇:
什么都不做,實(shí)施默認(rèn)行為。
提供一個(gè)explicit copy assignment operator。
明確拒絕一個(gè)class object指定給另一個(gè)class object。
一個(gè)class對(duì)于默認(rèn)的copy assignment operator,在以下情況下不會(huì)表現(xiàn)出 bitwise copy語(yǔ)意:
當(dāng)一個(gè)class的 base class 有一個(gè)copy assignment operator時(shí),
當(dāng)一個(gè)class 的 member object,而其 class 有一個(gè) copy assignment operator 時(shí),
當(dāng)一個(gè)class 聲明了任何 virtual functions 時(shí),
當(dāng)class繼承一個(gè) virtual base class 時(shí)。但盡可能不要允許一個(gè)virtual base class的拷貝操作,也盡量不要在其中聲明數(shù)據(jù)。
構(gòu)造這樣的一個(gè)繼承體系:
class Base {
public: virtual ~Base() {}
virtual void show() { cout << "Base" << endl; }
};
class Derived : public Base {
public: void show() { cout << "Derived" << endl; }
};
子類(lèi)Derived類(lèi)重寫(xiě)了基類(lèi)Base中的show方法。 編寫(xiě)下面的測(cè)試代碼:
Base b;
Derived d;
b.show();
d.show();
結(jié)果是:
Base
Derived
Base的對(duì)象調(diào)用了Base的方法,而Derived的對(duì)象調(diào)用了Derived的方法。因?yàn)橹苯佑脤?duì)象來(lái)調(diào)用成員函數(shù)時(shí)不會(huì)開(kāi)啟多態(tài)機(jī)制,故編譯器直接根據(jù)b和d各自的類(lèi)型就可以確定調(diào)用哪個(gè)show函數(shù)了,也就是在這兩句調(diào)用中,編譯器為它們每一個(gè)都確定了一個(gè)唯一的入口地址。這實(shí)際上類(lèi)似于一個(gè)重載多態(tài),雖然這兩個(gè)show函數(shù)擁有不同的作用域。 那這樣呢: Base b; Derived d; b.show(); b = d; b.show(); 現(xiàn)在,一個(gè)Base的對(duì)象被賦值為子類(lèi)Derived的對(duì)象。
那這樣呢:
Base b;
Derived d;
b.show();
b = d;
b.show();
現(xiàn)在,一個(gè)Base的對(duì)象被賦值為子類(lèi)Derived的對(duì)象。
結(jié)果是:
Base
Base
對(duì)于熟悉Java的人而言,這不可理解。但實(shí)際上,C++不是Java,它更像C?!癰 = d”的意思,并不是Java中的“讓一個(gè)指向Base類(lèi)的引用指向它的子類(lèi)對(duì)象”,而是“把Base類(lèi)的子類(lèi)對(duì)象中的Base子對(duì)象分割出來(lái),賦值給b”。所以,只要b的類(lèi)型始終是Base,那么b.show()調(diào)用的永遠(yuǎn)都是Base類(lèi)中的show函數(shù);換句話說(shuō),編譯器總是把Base中的那個(gè)show函數(shù)的入口地址作為b.show()的入口地址。這根本就沒(méi)用上多態(tài)。
單繼承下的重寫(xiě)多態(tài)
那我們?cè)龠@樣:
Base b;
Derived d;
Base *p = &b;
p->show();
p = &d;
p->show();
這時(shí),結(jié)果就對(duì)了:
Base
Derived
p是一個(gè)指向基類(lèi)對(duì)象的指針,第一次它指向一個(gè)Base對(duì)象,p->show()調(diào)用了Base類(lèi)的show函數(shù);而第二次它指向了一個(gè)Derived對(duì)象,p->show()調(diào)用了Derived類(lèi)的show函數(shù)。
總結(jié):也就是說(shuō),只有是指針或者引用才是真正的多態(tài),將子對(duì)象賦給父類(lèi)對(duì)象其實(shí)類(lèi)型向上轉(zhuǎn)型
個(gè)人覺(jué)得C++容易弄混淆的地方(持續(xù)更新):
1.const和指針的修飾問(wèn)題
const char * a; //一個(gè)指針a指向const char
char const *a; //這兩個(gè)是a指向的內(nèi)容是常量,不能改變
char * const a; //首先a 是指針然后還是const
const (char*) a; //這兩個(gè)是a指針本身是常量,指針本身不能改變
其實(shí),可以看出如果const修飾的char(也就是類(lèi)型本身或者是 *variable對(duì)指針的解引用)就是指針指向的內(nèi)容是常量,反之就是修飾指針本身的。那我們可以總結(jié)一個(gè)識(shí)別方法就是:看const 兩邊(當(dāng)然有的只有一邊)的類(lèi)型是類(lèi)型(指針指向的內(nèi)容)就是類(lèi)型變量本身是常量(如const char * a和char const a 的const兩邊是char,a)。
當(dāng)然兩者都是常量就是:const char * const a;第一個(gè)const是類(lèi)型常量,第二個(gè)才是指針常量。同樣給出 const char &a ;const char *a;在傳遞參數(shù)時(shí)使用。
2.數(shù)組和指針的組合問(wèn)題
char * a[M]; 這是指針數(shù)組,就是每一個(gè)元素是指針的數(shù)組,每個(gè)元素都要初始化。a[M]一看就是數(shù)組,這個(gè)數(shù)組每一個(gè)元素是char *,所以可以將char *擴(kuò)展為一維數(shù)組然后a[M]就是二維數(shù)組了。其實(shí)就是M個(gè)指針。
char (a)[N]; 這是一個(gè)指針,這個(gè)指針指向N個(gè)char元素,即指向數(shù)組的指針,其實(shí)就是一個(gè)指針。把(a)看著一個(gè)變量,這個(gè)變量是指向N個(gè)元素的指針,所以只是一個(gè)一維數(shù)組。把char (*a)[N]看成是char b[N]就可以了。
3.C++變量的初始化
對(duì)于內(nèi)置類(lèi)型局部變量不進(jìn)行初始化,但是分配地址,全局變量會(huì)進(jìn)行默認(rèn)初始化。對(duì)于類(lèi)類(lèi)型局部變量(沒(méi)有顯式初始化)會(huì)進(jìn)行默認(rèn)初始化(有默認(rèn)構(gòu)造函數(shù),否則報(bào)錯(cuò)),但其內(nèi)部的內(nèi)置數(shù)據(jù)成員不會(huì)進(jìn)行初始化(如果在默認(rèn)構(gòu)造函數(shù)沒(méi)有進(jìn)行初始化)。數(shù)組也是同樣。
參考文章
《深度探索C++對(duì)象模型(Inside The C++ Object Model )》學(xué)習(xí)筆記:https://dsqiu.iteye.com/blog/1669614
c++,為什么要引入虛擬繼承 :https://www.cnblogs.com/mylinux/p/4725833.html
RTTI、虛函數(shù)和虛基類(lèi)的實(shí)現(xiàn)方式、開(kāi)銷(xiāo)分析及使用指導(dǎo):http://baiy.cn/doc/cpp/inside_rtti.htm