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++虚函数表详细解释及实例分析     

     

  • 相关阅读:
    在mac上如何用safari浏览器调试ios手机的移动端页面
    VSCode 入门
    Redux和Context对比
    七种CSS左侧固定,右侧自适应两栏布局
    componentWillMount VS componentDidMount
    react-native IOS TextInput长按提示显示为中文(select | selectall -> 选择 | 全选)
    MySQL调优5---查询优化
    MySQL调优4---索引
    MySQL官网下载案例数据库
    MySQL调优3---执行计划
  • 原文地址:https://www.cnblogs.com/wujing-hubei/p/5194348.html
Copyright © 2011-2022 走看看