多态-vfptr指针剖析 c++

多态问题解决

多态是c++中的重要一部分,他在接口编程,协议制定方面都大有用处。
但是 用小白的话说 其实无非就是 利用父类指针变量来调用子类成员函数。
实现一个函数有多种形态。
ok
让我们首先利用仅有的知识解决父类指针调用子类函数
函数调用有两种方法,
一是指针->函数名
二是函数指针取函数地址进行调用

函数指针复习:

首先 定义函数指针有两种方法,

  1. 直接定义函数指针变量
    如:void (*ptr)(int);
    就是定义一个返回值为void参数为一个int的函数指针。
  2. 定义一个函数指针的类型 用该类型定义函数指针。
    如:typedef void(*ptr2)(int) ;
    ptr2 *ptr;
    此例中ptr就是与上面例子中的指针相同。
    !!!
    当用普通函数指针间接使用类中的函数时,类中的函数必须是静态函数(static关键字)
    且语法是 函数指针=&类名::函数名
    (成员函数指针与其类似)
    !!!
    细节见代码:
#include <stdio.h>
#include <iostream>
using namespace std;

class person {
public:
	static void show0(int  num)
	{
		cout << "你好3" << endl;
	}
};
void show1(int num3,int num2)
{
	cout << "你好2" << endl;
}
void show2(int num)
{
	cout << "你好1" << endl;
}
 	void (*ptr)(int,int);//定义函数指针变量
 typedef void(*ptr2)(int);//给一个函数指针的类型取别名
int main()
{
	ptr2 p = &show2;//注意ptr2是一种类型
	p(1);

	ptr = &show1;//而ptr是一种指针
	ptr(1, 2);

	//普通函数指针指向类中的函数,那个函数必须是静态函数
	void(*ptr3)(int);
	ptr3 = &person::show0;// 给函数指针赋别名, &类名::函数名
	ptr3(1);

	system("pause");
	return 0;
}


注意::
类中成员函数指针:
上面介绍了 的函数指针只能调用类中static修饰的函数,太局限。
所以c++大牛们提供了
::,.,->* 这三种用法来满足需求。(调用非静态函数,注意的是调用时需要调用对象,或者对象指针)
即 语法:返回值(类名::*指针名)(参数);
如 void(myclass::*ptr)(int );
来定义成员函数指针


person ptemp;
(ptemp.*ptr)(10);
来取成员函数地址
详情见代码:

#include <stdio.h>
#include <iostream>
using namespace std;
class person {
public:
	void sol(int num)
	{
		cout <<"num  "<<num<< endl;
	}
};
//::*  .*  ->* 是c++提供定义类中成员函数指针的方法
int main()
{
	void (person::*ptr)(int);//定义成员函数指针
	ptr = &person::sol;//指针指向成员函数
	//.*的使用
	person ptemp;
	(ptemp.*ptr)(10);//注意 ptemp.*ptr(10)是不合法的,必须加括号
	//->*的使用
	person *ptemp2=&ptemp;
	(ptemp2->sol)(11);
	
	system("pause");
	return 0;
}


解决问题:

首先我们尝试利用上述方法:
//方法一:调用函数指针+函数名
fa *pfa = new son;
//fa->show();
经测验很明显不好使
//方法二:调用父类的成员函数指针来指向子类的函数
void(fa::ptr)();
//ptr = &son::show;
这时候我们发现类型不匹配所以需要强转。
(记住去掉变量名就是变量的类型所以把ptr去掉把剩下的一大堆加上)
ptr = (void (fa::
)())&son::show;//测试一下果然可以
(哈哈哈终于解决啦)
见代码:

#include <stdio.h>
#include <iostream>
using namespace std;
class fa {

};
class son :public fa {
public:
	void show()
	{
		cout << "你好" << endl;
	}
};
int main()
{
	//方法一:指针加函数名
	fa *pfa = new son;
	//fa->show();
	//方法二:父类成员函数指针指向子类函数
	void(fa::*ptr)();
	//ptr = &son::show;类型不匹配所以需要强转。
	ptr = (void (fa::*)())&son::show;
	system("pause");
	return 0;
}


但是不是太麻烦了?? 没错就是很麻烦,光是强转类型就是一大串,而且在接口编程方面也不太现实。
所以c++大牛们提供了virtual关键字
即父类函数拉入虚函数表,子类重写该函数的内容。
在执行父类指针是会随着取得对象类型不同,调用的重写的函数也会随之改变。从而实现多态。
巴拉巴拉一大堆 不如见代码

