• c++ 虚函数和纯虚函数


     一、总结

    1.

    为了实现C++的多态,C++使用了一种动态绑定的技术。这个技术的核心是虚函数表,每个包含了虚函数的类都包含一个虚表。
    我们知道,当一个类(A)继承另一个类(B)时,类A会继承类B的函数的调用权。所以如果一个基类包含了虚函数,那么其继承类也可调用这些虚函数,换句话说,一个类继承了包含虚函数的基类,那么这个类也拥有自己的虚表。
    虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针。需要指出的是,普通的函数即非虚函数,其调用并不需要经过虚表,所以虚表的元素并不包括普通函数的函数指针。
    虚表内的条目,即虚函数指针的赋值发生在编译器的编译阶段,也就是说在代码的编译阶段,虚表就可以构造出来了。

    具体见:https://blog.csdn.net/lihao21/article/details/50688337

    在你设计一个基类的时候,如果发现一个函数需要在派生类里有不同的表现,那么它就应该是虚的。从设计的角度讲,出现在基类中的虚函数是接口,出现在派生类中的虚函数是接口的具体实现。通过这样的方法,就可以将对象的行为抽象化。 

    2.虚函数(impure virtual),C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现。子类可以重写父类的虚函数实现子类的特殊化。 

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

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

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

     纯虚函数
     1.当在基类中不能为虚函数给出一个有意义的实现时,可以将其声明为纯虚函数,其实现留待派生类完成。 
     2.纯虚函数的作用是为派生类提供一个一致的接口。
     3.纯虚函数不能实例化,但可以声明指针。 

     纯虚函数在基类只声明不用定义,继承类必须实现。


    4.虚函数和纯虚函数对比:
    4.1.虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。 

    4.2.虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义。 

    4.3.虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。 

    4.4.虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口。 

    4.5.虚函数的定义形式:virtual{method body} 纯虚函数的定义形式:virtual { } = 0; 在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。 

    4.6.如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。

    5.非类的成员函数不能定义为虚函数,类的成员函数中静态成员函数和构造函数也不能定义为虚函数,但可以将析构函数定义为虚函数。实际上,优秀的程序员常常把基类的析构函数定义为虚函数。因为,将基类的析构函数定义为虚函数后,当利用delete删除一个指向派生类定义的对象指针时,系统会调用相应的类的析构函数。而不将析构函数定义为虚函数时,只调用基类的析构函数。

     实例化时:先调用基类构造函数,然后子类构造函数;释放时:先子类析构函数,然后基类析构函数

    #ifndef BASE_H
    #define BASE_H
    
    class Base
    {
    public:
        Base();
        ~Base();
        virtual void test()=0;
    };
    
    #endif // BASE_H
    #include "base.h"
    #include <QDebug>
    Base::Base()
    {
        qDebug()<<"Base()";
    }
    
    Base::~Base()
    {
        qDebug()<<"~Base()";
    }
    #ifndef C1_H
    #define C1_H
    #include "base.h"
    
    class C1:public Base
    {
    public:
        C1();
        ~C1();
        void test() override;
    };
    
    #endif // C1_H
    #include "c1.h"
    #include<QDebug>
    C1::C1()
    {
        qDebug()<<"C1()";
    }
    
    C1::~C1()
    {
        qDebug()<<"~C1()";
    }
    
    void C1::test()
    {
        qDebug()<<"C1::test";
    }

    Base()

    C1()

    ~Base()

        

     基类析构函数声明为虚函数后,执行结果

    Base()

    C1()

    ~C1()

    ~Base()

    6.只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。 

    7.当将基类中的某一成员函数声明为虚函数后,派生类中的同名函数(函数名相同、参数列表完全一致、返回值类型相关)自动成为虚函数。 

    8.如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。在以该类为基类的派生类中,也不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。 

    9.构造函数不能声明为虚函数

    构造函数一般是用来初始化对象的,因而只有在一个对象生成之后才能发挥多态作用。虚函数表在构造函数调用之后才建立,因而构造函数不可能声明为虚函数。虚函数的调用需要虚函数表指针,而该指针存放在对象的内存空间中;
    若构造函数声明为虚函数,那么由于对象还未创建,还没有内存空间,更没有虚函数表地址来调用虚函数。

     

    10.静态成员函数不能是虚函数

    静态成员函数对于每一个类只有一份代码,所有的对象共享这份代码,它不归某个对象所有,所以没有动态绑定的必要性,不能被继承,只属于该类

     

    11.内联函数不能是虚函数

    内联函数在程序编译的时候展开,在函数调用处进行替换,虚函数是进行动态绑定的

     

    12.基类需要声名虚析构函数

    防止内存泄漏。假如没有虚析构函数,释放一个由基类指针指向的派生类对象时,不会触发动态绑定,则只会调用基类的析构函数,不会调用派生类的。派生类中申请的空间则得不到释放导致内存泄漏。

    #pragma once
    class Shape
    {
       public:
        Shape();
        virtual ~Shape();
    
        void Draw1();
        virtual void Draw2();
        virtual void Draw3()=0;
    };
    #include "Shape.h"
    #include <iostream>
    
    using namespace std;
    Shape::Shape()
    {
        cout << "shape 构造函数" << endl;
    }
    
    
    Shape::~Shape()
    {
        cout << "shape 析构函数" << endl;
    }
    
    
    void Shape::Draw1()
    {
        cout << "shape Draw1 画个图形" << endl;
    }
    void Shape::Draw2()
    {
        cout << "shape Draw2 画个图形" << endl;
    }
    #pragma once
    #include "Shape.h"
    class Rectangle:public Shape
    {
        public:
            Rectangle();
           ~Rectangle();
    
           void Draw1();
           void Draw2();
           void Draw3();
    };
    #include "Rectangle.h"
    #include <iostream>
    
    using namespace std;
    Rectangle::Rectangle()
    {
        cout << "Rectangle 构造函数" << endl;
    }
    
    
    Rectangle::~Rectangle()
    {
        cout << "Rectangle 析构函数" << endl;
    }
    
    void Rectangle::Draw1()
    {
        cout << "Rectangle Draw1 画个矩形" << endl;
    }
    void Rectangle::Draw2()
    {
        cout << "Rectangle Draw2 画个矩形" << endl;
    }
    
    void Rectangle::Draw3()
    {
        cout << "Rectangle Draw3 画个矩形" << endl;
    }
    #pragma once
    #include "Shape.h"
    class Circle:public Shape
    {
       public:
         Circle();
         ~Circle();
         void Draw1();
         void Draw2();
         void Draw3();
    };
    #include "Circle.h"
    #include <iostream>
    
    using namespace std;
    Circle::Circle()
    {
        cout << "Circle 构造函数" << endl;
    }
    
    
    Circle::~Circle()
    {
        cout << "Circle 析构函数" << endl;
    }
    void Circle::Draw1()
    {
        cout << "Circle Draw1 画个圆形" << endl;
    }
    void Circle::Draw2()
    {
        cout << "Circle Draw2 画个圆形" << endl;
    }
    
    void Circle::Draw3()
    {
        cout << "Circle Draw3 画个圆形" << endl;
    }
    // ConsoleApplication2.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    #include<iostream>
    #include<memory>
    #include "Shape.h"
    #include "Rectangle.h"
    #include "Circle.h"
    
    
    using namespace std;
    void Run()
    {
    
        //Shape *_c;
        //_c = new Rectangle();
        //_c->Draw1();
        //_c->Draw2();
    
        //_c = new Circle();
        //_c->Draw1();
        //_c->Draw2();
    
        //delete _c;
    
        shared_ptr<Shape> _s(new Rectangle());
        _s->Draw1();
        _s->Draw2();
        _s->Draw3();
        _s.reset(new Circle());
        _s->Draw1();
        _s->Draw2();
        _s->Draw3();
    
    
    
    
    }
    
    int main()
    {
    
        Run();
        
        
        system("pause");
        return 0;
    }

    运行结果:

    shape 构造函数
    Rectangle 构造函数
    shape Draw1 画个图形
    Rectangle Draw2 画个矩形
    Rectangle Draw3 画个矩形
    shape 构造函数
    Circle 构造函数
    Rectangle 析构函数
    shape 析构函数
    shape Draw1 画个图形
    Circle Draw2 画个圆形
    Circle Draw3 画个圆形
    Circle 析构函数
    shape 析构函数
    请按任意键继续. . .

    Qt 使用智能指针 Pro  添加CONFIG += c++11;

      智能指针属于std, std::shared_ptr<Shape> _s(new Rectangle());

  • 相关阅读:
    core 3.7.1 报错 SDK.InvalidRegionId : Can not find endpoint to access.
    定时30分钟清除缓存,重置
    文件的分割与合并
    mybatis <collection property="GoodsList" column="orderId" javaType="java.util.List" ofType="ui.model.vo.GoodsList" select="selectOrderDetail" fetchType="eager"/>
    hashMap 源码注释分析(二)
    hashMap 源码注释分析(一)
    ElasticSearch 入门
    Java 三高 ,高并发 ,高可用 。高性能
    使用MyBatis返回map对象,字段值为null时不返回或返回null,目标返回自定义的默认值...
    idea html 中文乱码,控制台中文乱码,工程文件中文乱码
  • 原文地址:https://www.cnblogs.com/ike_li/p/5809968.html
Copyright © 2020-2023  润新知