c++是基於面向對象的編程語言,面向對象的三大特性為:封裝、繼承和多態。本文對繼承與多態的知識點進行了總結歸納,這部分內容對於學習c++編程語言是非常重要的,文章加入我的個人理解,希望能給大家帶來幫助,如果有問題歡迎大家指出。
本文的所有代碼運行環境為【windows 10】vs2013
知識框架:
1.繼承的概念
什麼是繼承?為什麼有繼承?
通過繼承將有共同部分的、相互聯系的類構成一種層次關系,共同部分組成的類一般在最頂端稱為基類(父類),其他類直接或間接地繼承基類,通過繼承而來的這些類稱為派生類(子類)。這樣就可以實現復用,子類只需要繼承父類就會擁有父類的所有東西。
2。訪問控制與繼承關系:public、protected、private
繼承關系相當於給從基類繼承過來的所有成員外部加了繼承關系的限定符。
一個類使用protected來聲明那些他想與派生類分享但不想被其他公共部分訪問使用的成員。
3.繼承與轉換--賦值兼容規則 (public繼承的前提下)
在public繼承的前提下,滿足一下賦值兼容規則:
(1)子類對象可以賦值給父類對象,反之則不行。(學生類是人類的子類,你可以把學生說是人,但你不能說人一定就是學生)
(2)父類對象的指針/引用可以指向/引用子類對象,反之不行。(子類除了擁有父類的成員,還擁有自己特有的。若將父類對象的地址賦給子類的指針,相當於擴大了指針的權限,解引用就有可能訪問到非法空間,所以不可以)
4.成員函數的重定義/隱藏
當子類與父類成員(成員變量和成員函數)同名時,子類成員就會隱藏父類成員.(這裡只需要同名即可,與成員函數參數和返回值都無關)
5.單繼承和多繼承
單繼承:就是只有一個直接父類。
多繼承:有兩個或兩個以上的直接父類。
菱形繼承/鑽石繼承:
通過單繼承和多繼承合成了菱形繼承,這種繼承是存在一定問題的:
(1)數據冗余。(動物有一個成員是嘴巴,人和魚各自都繼承了嘴巴這個成員,而美人魚繼承自人和魚,他就會有兩個嘴巴,然而他只需要一個就行了。)
(2)訪問的二義性。(美人魚有了兩個嘴巴,它到底用哪個嘴巴呢?這裡就產生了二義性)
6.虛繼承
為了解決上面菱形繼承帶來的兩個問題,我們引入了虛繼承。
class A { public: A() :_a(1) {} protected: int _a; }; class B : virtual public A //也可以寫為class B : public virtual B { public: B() :_b(2) {} private: int _b; }; class C : virtual public A { public: C() :_c(3) {} private: int _c; }; class D : public B, public C { public: D() :_d(4) {} private: int _d; };
繼承時在繼承關系前加上virtual關鍵字,就會變為虛繼承。這樣_a成員在D類中就只保存了一份。這是怎麼實現的呢?
虛繼承解決了菱形繼承所帶來的問題,但它也降低了性能。
7.虛函數與多態
虛函數:在類的成員函數之前加virtual關鍵字。
虛函數重寫/覆蓋:當派生類與父類的虛函數完全相同時(函數名,參數,返回值都相同,協變<返回值為類類型的指針,滿足多態才有協變>和虛析構函數<函數名為類名>除外),子類的這個函數重寫/覆蓋了父類的函數。
多態:
之前已經介紹過函數重載也就是靜態多態了,這裡我們就只說動態多態了。
includeusing namespace std; class Book { public: void ShowPrice() { cout << "全價" << endl; } }; class BarginBook :public Book { public: void ShowPrice() { cout << "半價" << endl; } }; int main() { BarginBook barginbook; Book* pbook = &barginbook; pbook->ShowPrice(); Book book; pbook = &book; pbook->ShowPrice(); system("pause"); return 0; }
運行結果:
全價
全價
若將ShowPrice函數改為虛函數,即給父類和子類的ShowPrice函數前家virtual關鍵字,則程序運行結果就變為:
半價
全價
這裡,因為ShowPrice函數為虛函數,它與基類的虛函數完全相同,所以重寫了父類的虛函數。這種方式就實現了動態多態,根據基類指針指向不同的類對象,來調用不同的虛函數。
動態多態的實現條件:
1.子類虛函數重寫父類的虛函數(兩個類中的虛函數必須完全相同)。
2.使用父類的指針/引用來調用父類或子類的虛函數。
六個默認成員函數中,為什麼將析構函數寫為虛函數?
class Base { public: Base() { cout << "Base()" << endl; } ~Base() { cout << "~Base()" << endl; } }; class Derive:public Base { public: Derive() :_pi(new int(1)) {} ~Derive() {
delete _pi; cout << "Derive()" << endl; } protected: int* _pi; }; int main() { { Derive d; Base* pb = &d; } system("pause"); return 0; }
上面的代碼執行結果是什麼呢?
程序調用了Base和Derive的構造函數,卻只執行了Base的析構函數,這樣會導致Derive中開辟的內存沒有釋放,產生內存洩漏。將Base類和Derive類的析構函數定義為虛函數就會以子類的虛構函數重寫父類的虛構函數,調用子類的虛構函數是會自動調用父類的虛構函數,所以就解決了這樣的問題。
如果我們刪除的是一個指向派生類對象的基類指針,則需要虛析構函數。
8.友元與繼承
友元不能繼承。父類的友元不能繼承給子類,就像爸爸的朋友不是你的朋友一樣,但是並不是不能做朋友,只要你在子類裡面再聲明一次友元就可以了。
9.靜態成員與繼承
class Derive:public Base { public: Derive() { _count++; } void Show() { cout << _count << endl; } }; int main() { Base b; Derive d; b.Show(); d.Show(); system("pause"); return 0; }
運行結果是:
3
3
通過上面的程序我們就能看出來,靜態成員在整個繼承體系中只有一份,這個靜態成員屬於整個繼承體系,只要沒有訪問限定,整個體系中的類都可以訪問他。
到這裡,繼承與多態的基礎知識就差不多了,但是還有比較深入的知識,在繼承與多態(二)中進行總結歸納。謝謝閱讀,希望能給大家帶來幫助。