zoukankan      html  css  js  c++  java
  • 21、【C++基础】虚函数、重虚函数、接口

    面向对象程序设计(object-oriented programming)的核心思想是数据抽象、继承、动态绑定通过数据抽象,可以使类的接口与实现分离,使用继承,可以更容易地定义与其他类相似但不完全相同的新类,使用动态绑定,可以在一定程度上忽略相似类的区别,而以统一的方式使用它们的对象。

    虚函数的作用是实现多态性(Polymorphism),多态性是将接口与实现进行分离,采用共同的方法,但因个体差异而采用不同的策略。纯虚函数则是一种特殊的虚函数。虚函数联系到多态,多态联系到继承。所以本文中都是在继承层次上做文章。没了继承,什么都没得谈。

    一、虚函数

    1 . 定义

    在C++中,基类必须将它的两种成员函数区分开来:一种是基类希望其派生类进行覆盖的函数;另一种是基类希望派生类直接继承而不要改变的函数。对于前者,基类通过在函数之前加上virtual关键字将其定义为虚函数(virtual)。

    class Base{ // 基类 
    public: 
      virtual int func(int n) const; 
    }; 
      
    class Derive_Class : public Base{ //派生类
    public: 
      int func(int n) const; // 默认也为虚函数 
    }; 

    当我们在派生类中覆盖某个函数时,可以在函数前加virtual关键字。然而这不是必须的,因为一旦某个函数被声明成虚函数,则所有派生类中它都是 虚函数。任何构造函数之外的非静态函数都可以是虚函数。派生类经常(但不总是)覆盖它继承的虚函数,如果派生类没有覆盖其基类中某个虚函数,则该虚函数的 行为类似于其他的普通成员,派生类会直接继承其在基类中的版本。

    2 . 动态绑定

    当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定(dynamic binding)。因为我们直到运行时才能知道到底调用了哪个版本的虚函数,可能是基类中的版本也可能是派生类中的版本,判断的依据是引用(或指针)所绑 定的对象的真实类型。与非虚函数在编译时绑定不同,虚函数是在运行时选择函数的版本,所以动态绑定也叫运行时绑定(run-time binding)。

    3 . 静态类型与动态类型

    静态类型指的是变量声明时的类型或表达式生成的类型,它在编译时总是已知的;动态类型指的是变量或表达式表示的内存中的对象的类型,它直到运行时才 可知。当且仅当通过基类的指针或引用调用虚函数时,才会在运行时解析该调用,也只有在这种情况下对象的动态类型才有可能与静态类型不同。如果表达式既不是 引用也不是指针,则它的动态类型永远与静态类型一致。

    4 . final和override

    派生类中如果定义了一个函数与基类中虚函数同名但形参列表不同,编译器会认为这是派生类新定义的函数。如果我们的意图本是覆盖虚函数,则这种错误很 难发现。通过在派生类中的虚函数最后加override关键字使得意图更加清晰。如果我们使用override标记了某个函数,但该函数并没有覆盖已存在 的虚函数,编译器将报错。

    class Base{ // 基类 
    public: 
      virtual int func(int a, int b) const; 
    }; 
      
    class Derive_Class : public Base{ 
    public: 
      int func(int a) const override; // 报错,没有覆盖虚函数 
    }; 
    

    如果我们定义一个类,并不希望它被继承。或者希望某个函数不被覆盖,则可以把类或者函数指定为final,则之后任何尝试继承该类或覆盖该函数的操作将引发错误。 

    class Base final { /*  */ };   // 基类不能被继承 
    class Derive_Class : public Base { /* */ };   // 报错 
      
    void func(int) const final;  // 不允许后续的其他类覆盖func(int)

    5 . 回避虚函数的机制

    在某些情况下,我们希望对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本。可以使用作用域运算符实现这一目的。

    // 强行调用基类中定义的函数版本而不管baseP的动态类型是什么 
    int a = baseP->Base::func(42); 
    

     如果一个派生类虚函数需要调用它的基类版本,但是没有使用作用域运算符,则在运行时该调用将被解析为对派生类版本自身的调用,从而导致无限递归。


    二、纯虚函数

    1 . 定义

    为了方便使用多态特性,我们常常需要在基类中定义虚函数。在许多情况下,在基类中不能对虚函数给出有意义的实现。为了让虚函数在基类什么也不做,引 进了“纯虚函数”的概念,使函数无须定义。我们通过在函数体的位置(即在声明语句的分号之前)书写=0就可以将一个虚函数说明为纯虚函数(pure virtual)。其中,=0只能出现在类内部的虚函数声明语句处:

    class Base{ // 抽象基类 
    public: 
      virtual int func(int n) const =0; //纯虚函数
    }; 

    需要注意的是,我们也可以为纯虚函数提供定义,不过函数体必须定义在类的外部。

    2 . 抽象基类

    含有(或者未经覆盖直接继承)纯虚函数的类叫抽象基类(abstract base class)。抽象基类负责定义接口,而后续的其他类可以覆盖该接口。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还 是一个抽象基类。因为抽象基类含有纯虚函数(没有定义),所以我们不能创建一个抽象基类的对象,但可以声明指向抽象基类的指针或引用。

    Base base;  // 错误,不能实例化抽象基类 
    

    总结:

    ①.虚函数必须实现,不实现编译器会报错。

    ②.父类和子类都有各自的虚函数版本。由多态方式在运行时动态绑定。

    ③.通过作用域运算符可以强行调用指定的虚函数版本。

    ④.纯虚函数声明如下:virtual void funtion()=0; 纯虚函数无需定义。包含纯虚函数的类是抽象基类,抽象基类不能创建对象,但可以声明指向抽象基类的指针或引用。

    ⑤.派生类实现了纯虚函数以后,该纯虚函数在派生类中就变成了虚函数,其子类可以再对该函数进行覆盖。

    ⑥.析构函数通常应该是虚函数,这样就能确保在析构时调用正确的析构函数版本。

  • 相关阅读:
    来了!GitHub for mobile 发布!iOS beta 版已来,Android 版即将发布
    五角场之殇。曾与张江、漕河泾、紫竹齐名。如今,上海四大IT科技园是否还在?
    Visual Studio Online 的 FAQ:iPad 支持、自托管环境、Web 版 VS Code、Azure 账号等
    VS Code 1.40 发布!可自行搭建 Web 版 VS Code!
    Visual Studio Online,带来四种开发模式,未来已来。
    基于七牛云对象存储,搭建一个自己专属的极简Web图床应用(手摸手的注释讲解核心部分的实现原理)
    css伪元素::before与::after使用基础示例
    gcc生成静态链接库与动态链接库步骤,并链接生成可执行文件的简单示例
    shell求水仙花数
    shell实现简单的数组排序
  • 原文地址:https://www.cnblogs.com/Long-w/p/9563955.html
Copyright © 2011-2022 走看看