虚函数 虚函数表

虚函数是面向对象编程函数的一种特定形态,是C++用于实现多态的一种有效机制。C++的多态可以分为静态多态和动态多态。函数重载和运算符重载实现的多态属于静态多态,而通过虚函数可以实现动态多态。实现函数的动态联编其本质核心则是虚表指针与虚函数表。

虚函数的本质就是通过基类访问派生类定义的函数。每一个含有虚函数的类,其实例对象内部都有一个虚函数表指针,该虚函数表指针被初始化为本类的虚函数表的内存地址。所以在程序中,不管对象类型如何转换,但该对象内部的虚函数表指针是固定的,这样才能实现动态地对对象函数进行调用,这就是C++多态性的原理。

使用虚函数注意事项:

  • 只需在声明函数的类体中使用关键字virtual将函数声明为虚函数,在定义函数时不需要。
  • 将基类中某一成员函数声明为虚函数后,派生类中的同名函数自动成为虚函数。
  • 如果类(基类和派生类)中声明了某成员函数为虚函数,则类中不能再出现与之相同的非虚函数。

当父类有实现名为虚函数fucnA ,而子类没有实现虚函数funcA,那么子类将继承该虚函数,该虚函数将存在子类的虚函数表中。

子类继承父类,并且子类的虚函数覆盖父类的同名虚函数的时候,子类的虚函数表将存放着子类的虚函数及没有被覆盖的同名虚函数。

虚函数工作的原理:

编译器处理虚函数的方法是:给每个对象添加一个隐藏成员,隐藏成员中包含了一个指向函数地址数组的指针。这种数组称为虚函数表。虚函数表中存储了为类对象进行声明的虚函数地址。

基类对象包含一个指针,该指针指向基类中所有函数的地址表。派生类对象将包含一个指向独立指针表的指针,如果派生类提供了函数的新定义,该函数表将保存新的函数的地址,如果派生类没有重新定义函数,则虚函数表将保存函数原始版本的地址。如果派生类定义了新的虚函数,该函数的地址也将被添加到虚函数表中。

虚函数 虚函数表_第1张图片

调用虚函数时,程序将查看存储在对象中的虚函数表的地址。然后转向相应的函数地址表。使用虚函数时,在内存和执行速度方面有一定的成本,包括:

  • 每个对象都将增大,增大量为存储地址的空间;
  • 对于每个类,编译器都创建一个虚函数地址表( 数组);
  • 对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址。

虚函数,虚函数表里面内存如何分配?

编译时若基类中有虚函数,编译器为该的类创建一个一维数组的虚表,存放是每个虚函数的地址。基类和派生类都包含虚函数时,这两个类都建立一个虚表。构造函数中进行虚表的创建和虚表指针的初始化。在构造子类对象时,要先调用父类的构造函数,初始化父类对象的虚表指针,该虚表指针指向父类的虚表。执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。每一个类都有虚表。虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。当用一个指针/引用调用一个函数的时候,被调用的函数是取决于这个指针/引用的类型。即如果这个指针/引用是基类对象的指针/引用就调用基类的方法;如果指针/引用是派生类对象的指针/引用就调用派生类的方法,当然如果派生类中没有此方法,就会向上到基类里面去寻找相应的方法。这些调用在编译阶段就确定了。当涉及到多态性的时候,采用了虚函数和动态绑定,此时的调用就不会在编译时候确定而是在运行时确定。不在单独考虑指针/引用的类型而是看指针/引用的对象的类型来判断函数的调用,根据对象中虚指针指向的虚表中的函数的地址来确定调用哪个函数。

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。纯虚函数是虚函数再加上= 0。virtual void fun ()=0。当类中声明有纯虚函数时,则不能创建该类的对象。

虚函数的使用时注意:

1.构造函数不能是虚函数??

虚函数对应一个vtable,这大家都知道,可是这个vtable其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过vtable来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到vtable,所以构造函数不能是虚函数。

2.析构函数

析构函数应当是虚函数,除非类不用做基类。例如;假设Employee是基类,Singer 是派生类,并添加一个char *成员,该成员指向由new分配的内存。当Singer对象过期时,必须调用~Singer( )析构函数来释放内存。看下面的代码:

Employee *pe=new Singer ;

delete pe;

如果使用默认的静态联编,delete 语句将调用~ Employee( )析构函数。这将释放由Singer对象中的Employee部分指向的内存,但不会释放新的类成员指向的内存。但如果析构函数是虚的,则上述代码将先调用~ Singer析构函数释放由Singer组件指向的内存,然后,调用~Employee( )析构函数来释放由Employee组件指向的内存。
这意味着,即使基类不需要显式析构函数提供服务,也不应依赖于默认构造函数,而应提供虚析构函数,即使它不执行任何操作:
virtual ~BaseClass() 
顺便说一句,给类定义一个虚析构函数并非错误,即使这个类不用做基类;这只是一个效率方面的问题。

3.友元函数能不能是虚函数??

友元不能是虚函数,因为友元不是类的成员,而只有成员才能是虚函数。

 


 

你可能感兴趣的:(C++)