多基派生的梳理和练习

本文介绍了C++中虚函数、多态和继承的概念,并通过代码示例展示了它们在实际编程中的应用。
摘要由CSDN通过智能技术生成

前面学习了虚函数的原理和多态,今天来做个题熟练一下,也做个总结

前面文章的链接:
继承,多态(静态多态,动态多态)-CSDN博客

虚函数原理和多态(超详细)-CSDN博客

话不多说,直接上代码:
 

#include <iostream>

using std::cout;
using std::endl;

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

	virtual
	void b()
	{
		cout << "A::b()" << endl;
	}

	virtual
	void c()
	{
		cout << "A::c()" << endl;
	}
};

class B
{
public:
	virtual
	void a()
	{
		cout << "B::a()" << endl;
	}

	virtual
	void b()
	{
		cout << "B::b()" << endl;
	}

	void c()
	{
		cout << "B::c()" << endl;
	}

	void d()
	{
		cout << "B::d()" << endl;
	}

};

class C
: public A
, public B
{
public:
	virtual
	void a()
	{
		cout << "C::a()" << endl;
	}

	void c()
	{
		cout << "C::c()" << endl;
	}

	void d()
	{
		cout << "C::d()" << endl;
	}
};

void test()
{
	C c;//栈对象

	cout << "A指针指向C对象的执行结果:"  << endl;
	A *pa = &c;
	pa->a();
	pa->b();
	pa->c();

	cout << endl;
	cout << "B指针指向C对象的执行结果:" << endl;
	B *pb = &c;
	pb->a();
	pb->b();
	pb->c();
	pb->d();

	cout << endl;
	cout << "C指针指向C对象的执行结果" << endl;
	C *pc = &c;
	pc->a();
	/* pc->b();//二义性 */
	pc->A::b();		//作用域限定符解决二义性
	pc->B::b();
	pc->c();
	pc->d();
}
int main(int argc, char **argv)
{
	test();
	return 0;
}

根据前面的两篇文章,相信上面的结果大部分都能够写出来了,有问题的应该是:

pa->c()和pb->c()这个执行结果,因为A中的c是虚构函数,B中的c是普通成员函数,不知道C继承的到底是A中的虚构函数函数还是B中的虚构函数

那我们先来看看c对象里面到底是啥样的:

然后可以看到,c中有从A和B中继承过来的两个__vfptr,分别指向两张虚表,C中重新定义的虚函数a()和c()将从A中继承过来的虚表中的a()和c()覆盖,B中的虚函数也被C中重定义的a()覆盖,thunk后面的内容是指指针偏移过去寻找C::a(),看不懂不用深究,知道覆盖了就行(d()不是虚函数,不会出现在虚表中)

所以,我们就可以画个图来直观的看看:

所以,pa->c()和pb->c()的执行结果应该可以知道了,当A *pa = &c;pa的__vfptr就指向了c中的从A继承过来的虚表,B *pb = &c; pb中的__vfptr就指向了从B继承过来的虚表,执行的结果就是C::c()和B::c(),来看看结果:

注意,非虚函数都是静态类型,是编译时期确定的,(不知道啥是静态类型的去看上一篇),只有虚函数才涉及到多态,才要区分各种情况

最后再统一梳理一下:

	C c;//栈对象
	A *pa = &c;		//pa的__vfptr指向c中的从A继承过来的虚表
	pa->a();		//C::a(),实现了多态
	pa->b();		//A::b(),C中没有重定义b(),执行的是A的
	pa->c();		//C::c(),实现了多态

	cout << endl;
	B *pb = &c;		//pb的__vfptr指向c中的从B继承过来的虚表
	pb->a();		//C::a(),实现了多态
	pb->b();		//B::b(),C中没有重定义b(),执行的是B的
	pb->c();		//B::c(),c中从B继承过来的虚表没有c()的定义,这里就是静态类型,编译期确定,执行的是B自己的
	pb->d();		//B::d(),非虚函数,静态类型,由指针的类型确定执行的函数

	C *pc = &c;		//不用说了,都是C的
	pc->a();		//C::a()
	/* pc->b();//二义性 */
	pc->A::b();		//A::b(),作用域限定符解决二义性,写死了
	pc->B::b();		//B::b(),写死了
	pc->c();		//C::c()
	pc->d();		//C::d()

