C++类——多态(概念、实现、理论基础、实现原理、条件与作用)

多态的概念

    多态同样的调用语句有多种不同的表现形态, 即根据实际的对象类型决定函数调用语句的具体调用目标

  • 如果父类指针(引用)指向的是父类对象则调用父类中定义的函数。
  • 如果父类指针(引用)指向的是子类对象则调用子类中定义的重写函数。


这里写图片描述

多态的实现

    通过virtual关键字, 来修饰成员函数, 则该成员函数为虚函数

    虚函数允许派生类重新定义成员函数, 而派生类重新定义基类的做法称为override, 或重写

    虚函数的作用就是实现多态, 即以共同的方法, 对不同的对象采取不同的策略

    虚函数的定义: 虚函数只能是类的成员函数, 而且不能是静态的。 在成员函数定义或声明前加上关键字virtual, 即定义了虚函数:

class 类名
{
virtual 返回类型 函数名(形式参数列表); //虚函数
};
注意:
  1. 当在派生类中定义了一个同名的成员函数时, 只要该成员函数的参数个数、 参数类型、 返回类型与基类中同名的虚函数完全一样, 则派生类的这个成员函数无论是否添加virtual关键字, 它都将成为一个虚函数。
  2. 一般为了便于阅读, 派生类的虚函数也会添加上virtual关键字。
  3. 利用虚函数, 可在基类和派生类中使用相同的函数名定义函数的不同实现, 从而实现“ 一个接口, 多种方式” 。

多态实现的理论基础

静态联编和动态联编:
  • 联编: 又称为绑定, 即将模块或函数合并在一起生成可执行代码的处理过程, 同时对每个模块或者函数分配内存地址, 并且对外部访问也分配正确的内存地址。
  • 静态联编: 在编译阶段就将函数实现和函数调用绑定起来。 又称为早绑定。 它对函数的选择是基于指向对象的指针或引用本身的类型


这里写图片描述

  • 动态联编: 在程序运行的时候才进行函数实现和函数调用的绑定称为动态联编。 又称为晚绑定


这里写图片描述

注意:
  • C语言中, 所有的联编都是静态联编。
  • C++中一般情况下的联编也是静态联编。 一旦涉及到多态和虚函数就必须使用动态联编。

多态的实现原理

理论基础:

    当编译器编译含有虚函数的类时, 将会为这个类建立一个虚函数表

    虚函数表是一个存储类成员函数指针的数据结构, 相当于一个指针数组, 存放每个虚函数的入口地址

    并且, 编译器会为该类添加一个额外的数据成员, 这个数据成员是一个指向虚函数表的指针, 通常称为vptr


这里写图片描述

实现原理:

    使用基类的指针或引用调用函数时, 如果该函数为虚函数, 编译器找到指针或引用指向的对象的vptr指针, 并根据其所指的虚函数表找到函数并调用。 查找和调用都是在运行时进行的, 即动态绑定。

    如果该函数不是虚函数, 则编译器可以直接通过指针的类型确定被调用的函数, 并进行绑定, 即静态绑定。


这里写图片描述

多态成立的条件

1. 要有继承。
2. 要有函数重写。
3. 要有基类指针(引用)指向派生类对象。

多态的作用

C++的三大特性:
  • 封装: 突破了C语言函数的概念, 封装可以隐藏实现细节, 使得代码模块化
  • 继承: 继承可以扩展已存在的代码模块(类), 达到代码重用的目的。
  • 多态: 多态实现了接口重用, 使得可以使用未来, 即当前的框架不需要改变也可以使用后来的代码。
#include<iostream>

using namespace std;

class Base
{
public:
    virtual void print()
    {
        cout << "调用基类的print方法" << endl;
    }
};

class Derived : public Base
{
public:
    //无论是否添加virtual关键字,它都是一个虚函数
    //参数个数、参数类型、返回类型、函数名都与基类中的print函数一直。 重写
    //一般情况下,为了强调为虚函数,还是会添加上关键字virtual
    virtual void print()
    {
        cout << "调用派生类的print方法" << endl;
    }
};

//后定义的派生类
class Derived1 : public Base
{
public:
    virtual void print()
    {
        cout << "调用派生类D1的print方法" << endl;
    }
};

//先定义的函数
void print1(Base &b)
{
    b.print();
}

int main()
{
    Base b1;
    Derived d1;
    b1.print(); //调用基类的print方法
    d1.print();  //调用派生类的print方法
    d1.Base::print(); //调用基类的print方法

    ////未使用虚函数时,无法实现多态,都调用的是基类的print方法
    //Base *pb = &b1;
    //pb->print(); //调用基类的print方法
    //pb = &d1;
    //pb->print(); //调用基类的print方法

    //使用虚函数时,可以实现多态,会根据定义的指针指向的对象的类型,去调用不同的类中的同名方法
    //一个接口,多种实现
    Base *pb = &b1;
    pb->print(); //调用基类的print方法
    pb = &d1;
    pb->print(); //调用派生类的print方法

    //使用基类的对象的引用作为参数,根据传递的实参的类型,去选择调用的函数。
    print1(b1); 
    print1(d1);

    //可以使用未来
    Derived1 d2;
    print1(d2);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/xiaokunzhang/article/details/81014495