#include <stdio.h>
#include <iostream>
using namespace std;
class water {
public:
	virtual void show()
	{
		cout << "water::show" << endl;
	}
};
class coffee:public water {
public:
	void show()
	{
		cout << "coffee::show" << endl;
	}
};
class coco :public water {
public:
	void show()
	{
		cout << "coco::show" << endl;
	}
};
class milk :public water {
public:
	void show()
	{
		cout << "milk::show" << endl;
	}
};
void drink(water *p)
{
	p->show();
}
int main()
{
	water *pw = new water;
	milk *pm = new milk;
	coco *pcc = new coco;
	coffee *pc = new coffee;
	drink(pw);
	
	drink(pm);
	drink(pcc);
	drink(pc);

	system("pause");
	return 0;
}


没错前面那一大堆,都不如一个virtual来的有效。
但是函数指针 在其他方面也有更有效地使用。
(不过virtual的原理还是vfptr函数指针,只不过c++的大牛们封装好了)

vfptr原理浅谈:

首先我们将类的继承分为三部分空间
(里面的函数与我后面的代码样例相照应)

类的结构
vfptr指针(首地址前四个字节)
父类成员(虚函数AA,BB)
子类成员(重写的虚函数AA,BB,以及普通函数CC)

而指针指向虚函数表

虚函数表
虚函数AA
虚函数BB


而当子类继承的时候,子类的空间只会像第一个表一样重复叠加,而一个继承家族中虚函数表只有一份,并不会重复叠加,相当于

->A父类定义虚函数(加上virtual修饰符)
->虚函数表加上A父类的虚函数
->B子类重写A父类虚函数
->虚函数表加上重写的B虚函数
->孙子C重写A类虚函数
->虚函数表加上重写的C类的虚函数
然后…

!!!
值得注意的是 如果儿子B右重新声明了一个虚函数
仍然是在虚函数表B的那一部分 加上虚函数,不会生成新的虚函数表
即 一个家族只有一个虚函数表。

所以 代码编译时,一个继承关系的虚函数表只存在一份,而vfptr指针存在于每个声明的对象中(而且是类的前四个字节)。
虚函数表既然已经形成,那么父类指针指向的对象调用函数时,如果是虚函数的话会随着vfptr指针指向该对象在虚函数表中对应的函数。从而完成多态。而普通函数不存放于虚函数表,只会调用父类的普通函数。
比如:
void(fa *p)
{
p->AA();(就相当于于p->vfptr[i])//
注意这里为了说明运行时大致情况,
真正实现的细节过程见下面)
}
通过vfptr指针来调用函数,从而证明过程:
证明过程!!!
我们先取对象指针
fa *p=new son;
再取指针的前四个字节
*(int*)p;
由于函数指针无法做偏移调用,所以需要将其进行转成int *函数指针
(int*)*(int*)p
再偏移
(int*)*(int*)p+0
(int*)*(int*)p+1
(int*)*(int*)p+2
因为vfptr指针指向函数表,而函数本身就是函数首地址,所以很容易理解vfptr指针是一个二级指针。
那么调用时就需要加个*.

*(int*)*(int*)p+0
*(int*)*(int*)p+1
*(int*)*(int*)p+2
然后为了我们能辨别到底是否调用了对应函数中的地址,我们可以使用函数指针定义指针变量(注意还是需要一次强行转换),再通过vs编译器的下断点调试进行显示。

typedef void(*fun)();
fun pfun1=*(int*)*(int*)p+0;
~~~~~
上面是为了帮助理解,实际代码很短:

#include <stdio.h>
#include <iostream>
using namespace std;
//什么时候多态? 通过父类的指针调用实际的子类成员,使父类的指针有多种形态
//
//v_table
//vfptr指针 记录表的地址 是类中第一个成员 是类创建对象时候存在,每个对象有一份
class fa {
public:
	virtual void AA()
	{
		cout << "AA" << endl;
	}
	virtual void BB()
	{
		cout << "BB" << endl;
	}
	virtual void CC()
	{
		cout << "CC" << endl;
	}
};
class son:public fa {
public:
	 void AA()
	{
		cout << "AA_son" << endl;
	}
	 void BB()
	{
		cout << "BB_son" << endl;
	}
	 void CC()
	{
		cout << "CC_son" << endl;
	}
};
int main()
{
	fa *temp = new fa;
	//temp ->AA();
	*temp;//为取实际对象
	*(int*)temp;//取对象空间的前四个字节
	(int *)*(int*)temp;//将vfptr函数指针强转成int*的指针,使指针能够偏移
	typedef void (*pfun)();
	
	pfun fun1=(pfun)*((int *)*(int *)temp + 1);
	pfun fun2=(pfun)*((int *)*(int *)temp + 2);
	pfun fun3=(pfun)*((int *)*(int *)temp + 0);
	system("pause");
	return 0;
}


在这里插入图片描述
在这里插入图片描述
证明结束~~~;
虽然有点粗糙,但如果能帮到大家理解就很满足了。
如果有错误还希望大佬们不吝赐教。
多谢!!!

总结:

c++很深奥,需要细品,细品。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值