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