虚函数

在了解虚函数之前我们先了解一下多态,什么是多态???

多态:允许将子类类型的指针赋值给父类类型的指针,该指针会指向子类中的父类部分。多态的目的为了接口复用,即统一接口不同形态。

静多态    编译阶段确定函数的调用(函数入口地址)静态绑定   早绑定

通过函数重载和模板来实现

动多态   运行阶段确定函数的调用(函数入口地址)动态绑定 晚绑定

       使用虚函数来实现

动多态的实现是在编译期间生成vftable虚函数表,将含有入口地址的虚函数表放入只读数据段,运行时程序会将指令和数据加载到内存上,然后确定函数的入口地址。在运行时根据基类指针的实际指向放入对象确定所要调动的对象是基类还是父类。

虚函数:在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数,

用法格式:virtual 函数返回类型 函数名(参数表) {函数体};

实现多态性,通过指向派生类的基类指针或引用访问派生类中同名覆盖成员函数

作用:虚函数的作用是在程序的运行阶段动态地选择合适的成员函数。在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型,(参数类型的顺序也要一致),以实现统一的接口。如果在派生类中没有重新定义虚函数,则它继承基类的虚函数。

  1. 如果基类中有同名同参的函数为虚函数,那么派生类中该函数也为虚函数,并且会发生函数覆盖。
  2. 派生类对象内存布局中有vfptr虚函数指针,该指针指向vftable虚函数表,虚函数的每个类都有一张虚表。

通过一段代码,我们来分析一下虚函数

#include<iostream>
using namespace std;

class Base
{
public:
	Base(int a) :ma(a)
	{
		std::cout << "Base::Base(int)" << std::endl;
	}
	virtual ~Base()
	{
		std::cout << "Base::~Base()" << std::endl;
	}
	virtual void Show()
	{
		std::cout << "Base::Show ==> ma:" << ma << std::endl;
	}
protected:
	int ma;

};
class Derive : public Base
{
public:
	Derive(int b) :Base(b), mb(b)
	{
			std::cout << "Derive::Derive(int)" << std::endl;
	}
	~Derive()
	{
		std::cout << "Derive::~Derive()" << std::endl;
	}
	void Show()
	{
		std::cout << "Derive::Show ==> mb:" << mb << std::endl;
	}
private:
	int mb;
};

int main()
{
	std::cout << sizeof(Base) << std::endl;//基类的大小
	std::cout << sizeof(Derive) << std::endl; //派生类的大小
	Base* pb = new Derive(10);                //构造
	std::cout << typeid(pb).name() << std::endl;// pb的类型
	std::cout << typeid(*pb).name() << std::endl;//*pb的类型
	pb->Show();//调动Show函数
	delete pb; //对pb进行析构
	return 0;
}

基类的内存布局

  • RTTI:运行时的类型信息,运行时提取相应的类型信息
  • 0:ptr相对于总体作用域的偏移(0-vfptr)
  • &Base::Show:虚函数的入口地址。

派生类继承基类后内存布局

派生类继承基类,同时继承虚表,发生虚表合并,此时派生类中同名同参的虚函数会覆盖基类中的虚函数

执行结果分析:

  1. 基类中增加了virtual 关键字之后,大小为8,此时基类的内存布局中包含了vfptr虚函数指针的大小和成员变量ma的大小。派生类继承基类之后又定义成员变量mb,所以其内存大小为12.
  2. Base* pb = new Derive(10);  基类的指针指向派生类对象,调动两次构造,基类的指针调动基类的构造,派生类的对象生成调动派生类的对象。基类的构造优先级 > 派生类
  3. 此时 pb的类型依旧是 class Base *,而对pb解引用,即*pb为 class Derive,基类的指针指向派生类的对象。
  4. 调动Show函数,执行结果为派生类的Show() ,在虚表的合并阶段,发生了派生类中的虚函数覆盖基类中的虚函数。
  5. 析构 pb 时,首先调动派生类的析构,在调动基类的析构。

去掉virtual关键字,使函数为普通的成员函数

基类大小为 4字节,派生类为 8 字节(无虚函数指针),pb的类型为class Base * 的基类指针,对pb 进行解引用 *pb为基类类型,此时调动的Show 方法是基类的Show方法,析构也只执行一次,基类的析构。

成为虚函数的条件:

1.能取地址

2.依赖对象的调动

  • 普通全局函数 :不可以,不依赖对象的调动
  • 普通的类成员方法 :可以
  • 静态的类成员方法 :不可以,静态的类成员方法不依赖对象的调动
  • inline函数 :不可以,inline函数不生成符号(编译期间展开,而虚函数是在运行期间绑定的)。
  •  构造函数 :不可以,构造函数不依赖对象的调动
  • 析构函数 :可以
  • 友元函数:不可以,C++不支持友元函数的继承

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值