關於多態的簡單闡述

在上篇博客中,簡單提了一下C++的第一個特點繼承,今天就來聊聊第二個特點:多態

一、多態的概念

1、什麼是多態呢?簡單來說,多態就是面向對象的重要特性,也可以說是同一種事物的不同體現,或者是同一事物表現出得多種形態。

2、多態的分類

   

靜態多態:編譯器在編譯期間完成,編譯器根據函數實參的類型(可能會進行隱式轉換)。

動態多態:在程序運行期間(非編譯期間)判斷所引用對象的實際類型,根據其實際類型調用相應的方法。

3、動態多態實現的兩個條件:

(1)調用虛函數--->一定在派生類中對基類的虛函數進行重寫

重寫(覆蓋):在繼承體系中,如果基類中有虛函數,在派生類中有和基類虛函數原型相同的虛函數。

重寫的特例:1>協變:基類返回基類指針,派生類返回派生類指針

        2>析構函數:基類調用基類析構函數 ~Base() 派生類調用派生類構造函數 ~Derived()

(2)通過基類的指針或引用來調用虛函數

二、動態多態的實現及其原理

多態通過虛函數結合動態綁定來實現。

虛函數:通過關鍵字virtual所修飾的成員函數。

在講動態多態的實現之前,必須先來講一下虛表以及上篇提過的對象模型


先看一下下面的代碼

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

class A
{
public:
	void Test1()
	{
		cout << "A::Test1()" << endl;
	}
};

class B
{
public:
	virtual void Test1()
	{
		cout << "B::Test1()" << endl;
	}
};
int main()
{
	cout <<"A的大小" << sizeof(A) << endl;
	cout <<"B的大小" << sizeof(B) << endl;
	system("pause");
	return 0;
}

輸出結果及分析

1、普通函數的調用:call函數地址

       虛函數的調用:   通過查詢虛表

2、先來提兩個問題

1>同一個類的對象是否公用一張虛表?   答案:是,公用同一張虛表

2>派生類是否和基類公用一張虛表?  答案:否,不共用

下面就重點講一下虛表的構造

基類中的虛表:虛函數在類中的申明次序

派生類中的虛表:

  1、單繼承

         1>先拷貝基類中的虛表

         2>檢測派生類中是否對基類中的虛函數進行重寫----是->用派生類中重寫的虛函數來替代相同偏移量位置的基類虛函數

         3>在虛表之後添加派生類自己的虛函數

在此處說一下虛函數的調用:1>取對象地址2>取對象前四個字節中的內容—>虛表指針3>傳this指針4>調對應指針

 下面就用幾行代碼來通俗的演示及解釋一番

class A
{
public:
	virtual void Test1()
	{
		cout << "A::Test1()" << endl;
	}

private:
	int _a;
};

class B:public A
{
public:
	//重寫基類虛函數
	virtual void Test1()
	{
		cout << "B::Test1()" << endl;
	}
	//派生類新定義的虛函數
	virtual void Test2()
	{
		cout << "B::Test2()" << endl;
	}
private:
	int _b;
};
typedef void(*Pfun)();
//遍歷虛表內容
void Display(const  A&a)
{
	Pfun*pfun = (Pfun*)*(int*)&a;
	while (*pfun)
	{
		(*pfun)();
		++pfun;
	}
}
void test()
{
	A a;
	B b;
	Display(a);
	Display(b);
}
int main()
{
	
	test();
	system("pause");
	return 0;
}


通過對結果的分析可以看出在派生類的對象模型中,不僅繼承了基類中的,而且也對在派生類中重寫的虛函數做了修改,然後再下邊補上了派生類自己的虛函數。

2、多繼承

   在第一張虛表之後加上派生類自己的虛函數,其他與單繼承類似。        

   將派生類的虛函數加在第一張虛表之後的好處:效率快,僅訪問前四個字節就能訪問到派生類虛函數。

