C++虚基类、虚函数、虚析构函数、纯虚函数

举报
yd_221104950 发表于 2022/08/17 23:59:37 2022/08/17
【摘要】 什么时多态 多态,即多种形态,是一种“泛型技术”,它企图使用不变的模板代码来实现可变的算法。在C++中,多态分为两种: 静态多态,就是说在编译时就能确定函数地址,通过复用函数名实现:如函数重载、运算符...

什么时多态

多态,即多种形态,是一种“泛型技术”,它企图使用不变的模板代码来实现可变的算法。在C++中,多态分为两种:

  1. 静态多态,就是说在编译时就能确定函数地址,通过复用函数名实现:如函数重载、运算符重载。
  2. 动态多态,就是能够在运行时确定函数地址,通过派生类和虚函数一起在运行时实现。

它们两者的区别就在于函数地址绑定的时间不同。函数重载和运算符载比较好理解。我们接下来主要了解派生类与虚函数一起是如何实现多态的。

虚函数

首先,我们要区分一下虚基类与虚函数,它们是不同的。基类是使用基类唯一化,虚函数则是能够调用派生类的函数,自身的函数实现被隐藏。

什么是虚基类

举个例子来说明一下什么是虚基类吧。

#include <iostream>

using namespace std;

class Base {
public:
    Base(){
        cout<< "Base" << endl;
    }
};

class DerivedA:  public Base{
public:
    DerivedA(){
        cout<< "Derived A" << endl;
    }
};

class DerivedB:  public Base{
public:
    DerivedB(){
        cout<< "Derived B"<<endl;
    }
};

class DerivedAll: public DerivedA,public DerivedB{
public:
    DerivedAll(){
        cout<< "Derived All"<<endl;
    }
};


int main()
{
   DerivedAll a;

    return 0;
}

打印结果:

Base
Derived A
Base
Derived B
Derived All

从上面打印出来的结果可以看出Base在内存中有两个副本。但实际上只需要一个Base副本就可以了。此时在继承类前加上关键字virtual,即:

class DerivedA:  virtual public Base{
public:
    DerivedA(){
        cout<< "Derived A" << endl;
    }
};

class DerivedB: virtual public Base{
public:
    DerivedB(){
        cout<< "Derived B"<<endl;
    }
};

再次运行结果:

Base
Derived A
Derived B
Derived All

此时Base在内存只有一份了。因此虚基类就是让基类在内存中唯一化。

什么是虚函数

虚函数是指一个类中要重载的成员函数,当一个基类指针或引用指向一个继承类对象的时候,调用一个虚函数,实际调用的是派生类中的。否则,调用的就是基类中的。

#include <iostream>

using namespace std;

class Base {
public:
    void func(){
        cout<< "Base"<<endl;
    }
};

class DerivedA: public Base{
public:
    void func(){
        cout<< "Derived A"<<endl;
    }
};

int main()
{
   Base * pb = new DerivedA();
   pb->func();
    return 0;
}

打印结果:

Base

本来我们想通过基类指针调用派生类中的func方法,现在去调用了基类的。其实只需将基类中的要重载的方法前加上关键字virtual,使其成为虚函数,就可以实现用基类指针或引用来调用派生类中重载了的方法:

class Base {
public:
    virtual void func(){
        cout<< "Base"<<endl;
    }
};

再次运行的结果:

Derived A

编译器给每个对象和虚函数添加了一个隐形的成员:指向虚函数表的指针。虚函数表包含了虚函数的地址,由所有虚函数对象共用。
当派生类重新定义虚函数时,则将该函数的地址添加到虚函数表中。当一个基类指针指向一个派生类对象时,虚函数表指针指向派生类对象的虚函数表。
当调用虚函数时,由于派生类对象重写了派生类对应的虚函数表项,基类在调用时会调用派生类的虚函数,从而产生多态。

请记住,无论对象中定义了多少个虚函数,虚函数表指针只有一个,相应地,每个对象在内存中的大小要比没有虚函数大8B(64位机)或4B(32位机)。这是指针的大小。

