1. 程式人生 > >虛擬函式、多型、單繼承虛表

虛擬函式、多型、單繼承虛表

虛擬函式 虛擬函式:類的成員函式前加virtual關鍵字,則稱這個成員函式為虛擬函式; 虛擬函式重寫 虛擬函式重寫:當在子類中定義了一個與父類完全相同的虛擬函式時,則稱子類的虛擬函式重寫(覆蓋)了父類的虛擬函式。子類重寫的虛擬函式可以沒有virtual(c++語法坑)。 多型 多型:當使用父類的引用或指標呼叫重寫的虛擬函式時,當指向父類呼叫的是父類的虛擬函式,指向子類呼叫的是子類的虛擬函式。

class Person
{
public:
       virtual void BuyTickets()
       {
              cout << "成人買票" << endl;
       }
protected:
       string _name;
};
class Student: public Person
{
public:
       virtual void BuyTickets()  //子類裡的virtual可以沒有,由於是繼承,繼承了基類虛擬函式的屬性
       {
              cout << "學生買票" << endl;
       }
protected:
       int _num;
};
// 呼叫重寫的虛擬函式時,物件必須是基類的指標或引用.
// 函式呼叫和物件有關,指向誰,呼叫誰的虛擬函式。當指向父類呼叫的就是父類的虛擬函式,當指向子類呼叫的就是子類的虛擬函式
void  Buy1(Person* p) //使用基類的指標
{
       p->BuyTickets();
}
void Buy2(Person &p) //使用基類的引用
{
       p.BuyTickets();
}
void Buy3(Person p) 
{
       p.BuyTickets();
}
int main()
{
       Person p;
       Student s;
       p.BuyTickets();  //普通呼叫,物件型別是Person,會到Person類裡找Buytickets();
       s.BuyTickets(); //普通呼叫,物件型別是Student,會到S類裡找Buytickets();
       //基類是指標:必須要&
       Buy1(&p);  // 成人買票 
       Buy1(&s);  // 學生買票
       //基類是引用
       Buy2(p);   // 成人買票  多型呼叫:物件指向p
       Buy2(s);   // 學生買票  多型呼叫:物件指向s
       //基類既不是指標也不是引用
       Buy3(p);   //成人買票
       Buy3(s);   //成人買票
       system("pause");
       return 0;
}

多型呼叫:具有靈活性,函式呼叫和物件有關,指向誰,呼叫誰; 普通呼叫:和型別有關,是那個型別的物件,就呼叫誰的虛擬函式; 虛表指標和虛表(多型機理) 在這裡插入圖片描述 從監視視窗我們可以看到,基類裡變數不只有_name ,還有前4個位元組為 _vfptr,_vfptr是虛表指標,虛表指標指向父類虛表,虛表裡存的是父類虛擬函式地址。而派生類繼承了基類,同樣也有虛表指標,這個虛表指標指向的是派生類虛表,虛表裡存放的是派生類虛擬函式地址。通過這個虛表指標就可以找到基類和派生類該呼叫的虛擬函式: 在這裡插入圖片描述 虛表指標是在呼叫建構函式初始化的,虛表存放於資料段(靜態區或全域性區),不能存放於棧和堆。 一個類有一個虛表指標,即一個虛表,但虛表裡存放了這個類全部的虛擬函式地址。如下:

class A
{
public:
	virtual void fun1()
	{

	}
	virtual void fun2()
	{

	}
private:
	int _a;
};
int main()
{
	A a1;
}

在這裡插入圖片描述

在這裡插入圖片描述 單繼承的虛擬函式表

class A
{
public:
	virtual void fun1()
	{
		cout << "A::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "A::fun2()" << endl;
	}
public:
	int _a;
};
class B : public A
{
	//子類會繼承父類的fun1()函式
	virtual void fun2()  //子類func2()會覆蓋父類fun2()
	{
		cout << "B::fun2()" << endl;
	}
	virtual void fun3()
	{
		cout << "B::fun3()" << endl;
	}
	void fun4()  //fun4()不是虛擬函式
	{
		cout << "B::fun3()" << endl;
	}
};
int main()
{
	A a1;
	B b1;
}

在這裡插入圖片描述 從監視視窗,我們可以看到子類的虛表裡有繼承了父類的A::fun1( ),還有覆蓋的B::fun2( )函式,由於fun4( )不是虛擬函式,所以虛表裡當然沒有fun4( ),儘管沒有看見子類的fun3虛擬函式,但是這個函式就在虛表裡的fun2()函式後,只是vs監視沒有顯示出來,可以認為是vs的bug。但我們可以從以下方式看到真實的虛表情況:

typedef void(*FUNC)();   

void printVtable(int *vfter)  
{
	cout << "虛表地址:" << vfter << endl;
	for (int i = 0; vfter[i] != 0; i++) //因為虛表最後一個元素是0
	{
		printf("第%d個虛擬函式地址:0x%x,->", i, vfter[i]);
		FUNC f = (FUNC)vfter[i]; 
		f(); //呼叫該虛擬函式
	}
	cout << endl;
}

int main()
{
	A a1;
	B b1;
	int *vfter1 = (int*)(*(int*)(&a1));  //vfter是虛表地址
	int *vfter2 = (int*)(*(int*)(&b1));
	printVtable(vfter1);
	printVtable(vfter2);
	system("pause");
	return 0;
}

在這裡插入圖片描述 在這裡插入圖片描述