虚函数、纯虚函数、虚继承

实现多态。
虚函数都是动态绑定,绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期;

虚函数

  • 类里如果声明了虚函数,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被覆盖,这样的话,这样编译器就可以使用后期绑定来达到多态了。
  • 虚函数的类用于 “实作继承”,继承接口的同时也继承了父类的实现。

特性:

  • 普通函数(非类成员函数)不能是虚函数
  • 静态函数(static)不能是虚函数
  • 构造函数不能是虚函数(因为在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调- 用完成后才会形成虚表指针)
  • 内联函数不能是表现多态性时的虚函数

纯虚函数

  • 只是一个接口,是个函数的声明而已。
  • 纯虚函数关注的是接口的统一性,实现由子类完成。
  • 带纯虚函数的类叫虚基类,这种基类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。对于虚基类,是含有纯虚函数的类,它如果被继承,那么子类就必须实现虚基类里面的所有纯虚函数,其子类不能是抽象类。

虚析构函数:为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。

class Shape{
public:
    Shape();                    // 构造函数不能是虚函数
    virtual double calcArea();
    virtual ~Shape();           // 虚析构函数
};

class Circle : public Shape{     // 圆形类
public:
    virtual double calcArea();
    ...
};

int main(){
    Shape * shape1 = new Circle(4.0);
    shape1->calcArea();    
    delete shape1;  // 因为Shape有虚析构函数,所以delete释放内存时,先调用子类析构函数,再调用基类析构函数,防止内存泄漏。
    shape1 = NULL;
    return 0}

虚函数指针和虚函数列表

当一个类定义了虚函数会隐式的创建一个自己的虚函数指针和虚函数列表。
虚函数指针:在含有虚函数类的对象中,指向虚函数表,在运行时确定。
虚函数表:在程序只读数据段,存放虚函数指针,如果派生类实现了基类的某个虚函数,则在虚表中覆盖原本基类的那个虚函数指针,在编译时根据类的声明创建。

虚继承

虚继承用于解决多继承条件下的菱形继承问题(浪费存储空间、存在二义性)。
菱形继承问题
虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。

实际上,vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。

#include<iostream>
using namespace std;
 
class A  //大小为4
{
public:
	int a;
};
class B :virtual public A  //大小为12,变量a,b共8字节,虚基类表指针4
{
public:
	int b;
};
class C :virtual public A //与B一样12
{
public:
	int c;
};
class D :public B, public C //24,变量a,b,c,d共16,B的虚基类指针4,C的虚基类指针
{
public:
	int d;
};
 
int main()
{
	A a;
	B b;
	C c;
	D d;
	cout << sizeof(a) << endl;//4
	cout << sizeof(b) << endl;//12
	cout << sizeof(c) << endl;//12
	cout << sizeof(d) << endl;//24
	return 0;
}

虚继承、虚函数 关系

  • 相同之处:
    • 都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)
  • 不同之处:
    • 虚继承
      • 虚基类依旧存在继承类中,只占用存储空间
      • 虚基类表存储的是虚基类相对直接继承类的偏移
    • 虚函数
      • 虚函数不占用存储空间
      • 虚函数表存储的是虚函数地址

模板类、成员模板、虚函数 关系

模板类中可以使用虚函数
一个类(无论是普通类还是类模板)的成员模板不能是虚函数

成员模板

class D{
public:
	template<class T>
	void ggg(T a) {
		cout << a << endl;
	}
};

D d;
d.ggg<int>(3);

猜你喜欢

转载自blog.csdn.net/tony__lin/article/details/83830767
今日推荐