class A
{
public:
	virtual void Test1()
	{
		cout << "A::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "A::Test2()" << endl;
	}
private:
	int _a;
};
class B
{
public:
	virtual void Test3()
	{
		cout << "B::Test3()" << endl;
	}

private:
	int _b;
};
class C:public A,public B
{
public:
	//重寫基類虛函數
	virtual void Test1()
	{
		cout << "C::Test1()" << endl;
	}
	virtual void Test3()
	{
		cout << "C::Test3()" << endl;
	}
	//派生類新定義的虛函數
	virtual void Test4()
	{
		cout << "C::Test4()" << endl;
	}
	
	
private:
	int _c;
};
typedef void(*Pfun)();
//遍歷虛表內容
void Display(const  A&a)
{
	//訪問A的虛表
	Pfun*pfun = (Pfun*)*(int*)&a;
	while (*pfun)
	{
		(*pfun)();
		++pfun;
	}
	//跳過A的內容訪問B的虛表
	pfun = (Pfun*)*(int*)((char*)&a + sizeof(A));
	while (*pfun)
	{
		(*pfun)();
		++pfun;
	}
}
void test()
{
	C c;
	Display(c);
}
int main()
{
	test();
	system("pause");
	return 0;
}


對代碼的解讀如下:


    多繼承:先繼承的基類的虛表在前,再把派生類自己的虛函數加在第一張虛表之後,再把第二個繼承的基類的虛表接在後面。

3、菱形繼承

   1)帶虛函數的虛擬繼承

           在此繼承的體系下,則對象的對象模型需要再多四個字節,用來存放偏移量表格。

           <1>先看派生類僅重寫了基類的虛函數,而無自己的虛函數的情況

class A
{
public:
	virtual void Test1()
	{
		cout << "A::Test1()" << endl;
	}
	int _a;
};
class B:virtual public A
{
public:
	virtual void Test1()
	{
		cout << "B::Test1()" << endl;
	}
	int _b;
};
int main()
{
	cout << sizeof(B) << endl;
	A a;
	B b;
	b._a = 0;
	b._b = 1;
	system("pause");
	return 0;
}


          <2>先看派生類不僅重寫了基類的虛函數,而且擁有自己的虛函數的情況

class A
{
public:
	virtual void Test1()
	{
		cout << "A::Test1()" << endl;
	}
	int _a;
};
class B:virtual public A
{
public:
	virtual void Test1()
	{
		cout << "B::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "B::Test2()" << endl;
	}
	int _b;
};
int main()
{
	cout << sizeof(B) << endl;
	A a;
	B b;
	b._a = 0;
	b._b = 1;
	system("pause");
	return 0;
}



    2)菱形繼承

class A
{
public:
	virtual void Test1()
	{
		cout << "A::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "A::Test2()" << endl;
	}
	int _a;
};
class B1:virtual public A
{
public:
	virtual void Test1()
	{
		cout << "B1::Test1()" << endl;
	}
	virtual void Test3()
	{
		cout << "B1::Test3()" << endl;
	}
	int _b1;
};
class B2 :virtual public A
{
public:
	virtual void Test1()
	{
		cout << "B2::Test1()" << endl;
	}
	virtual void Test4()
	{
		cout << "B2::Test4()" << endl;
	}
	int _b2;
};
class D :public B1,public B2
{
public:
	virtual void Test1()
	{
		cout << "D::Test1()" << endl;
	}
	virtual void Test3()
	{
		cout << "D::Test3()" << endl;
	}
	virtual void Test4()
	{
		cout << "D::Test4()" << endl;
	}
	virtual void Test5()
	{
		cout << "D::Test5()" << endl;
	}
	int _d;
};
typedef void(*FunPtr)();

void Print(A& a)
{
	FunPtr* pfun = (FunPtr*)(*(int*)&a);

	while (*pfun)
	{
		(*pfun)();
		pfun++;
	}
	cout << endl;
}

void Print(B1& b1)
{
	FunPtr* pfun = (FunPtr*)(*(int*)&b1);

	while (*pfun)
	{
		(*pfun)();
		pfun++;
	}
	cout << endl;
}

void Print(B2& b2)
{
	FunPtr* pfun = (FunPtr*)(*(int*)&b2);

	while (*pfun)
	{
		(*pfun)();
		pfun++;
	}
	cout << endl;
}

int main()
{
	cout << sizeof(D) << endl;
	D d;
	B1& b1 = d;
	Print(b1);

	B2& b2 = d;
	Print(b2);

	A& a = d;
	Print(a);
	return 0;
}


對於上述帶虛函數的菱形繼承的代碼根據內存塊的分析

以上就是我對多態的一點小小總結,希望各位大佬們多指點。


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