1. 程式人生 > >虛擬函式,虛繼承

虛擬函式,虛繼承

虛擬函式
多型時實現了介面複用
c++中實現多型有兩種,一種是靜多型,另一種是動多型
靜多型通過過載完成
靜多型又叫靜態繫結或早繫結,在編譯期間確定函式入口地址
動多型通過虛擬函式完成
動多型又稱動態繫結或晚邦定(在執行時動態確定函式入口地址,call的是暫存器)
虛擬函式關鍵字virtual
在基類中定義一個虛擬函式會產生一個虛擬函式指標,指向一個虛表(虛表在編譯期間生成)只有資料段和指令段才能載入到記憶體,把虛表在資料段中載入在記憶體中
基類中的函式為虛擬函式則派生類中同名同參的函式也為虛擬函式,同時在派生類中虛擬函式指標會進行合併,從派生類向基類中和並,此時同名同參的函式會發生函式的覆蓋關係


什麼時候發生動多型
1 通過指標或引用呼叫虛擬函式
2.物件要完整
成為虛擬函式的條件
1,能得到地址
2,依賴物件呼叫
構造,不可以成為虛擬函式(構造就是生成物件的)
析構:可以
inline(內聯):不可以,無法得到地址(在呼叫點直接展開)
static(靜態函式):不可以(靜態函式是屬於類的不屬於物件,,可以不依賴物件呼叫)
純虛擬函式:定意:virtual void back()=0;
擁有純虛擬函式的類為抽象類,不能例項化,只是保留了介面,但是抽象類可以做指標或引用
抽象類做指標或引用的程式碼如下:實現了介面的複用

# include<iostream>
using namespace std;
#pragma warning(disable:4996)//vs2015不讓使用strcpy函式,此行是忽略編譯器對strcpy的報錯
class animal               //動物類
{
public:
	virtual void animal_call() = 0;//叫聲函式
protected:
	char *name;//動物名稱
};
class dog:public animal//狗類
{public:
	dog(char *tname)
	{
		int len = strlen(tname);
		name = new char[len + 1]();
		strcpy(name,tname);
	}
	void animal_call()
	{
		cout << name <<" "<<"汪 汪" << endl;
	}
	~dog() {
		delete name;
	}
};
class cat :public animal//貓類
{public:
	cat(char *tname)
	{
		int len = strlen(tname);
		name = new char[len + 1]();
		strcpy(name, tname);
	}
	void animal_call()
	{
		cout << name<<" "<< "喵 喵"<< endl;
	}
	~cat() {
		delete name;
	}
};
void is_animal_call(animal &rsh)//發出叫聲,基類的指標或引用派生類物件,整個過程中發生了動多型,派生類中同名同參的叫聲函式在虛表中替換了基類中的叫聲虛擬函式,發生了函式的覆蓋
{
	rsh.animal_call();
}
int main()
{
	dog my_dog("dog");
	cat my_cat("cat");
	is_animal_call(my_dog);
	is_animal_call(my_cat);
}

因為虛指標在類中的優先順序最高,所以他在派生類的起始位置,但虛擬函式指標式從派生類往基類中合併,所以當使用基類指標指向派生類物件時指標指向的是派生類物件起始處
但存在一個問題,如果基類中無虛擬函式,但在派生類中有虛擬函式,當使用基類的指標指向派生類物件時指標並沒有指向派生類的起始位置,當使用基類指標釋放派生類物件時因為沒有指向派生類物件的起始位置,釋放會存在問題,而解決這種問題的方法是把基類的解構函式變成虛析構,使基類中存在虛擬函式指標。
虛繼承
在繼承中除了單繼承,還有多繼承
在多繼承中有一種繼承為菱形繼承:
**菱形繼承**
則派生類D中的記憶體結構為:
D的記憶體結構
從圖中可以看出派生類D中基類A含有兩個,分別在D的兩個基類中,這樣會造成二義性,和記憶體浪費,所以此處可以程序虛繼承。
虛繼承的關鍵字和虛擬函式的關鍵字一直都為:“virtual”,是哪個類出現了問題,就在繼承哪個類時加入virtual,如上例所示,D中是A類出現問題所以在B和C繼承A時加入virtual,
class B:virtual public A
{};
class C:virtual public A
{};
class D:public B,public C
{};
虛繼承可以解決以上問題,他的解決方法是把基類A放在當前作用域的最下方,此時在類中生成一個虛基類指標指向虛基類表(存放偏移量)通過偏移量找到A類資料
虛繼承D的記憶體結構

當vbptr處於同級是他們不會合並,但vbptr不處於同級時會進行合併
eg:
class B:virtual public A
{};
class C:virtual public A
{};
class D:virtual public B,public C
{};

D的記憶體結構
原本類B在類D中會有一個指向B的vbptr但因為類C中有一個類A的vbptr所以兩個vbptr發生了合併,按照向上合併規則,所以合併到了C中的vbptr中 此時C中vbptr指向兩個類 C:中vbptr內容
在這裡插入圖片描述
B中vbptr內容
在這裡插入圖片描述