您的位置:首页 > 编程语言 > C语言/C++

C++ 多态的实现原理面试知识点总结

2019-05-15 08:24 411 查看

一、什么是多态

在面向对象开发中,多态是一个很重要的特性。
什么是多态呢?就是程序运行时,父类指针可以根据具体指向的子类对象,来执行不同的函数,表现为多态。

二、C++ 多态的实现原理

【1】 实现原理

1、当类中存在虚函数时,编译器会在类中自动生成一个虚函数表
2、虚函数表是一个存储类成员函数指针的数据结构
3、虚函数表由编译器自动生成和维护
4、virtual 修饰的成员函数会被编译器放入虚函数表中
5、存在虚函数时,编译器会为对象自动生成一个指向虚函数表的指针(通常称之为 vptr 指针)

【2】举个例子
看完上面的实现原理,你可能会觉得有点懵,接下来我们就一点点分析和验证上面的结论。
#include <iostream>
using namespace std;
class Parent
{
public:
// 父类虚函数必须要有 virtual 关键字
virtual void fun()
{
cout << "父类" << endl;
}
};

class Child : public Parent
{
public:
// 子类有没有 virtual 关键字都可以
void fun()
{
cout << "子类" << endl;
}
};

int main()
{
Parent *p = NULL; // 创建一个父类的指针
Parent parent;
Child child;
p = &parent; // 指向父类的对象
p->fun(); // 执行的是父类的 fun() 函数
p = &child; // 指向子类的对象
p->fun(); // 执行的是子类的 fun() 函数
return 0;
}

如上例代码所示,当我们传入父类对象时,将调用和执行父类的函数,当我们传入子类对象时,将调用和执行子类的函数。而 C++ 编译器的执行过程其实是这样的:
1、父类的 fun() 是个虚函数,所以编译器给父类对象自动添加了一个 vptr 指针,指向父类的虚函数表,这个虚函数表里存放了父类的 fun() 函数的函数指针。
2、子类的 fun() 函数是重写了父类的,即写不写 virtual 编译器都会为其自动添加一个 virtual,然后编译器给子类对象自动添加了一个 vptr 指针,指向子类的虚函数表,这个虚函数表里存放了子类的 fun() 函数的函数指针。
3、执行 p->fun() 时,编译器检测到 fun() 是一个虚函数,所以不会静态的将 Parent 类的 fun() 方法直接编译过来,而是是运行的时候,动态的根据 base 指向的对象,找到这个对象的 vptr 指针,然后找到这个对象的虚函数表,最后调用虚函数表里对应的函数,实现多态。

三、证明 vptr 的存在
上面说了这么多,那么怎么证明说的都是对的呢?vptr 指针真的存在么?
其实要证明 vptr 的存在很简单,我们只需要创建两个相同的类,一个类有虚函数,一个类没有虚函数,然后通过 sizeof() 方法打印出类对象的大小就行。如下:

#include <iostream>
using namespace std;
class Parent1
{
public:
int a;
void fun() {} // 非虚函数
};
class Parent2
{
public:
int a;
virtual void fun() {} // 虚函数
};

int main()
{
Parent1 p1;
Parent2 p2;
cout << sizeof(p1) << endl;
cout << sizeof(p2) << endl;
return 0;
}
运行结果:
4
8

可以看到,存在虚函数的类的对象,大小大了4个字节,这正好是一个指针对象的大小(指针对象的大小可能会根据运行环境而改变,32位的系统指针的大小是4个字节),这说明编译器确实给我们添加了这么一个指针对象 vptr。

四、父类的构造方法中调用虚函数,会发生多态吗?
看下面这个例子:

#include <iostream>
using namespace std;
class Parent
{
public:
Parent()
{
// 父类的构造方法中执行虚函数,会发生多态吗?
fun();
}
virtual void fun()
{
cout << "父类" << endl;
}
};
class Child : public Parent
{
public:
Child()
{
fun();
}
void fun()
{
cout << "子类" << endl;
}
};

void main()
{
Child c;
return 0;
}

执行程序会发现,创建子类对象时,会先创建父类对象,而父类的构造方法中调用虚函数,执行的并不是子类的 fun() 函数,而是父类自己的 fun() 函数,并没有发生多态。
即答案是:父类的构造方法中调用虚函数,不会发生多态。这个和 vptr 的分步初始化有关。

五、vptr 的分步初始化
从上例中我们看到,在父类中调用虚函数时,执行的还是父类的函数,没有发生多态。这是因为当创建子类对象时,编译器的执行顺序其实是这样的:

1、对象在创建时,由编译器对 vptr 进行初始化
2、子类的构造会先调用父类的构造函数,这个时候 vptr 会先指向父类的虚函数表
3、子类构造的时候,vptr 会再指向子类的虚函数表
4、对象的创建完成后,vptr 最终的指向才确定
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: