面向對象特徵-多態

1、多態的相關概念
多態的意思是:一個事物有多種形態。

多態是指同一方法由於調用對象不同,產生不同的行爲。其實就是對象的多種形態。

系統實現的角度看,多態性分爲兩類:靜態多態和動態的多態。函數的重載和運算符的重載都屬於靜態的多態,在程序編譯時系統就能決定調用的是哪一個函數,因此靜態的多態又稱爲編譯時的多態性;動態的多態性是在程序運行過程中才動態的確定操作所針對的對象,因此又稱爲運行時的多態。動態的多態性是通過虛函數實現的。

2、構成多態的條件:
(1)必須要使用關鍵字virtual修飾基類的成員函數,指明該函數是虛函數;
(2)並且在派生類裏面要重新實現,才能實現動態的多態。

3、重寫的條件:(重寫是實現動態多態的一種重要的方法)
(1)要形成重寫必須聲明基類裏面的函數是虛函數。
(2)在派生類裏面的這個函數必須和基類裏面的這個函數是函數名相同,參數列表相同,返回值相同(協變除外)。派生類裏面可加也可不加virtual這個關鍵字,但最好加上。
 

#include<iostream>  
using namespace std;

class Base
{
public:
	void virtual FunTest1(int i)
	{
		cout << "Base::FunTest1" << endl;
	}
	void FunTest2(int i)
	{
		cout << "Base:: FunTest2" << endl;
	}
	virtual void FunTest3(int i)
	{
		cout << "Base:: FunTest3" << endl;
	}
	virtual void FunTest4(int i, int j)
	{
		cout << "Base:: FunTest4" << endl;
	}
};
class Derived :public Base
{
public:
	void virtual FunTest1(int i)
	{
		cout << "Derived:: FunTest1" << endl;
	}
	virtual void FunTest2(int i)
	{
		cout << "Derived:: FunTest2" << endl;
	}
	void FunTest3(int i)
	{
		cout << "Derived::FunTest3" << endl;
	}
	virtual void FunTest4(int i)
	{
		cout << "Derived::FunTest4" << endl;
	}
};
void FunTest(Base *b)
{
	b->FunTest1(0);
	b->FunTest2(0);
	b->FunTest3(0);
	b->FunTest4(0, 0);
}
int main()
{
	Base b;
	Derived d;
	Base*pb = &b;
	FunTest(pb);
	pb = &d;
	FunTest(pb);
	system("pause");
	return 0;
}

運行結果爲:

析:
由上述結果可以分析出:FunTest1()這個函數形成了重寫,而FunTest 2()這個函數沒有形成重寫,FunTest 3()這個函數形成了重寫,而FunTest()這個函數沒有形成重寫。

(3)(該條件獨立存在)所謂協變就是基類的虛函數的返回值是指針或者引用,而派生類的返回值是派生類的指針或者引用。
【例】
 

class Base
{
public:
	virtual Base*pFunTest1()
	{
		cout << "Base::pFunTest1()" << endl;
		return this;
	}

};
class Derived :public Base
{
//與基類的返回值不同,返回的是派生類的指針,並且函數名和參數與基類相同,構成了協變
	virtual Derived*pFunTest1()
	{
		cout << "Derived::pFunTest1()" << endl;
		return this;
	}
};
void pFunTest(Base*p)
{
	p->pFunTest1();
}

4、重寫和訪問修飾符沒有關係。
因爲我們知道它是在編譯期間就形成了虛表,如果你將基類的虛函數的訪問限定符設置成私有的和你實現動態多態是沒有關係的,因爲這個虛函數的入口地址已經存放進入虛函數表了,你加訪問限定符只是爲了不讓類外的成員去訪問它或者說去調用它只能由基類自己的成員函數作爲外部接口去調用它。一般實現動態的多態我們將派生類裏重寫時加訪問限定符則不影響因爲在調用虛函數的時候我們是要用基類的指針或引用去調用在編譯時是將它作爲基類的虛函數處理的(即靜態的多態)。調用是按基類裏面的函數靜態調用,執行是按照動態執行的。

5、哪些函數不能作爲虛函數?
(1)構造函數不能作爲虛函數
分析:調用構造函數是爲了創建對象,而如果構造函數作爲虛函數那麼對象還沒有創建成功是不可能將它的虛表指針(也就是一個地址)存放在對象的前四個字節中。
(2)靜態成員函數不能作爲虛函數
分析:靜態成員函數是沒有this指針的,它不是特定的指向某一個對象我們可以通過類名就直接可以訪問靜態成員函數,但是我們知道虛表指針是要存放在對象的前四個字節中的。
(3)賦值運算符可以作爲虛函數
分析:賦值運算符作爲虛函數是爲了實現多態,但是根據賦值兼容規則我們知道派生類的對象可以直接賦值給基類的對象,而基類的對象不能直接賦值給派生類的對象,如果我們將賦值運算符作爲虛函數是可以實現子類給父類賦值,但如果我們父類給子類賦值程序就有可能會崩潰。
(4)友元函數不能作爲虛函數
分析:友元函數根本就不是類的成員函數,因此它不能作爲虛函數。
(5)析構函數可以作爲虛函數,並且最好將析構函數寫成虛函數。
【例】
 

#include<iostream>  
using namespace std;
class Base
{
public:
	Base()
	{
		cout << "Base()" << endl;
	}
	virtual ~Base()
	{
		cout << "~Base()" << endl;
	}
};
class Derived :public Base
{
public:
	Derived()
	{
		cout << "Derived()" << endl;
	}
	virtual  ~Derived()
	{
		cout << "~Derived()" << endl;
	}
};
int main()
{
	Base *pb = new Derived;
	delete pb;
	system("pause");
	return 0;
}

運行結果爲:

分析:
如果不加上virtual這個關鍵字,程序就會調用基類裏面的析構函數,這樣只是釋放了一部分的空間。而加上這個關鍵字,就可以判斷出它實際上開闢的是派生類對象的空間,因此會去調用派生類的析構函數,而且又因爲繼承的關係再去調用基類的析構函數,這樣就不會造成內存的泄露。

(6)注意:不要在構造函數和析構函數裏調用虛函數,因爲對象是不完整的,可能造成未定義的行爲。
 

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