C++虚基类、虚函数、虚析构函数、纯虚函数
什么时多态
多态,即多种形态,是一种“泛型技术”,它企图使用不变的模板代码来实现可变的算法。在C++中,多态分为两种:
- 静态多态,就是说在编译时就能确定函数地址,通过复用函数名实现:如函数重载、运算符重载。
- 动态多态,就是能够在运行时确定函数地址,通过派生类和虚函数一起在运行时实现。
它们两者的区别就在于函数地址绑定的时间不同。函数重载和运算符载比较好理解。我们接下来主要了解派生类与虚函数一起是如何实现多态的。
虚函数
首先,我们要区分一下虚基类与虚函数,它们是不同的。基类是使用基类唯一化,虚函数则是能够调用派生类的函数,自身的函数实现被隐藏。
什么是虚基类
举个例子来说明一下什么是虚基类吧。
#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
- 首先,使用new关键字创建一个Derived对象,pb指针指向它。调用派生类构造函数会先调用基类的构造函数,然后再调用派生类的构造函数。
- 由于Derived继承了Base,所以Derived拥有Base的属性与方法,因此,对于pb->func1()时会调用Base的func1()函数。
- 由于Derived重写了func2()函数,在Derived对象中的虚函数表项中指向func2()函数的指针被修改为Derived::func2(),由于虚函数表指针为类对象的第一个字段,即基类指针指向派生类对象时,仍然会获取到派生类的虚函数表指针。因此pb->func2()时,程序会先通过派生类的虚函数表指针获取func2()的入口。
- 当进行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
- 点赞
- 收藏
- 关注作者
评论(0)