前面学习了虚函数的原理和多态,今天来做个题熟练一下,也做个总结
前面文章的链接:
继承,多态(静态多态,动态多态)-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里面新增的虚函数都会放在第一张虚表里面