派生类继承了基类的虚函数表指针,因此大小与基类一致。如果多重继承的另一个类也包括了虚函数的基类,那么隐藏成员就包括了两个虚函数表指针。例如:

#include <iostream>

using namespace std;

class Base {
public:
    void func1(){
        cout<< "Base func1"<<endl;
    }
    virtual void func2(){
        cout<< "Base func2"<<endl;
    }
    virtual void func3(){
        cout<< "Base func3"<<endl;
    }
};

class Derived: public Base{
public:
    virtual void func2(){
        cout<< "Derived func2"<<endl;
    }
};

int main()
{
   Base * pb = new Derived();
   pb->func1();
   pb->func2();
   pb->func3();
    return 0;
}


Base func1
Derived func2
Base func3
  1. 首先,使用new关键字创建一个Derived对象,pb指针指向它。调用派生类构造函数会先调用基类的构造函数,然后再调用派生类的构造函数。
  2. 由于Derived继承了Base,所以Derived拥有Base的属性与方法,因此,对于pb->func1()时会调用Base的func1()函数。
  3. 由于Derived重写了func2()函数,在Derived对象中的虚函数表项中指向func2()函数的指针被修改为Derived::func2(),由于虚函数表指针为类对象的第一个字段,即基类指针指向派生类对象时,仍然会获取到派生类的虚函数表指针。因此pb->func2()时,程序会先通过派生类的虚函数表指针获取func2()的入口。
  4. 当进行pb->func3()时,由于派生类没有重写它,因此派生类的虚函数表里的func3()的入口仍然是Base的func3()函数。

虚析构函数

一个基类指针可以通过new产生一个派生类对象,如果delete关键字去删除这个指针时,仅仅会调用基类的析构函数,而派生类的空间没有被释放,这样会造成内存泄露。

为了防止内存泄露,当派生类中有指针成员变量时,才会使用到虚析构函数。虚析构函数使在删除指向派生类对象的基类指针时,可以通过调用派生类的析构函数来实现释放派生类所占内存,从而防止内存泄露。

#include <iostream>

using namespace std;

class Base {
public:
    virtual ~ Base(){
        cout<< "delete Base" << endl;
    }
    virtual void func(){
        cout<< "Base func"<<endl;
    }
};

class Derived: public Base{
public:
    ~ Derived(){
        cout << "delete Derived"<< endl;
    }
    void func(){
        cout<< "Derived func2"<<endl;
    }
};

int main()
{
   Base * pb = new Derived();
   pb->func();
   delete pb;
    return 0;
}

Derived func2
delete Derived
delete Base

如果基类的析构函数不加virtual关键字,那它就是一个普通的析构函数。如果没有将基类的析构函数声明为虚析构函数,那么删除基类指针时,只会调用基类的析构函数,而不会调用派生类的析构函数。如果基类的析构函数声明为虚析构函数,那么删除基类指针时,会先调用派生类的析构函数,再调用基类的析构函数。

纯虚函数

纯虚函数相当于java、kotlin中的接口中的方法,只是声明,实现留给派生类来完成。纯虚函数的语法:

virtual 返回类型 函数名(参数列表)=0

纯虚函数与虚函数的区别是前者不需要实现,后者需要实现。在虚函数后加上“=0”,虚函数就变成纯虚函数了。

当类中有了纯虚函数,这个类就叫抽象类。由于抽象类中没有实现函数,所以它不能被实例化,其派生类必须重写纯虚函数,否则派生类也是一个抽象类。抽象类只是为了给其他类提供一个适合的基类。抽象类可以实现部分功能,把不能确定的部分声明为纯虚函数,留给其派生类来实现。纯虚函数一般会作为接口使用,用它约束代码,使用代码符合规范,利于开发大型项目。

class Base {
public:
    virtual void func() = 0;
};

class Derived: public Base{
public:
    virtual void func(){
        cout<< "Derived func"<<endl;
    }
};

文章来源: blog.csdn.net,作者:WongKyunban,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/weixin_40763897/article/details/126338554

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。