天天看點

虛函數(重寫)、多态、單繼承虛表、多繼承虛表

虛函數

虛函數:類的成員函數前加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;
}
           
虛函數(重寫)、多态、單繼承虛表、多繼承虛表

在虛表裡虛函數位址存放順序和在基類中聲明順序一樣,如下:先聲明fun1,後聲明fun2,那麼就先存放fun1虛函數位址,後存放fun2虛函數位址。

虛函數(重寫)、多态、單繼承虛表、多繼承虛表

單繼承的虛函數表

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;
}
           
虛函數(重寫)、多态、單繼承虛表、多繼承虛表
虛函數(重寫)、多态、單繼承虛表、多繼承虛表

多繼承虛函數表

首先看多繼承代碼:

class A
{
	virtual void func1()
	{
		cout << "A::func1()" << endl;
	}
	virtual void func2()
	{
		cout << "A::func2()" << endl;
	}
protected:
	int _a;
};
class B
{
	virtual void func1()
	{
		cout << "B::func1()" << endl;
	}
	virtual void func2()
	{
		cout << "B::func2()" << endl;
	}
protected :
	int _b;
};
class C : public A, public B
{
	virtual void func1()
	{
		cout << "C::func1()" << endl;
	}
	virtual void func3()
	{
		cout << "C::func3()" << endl;
	}
protected :
	int _c;

};
typedef void(*FUNC)(); //聲明一個函數指針類型FUNC

void printTable(int *vfptr)
{
	printf("vtable address:%p\n", vfptr);
	for (int i = 0; vfptr[i] != 0; i++)
	{
		printf("第%d個虛函數位址:%p->", i, vfptr[i]);
		FUNC f = (FUNC)(vfptr[i]);
		f();  //調用這個函數
	}
	printf("\n");
}

int main()
{
	C c;
	cout<<sizeof(c)<<endl;
	//因為c有兩個直接父類:A和B
	int *vfptr1 = (int*)(*((int*)(&c)));
	printTable(vfptr1);
	//因為第二個虛表指針存放在第一個父類成員變量下邊,是以第二個虛表指針在&c後需要加上第一個父類的大小,
	//但是由于指針+1,加的是所指類型的大小,是以需要把&a強轉為char*,或者+sizeof(A)/4,
	int *vfptr2 = (int*)(*(int*)(((char*)(&c)) + sizeof(A))); 
	printTable(vfptr2);
	system("pause");
	return 0;
}
           
虛函數(重寫)、多态、單繼承虛表、多繼承虛表
虛函數(重寫)、多态、單繼承虛表、多繼承虛表

繼續閱讀