C++ 多态

什么是多态

多态是面向对象编程的一个核心概念,它允许处理对象的代码基于对象的运行时类型,以不同的方式进行响应。直观地讲,多态就是一种可以以多种形态呈现的性质。在计算机编程中,我们指的是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

C++中的多态主要有两种形式:编译时多态(静态多态)和运行时多态(动态多态)。

  1. 编译时多态:这主要由函数重载和运算符重载实现。例如,你可以重载一个函数,使得它根据参数的不同类型执行不同的代码。

  2. 运行时多态:这主要由虚函数实现,只有指针或引用可以实现运行时多态。例如,你可以有一个指向基类的指针,但实际上它指向的是派生类的实例,当你通过这个基类指针调用一个虚函数时,实际上调用的是派生类的虚函数。

多态的定义及其实现

多态的两个条件

1.虚函数重写              (只有虚函数才能被覆盖或重写)

2.父类指针或引用去调用虚函数

特例 :

子类虚函数不加virtual,依旧构成重写(建议写,增加代码可读性)

重写的协变:协变允许子类在覆盖基类中的方法时,将返回的类型改为该基类方法返回类型的子类型。这是类型安全的,因为子类型的对象可以被认为是父类型的对象。

例:

class Food {};

class Seed : public Food {};

class Animal {
public:
    virtual Food* getFavoriteFood() {
        return new Food();
    }
};

class Bird : public Animal {
public:
    Seed* getFavoriteFood() override {
        return new Seed();
    }
};

基类Animal有一个成员函数getFavoriteFood,它返回一个Food*。 Bird类是Animal的子类,它覆盖了基类的成员函数getFavoriteFood,并返回了一个更具体的类型Seed*

虚函数和普通函数的区别 

虚函数重写是接口继承,重写实现。
普通函数继承是继承实现

        先看一下两种概念

  • 接口继承:指的是派生类继承基类的函数声明(也就是接口)。这意味着派生类承诺提供基类函数的一套实现,不论是通过继承基类的实现,还是通过提供自己的实现。接口继承关乎于“能做什么”,它定义了一个类可以做哪些操作,但不关心具体如何实现这些操作。

  • 实现继承:指的是派生类继承基类的函数实现。在这种情况下,除非派生类提供了自己的实现(即重写了基类的方法),否则派生类会自动使用基类的实现。实现继承关乎于“如何做”。

所以在C++中,如果基类的虚函数有一个缺省参数(默认参数),然后派生类重写这个虚函数时,这里会有一个潜在的问题:默认参数是静态绑定的,而虚函数是动态绑定的。这意味着,默认参数的值是由对象的静态类型决定的,而不是它的动态类型。

本质

每一个带有虚函数的类都会有一个虚函数表(vtable),这个表是一个存储了该类所有虚函数地址的数组。每个对象中则包含一个指向其类的虚函数表(vtable)的指针。这个指针通常被称为虚指针(vptr)。虚指针的值在对象构造时被设置,以确保它指向正确的虚函数表。当我们调用一个虚函数时,实际上是通过对象的虚指针找到虚函数表,然后通过虚函数表找到具体函数的地址,最后调用该地址的函数。

普通函数在编译链接时确定函数的地址,运行时直接调用

析构函数会被处理成destructor,所以基类析构函数要声明成虚函数重写

static成员函数是否可以是虚函数

static成员函数不能被声明为虚函数。这两个概念是互斥的:

  1. 虚函数的目的:虚函数主要用于实现多态性。当通过基类的指针或引用调用一个虚函数时,程序运行时会根据对象的实际类型来决定调用哪个派生类的函数。这需要对象特有的信息(通常是对象中的虚函数表指针)来确定具体调用哪个函数。

  2. static成员函数的特性static成员函数不属于任何对象实例,而是属于类本身。这意味着static函数不需要通过对象来调用,也没有this指针。因为static函数与具体的对象实例无关,所以它们无法使用对象的虚函数表来支持多态性。

inline函数是否可以是虚函数

C++的虚函数可以是内联函数。但 inline virtual 唯一可以内联的时候是编译器知道所调用的对象是哪个类。它们在多态调用上不会被内联。

虚函数:类成员函数前面添加virtual关键字,则该函数被称为虚函数。

内联函数:在函数前面添加inline关键字,则该函数被称为内联函数。
但是inline声明对编译器来说只是一种建议,编译器可以选择忽略这个建议。比如将一个1000多行的函数指定为inline,编译器就会忽略这个inline,将这个函数还原成普通函数。

虚表

一个类中只能有一张虚表。每个使用了虚函数的类都会有一个对应的虚表。这里有几个关键点来解释虚表的概念和作用:

  1. 虚表的作用:虚表用来存储类的虚函数地址。当调用一个虚函数时,程序会根据对象的虚表来查找并调用正确的函数实现。

  2. 虚表的存储:一般而言,每个实例对象都会有一个指针,称为虚指针(vptr),指向它所属类的虚表。不同实例的虚指针指向相同类的虚表。虚表指针一般是类的第一个成员且是私有的,在public继承后父类的虚表指针是不可见的,所以他没法访问父类的数函数表。

  3. 构造函数和虚表:对象在构造期间,虚指针会被初始化,指向相应的虚表以确保虚函数调用的正确性。

  4. 继承和虚表: 


    虚表本身不会被继承。当继承发生时,子类会把所有虚函数的地址都放在自己的虚表里。如果派生类覆盖(重写)了基类的虚函数,派生类的虚表中会用新的函数地址替换从基类继承的函数地址。如果派生类新增了虚函数,这些函数地址也会被加入到虚表里。


    继承会把父类的所有成员包括虚表指针也继承下来,但是基类的私有成员无法访问。


  5. 析构函数和虚表:在多态基类的析构函数应该被声明为虚析构函数以确保正确的析构顺序,同时允许通过基类指针来销毁派生类对象。

  6. C++虚表(虚函数表)的大小是不确定的,因为它取决于类的继承层次结构和虚函数的数量。每个类只有一个虚函数表,其中包含指向虚函数的指针。如果该类没有虚函数,则不会创建虚函数表。当类被继承时,子类的虚函数表是将父类的虚函数表“抄一份”,其中重写的虚函数修改成自己的,新增的虚函数添加到表中。
    需要注意的是,虚表的存在只会增加对象的大小,而不会改变类的大小。在类的每个对象中,只有一个指向虚表的指针,而类的所有对象共享同一个虚表。

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值