虛函數

0、前言

從實現的角度來講,多態可以分爲兩類:編譯時的多態性和運行時的多態性。前者是通過靜態聯編來實現的,比如C++中通過函數的重載和運算符的重載。後者則是通過動態聯編來實現的,在C++中運行時的多態性主要是通過虛函數來實現的。

1、概念

虛函數:在基類中聲明爲virtual,並在一個或多個派生類中被重新定義的成員函數。

存在虛函數的類都有一個一維的虛函數表叫做虛表。類的對象有一個指向虛表開始的虛指針。
虛表是和類對應的,虛表指針是和對象對應的。

2、作用

虛函數的作用就是實現多態性,多態性是將接口與實現進行分離;用形象的語言來解釋就是實現以共同的方法,但因個體差異,而採用不同的策略。

3、示例程序

源程序

#include <iostream>
using namespace std;

class A
{
public:
	void Print()
	{
		cout<<"This is A"<<endl;
	}
};

class B:public A
{
public:
	void Print()
	{
		cout<<"This is B"<<endl;
	}
};

int main()
{
	A a;
	B b;
	a.Print();
	b.Print();

	getchar();
	return 0;
}
輸出結果:


把main函數裏的代碼改成如下這樣:

int main()
{
	A a;
	B b;
	A *pA = &a;
	A *pB = &b;
	pA->Print();
	pB->Print();

	getchar();
	return 0;
}
結果爲:



如果我們把class A改成下面這樣,即在Print函數前面加virtual修飾

class A
{
public:
	virtual void Print()
	{
		cout<<"This is A"<<endl;
	}
};
則輸出爲:

這時,class A的成員函數Print()已經變成了虛函數,而class B的Print()也成了虛函數。

也就是說:在把基類的成員函數設爲virtual後,其派生類的相應的函數也會自動變爲虛函數。對於在派生類的相應函數前是否需要用virtual關鍵字修飾,那就是你自己的問題了(語法上可加可不加,不加的話編譯器會自動加上,但爲了閱讀方便和規範性,建議加上)。

指向基類的指針在操作它的多態類對象時,會根據不同的類對象,調用其相應的函數。

4、定義虛函數的限制:

(1)非類的成員函數不能定義爲虛函數,類的成員函數中靜態成員函數和構造函數也不能定義爲虛函數,但可以將析構函數定義爲虛函數。實際上,優秀的程序員常常把基類的析構函數定義爲虛函數。因爲,將基類的析構函數定義爲虛函數後,當利用delete刪除一個指向派生類定義的對象指針時,系統會調用相應的類的析構函數。而不將析構函數定義爲虛函數時,只調用基類的析構函數。
(2)只需要在聲明函數的類體中使用關鍵字“virtual”將函數聲明爲虛函數,而定義函數時不需要使用關鍵字“virtual”。
(3)當將基類中的某一成員函數聲明爲虛函數後,派生類中的同名函數(函數名相同、參數列表完全一致、返回值類型相關)自動成爲虛函數。
(4)如果聲明瞭某個成員函數爲虛函數,則在該類中不能出現和這個成員函數同名並且返回值、參數個數、類型都相同的非虛函數。

5、使用虛函數的注意事項:

(1)使用虛函數,派生類必須是基類公有派生的;
(2)定義虛函數,不一定要在最高層的類中,而是看在需要動態多態性的幾個層次中的最高層類中聲明虛函數;
(3)一個虛函數無論被公有繼承了多少次,它仍然是虛函數;
(4)虛函數必須是所在類的成員函數,而不能是友元函數,也不能是靜態成員函數。因爲虛函數調用要靠特定的對象來決定該激活哪一個函數;
(5)構造函數不能是虛函數,但析構函數可以是虛函數;

6、抽象類

如果一個類中至少有一個純虛函數,那麼就稱該類爲抽象類。
注意:
(1)抽象類只能作爲其他類的基類來使用,不能建立抽象類對象;
(2)不允許從具體類中派生出抽象類(不包含純虛函數的普通類);
(3)抽象類不能用作函數的參數類型、返回類型和顯式轉化類型;
(4)如果派生類中沒有定義純虛函數的實現,而只是繼承成了基類的純虛函數。那麼該派生類仍然爲抽象類。一旦給出了對基類中虛函數的實現,那麼派生類就不是抽象類了,而是可以建立對象的具體類;

7、虛函數實現多態的原理

源代碼:

#include <iostream>
#include <string>
#include <Windows.h>

using namespace std;

class A
{
public:
	virtual void fun() {cout<<"A::fun()"<<endl;}
	virtual void fun2(){cout<<"A::fun2()"<<endl;}
	void A_NonVirtual(){}
	
private:
	int num_A;

};

class B:public A
{
public:
	void fun() {cout<<"B::fun()"<<endl;}
	void fun2(){cout<<"B::fun2()"<<endl;}
	void B_NonVirtual(){}
	
private:
	int num_B;
};

int main()
{
	A a;
	B b;

	A *p = NULL;

	p = &a;
	p->fun();

	p = &b;
	p->fun();

	system("pause");
	return 0;
}

程序運行結果:


對象a,和b的內存佈局如下圖所示:


當執行p = &a;p->fun();時,將從對象a的虛函數表中查找fun函數。也即是A::fun();

當執行p = &b;p->fun();時,將從對象b的虛函數表中查找fun函數。也即是B::fun();

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