C++多态实现原理详解

阅读引言: 我想象了一下, 假如人有突然问我什么是多态, 我该如何给别人说清楚呢?所以写下这篇文章, 希望大家看完有所收获。

目录

①. 开胃小菜

②. 多态常见的一个小小面试题

③, 虚函数指针虚函数表

④. 多态的理解

⑤, 问题


①. 开胃小菜

先看这样一个开胃小菜

这里我有点小小的疑惑, 大小为啥是1。

在C++中,结构体(struct)A的对象a的大小为1的原因可能是因为编译器对结构体进行了内存对齐。默认情况下,C++编译器会将结构体的起始地址与最大成员的对齐要求进行对齐。在这个例子中,结构体A是空的,没有任何成员,所以编译器将其大小设置为1字节,以满足内存对齐的要求。

结构体对齐, 大小为1, 1是不是能被任意的地址整除, 这是结构体对齐的知识。

②. 多态常见的一个小小面试题

假设使用上面的类实例化出一个类对象, 使用sizeof求该对象的大小?

结果为12个字节,这里使用的是32位环境。


为什么多出了四个字节呢?这多出来的四个字节是啥?为什么需要这个四个字节的空间, 用来干什么?


1,因为当一个类中出现虚函数的时候, 无论是自己本身的还是继承来的, 都会多出四个字节的空间, 这四个字节的空间其实是一个指针, 专业名词叫做虚函数表指针, 用来指向虚函数表。虚函数表指针(vptr)、虚函数表后面会介绍。

2, 这多出来的四个字节就是一个指针, 指向虚函数表, 这是编译器载编译期间帮我们做的, 伪代码如下。

3, 因为需要直到虚函数表的内存地址, 才能通过指针访问到虚函数, 虚函数表中的内容其实就是虚函数的入口地址。

③, 虚函数指针虚函数表

虚函数指针: 本质就是一个指针变量, 用来保存虚函数表的地址

虚函数表: 本质是内存中的一段连续空间, 空间内的每一项都是一个函数指针, 用来保存虚函数的入口地址。

内存布局: 

需要注意的是, 虚函数表属于类, 然后需要直到虚函数指针被赋值的时机是载钩爪函数中, 这是编译器默默为我们做的。

④. 多态的理解

代码层面上: 

多态存在的条件: 类中必须存在虚函数, 调用虚函数的方式必须使用虚函数表指针, 找到虚函数表, 接着调用里面的虚函数。换句话说就是必须是指针或引用调用的虚函数才是多态, 而静态创建的对象出现不了多态。

表现形式上看多态: 

第一条: 子类中重写父类中的虚函数是由要求的, 也就是函数的返回值、函数名、参数列表都需要相同, 但是这里有一个小小的特殊情况, 就是当基类中的虚函数返回的值基类指针或者引用的时候, 允许派生类中的函数的返回值返回派生类的指针或者引用。

#include <iostream> 
using namespace std; 
class A{};
class B:public A{}; 
class Base{ 
public: 
    virtual void func(void){
        cout << "Base func" << endl; 
	} 
	virtual A* foo(void){
        cout << "Base foo" << endl; 
	} 
}; 

class Derived: public Base{ 
	void func(void) { 
        cout <<"Derived func" << endl; 
	}
    B* foo(void){                       //允许返回子类的指针或者引用,构成虚函数的覆盖条件 					
        cout << "Derived foo" << endl; 
	}
}; 

int main(void){ 
    Derived d1;
    Base *pd1 = &d1; 
    pd1->func();
    Base& pd2 = d1;
    pd2.foo();
    return 0; 
} 

总结一下虚函数覆盖的条件: 

  • 只有类中的成员函数才能声明为虚函数,而全局函数、静态成员函数、构造函数都不能被声明为虚函数

  • 只有在基类中以virtual关键字声明的虚函数,才能作为虚函数被子类覆盖,而与子类中的virtual关键字无关

  • 虚函数在子类中的版本和基类中版本要具有相同的函数名,即函数名、参数表、常属性一致

  • 如果基类虚函数返回基本类型的数据,那么子类中的版本必须返回相同类型的数据;如果基类虚函数返回类类型指针(A)或引用(A&),那么允许子类中的版本返回其子类类型指针(B)或引用(B&)

我们来重点看一下第三句话

简单的看一下实现的原理 

当一个基类中存在虚函数的时候, 派生类机会从基类那里将其继承过来, 当派生类中函数满足基类中虚函数的覆写条件的时候, 就会将自己的虚函数表原先基类中的虚函数地址给替换成自己的函数地址, 这样, 不同的派生类只要是满足了虚函数的调用条件, 调用虚函数的时候, 调用的就是自己虚函数表中的那个自己实现的函数, 从而实现了多态。

从上面我们可以看出, 多态的实现其实就是将继承过来的虚函数表中原先的函数地址, 换成了自己的函数地址。

⑤, 问题

简单讲一下什么是多重继承, 就是一个类继承了多个基类。

在C++中,当进行多重继承时,子类中会有多个虚函数表指针,并且也会有多个虚函数表

首先,我们来理解虚函数表指针和虚函数表的概念。在C++中,如果一个类定义了至少一个虚函数,那么这个类就会拥有一个虚函数表(vtable),它存储了该类所有虚函数的地址。而虚函数表指针(vptr)是指向这个虚函数表的指针,它存在于每个拥有虚函数的类的实例中。

在多重继承的场景中,每个含有虚函数的基类都会为子类贡献一个虚函数表和对应的虚函数表指针。因此,如果一个子类从两个或多个含虚函数的基类继承而来,那么它就会拥有与这些基类数量相同的虚函数表指针。这些虚函数表指针的顺序与继承的顺序一致。

至于虚函数表的数量,如果子类没有新增虚函数,那么多重继承的子类会将全部基类的虚函数表继承下来。如果子类新增了虚函数,则这些新的虚函数会被添加到继承的第一个基类的虚函数表中,除非存在虚函数的重写,这种情况下新的虚函数会覆盖掉基类原有的虚函数

综上所述,在C++多重继承的情况下,子类中的虚函数表指针数量等于其含虚函数的基类数量,而虚函数表的数量则取决于子类是否新增了虚函数以及是否重写了继承自基类的虚函数。

好了, 以上就是全部内容, 图片资源部分来自网络, 如有侵权, 请联系我, 将其删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@daiwei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值