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;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值