C++虚函数指针虚函数表

C++的多态可以分为静态多态和动态多态。函数重载和运算符重载实现的多态属于静态多态,而通过虚函数可以实现动态多态。实现函数的动态联编其本质核心则是虚表指针与虚函数表。

 

1. 虚函数与纯虚函数区别

1)虚函数在子类里面也可以不重载的;但纯虚必须在子类去实现

2)带纯虚函数的类叫虚基类也叫抽象类,这种基类不能直接生成对象,只能被继承,重写虚函数后才能使用,运行时动态动态绑定!

 

2.子类继承父类(子类和父类无同名虚函数的时候)

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

 

3.子类继承父类(子类覆盖父类的同名虚函数时)

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

 

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

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



虚函数表和虚指针


虚函数表的地址vptr

虚指针函数在vtable里面






从上图可以看出,Derived继承Base,子类Derived实现的虚函数将覆盖父类Base的同名虚函数,如start,stop,另外子类Derived将会继承没有实现的Base虚函数,如stop2

从图中可以看出,虚函数表是实现多态的核心,即就算使用基类指针指向派生类对象,调用的虚函数跟通过虚函数表来查找。


下面的代码将显示出虚函数的地址

#include <iostream>

class Base
{
public:
	virtual int start()
	{
		std::cout<<"Base start"<<std::endl;
	}
	
	virtual int stop()
	{
		std::cout<<"Base stop"<<std::endl;
	}

	virtual int stop2()
	{
		std::cout<<"Base stop2"<<std::endl;
	}
	int func()
	{

	}
private:
	int m_b;
};

class Derived : public Base
{
public:
    virtual	int start()
	{
		std::cout<<"Derived start"<<std::endl;
	}

	virtual int middle()
	{
		std::cout<<"Derived middle"<<std::endl;
	}
	 
    virtual	int stop()
	{
		std::cout<<"Derived stop"<<std::endl;
	}
	
private:
	int m_d;
};

typedef int (*pfun)();
int main()
{
	Base base;
	Derived derived;

	Base *pBase  = new Derived();
	pfun pf = NULL;
	
	pf = (pfun) ((int **)(*(int *)pBase))[0];
	pf();
	pf = (pfun)((int **)(*(int *)pBase))[1];
	pf();
	pf = (pfun)((int **)(*(int *)pBase))[2];
	pf();
	pf = (pfun)((int **)(*(int *)pBase))[3];
	pf();

	 //虚函数表的地址
	std::cout<<"  virtual  table  addr " << (int *) pBase<<std::endl;
	
	std::cout<<((int **)(*(int *)pBase))[0]<<std::endl;
    std::cout<<((int **)(*(int *)pBase))[1]<<std::endl;
    std:: cout<<((int **)(*(int *)pBase))[2]<<std::endl;
    std::cout<<((int **)(*(int *)pBase))[3]<<std::endl;

    return 0;
}






常见虚函数面试题


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


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


46. 纯虚函数如何定义?含有纯虚函数的类称为什么?为什么析构函数要定义成虚函数?


纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。纯虚函数是虚函数再加上= 0。virtual void fun ()=0。含有纯虚函数的类称为抽象类在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。如果析构函数不是虚函数,那么释放内存时候,编译器会使用静态联编,认为p就是一个基类指针,调用基类析构函数,这样子类对象的内存没有释放,造成内存泄漏。定义成虚函数以后,就会动态联编,先调用子类析构函数,再基类。
47. C++ 中哪些不能是虚函数?


1)普通函数只能重载,不能被重写,因此编译器会在编译时绑定函数。
2)构造函数是知道全部信息才能创建对象,然而虚函数允许只知道部分信息。
3)内联函数在编译时被展开,虚函数在运行时才能动态绑定函数。
4)友元函数 因为不可以被继承。
5)静态成员函数 只有一个实体,不能被继承。父类和子类共有。





  • 12
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值