C++:繼承與多態(2)

繼承方式:

私有繼承:has_a 關係

保護、公有:is_a 關係

隱藏:(有,看不見)繼承關係下,不同的作用域。

覆蓋:(不存在)派生類裏面的同名函數把基類的覆蓋了(相當於替換)

基類和派生類的相互指向

#include<iostream>

using namespace std;

class Base//這個類中,兩個Show()是重載關係
{
public:

	Base(int a) :ma(a)
	{
		cout << "Base::Base() " << endl;
	}
	~Base()
	{
		cout << "Base::~Base()" << endl;
	}
protected://可以擴展,且不能在類外訪問
	int ma;
};

class Derive :public Base
{
public:
	Derive(int b) :mb(b),Base(b)
	{
		cout << "Derive::Derive() " << endl;
	}
	~Derive()
	{
		cout << "Derive::~Derive()" << endl;
	}
private:
	int mb;
};

int main()
{
	Base base(10);
	Derive derive(20);
	//Derive* pd = &base;//  ×
	Base* pb = &derive;//  √
	return 0;
}

 

解釋:

            

解釋:

假如把基類比作普通人,學生比作派生類,學生會做的事情一個普通的人並非會做,比如解高數。

Derive* pd = &base;// × 相當於把base(人)這個對象賦給pd(學生), 讓一個普通人去做學生做的事情,比如解高數。
Base* pb = &derive;// √ 相當於把pd(學生)   這個對象賦給base(人),   讓一個學生去做普通人做的事情,學生本身即是人繼承來的,理所應當會。

同理:

Base& rb = derive;   //√
//Derive& rd = base; //  ×

總結:基類的指針或引用可以指向或者引用派生類對象,派生類的指針或引用不可指向或者引用基類對象。

觀察下面的代碼:

#include<iostream>
#include<string>
#include<vector>
using namespace std;

class Base
{
public:

	Base(int a) :ma(a)
	{
		cout << "Base::Base() " << endl;
	}
	~Base()
	{
		cout << "Base::~Base()" << endl;
	}
	void Show()
	{
		cout << "Base::Show()" << endl;
		cout << "ma " << ma << endl;
	}
protected:
	int ma;
};

class Derive :public Base
{
public:
	Derive(int b) :mb(b),Base(b)
	{
		cout << "Derive::Derive() " << endl;
	}
	~Derive()
	{
		cout << "Derive::~Derive()" << endl;
	}
	void Show()
	{
		cout << "Derive::Show()" << endl;
		cout << "ma " << ma << endl;
		cout << "mb " << mb << endl;
	}
private:
	int mb;
};

int main()
{
	cout << sizeof(Base) << endl;//4
	cout << sizeof(Derive) << endl;//8

	Base* pb = new Derive(10);
	cout << typeid(pb).name() << endl;//class Base *
	cout << typeid(*pb).name() << endl;//class Base

	pb->Show();//Base::Show()
	return 0;
}

打印結果:

                        

若將Base裏面的Show()函數修改如下:

	virtual void Show()
	{
		cout << "Base::Show()" << endl;
		cout << "ma " << ma << endl;
	}

打印結果:

                 

對比觀察兩個圖:

               

virtual 關鍵字帶來的改變:

            

基類中是虛函數,派生類中同名且參數類型相同的函數也是虛函數。

在基類中再加入這樣的代碼:

	virtual void Show(int)
	{
		cout << "Base::Show(int)" << endl;
		cout << "ma " << ma << endl;
	}

                       

虛函數表:

         

虛函數指針指向虛表,這裏的RTTI指的是: runtime tyoe information 運行時類型信息

內存佈局中,vfptr優先級高,這裏的 0 指的是vfptr相對於內存佈局的偏移,所以爲0。

Base* pb = new Derive(10);   *pb解引用,對象是派生類對象

                       

這裏派生類  &Base::Show是帶有一個整形參數是繼承基類得來的。

這裏有沒有奇怪?爲什麼只有一個虛函數指針?派生類的vfptr難道不該有兩個嗎?

解釋:派生類的向基類中的合併,合併規則:沿着繼承鏈。一個虛表對應一個類。

    

注意:虛函數表是在編譯期間確定的,編譯期間進行語法語義的分析,存放在 .rodata(只讀數據段),運行時vfptr指向這個表。

虛函數的機制是爲了實現動多態。

早綁定、晚綁定

普通函數調用 :call函數入口地址(編譯期間確定)

虛函數調用:     call寄存器(運行期間)

早綁定(靜態綁定)(編譯期間確定函數的調用)

晚綁定(動態綁定)(運行期間確定函數的調用)(動多態)

程序怎樣拿到虛函數入口地址?

將虛函數的入口地址放到 .rodata 中, .rodata會加載到內存中,call eax時,可以找到函數入口地址。

哪些函數可以成爲虛函數?

1)函數能取地址(內聯函數不可以,不生成符號,無法取地址)

2)必須依賴對象調用(通過對象內存中的vfptr找vftable)(構造、static修飾的成員方法不可以)

3)析構函數,普通函數,可以設置爲虛函數

動多態發生條件:

對象調用虛函數是靜多態,指針調用虛函數是動多態且對象必須調用完整(也就是說,指針必須指向一個完整的對象,然後在在調用虛函數)。若在構造中調用虛函數,則對象不完整,爲靜多態。若在析構中調用虛函數,則對象不完整,爲靜多態。

觀察下面的代碼

#include<iostream>
#include<string>
#include<vector>
using namespace std;

class Base
{
public:

	Base(int a) :ma(a)
	{
		cout << "Base::Base() " << endl;
	}
	~Base()
	{
		cout << "Base::~Base()" << endl;
	}
	virtual void Show()
	{
		cout << "Base::Show()" << endl;
		cout << "ma " << ma << endl;
	}
	void Show(int a)
	{
		cout << "Base::Show(int)" << endl;
		cout << "ma " << ma << endl;
	}
protected:
	int ma;
};

class Derive :public Base
{
public:
	Derive(int b) :mb(b),Base(b)
	{
		cout << "Derive::Derive() " << endl;
	}
	~Derive()
	{
		cout << "Derive::~Derive()" << endl;
	}
	void Show()
	{
		cout << "Derive::Show()" << endl;
		cout << "ma " << ma << endl;
		cout << "mb " << mb << endl;
	}
	void Show(int a)
	{
		cout << "Derive::Show(int)" << endl;
		cout << "ma " << ma << endl;
		cout << "mb " << mb << endl;
	}
private:
	int mb;
};

int main()
{
	Base* pb = new Derive(10);
	pb->Show(); //動多態
	delete pb;
	return 0;
}

打印結果:

         

仔細觀察,會發現沒有派生類的析構。

delete pb;  //pb->~Base(); //靜態綁定  只釋放了基類的,沒有釋放派生類自己的,內存泄漏。

將基類析構函數代碼改爲:

	virtual ~Base()
	{
		cout << "Base::~Base()" << endl;
	}

繼承關係中,基類的析構和派生類的析構是覆蓋關係。(基類是虛析構,同名的派生類中析構也將變爲虛析構)

調用派生類析構時,系統自動調用基類析構。

打印結果:

                       

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章