商業(yè)網(wǎng)站改版需要多久愛站網(wǎng)綜合查詢
【歡迎關(guān)注編碼小哥,學(xué)習(xí)更多實用的編程方法和技巧】
1、類的繼承
子類對象在創(chuàng)建時會首先調(diào)用父類的構(gòu)造函數(shù)
父類構(gòu)造函數(shù)執(zhí)行結(jié)束后,執(zhí)行子類的構(gòu)造函數(shù)
當(dāng)父類的構(gòu)造函數(shù)有參數(shù)時,需要在子類的初始化列表中顯式調(diào)用
Child(int i) : Parent("在子類構(gòu)造函數(shù)中的初始化列表進行顯式調(diào)用父類構(gòu)造函數(shù)")
析構(gòu)函數(shù)調(diào)用的先后順序與構(gòu)造函數(shù)相反
繼承與組合的混搭
類中的成員變量可以是其它類的對象(組合)
口訣:先父母,后客人,再自己。
當(dāng)子類中定義的成員變量與父類中的成員變量同名時
子類依然從父類繼承同名成員
在子類中通過作用域分別符::進行同名成員區(qū)分
同名成員存儲在內(nèi)存中的不同位置
cout << "Parent::i = " << Parent::i << endl;
cout << "Child::i = " << Child::i << endl;
cout << "Parent::f = " << Parent::f << endl;
子類對象可以當(dāng)作父類對象使用
子類對象在創(chuàng)建時需要調(diào)用父類構(gòu)造函數(shù)進行初始化
子類對象在銷毀時需要調(diào)用父類析構(gòu)函數(shù)進行清理
先執(zhí)行父類構(gòu)造函數(shù),再執(zhí)行成員構(gòu)造函數(shù)
在繼承中的析構(gòu)順序與構(gòu)造順序?qū)ΨQ相反
同名成員通過作用域分辨符進行區(qū)分
2、函數(shù)的重寫
父類中被重寫的函數(shù)依然會繼承給子類
默認(rèn)情況下子類中重寫的函數(shù)將隱藏父類中的函數(shù)
通過作用域分辨符::可以訪問到父類中被隱藏的函數(shù)
Parent *p = &child;
p->print();
Parent& rP = child;
rP.print();
打印輸出的是父類的函數(shù)。
C++編譯器支持靜態(tài)聯(lián)編,就是在編譯階段就可以確定下來的多態(tài)。
對于動態(tài)聯(lián)編,只有在運行時才能確定的對象類型,編譯器是不能作判斷的,最穩(wěn)妥的做法就是使用父類的類型進行操作。
C++與C相同,是靜態(tài)編譯型語言
在編譯時,編譯器自動根據(jù)指針的類型判斷指向的是一個什么樣的對象
所以編譯器認(rèn)為父類指針指向的是父類對象(根據(jù)賦值兼容性原則,這個假設(shè)合理)
由于程序沒有運行,所以不可能知道父類指針指向的具體是父類對象還是子類對象
從程序安全的角度,編譯器假設(shè)父類指針只指向父類對象,因此編譯的結(jié)果為調(diào)用父類的成員函數(shù)
面向?qū)ο蟮男滦枨?/p>
根據(jù)實際的對象類型來判斷重寫函數(shù)的調(diào)用
如果父類指針指向的是父類對象則調(diào)用父類中定義的函數(shù)
如果父類指針指向的是子類對象則調(diào)用子類中定義的重寫函數(shù)
實現(xiàn)了以上的功能就是面向?qū)ο笾械亩鄳B(tài)。
多態(tài)
根據(jù)運行時實際的對象類型表現(xiàn)出不同的行為狀態(tài),叫多態(tài)。
C++中通過virtual關(guān)鍵字對多態(tài)進行支持
使用virtual聲明的函數(shù)被重寫后即可展現(xiàn)多態(tài)特性
虛函數(shù)
在父類函數(shù)聲明前面加上virtual關(guān)鍵字,使其成為虛函數(shù)。這時,函數(shù)就表現(xiàn)出多態(tài)性。
函數(shù)重載
必須在同一個類中進行
子類無法重載父類的函數(shù),父類同名函數(shù)將被覆蓋
重載是在編譯期間根據(jù)參數(shù)類型和個數(shù)決定調(diào)用函數(shù)
函數(shù)重寫
必須發(fā)生于父類與子類之間
并且父類與子類中的函數(shù)必須有完全相同的原型
使用virtual聲明之后能夠產(chǎn)生多態(tài),子類在重寫時自動成為虛函數(shù)
多態(tài)是在運行期間根據(jù)具體對象的類型決定調(diào)用函數(shù)
隱藏:
派生類中的函數(shù)與基類中的函數(shù)同名并且參數(shù)相同,但基類函數(shù)不是虛函數(shù)
派生類中的函數(shù)與基類中的函數(shù)同名,參數(shù)不同,不管基類函數(shù)是否是虛函數(shù),基類函數(shù)都會被屏蔽。
child.Parent::func(); //使用作用域分別符可以調(diào)用
child.func(); //不可直接調(diào)用,因為父類的同名函數(shù)被隱藏。
3、C++中多態(tài)的實現(xiàn)原理
當(dāng)類中聲明虛函數(shù)時,編譯器會在類中生成一個虛函數(shù)表
虛函數(shù)表是一個存儲類成員函數(shù)指針的數(shù)據(jù)結(jié)構(gòu)
虛函數(shù)表是由編譯器自動生成與維護的
virtual成員函數(shù)會被編譯器放入虛函數(shù)表中
存在虛函數(shù)時,每個對象中都有一個指向虛函數(shù)表的指針VPTR
每一個類都會由編譯器自動生成一個虛函數(shù)表,而且每個類只有唯一一個表。此類生成的每一個對象里面都隱含著一個指向該表的指針。
調(diào)用虛函數(shù)時,會通過VPTR指針指向的虛函數(shù)表中查詢該函數(shù),找到入口地址,并調(diào)用。這個過程相對比較耗時,因此執(zhí)行效率相對比較低,因此,沒有必要把所有的函數(shù)都設(shè)計為虛函數(shù)。
對象在創(chuàng)建的時候由編譯器對VPTR指針進行初始化
只有當(dāng)對象的構(gòu)造完全結(jié)束后VPTR的指向才最終確定
父類對象的VPTR指向父類虛函數(shù)表
子類對象的VPTR指向子類虛函數(shù)表
構(gòu)造函數(shù)中調(diào)用虛函數(shù)無法實現(xiàn)多態(tài)。
純虛函數(shù)
面向?qū)ο笾械某橄箢?/p>
抽象類可用于表示現(xiàn)實世界中的抽象概念
抽象類是一種只能定義類型,而不能產(chǎn)生對象的類
抽象類只能被繼承并重寫相關(guān)函數(shù)
抽象類的直接特征是純虛函數(shù)
純虛函數(shù)是只聲明函數(shù)原型,而故意不定義函數(shù)體的虛函數(shù)。
統(tǒng)一的格式:
virtual 返回類型 函數(shù)名(參數(shù)列表)= 0;
抽象類與純虛函數(shù)
包含著純虛函數(shù)的類叫抽象類
抽象類不能用于定義對象
抽象類只能用于定義指針和引用
抽象中的純虛函數(shù)必須被子類重寫
class Shape{public:virtual double area() = 0;};class Rectangle : public Shape{public:Rectangle(double a, double b){m_a = a;m_b = b;}double area(){return m_a * m_b;}private:double m_a;double m_b;};class Circle : public Shape{private:double m_r;public:Circle(double r){m_r = r;}double area(){return 3.14 * m_r * m_r;}};void func(Shape *s){cout << s->area() << endl;}int main(int argc, char *argv[]){Rectangle rect(3,2);Circle c(4);func(&rect);func(&c);return EXIT_SUCCESS;}多態(tài)與數(shù)組class Parent{protected:int i;public:virtual void func(){cout << "Parent::func()" << endl;}};class Child : public Parent{protected:int j;public:Child(int a, int b){i = a;j = b;}void func(){cout << "i = " << i << " , j = " << j << endl;}};int main(int argc, char *argv[]){Parent *pp = NULL;Child *pc = NULL;Child ca[] = {Child(1,2),Child(3,4),Child(5,6),Child(7,8)};pp = ca;pc = ca;cout << "sizeof(Parent) = " << sizeof(Parent) << endl;cout << "sizeof(Child) = " << sizeof(Child) << endl;cout << setbase(16) << "pp = " << pp << endl;cout << setbase(16) << "pc = " << pc << endl;pp->func();pc->func();pp++;pc++;cout << setbase(16) << "pp = " << pp << endl;cout << setbase(16) << "pc = " << pc << endl;//pp->func();//pc->func();return EXIT_SUCCESS;}
注:Parent類對象占有8個字節(jié),因為需要維護一個虛函數(shù)表的指針也占4個字節(jié)。同樣的,Child對象除自身的變量j以外,還要從父類繼承一個變量i,也要維護一個虛函數(shù)表的指針,一共12個字節(jié)。
不要將多態(tài)應(yīng)用于數(shù)組
指針運算是通過指針的類型進行的 (編譯時確定)
多態(tài)是通過虛函數(shù)表實現(xiàn)的 (運行時確定)
虛基類及多重繼承
被實際開發(fā)經(jīng)驗拋棄的多繼承
工程開發(fā)中真正意義上的多繼承是幾乎不被使用的
多重繼承帶來的代碼復(fù)雜性遠(yuǎn)多于其帶來的便利
多重繼承對代碼維護性上的影響是災(zāi)難性的
在設(shè)計方法上,任何多繼承都可以用單繼承代替
為了解決從不同途徑繼承來的同名數(shù)據(jù)成員造成的二義性問題 , 可以將共同基類設(shè)置為虛基類 。 這時從不同的路徑繼承過來的同名數(shù)據(jù)成員在內(nèi)存中就只有一個拷貝。
class B : virtual public A
{
};
class C : virtual public A
{
};
這就是虛基類的來源。
C++的接口設(shè)計
實際工程經(jīng)驗證明
多重繼承接口不會帶來二義性和復(fù)雜性問題
多重繼承可以通過精心設(shè)計用單繼承和接口代替
接口類只是一個功能說明,而不是功能實現(xiàn) 。
子類需要根據(jù)功能說明定義功能實現(xiàn) 。
絕大多數(shù)面向?qū)ο笳Z言都不支持多繼承,但都支持接口的概念
C++中沒有接口的概念
C++中可以使用純虛函數(shù)實現(xiàn)接口
class Interface
{
public:
virtual void func1() = 0;
virtual void func2(int i) = 0;
virtual void func3(int i, int j) = 0;
};
接口類中只有函數(shù)原型的定義,沒有任何數(shù)據(jù)的定義。