简单的c++ 多态讲解


前言

多态又分为静态多态和动态多态:
(1)静态多态:函数重载和函数模板实例化出多个函数(本质也是函数重载)即静态多态也称为编译期间的多态。

(2)动态多态,也称为动态绑定或后期绑定(晚绑定):运行时的多态性可通过虚函数实现。
此文章只进行动态多态的讲解


一、什么是多态?

简单点来说,多态就是函数调用的多种形态,使用多态能够使得不同的对象去完成同一件事时,产生不同的动作和结果。

多态的产生必须具备两个条件:

  1. 必须通过基类的指针或者引用调用虚函数。

  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。


二、举个例子

class A {
    
    
public:
    A() {
    
    }
   virtual void print(int num) {
    
    
        cout << "num:---" << num<< endl;
    }
}

class B :public A{
    
    
public:
    B(int cb) :n_(cb) {
    
    }
     void print(int num) {
    
    
        int re = num * n_;
        cout << "num:+++++++" << re << endl;
    }
private:
    int n_;
};
int main(){
    
    
   
    A *temp = new B(2);
    temp->print(3);
    delete temp;
    return 0;  
}

子类B继承了基类A,并且重写了基类A中虚函数print;在main函数中,A的指针指向了B的对象,这便满足了多态的条件。所以打印出来的结果应该是B中print函数打印的结果。

在这里插入图片描述


三、虚函数表

为何会产生多态这种现象,原因来自虚函数表
首先我们先看一下classA的内部结构

class A {
    
    
public:
    A() {
    
    }
   virtual void print(int num) {
    
    
        cout << "num:---" << num<< endl;
    }
}

我们知道,一个空类在内存中的大小为1,所以如果去掉virtual修饰,A的大小其实为1。这里我们可以通过VS自带的Developer Command Prompt去验证。
在这里插入图片描述
但如果重新加上virtual修饰后,A的大小就变为了4,并且还多了一个vftable。
在这里插入图片描述
这是因为,当一个类的非静态成员函数前添加virtual之后,该类会产生一个指针vfptr(virtual function pointer),即虚函数指针(4个字节)去指向一个vftable(virtual function table) 虚函数表,虚函数表中保存了该成员函数的地址&A::print。
那如果子类B继承一下基类A会产生什么结果?我们同样使用Developer Command Prompt去查看一下。

首先我们只继承,不去重写基类的虚函数。

class B :public A{
    
    
public:
    B(int cb) :n_(cb) {
    
    }
    //virtual void print(int num) {
    
    
    //    int re = num * n_;
    //    cout << "num:+++++++" << re << endl;
    //}
private:
    int n_;
};

在这里插入图片描述
此时我们注意到,B的大小是8,即继承的A的虚函数指针,还有B自己的int n_,但此时B中虚函数表中存放的地址却是继承过来的&A::print。

然后我们去重写基类虚函数函数

class B :public A{
    
    
public:
    B(int cb) :n_(cb) {
    
    }
    virtual void print(int num) {
    
    
        int re = num * n_;
        cout << "num:+++++++" << re << endl;
    }
private:
    int n_;
};

在这里插入图片描述
此时我们发现B中虚函数表中的&A::print被覆盖为&B::print。

当满足多态条件以后,父类的指针或引用调用虚函数时,在运行时到指向的对象中的虚表中去找对应的虚函数调用,引用的底层也是由指针实现,父类在指向子类时会发生切片。所以指针指向父类的对象,调用的就是父类的虚函数,指向的是子类对象,调用的就是子类的虚函数。

四、一些小知识

在进行继承时,子类的虚函数不加virtual关键字也可以构成重写,因为继承后基类的虚函数被继承下来了,在派生类中依旧保持虚函数属性。但不建议这样写,因为子类也有可能被继承,所以我们在重写时就老老实实写上罢。

虚函数要求返回值类型相同、函数名相同以及参数列表完全相同,但是协变是个例外,子类重写基类虚函数时,与基类虚函数返回值类型可以不同。但是返回值类型也必须满足父子关系。

总结

本人第一次写文章,难免有疏漏,如果你觉得有用,那你就觉得有用,如果你觉得我写的不好,我看见了会改的,谢谢铁子们。

猜你喜欢

转载自blog.csdn.net/Done_for_me/article/details/129681648