[笔记]类中普通成员函数、虚函数的访问方式详解

普通成员函数

类中普通成员函数的访问方式是通过调用者的类型访问

test:该测试存在巧合:两个类中对应位置的变量类型相同

class A
{
	int a;
	char c;
public:
	A(){cout<<"A()"<<endl;}
	~A(){cout<<"~A()"<<endl;}
public:
	void fun()
	{
		cout<<"A_fun()"<<endl;
	}
};

class B:public A
{
	int a;
	char c;
public:
	B(){cout<<"B()"<<endl;}
	~B(){cout<<"~B()"<<endl;}
public:
	void fun()
	{
		cout<<"B_fun()"<<endl;
	}
};
int main(int argv,char argc[])
{
	A *pa,a;
	B *pb,b;
	pa = &a; pa->fun();//假定此处第10行
	pa = &b; pa->fun();//11
	pb = &b; pb->fun();//12
	pb = (B*)&a; pb->fun();//13
	return 0;
}

得到结果:
在这里插入图片描述
第10行和12行,都是自己的指针指向自己的对象调用fun()函数,调用自身的函数,不难理解。11行用Al类指针指向了B类对象(基类指针可以指向派生类对象),调用的是A类的函数。而14行是B类指针指向了A类对象,在c++语法中是不允许的,所以我们通过强制类型转换为B*类型,pb->fun();调用的是B类函数。
原因:
可以这样理解…现在,在我们的代码区有两个fun()函数,在调用时程序将两个函数编译成了有不同参数,也就是当前对象的指针this
A类fun函数:

fun(A * const this);

B类fun函数:

fun(B * const this);

相当于做了函数重载,来区分两个同名函数,将pa->fun();编译成fun(pa);将pb->fun();编译成fun(pb);。所以得出结论:普通成员函数的访问是通过调用者类型来访问的

在来看一下他们的同名数据如何调用…

用于测试,就不管数据的封装性了,将成员变量都写成public,做以下更改,给成员变量赋值,并将其打印
在这里插入图片描述
得到结果:
在这里插入图片描述
第10行和12行不难理解,而第11行,打印的a是A类中的。
计算对象a的内存大小为8字节,b为16字节,其中8字节继承与A类,因为pa指针只能指向8字节大小,pa=&b所得到的是内存中前面的8字节(也就是b对象中无名的A类对象内存),后面的8字节会无法保存被丢弃,所以打印的变量a是对象b中无名的A类对象的变量a
画内存图解释…
在这里插入图片描述
而13行,pb = (B*)&a; 打印的值却是一个未知值?是因为pb所能指的是16字节大小的内存,前面8字节保存继承下来的值(分别是10,和字符A),后面8字节保存自己的值,因为是将A强制转换为B,此时后面的8字节是没有数据的,所以打印是未知值,这也是c++语法为什么允许这样做的原因!
pb=(B*)&a;内存图如下…

在这里插入图片描述

虚函数的访问方式

虚函数是通过虚表访问
如果类中有虚函数,那么这个类会多出4个字节,这4个字节保存的虚表地址,也就是指向一个函数指针数组的指针,保存在内存前4个字节。虚函数可以被继承,但虚表本身不会,派生类可以重写覆盖基类虚函数。如果类中有虚函数,那么该类的析构函数需要写成虚析构,否则在堆区申请的派生类对象,delete时无法被释放。

test:

class A
{
	int a;
	char c;
public:
	virtual void fun1()
	{
		cout << "A_fun1()" << endl;
	}

	virtual void fun2()
	{
		cout << "A_fun2()" << endl;
	}
public:
	A() { cout << "A()" << endl; }
	 ~A() { cout << "~A()" << endl; }
};

int main(int argv, char argc[])
{
	A *pa;
	B b;
	pa=&b;
	pa->fun1();
	pa->fun2();
	return 0;
}

结果为:
在这里插入图片描述

B类公有继承与A类,则B的虚函数表中也有fun1(),fun2()。B类中只重写了fun1()函数,从A中继承下来的fun1()函数被覆盖,fun2()没有。

在这里插入图片描述

通过virtual多出的4字节指针访问虚函数表

int main(int argv, char argc[])
{
	typedef void(*pFun)();//函数指针类型取别名
	A m;
	pFun p;
	for (int i = 0; i < 2; i++)
	{
		p = (pFun)*((int *)*(int *)(&m) + i);
		p();
	}
	return 0;
}

得到结果:
在这里插入图片描述
对象的首地址的前4字节是一个指向一个函数指针数组的指针,可以通过偏移指向数组首地址进行访问虚函数。得到对象地址 &m,将这个地址看成是指向int类型的指针 (int *)&m,得到一个十六进制的整数地址,该地址保存的是函数指针数组首地址 *(int *)(&m),将这个地址看成是指向int类型的指针,然后做偏移( (int )(int *)(&m))+i,再解引用得到虚函数的首地址,将其转为函数指针(pFun),再对函数指针进行解引用(即调用虚函数),p();

模拟内存图
在这里插入图片描述
{虚函数按书写顺序写入虚表,类中的成员变量也是同理}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值