然后这里再发散一下,C中的c()到底是不是虚函数呢,从A继承过来是虚函数,从B继承过来不是虚函数,到底是不是虚函数呢?

要解决这个问题,那我们再增加一个类D,继承C,如果D的虚表里有c(),就是虚函数,能够实现多态,反之就不是,大家可以先猜猜答案

class D
:public C
{
public:
	void c()
	{
		cout << "D::c()" << endl;
	}
};

void test()
{
	C c;//栈对象

	cout << "A指针指向C对象的执行结果:" << endl;
	A *pa = &c;		//pa的__vfptr指向c中的从A继承过来的虚表
	pa->a();		//C::a(),实现了多态
	pa->b();		//A::b(),C中没有重定义b(),执行的是A的
	pa->c();		//C::c(),实现了多态

	cout << endl;
	cout << "B指针指向C对象的执行结果:" << endl;
	B *pb = &c;		//pb的__vfptr指向c中的从B继承过来的虚表
	pb->a();		//C::a(),实现了多态
	pb->b();		//B::b(),C中没有重定义b(),执行的是B的
	pb->c();		//B::c(),c中从B继承过来的虚表没有c()的定义,这里就是静态类型,编译期确定,执行的是B自己的
	pb->d();		//B::d(),非虚函数,静态类型,由指针的类型确定执行的函数

	cout << endl;
	cout << "C指针指向C对象的执行结果" << endl;
	C *pc = &c;		//不用说了,都是C的
	pc->a();		//C::a()
	/* pc->b();//二义性 */
	pc->A::b();		//A::b(),作用域限定符解决二义性,写死了
	pc->B::b();		//B::b(),写死了
	pc->c();		//C::c()
	pc->d();		//C::d()

	cout << endl;
	cout << "C指针指向D对象的执行结果" << endl;
	D d;
	C* pc1 = &d;
	pc1->c();
	
}

执行结果:

所以c()是虚函数

看看pc1里面有啥:

看着复杂,其实就是从C继承过来的两张虚表,C::c()被D::c()覆盖了,pc1->c()执行的时候现在第一张虚表里面寻找,找不到再去第二张寻找,所以这里执行的是D的函数c()

要始终注意一点:
        只有虚函数才在虚函数表(虚表)里面找,非虚函数和虚函数表没有任何关系,不要搞混了,非虚函数是静态类型,是编译期的事,由指针类型决定执行,虚函数是运行时多态,由虚函数表里面的函数确定

细心的人会发现,pa和pb指向&c过后,除了c里面的__vfptr,自己也会有一个__vfptr,而pc和pc1中,没有自己的__vfptr,为啥?

这是因为pa和pb没有继承自任何类,他们有自己的虚函数表,所以需要一个自己__vfptr来指向自己的虚函数表,而pc和pc1有继承关系,它可以复用基类的__vfptr,所以不用新增自己的__vfptr,新增的虚函数就可以放在从基类继承过来的虚函数表中(放在第一个虚函数表中)

在看内存的时候,发现一个问题:
C里面新增了虚函数,但是调试框的监视里面看不到,这是因为调试框里面隐藏了

class C
: public A
, public B
{
public:
	virtual
	void a()
	{
		cout << "C::a()" << endl;
	}

	void c()
	{
		cout << "C::c()" << endl;
	}

	void d()
	{
		cout << "C::d()" << endl;
	}
    //多加了个e
	virtual
	void e(){
		cout << "C::e()" << endl;
	}
};

这里找不到e:

用项目里面属性,修改命令行设置,查看具体的内存布局

才看到e被隐藏了,实际上是存在第一张虚函数表里面

C里面新增的虚函数都会放在第一张虚表里面

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值