zoukankan      html  css  js  c++  java
  • C++纯虚函数、虚函数、实函数、抽象类,重载、重写、重定义

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

      虚函数的作用是实现多态性(Polymorphism),多态性是将接口与实现进行分离,采用共同的方法,但因个体差异而采用不同的策略。纯虚函数则是一种特殊的虚函数。虚函数联系到多态,多态联系到继承。

    一、虚函数

    1 . 定义

      C++的虚函数主要作用是运行时多态,父类中提供虚函数的实现,为子类提供默认的函数实现

      子类可以重写父类的虚函数实现子类的特殊化

      如下就是一个父类中的虚函数:

    class A
    {
    public:
        virtual void out2(string s)
        {
            cout<<"A(out2):"<<s<<endl;
        }
    };

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

    2 . 动态绑定

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

    3 . 静态类型与动态类型

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

    二、纯虚函数

    1 . 定义

      C++中包含纯虚函数的类,被称为是“抽象类”。抽象类不能使用new出对象,只有实现了这个纯虚函数的子类才能new出对象

      C++中的纯虚函数更像是“只提供申明,没有实现”,是对子类的约束,是“接口继承”。

      C++中的纯虚函数也是一种“运行时多态”。

      如下面的类包含纯虚函数,就是“抽象类”:

    class A
    {
    public:
        virtual void out1(string s)=0;
        virtual void out2(string s)
        {
            cout<<"A(out2):"<<s<<endl;
        }
    };

      请注意,纯虚函数应该只有声明,没有具体的定义,即使给出了纯虚函数的定义也会被编译器忽略。

    2.引入原因:
          1) 为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
          2) 在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
         为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重载以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

    三、普通函数(no-virtual)

      普通函数是静态编译的,没有运行时多态,只会根据指针或引用的“字面值”类对象,调用自己的普通函数

      普通函数是父类为子类提供的“强制实现”。

      因此,在继承关系中,子类不应该重写父类的普通函数,因为函数的调用至于类对象的字面值有关。

    四、重载、重写、重定义

      重载overload:是函数名相同,参数列表不同。重载只是在类的内部存在。但是不能靠返回类型来判断。
      重写override:也叫做覆盖。子类重新定义父类中有相同名称和参数的虚函数函数特征相同。但是具体实现不同,主要是在继承关系中出现的。
      重写需要注意:
      1 被重写的函数不能是static的。必须是virtual的
      2 重写函数必须有相同的类型,名称和参数列表
      3 重写函数的访问修饰符可以不同。例如:尽管virtual是private的,派生类中重写改写为public,protected也是可以的

      重定义 (redefining)也叫做隐藏:

      子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。

      如果一个类,存在和父类相同的函数,那么,这个类将会覆盖其父类的方法,除非你在调用的时候,强制转换为父类类型,否则试图对子类和父类做类似重载的调用是不能成功的。
     

    五、 虚析构函数

    虚析构函数: 在析构函数前面加上关键字virtual进行说明,称该析构函数为虚析构函数。虽然构造函数不能被声明为虚函数,但析构函数可以被声明为虚函数。

    一般来说,如果一个类中定义了虚函数, 析构函数也应该定义为虚析构函数。

    六、 抽象基类

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

      之所以要存在抽象类,最主要是因为它具有不确定因素。我们把那些类中的确存在,但是在父类中无法确定具体实现的成员函数称为纯虚函数。纯虚函数是一种     特殊的虚函数,它只有声明,没有具体的定义。抽象类中至少存在一个纯虚函数;存在纯虚函数的类一定是抽象类。存在纯虚函数是成为抽象类的充要条件。
      
    #include <iostream>
    using namespace std;
    
    class A
    {
    public:
        virtual void out1()=0;  ///由子类实现
        virtual ~A(){};
        virtual void out2() ///默认实现
        {
            cout<<"A(out2)"<<endl;
        }
        void out3() ///强制实现
        {
            cout<<"A(out3)"<<endl;
        }
    };
    
    class B:public A
    {
    public:
        virtual ~B(){};
        void out1()
        {
            cout<<"B(out1)"<<endl;
        }
        void out2()
        {
            cout<<"B(out2)"<<endl;
        }
        void out3()
        {
            cout<<"B(out3)"<<endl;
        }
    };
    
    int main()
    {
        A *ab=new B;
        ab->out1();
        ab->out2();
        ab->out3();
        cout<<"************************"<<endl;
        B *bb=new B;
        bb->out1();
        bb->out2();
        bb->out3();
       bb->A::out3();
    delete ab; delete bb; return 0; }
    out3()是一个实函数的重定义.
    调用ab->out3();会去调A类中的out3(),它是在我们写好代码的时候就会定好的。因为out3()不是虚函数,不会动态绑定,也就是根据它是由A类定义的,这样就调用这个类的函数。 
    out2()是虚函数。调用ab->out2();会调用bb中保存的对象中对应的这个函数。这是由于new的B对象。
    out1()与out2()一样,只是在基类中不需要写函数实现。
    同时,还可以通过作用域运算符来实现在子类中调用父类的虚函数。

    总结:

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

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

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

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

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

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

     c++虚函数表:

       C++虚函数表详细解释及实例分析     

     

  • 相关阅读:
    Linux命令应用大词典-第11章 Shell编程
    Kubernetes 学习12 kubernetes 存储卷
    linux dd命令
    Kubernetes 学习11 kubernetes ingress及ingress controller
    Kubernetes 学习10 Service资源
    Kubernetes 学习9 Pod控制器
    Kubernetes 学习8 Pod控制器
    Kubernetes 学习7 Pod控制器应用进阶2
    Kubernetes 学习6 Pod控制器应用进阶
    Kubernetes 学习5 kubernetes资源清单定义入门
  • 原文地址:https://www.cnblogs.com/wujing-hubei/p/5194348.html
Copyright © 2011-2022 走看看