【C++】静态多态和动态多态,虚函数的调用一定是动态绑定吗?

【C++】静态多态和动态多态,虚函数的调用一定是动态绑定吗?

我们知道,

多态指的意思就是一个函数名有多种状态,同样的函数名有通过函数重载,函数模板,虚函数,可以有不同的代码实现。

也知道,

静态多态—主要是指函数重载和函数模板。在编译时的多态。

动态多态—主要指虚函数的使用。在运行时的多态。

实际上我还有疑问

  • 问题1: 什么叫编译时的多态和运行时的多态?静态绑定和动态绑定是是什么?
  • 问题2:为什么函数重载和模板就能够在编译的时候实现多态,**虚函数的调用一定是动态绑定吗?**答案是否

一、首先,我们要知道 怎么实现多态的?

1.函数重载 和 函数模板 实现多态

​ 实际上这两种我们接触的非常多,简单讲一下就是

  • 函数重载 就是 一个 相同名字的函数,由于参数 类型和个数不同,对应这不同的函数内容。
  • 函数模板 就是利用了模板,我们不指定参数类型,提前实现函数内容。

这里确实就很简单的实现了多态(一种函数多种方法)

2.虚函数 实现多态

一开始,我对虚函数的理解非常浅,我的理解就是

  • 基类会 使用virtual关键字,指明一个函数为虚函数,然后子类 可以重写这个虚函数。实际上理解就是虚函数好像是一种提供接口的作用。好像并没有体现多态。

那,虚函数的多态的怎么实现的呢? 有三点要求

  • 1.基类要有虚函数
  • 2.要有父类指针指向子类对象,或者对子类引用。
  • 3.子类对虚函数进行重写

我们需要知道一点,一个子类对象是可以给一个父类对象赋值的。也就是对应上面的第二点。

#include <iostream>
#include <string>
#include <vector>
//这里 我们先构造 一个基类Base 
class Base
{
    
    
public:
    Base(){
    
    };
    ~Base(){
    
    };
public:
    virtual void fun1(){
    
     std::cout<< "fun1--base--virtual" <<std::endl;};
    void fun2(){
    
     std::cout<< "fun2--base" <<std::endl;};

};
//子类1
class Child:public Base
{
    
    
public:
    Child(){
    
    };
    ~Child(){
    
    };
public:
     void fun1(){
    
     std::cout<< "fun1--child--overwrite" <<std::endl;};
     void fun2(){
    
     std::cout<< "fun2--child" <<std::endl;};

};


int main()
{
    
    
    Base* pBase = new Base;
    Child* pChild = new Child;
    pBase = pChild;//将一个子类对象 赋值 给基类 
    
    pBase->fun1();//基类调用 虚函数 
    pBase->fun2();//基类调用 非虚函数
    pChild->fun1();//子类调用 重写后的虚函数
    pChild->fun2();//子类调用 重写的非虚函数
    
    getchar();
    return 0 ;
}
执行结果:
fun1--child--overwrite
fun2--base
fun1--child--overwrite
fun2--child

我们可以看到, 当我们把一个基类对象 赋值 给一个父类对象,由于我们的基类对虚函数进行了重写,

虚函数从结果1,3行来看,无论输出的结果都是重写以后的。都执行的结果是子类重写当中的fun2

非虚函数 从2,4行来看,还是执行原来类内的函数,基类对象执行基类内的fun2子类对象执行子类内的fun2.

到此,我们考虑一个问题,

对于一个基类对象,如果我们用不同的衍生类(子类)对其进行赋值,那是不是这个对象就会调用不同的重写函数呢。这样,基类当中的函数,是不是就起到了多态的效果。

二、为什么叫动态联编。动态

实际上,到此,我还有一个疑问,就是为什么这样的实现就是叫做动态联编呢?或者说什么情况下才叫做动态联编呢?

我们开始引入两个概念。

1.静态联编和动态联编(binding)也称为静态绑定和动态绑定。

所谓绑定, 就是我们要通过一个函数名找到对应的代码块

程序调用函数时,将使用哪个可执行代码块?编译器负责回答这个问题。将源代码中的函数调用解释为执行的函数代码被称为函数名联编(绑定)。在c语言中,因为每个函数名都对应一个不同的函数,很容易找到对应的代码块(进行联编)。

C++中,由于引入了多态的概念。导致,通过一个函数名找到一个对应函数的方法比较困难。

  • 由于函数重载和函数模板引入的多态问题,编译器就能够解决,编译器必须查看函数参数以及函数名才能确定使用哪个函数。然而C/C++编译器可以在编译过程中完成这种联编。在编译过程中进行联编被称为静态联编,又称为早期联编。

  • 虚函数的出现,使这项工作变得十分困难,使用哪个函数是不能在编译时确定的。只有在函数运行当中才能够选择正确的虚方法的代码。

至此,**问题1: 什么叫编译时的多态和运行时的多态?静态绑定和动态绑定是是什么?**我们差不多可以理解了。一个是编译时就能找到对应代码块,一个是只有执行时才能够找到代码块。

2.用虚函数一定是动态联编吗?不一定

虚函数在编译时进行联编,静态绑定

    Base* pBase_1 = new Base;
    Child_1* pChild_1 = new Child_1;
    pBase_1 = pChild_1;//将一个子类对象 赋值 给基类 
    pBase_1->fun1();调用虚函数
    
    Base* pBase_2 = new Base;
    Child_2* pChild_2 = new Child_2;
    pBase_2 = pChild_2;//将一个子类对象 赋值 给基类 
    pBase_2->fun1();

对于这个代码。

  • 第一行 创建一个基类对象,pBase_1
  • 第二行 创建一个子类对象,pChild_1
  • 第三行 将子类对象 赋值 给基类 对象。
  • 第四行 调用虚函数fun1 ,至此基类对象pBase_1,调用那个函数块,是很确定的,就是子类1中的fun1()。
  • 即使我们继续,
  • pBase_2->fun1();pBase_2调用的函数块就是子类2当中的fun1()。
  • 所以,既然能够找到对应的代码块,那他就应该在编译的时候,就能够联编或者绑定。这种情况下应该是静态联编

虚函数在运行时进行联编,动态绑定

还存在一种情况,就是用一个基类对象来接收 子类对象 参数。这个参数类型在编译的时候不确定。

void CallVirtualFun(Base* Base){
    
    

    Base->fun1();
    return ;
}

把这个函数当作对外接口,只有在程序运行的时候,再输入一个基类的派生类,用基类进行接收,这个时候,Base->fun1();才会绑定对应的代码块。

所以,这个时候才体现出虚函数的动态联编

猜你喜欢

转载自blog.csdn.net/m0_57168310/article/details/126998231