說(shuō)明:
? <u>不是很清楚的點(diǎn)</u>,用下劃線临庇。
? 解答,用斜體假夺;
? 重點(diǎn)淮蜈,用粗體加粗已卷;
第四章 Function 語(yǔ)意學(xué)
4.1 Member的各種調(diào)用方式
1. Nonstatic Member Functions
實(shí)際上編譯器是將member function被內(nèi)化為nonmember的形式,經(jīng)過(guò)下面轉(zhuǎn)化步驟:
- 給函數(shù)添加額外參數(shù)——this侧蘸;
- 將對(duì)每一個(gè)nonstaitc data member的存取操作改為this指針來(lái)存取讳癌;
- 將member function 重寫成一個(gè)外部函數(shù)。對(duì)函數(shù)名采用mangling 處理晌坤,使之成為獨(dú)一無(wú)二的語(yǔ)匯逢艘;
2. Virtual Member Functions(虛成員函數(shù))
將
? ptr->f(); //f()為虛成員函數(shù)
內(nèi)部轉(zhuǎn)化為
? (*ptr->vptr[1])(ptr);
其中:
- vptr表示編譯器產(chǎn)生的指針泡仗,指向virtual table埋虹。它被安插在每一個(gè)聲明有(或繼承自)一個(gè)或多個(gè)virtual functions 的class object 中。
- 1 是virtual table slot的索引值娩怎,關(guān)聯(lián)到normalize()函數(shù)搔课。
- 第二個(gè)ptr表示this指針。
3. Static Member Functions (靜態(tài)成員函數(shù))
Static Member Functions的主要特性就是它沒(méi)有this指針
次要特性:
- 不能被聲明為const、volatile爬泥、virtual柬讨;
- 不能夠直接存取其class中的非靜態(tài)數(shù)據(jù)成員;
Static Member Functions由于缺乏this指針袍啡,因此差不多等同于非成員函數(shù)踩官。
如果取一個(gè)static member function 的地址,獲得的是其在內(nèi)存的位置(也就是地址)境输,而不是一個(gè)指向“class member function”的指針蔗牡,如下:
&Point::count();
會(huì)得到一個(gè)數(shù)值,類型是:
unsigned int(*)();
而不是:
unsigned int(Point::*)();
4.2 Virtual Member Functions
C++中嗅剖,多態(tài)表示以“一個(gè)public base class 的指針(或reference)辩越,尋址出一個(gè)derived class object”。
為了在執(zhí)行期調(diào)用正確的虛函數(shù)信粮,在編譯時(shí)期:
- 可以在每一個(gè)多態(tài)的class object身上增加兩個(gè)members:
- 一個(gè)字符串或數(shù)字黔攒,表示class的類型;
- 一個(gè)指針vptr强缘,指向某一個(gè)表格督惰,表格中持有程序的虛函數(shù)們的執(zhí)行期地址;
- 每一個(gè)虛函數(shù)都被指派一個(gè)表格索引值旅掂;
那么赏胚,表格中的虛函數(shù)們地址如何被購(gòu)建起來(lái)?
在C++中辞友,虛函數(shù)們可經(jīng)由其class object被調(diào)用栅哀,可以在編譯時(shí)期獲知。此外称龙,這一組地址是固定不變的,執(zhí)行期不可能新增或替換之戳晌。由于程序執(zhí)行時(shí)鲫尊,表格的大小和內(nèi)容都不會(huì)改變,所以其建構(gòu)和存取皆可以由編譯器完全掌控沦偎。不需要執(zhí)行期的任何介入疫向。**
而執(zhí)行期要做的,只是在特定的虛函數(shù)表slot中激活虛函數(shù)豪嚎。
每一個(gè)class 只會(huì)有一個(gè)virtual table搔驼,每一個(gè)table 含有對(duì)應(yīng)的class object中所有active virtual functions 函數(shù)實(shí)體地址。這些active virtual function 包括:
- 這個(gè)class 所定義的函數(shù)實(shí)體(包括改寫一個(gè)可能存在的base class virtual function函數(shù)實(shí)體)舌涨。
- 繼承自base class 的函數(shù)實(shí)體(不被derived class改寫)
- 一個(gè)pure_virtual_called()扔字。
繼承過(guò)程中温技,virtual table的三種可能性:
- 繼承base class 所聲明的virtual functions的函數(shù)實(shí)體。正確地說(shuō)舵鳞,是該函數(shù)實(shí)體的地址會(huì)被拷貝到derived class的virtual table相對(duì)應(yīng)的slot之中。
- 使用自己的函數(shù)實(shí)體蜓堕。這表示它自己的函數(shù)實(shí)體地址必須放在對(duì)應(yīng)的slot之中博其。
- 可以加入一個(gè)新的virtual function。這時(shí)候virtual table 的尺寸會(huì)增大一個(gè)slot放進(jìn)這個(gè)函數(shù)實(shí)體地址霜旧。
編譯時(shí)期設(shè)定virtual function的調(diào)用:
ptr->z();
- 一般而言儡率,我并不知道ptr 所指對(duì)象的真正類型。然而可以經(jīng)由ptr 可以存取到該對(duì)象的virtual table儿普。
- 雖然我不知道哪個(gè)Z()函數(shù)實(shí)體被調(diào)用,但知道每一個(gè)Z()函數(shù)地址都被放置某一個(gè)slot(如slot 4)的索引个绍。
這樣我們就可以將
ptr->z();
轉(zhuǎn)化為:
(*ptr->vptr[4])(ptr);
唯一一個(gè)在執(zhí)行期才能知道的東西是:slot4所指的到底是哪一個(gè)class object的z()函數(shù)實(shí)體浪汪。
多重繼承下的 Virtual Functions
在多重繼承中支持virtual functions,其復(fù)雜度圍繞在第二個(gè)及其后面的base class 上广恢,以及“必須在執(zhí)行期調(diào)整this 指針”這一點(diǎn)呀潭。
一般規(guī)則是,經(jīng)由指向“第二或后繼base class 的指針”來(lái)調(diào)用derived class virtual function钠署。調(diào)用操作連帶的“必要的this指針調(diào)整”操作,必須在執(zhí)行期完成舰蟆。
在多重繼承下,一個(gè)派生類內(nèi)含n-1
個(gè)額外的虛函數(shù)表信卡,n
表示其上一層基類的個(gè)數(shù)(因此,單一繼承將不會(huì)有額外的虛函數(shù)表)傍菇〗缗猓【若是雙重繼承,那么就會(huì)有一個(gè)與派生類共享的虛函數(shù)表】
如下圖:
【注】
- base1的虛函數(shù)表是主要表格咐低,base2的是次要表格袜腥;
- 函數(shù)后面有加*,表示這不是真實(shí)的函數(shù)實(shí)例鲤屡,是需要加this指針指向真正的函數(shù)實(shí)例。
4.3 函數(shù)的效能
non-member酒来、static member或non-static member函數(shù)都被轉(zhuǎn)換為完全相同形式肪凛,所以三者效率完全相同。
4.4 指向Member Function的指針
取一個(gè)non-static data member的地址翘鸭,得到的結(jié)果是該member在class 布局中的bytes位置(再加1)远荠,所以它需要綁定于某個(gè)class object的地址上,才能夠被存取譬淳。
取一個(gè)non-static member function的地址邻梆,如果該函數(shù)是non-virtual绎秒,則得到的是內(nèi)存的真正地址,然后這個(gè)值也是不完全的,也需要綁定于某個(gè)class object的地址上蠢涝,才能夠調(diào)用函數(shù)阅懦。(所有的non-static member function都需要對(duì)象的地址,以參數(shù)this指出)
支持“指向Virtual Member Function”之指針
對(duì)于一個(gè)virtual function耳胎,其地址在編譯時(shí)期是未知的,所能知道的僅是virtual function在其相關(guān)之virtual table的索引值废登,也就是說(shuō)郁惜,對(duì)于一個(gè)virtual member function 取其地址,所能獲得的只是一個(gè)索引值兆蕉。
4.5 Inline Function
形參:
- 傳入?yún)?shù),直接替換半醉;
- 傳入常量劝术,連替換都省了,直接變成常量养晋;
- 傳入函數(shù)運(yùn)行結(jié)果,則需要導(dǎo)入臨時(shí)變量逊抡,以避免重復(fù)求值;
局部變量:
一般而言冒嫡,inline函數(shù)中的每一個(gè)局部變量都必須被放在函數(shù)調(diào)用的一個(gè)封閉區(qū)段中四苇,擁有一個(gè)獨(dú)一無(wú)二的名稱。
如果一次性調(diào)用N次蟀架,就會(huì)出現(xiàn)N個(gè)臨時(shí)變量……程序的體積會(huì)暴增,如下:
minval = min( val1, val2 ) + min( foo(), foo()+1 );
因此要以分離的多個(gè)式子被調(diào)用片拍。