【C++】多态

目录

一、多态的定义和构成条件

1.定义

2.构成条件

二、虚函数重写

三、重载、重写、隐藏 (重定义)的比较

四、静态绑定与动态绑定

1.静态绑定

2.动态绑定


一、多态的定义和构成条件

1.定义

多态即多种形态,当不同对象完成某个行为时,产生的效果是不同的,例如,动物叫声,猫和狗都继承了动物,但是当调用它们的叫声时是不一样的,再如不同职业的人薪资是不一样的......

2.构成条件

我们先举一个需要多态的例子:

#include <iostream> 
using namespace std;

class Shape {

public:
    int width, height;

    Shape(int a = 0, int b = 0)
    {
        width = a;
        height = b;
    }
    void area()
    {
        cout << "Shape area : " << endl;
    }
};

class Rectangle : public Shape {
public:
    Rectangle(int a = 0, int b = 0) :Shape(a, b) { }
    void area()
    {
        cout << "Rectangle class area :" << width * height << endl;
    }
};

int main()
{
    Rectangle rec(3, 4);
    Shape* p = &rec;
    p->area();
    return 0;
}

我们期望最后算出来的值是矩形的面积,但是通过用指向矩形对象的指针调用面积函数,发现最后调用的还是基类的面积函数,但倘若面积函数是虚函数,则就可以按预期调用矩形的面积函数了。

虚函数就是被关键字virtual修饰的成员函数,并且该函数是类的非static成员函数。

所以,构成多态需要两个条件,即①通过基类的指针或者引用调用虚函数,②被调用的函数必须是虚函数,并且派生类中要对虚函数进行重写。

二、虚函数重写

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名、参数列表完全相同),称派生类的虚函数重写了基类的虚函数。

在派生类中重写虚函数时,也可不加virtual,默认是有virtual的,但是为了可读性等原因,一般建议还是加上。

虚函数重写的两个例外:

①协变:派生类重写基类虚函数时,与基类虚函数返回类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

②析构函数的重写:如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,因为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。

两个关键字:

①final

final修饰虚函数后,表示该虚函数不能再被重写了。

例如:

virtual void area() final
{
    cout << "Rectangle class area :" << width * height << endl;
}

 ②override

override是用来检查派生类虚函数是否重写了基类某个函数,如果没有重写就会报错。

例如:

 virtual void area() override
 {
     cout << "Rectangle class area :" << width * height << endl;
 }

三、重载、重写、隐藏 (重定义)的比较

重载的构成条件:同一作用域,函数名相同,参数不同(类型或者个数)的函数

重写的构成条件:两个函数分别在基类和派生类的作用域,函数名、返回值类型、参数完全一致,是虚函数

隐藏的构成条件:两个函数分别在基类和派生类的作用域,函数名相同

四、静态绑定与动态绑定

1.静态绑定

静态绑定(亦早期绑定)是指在程序编译期间就已经确定了方法调用的绑定对象。

静态绑定的特点:

①在编译期间,系统根据函数调用定位到执行函数的定义体

②当通过对象/对象指针/对象引用来调用时,只能调用该对象/对象指针/对象引用所属类的成员函数,不能根据对象指针/对象引用所指实际对象来调用该类的成员函数。例如在前边的例子中,面积函数不是虚函数时,对象指针p实际上指向的是派生类对象,但是最后调用的还是基类中的面积函数。

2.动态绑定

动态绑定(亦后期绑定)是指在程序运行时才确定方法调用的绑定对象。

也就是说,在程序运行时,根据具体的类型来确定要调用的函数,例如上边例子的中,把面积函数设置为虚函数,p实际指向的是派生类对象,所以最后调用的就是派生类中的面积函数。

多态的实现正是依托于动态绑定才得以实现,接下来详细讲解是如何实现动态绑定的。

我们依旧以上面的例子来讲解,Shape类中有两个成员变量width和height,当面积函数是虚函数时,我们求Shape对象的大小:

 当面积函数不是虚函数时,我们再求其大小:

可以看到,面积函数是否是虚函数是会对对象的大小造成影响的,我们知道,成员函数在代码区,不在对象中,所以用sizeof算对象的大小时是不会算入其中的,而有了虚函数后,大小发生了变化,这是因为对象中多了一个虚函数表指针

虚函数表指针是指向虚函数表的指针,并且该指针位于类的内存顶部。

一个含有虚函数的类中都至少有一个虚函数表指针,虚函数的地址就是放于虚函数表中的

接下来通过草图来加深理解;

几个注意点;

1)派生类会继承基类的虚函数表,虚函数表中的地址与基类一致,但是如果派生类对基类中的虚函数重新定义,则对应的虚函数指针修改为新定义的函数地址

2)多继承时,如果对于每个基类都有虚函数,那么派生类对象中会有多个虚函数表指针

3)虚函数和虚函数表都存储在代码区

 所以,当使用基类指针或引用调用虚函数时,系统会先根据对象的类型来查找对应的虚函数表,然后在虚函数表中查找所调用的虚函数的地址,最后根据地址来调用相应的虚函数,也就实现了多态。

  • 25
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值