面向对象—多态

本文详细解释了面向对象编程中的多态概念,包括定义、实现原理及其与重载、覆盖的区别。通过具体代码示例展示了如何利用虚函数实现多态,并深入探讨了虚函数表的工作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

面向对象—多态

  • 定义
  • 实现
  • 原理

1、定义

多态可以简单的概括为“一个接口,多种方法”,在程序运行过程中才决定调用的函数,简单的说就是,允许将子类类型的指针赋值给父类类型的指针,赋值后,父类对象就可以根据当前赋给它的子类对象的特征以不同的方式运行。

容易混淆的两个概念:

  • 1、重载

是指允许存在多个同名的函数,而这些函数的参数表不同(或许参数个数不同,或许参数的类型不同,或许两者都不同)。其实,重载的实现是编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(编译器是这么认为的)。

对于重载的两个函数的调用,在编译器编译期间就已经确定下来了,是静态的。也就是说他们的地址在编译期间就绑定了(早绑定),因此和多态无关,与面向对象无关。

  • 2、覆盖

又称静态多态,是指子类重新定义父类的虚函数的做法。当子类重新定义了父类的虚函数之后,父类的指针根据赋给它的不同的子类指针,动态的调用属于该子类的函数,这样的调用无法在编译期间确定(调用子类的虚函数的地址无法给出)。这才是真正的多态,也是面向对象的核心。

作用:我们知道,封装可以隐藏实现的细节,使得代码模块化;继承的作用是可以扩展已存在的代码模块(类);它们的目的都是为了代码的重用。而多态是为了实现另一个目的——接口重用。

2、实现

实现多态时,必须使用基类类型的指针变量或者引用,使该指针指向该基类的不同派生类的对象,并通过该指针指向虚函数,这样就可以实现动态多态了。

下面举个实现多态的例子:

class animal
{
public:
    void sleep()
    {
        cout<<"animal sleep"<<endl;
    }

    virtual void breathe()
    {
        cout<<"animal breathe"<<endl;
    }
};

class fish:public animal
{
public:
    void breathe()
    {
        cout<<"fish bubble"<<endl;
    }
};

int main(void)
{
    fish fh;
    animal *pAn=&fh;
    pAn->breathe();
    return 0;
}

当在main()函数中执行pAn->breathe()时,调用的就是fish对象的breathe函数。

3、原理

当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储类成员函数指针的数据结构,虚函数表是由编译器自动生成与维护的,virtual成员函数会被编译器放入虚函数表中,存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)。

如下图所示:

这里写图片描述

编译器为每个类的对象提供一个虚表指针vptr,这个指针指向对象所属类的虚函数表。在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向所属类的虚表,从而在调用虚函数时,就能够找到正确的函数。

fish fh;
animal *pAn=&fh;
pAn->breathe();

由于父类的指针pAn实际指向的对象类型是子类的对象,因此vptr指向的子类fish 类的vtable,当调用pAn->breathe()时,根据虚表中的函数地址找到的就是fish类的breathe()函数。正是由于每个对象调用的虚函数都是通过虚表指针来索引的,也就决定了虚表指针的正确初始化是非常重要的。换句话说,在虚表指针没有正确初始化之前,我们不能够去调用虚函数。

那么虚表指针在什么时候,或者说在什么地方初始化呢?

c++是在构造函数中进行虚表的创建和虚表指针的初始化。

构造函数的调用顺序:在构造子类对象时,要先调用父类的构造函数,此时编译器只“看到了”父类,并不知道后面是否后还有继承者,它初始化父类对象的虚表指针vptr,该虚表指针指向父类的虚表。当执行子类的构造函数时,子类对象的虚表指针vptr被初始化, 此时 vptr指向自身的虚表。当fish类的fh对象构造完毕后,其内部的虚表指针也就被初始化为指向fish类的虚表。

注意: 通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。出于效率考虑,没有必要将所有成员函数都声明为虚函数

对象在创建的时,由编译器对VPTR指针进行初始化,只有当对象的构造完全结束后VPTR的指向才最终确定,到底是父类对象的VPTR指向父类虚函数表还是子类对象的VPTR指向子类虚函数表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值