繼承方式:
私有繼承: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;
}
繼承關係中,基類的析構和派生類的析構是覆蓋關係。(基類是虛析構,同名的派生類中析構也將變爲虛析構)
調用派生類析構時,系統自動調用基類析構。
打印結果: