c++---------虛函數及繼承(一)

1.class的內存佈局(無虛函數&繼承版)

首先,需要弄清楚一件事情,平時所聲明的類只是一種類型定義,它本身是沒有大小可言的。 我們這裏指的類的大小,其實指的是類的對象所佔的大小。因此,如果用sizeof運算符對一個類型名操作,得到的是具有該類型實體的大小。

關於類/對象大小的計算

  • 首先,類大小的計算遵循結構體的對齊原則
  • 類的大小與普通數據成員有關,與成員函數和靜態成員無關。即普通成員函數,靜態成員函數,靜態數據成員,靜態常量數據成員均對類的大小無影響,並且類中的靜態數據成員是存儲在靜態存儲區,被這個類的所有對象共享
  • 虛函數和虛繼承對類的大小均有影響
  • 空類的大小是一個特殊情況,空類的大小爲1字節,它被安插了一個char
class PP
{
public:
	PP() = default;
	PP(int _a, char _b) :a(_a), b(_b) {};

	int name() {
		return a;
	}

private:
	int a;
	char b;
	int *ptr;

};

 

我們是在64位下計算的:

可以得出,其實際的需要計算的數據成員只有數據成員a,b,*ptr;我們知道int型的字節爲4,char 爲1字節,指針8字節,所以總共是13字節,由於字節對齊的原因,所以總共是16字節。

類的虛函數與繼承的實現方式

C++中的虛函數的作用主要是實現了多態的機制,這是一種泛型技術,通過指針和引用可以指向不同的具有關係的類對象。

而虛函數是通過虛函數表來實現的,當有一個類有虛函數時,那麼就會產生一張虛函數表,此虛函數表在這個類中會產生一個虛指針(vptr),這個指針指向虛函數表,裏面存的是虛函數的地址。這樣,當有繼承時,那麼派生類調用基類這個虛函數時,只會存在一份實例,它通過指針指向此函數;like:

class  Hogwards
{
public:
	 Hogwards() = default;
	 Hogwards(const int _a,const char* _b) :Ha(_a),Hb(_b) {};

	virtual string name() {
		return Hb;
	}

private:
	int Ha;
	string Hb;
	

};

class Poter:public  Hogwards
{
public:
	Poter() = default;
	Poter(const int H_a,const char* H_b,const int P_a, const char* P_b) : 
                Hogwards(H_a,H_b),Pa(P_a),Pb(P_b) {};

	string name()
	{
		return Pb;
	}


private:
	int Pa;
	string Pb;
};

Hogwards的內存佈局:

其中_fptr是虛指針,然後指向虛表,其中只有一個虛函數name;

下面是Poter的內存佈局:

這裏的虛函數就會存在覆蓋問題,此時虛指針指向得就是派生類得name,當然這裏只有通過指針和引用才能實現。

多重繼承以及虛函數問題

class Hogwards
{
public:
	Hogwards() = default;
	Hogwards(const int _a,const char* _b) :Ba(_a),Bb(_b) {};

	virtual string name() {
		return Bb;
	}


	

private:
	int Ba;
	string Bb;
	

};


class Hermione
{
public:
	Hermione() = default;
	Hermione(const int _a, const string _b) :a(_a), b(_b) {};

	void Avadagendva()
	{
		cout << "你已經die了" << endl;
	}

	virtual string name()
	{
		return b;
	}


private:
	int a;
	string b;
};


class Poter:public Hogwards,public Hermione
{
public:
	Poter() = default;
	Poter(const int B_a,const char* B_b, const int H_a, const char* H_b,const int P_a, const char* P_b) 
			:Hogwards(B_a,B_b), Hermione(H_a,H_b),Pa(P_a),Pb(P_b) {};

	string name()
	{
		return Pb;
	}



private:
	int Pa;
	string Pb;
};

這裏Poter分別繼承了Hogwards和Hermione,由於這兩個基類都是有虛函數的,從而就會有虛表:

如果派生類本身也存在獨立的虛函數,比如Poter有一個expelliarmus虛函數,那麼內部:

是的,這個expelliarmus函數就會在第一個虛指針下放着;

需要注意的是,誰有虛函數,那麼就會在前面;


下面是關於虛函數的實現: 

#include <iostream>

using namespace std;


class Base1
{
public:
	int base1_1;
	int base1_2;

	virtual void __cdecl base1_fun(int x)
	{
		std::cout << "Base1::base1_fun(" << x << ")\n";
	}
};

class Base2
{
public:
	int base2_1;
	int base2_2;

	virtual void __cdecl base2_fun(int x)
	{
		std::cout << "Base2::base2_fun(" << x << ")\n";
	}

	
};

class Derive : public Base1, public Base2
{
public:
	Derive()
	{
		base1_1 = 11;
		base1_2 = 12;
		base2_1 = 21;
		base2_2 = 22;

		derive1 = 1;
		derive2 = 2;
	}

	int derive1;
	int derive2;

	virtual void __cdecl derive_fun1() {
		cout << "Hello my friend!" << endl;
		return ;
	};
	
};



void foo(Base1* pb1, Base2* pb2, Derive* pd)
{
	
	std::cout << "Base1:\n"
		<< "\tbase1_1 = " << pb1->base1_1 << "\n"
		<< "\tbase1_2 = " << pb1->base1_2 << "\n"
		<< std::endl;

	std::cout << "Base2:\n"
		<< "\tbase2_1 = " << pb2->base2_1 << "\n"
		<< "\tbase2_2 = " << pb2->base2_2 << "\n"
		<< std::endl;

	std::cout << "Derive:\n"
		<< "\tderive1 = " << pd->derive1 << "\n"
		<< "\tderive2 = " << pd->derive2 << "\n"
		<< std::endl;

	pb1->base1_fun(11);
	pb2->base2_fun(22);

	pd->derive_fun1();
	
}

struct Base1_VPTR_VPTR {
	void(__cdecl* base1_fun)(Base1* that, int x);
	void(__cdecl *derive_fun1)(Derive *that);
};

struct Base1_VPTR {
	Base1_VPTR_VPTR* pvptr;
	int base1_1;
	int base1_2;
};

struct Base2_VPTR_VPTR {
	void(__cdecl* base2_fun)(Base2* that, int x);
};

struct Base2_VPTR {
	Base2_VPTR_VPTR* pvptr;
	int base2_1;
	int base2_2;
};

void __cdecl base1_fun(Base1* that, int x)
{
	std::cout << "Base1::base1_fun(" << x << ")\n";
}

void __cdecl base2_fun(Base2* that, int x)
{
	std::cout << "Base2::base2_fun(" << x << ")\n";
}

//這裏沒有用到,因爲Derive 的虛函數會放在第一個虛指針所指的虛表裏面
//所以Base的虛表需要重新構造,這就是爲什麼我上面的Base1的虛表有兩個函數
struct Derive_C_VPTR
{
	void(__cdecl *derive_fun1)(Derive *that);
};

struct Derive_C
{
	Base1_VPTR base1;
	Base2_VPTR base2;
	int derive1;
	int derive2;

};


void __cdecl derive_fun1(Derive* that)
{
	cout << "Hello my friend!" << endl;
	return;
}

int main()
{
	Derive dd;
	foo(&dd, &dd, &dd);
	dd.derive_fun1();

	Derive_C d;
	Base1_VPTR_VPTR base1_vptr_vptr;
	Base2_VPTR_VPTR base2_vptr_vptr;
	Derive_C_VPTR C_vptr;

	base1_vptr_vptr.base1_fun = base1_fun;
	base1_vptr_vptr.derive_fun1 = derive_fun1;
	base2_vptr_vptr.base2_fun = base2_fun;
	C_vptr.derive_fun1 = derive_fun1;


	d.base1.base1_1 = 11;
	d.base1.base1_2 = 12;
	d.base1.pvptr = &base1_vptr_vptr;

	d.base2.base2_1 = 21;
	d.base2.base2_2 = 22;
	d.base2.pvptr = &base2_vptr_vptr;

	d.derive1 = 1;
	d.derive2 = 2;


	foo((Base1*)&d.base1, (Base2*)&d.base2, (Derive*)&d);


	return 0;
}

 

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