• 【c++】虚函数,纯虚函数,抽象类


    虚函数

    虚函数:给成员函数前面加上virtual关键字。

    1.派生类中定义虚函数必须与基类中的虚函数同名外,还必须同参数表,同返回类型。否则被认为是重载,而不是重写。如基类中返回基类指针,派生类中返回派生类指针是允许的,这是一个例外(协变)。

    2.只有类的成员函数才能说明为虚函数。这是因为虚函数仅适用于有继承关系的类对象。友元函数和全局函数不能作为虚函数。

    3.静态成员函数,是所有同一类对象共有,不受限于某个对象,不能作为虚函数。

    4.内联函数每个对象一个拷贝,无映射关系,不能作为虚函数。

    5.构造函数(拷贝构造,移动构造)不能作为虚函数。因为构造函数的另一个作用是设置虚表指针。

    6.析构函数可定义为虚函数,在基类中及其派生类中都获取系统资源时时,必须把析构函数定义为虚函数,实现撤消对象时的多态性。

    7.实现动态多态性时,必须使用基类类型的指针变量或引用,使该指针指向该基类的不同派生类的对象,并通过该指针指向虚函数,才能实现动态的多态性。

    8.虚函数执行速度要稍慢一些,为了实现多态性,每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现。所以多态性总是要付出一定代价,但通用性是一个更高的目标。

    9.如果虚函数定义放在类外,virtual只能加在函数声明前面,不能再加在函数定义前面。

    10.如果不使用继承和多态,那么就把函数中的virtual关键字去掉,加上的话virtual也没必要,还会增大程序的开销。

    纯虚函数

    纯虚函数:没有具体实现的虚成员函数

    定义纯虚函数的一般格式为:

    virtual  返回类型  函数名(参数表) = 0;
    
    • 1

    “=0"表明程序员将不定义该函数,函数声明是为派生类保留一个位置。”=0"本质上是将指向函数体的指针定为NULL(不同编译器的处理不同)。
    告诉编译器我们不实现这个函数,由派生类来实现。

    抽象类

    含义纯虚函数的类是抽象类

    1.抽象类不能实例化对象
    2.派生类继承了抽象类,则必须把抽象类中的纯虚函数实现了,否则也不能创建对象
    3.在程序设计时,抽象类一般都做为基类来使用
    当基类继承抽象类时,必须把抽象类中的所有纯虚函数进行重写,如果有一个没有重写,那么基类就是抽象类,全部实现之后,就称为(实现类)
    4.抽象类不能做参数类型函数返回值类型或显示类型转换,抽象类型可以定义指针和引用(可以指向它不同的派生对象,以实现运行时多态)

    应用类型:不提供派生也不提供继承
    节点类型:提供继承和多态的继承(有虚函数),但没有纯虚函数
    抽象类型:抽象类,只能用作基类
    接口类:没有属性,所有函数都是纯抽象函数
    实现类:继承了接口或者抽象类,实现了纯虚函数
    
    • 1
    • 2
    • 3
    • 4
    • 5

    为什么抽象类不能创建对象?

    纯虚函数没实现时,对应的该函数指向nullpter(不同的环境实现不同),当创建对象时,进行查虚表,当它的函数为nullpter时,则会报错(纯虚函数会在虚表中保留一个位置,让派生类实现虚表时,把它对该函数的具体实现的地址填充进去它)
    visaul stdio的实现:它则把这些函数指向一个函数(默认的缺省的纯虚函数,调用时会抛出异常)

    注意点:

    class Base
    {
    public:
    	virtual void fun() = 0;
    	virtual ~Base(){}
    };
    class Child :public Base
    {
    public:
    	virtual void fun()const {}
    	virtual ~Child(){}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Child 可以创建对象吗?

    不能,因为这是同名隐藏(两个函数的参数列表不同),不属于同名覆盖。去掉const则就可以了

    接口

    类中全部都是成员方法,没有属性,而且成员方法全部都是纯虚函数

    类如何设计

    • 有时希望派生类只继承成员函数的接口(声明),纯虚函数。
    • 有时希望派生类同时继承函数的接口和实现,但允许派生类改写实现,虚函数。
    • 有时则希望同时继承接口和实现,并且不允许派生类改写任何东西,非虚函数。

    申明纯虚函数的目的,使派生类继承函数的接口和强制性实现

    虚析构

    如果抽象类里面有虚函数,建议把析构函数定义成虚函数,

    防止出现内存泄漏

    using namespace std;
    class Base
    {
    public:
    	 virtual ~Base() { cout << "delete base" << endl; }
    };
    class Child :public Base
    {
    public:
    	
    	 ~Child(){ cout << "delete Child" << endl; }
    };
    int main()
    {
    	Base * c = new Child();
    	delete c;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    运行结果
    在这里插入图片描述
    如果没虚析构的话

    using namespace std;
    class Base
    {
    public:
    	  ~Base() { cout << "delete base" << endl; }
    };
    class Child :public Base
    {
    public:
    	
    	 ~Child(){ cout << "delete Child" << endl; }
    };
    int main()
    {
    	Base * c = new Child();
    	delete c;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    总结:

    (1)如果父类的析构函数不加virtual关键字
    当父类的析构函数不声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,只调动父类的析构函数,而不调动子类的析构函数。
    (2)如果父类的析构函数加virtual关键字
    当父类的析构函数声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,先调动子类的析构函数,再调动父类的析构函数。

    构造函数为什么不能是虚函数

    因为构造函数的一个作用就是设置虚表指针,如果把构造函数设置成虚函数,那么构造函数将在虚表中存着,那么在调用构造函数的时候,虚表指针还没有设置,这样就无法找到虚表,就无法调用构造函数,而要设置虚表指针,就必须得调用构造函数,此时就已经产生矛盾了,所以构造函数不能定义为虚函数。

    有虚函数的对象创建过程

    1.确定this指针。(先确定this指针才可以对之后的进行构建,靠ecx传递过来)
    2.如果有虚函数,则让虚表指针指向虚函数表(存放在.data)
    3.构造其它成员属性

  • 相关阅读:
    事务的七种传播行为
    Android学习笔记 2. Button
    SecureCRT 自动测试脚本的使用方法
    [附源码]JAVA毕业设计酒店管理系统(系统+LW)
    Spring MVC(建立连接 + 请求)
    高层次人才申报
    java计算机毕业设计springboot+vue高校本科学生综评系统
    vSphere6.7创建Windows Server 2012虚拟机及磁盘扩容
    如何提高FPGA的逻辑利用率与资源效率!!!
    100+Python挑战性编程练习系列 -- day 22
  • 原文地址:https://blog.csdn.net/aoeaoao/article/details/126044083