虚函数相关

1、何为虚函数

虚函数是C++多态实现的基石。

虚函数是为了实现动态编译而设计,即:只有在具体的运行中,才会知道到底调用父类或者子类哪个版本的虚函数。其出现的根本原因是在C++中父类指针既可以指向子类对象也可以指向父类对象(反之不行)。

如果没有虚函数,且子类中如果重写了与父类中函数原形相同的函数,则会隐藏父类中的此函数,编译器在编译阶段并不知道这个父类指针到底指向什么对象,所以最安全的办法就是调用父类中的该函数(因为是父类指针),那么当运行之时,不管传什么对象给这个父类指针,它都已经被确定调用父类中的函数。这就是静态联编。

如果我们想让程序不要再编译阶段就确定调用哪个办法,那就应该明确告诉编译器,这个函数的确认你要“缓一缓”。那这个关键字就是virtual,即定义为虚函数,编译器就知道这个函数我要特殊照顾一下。运行时,根据传过来的对象,再决定调用哪个版本的函数。

2、虚函数机制实现

虚函数在实现时,利用表的机制,上述“特殊照顾”即是建立一个虚函数表,将这些需要缓一缓的函数入口地址(函数指针)放进去,每个对象都会被编译器赋予一个指向这个虚函数表的指针(vptr指针),同时如果基类中被声明为virtual函数,则在子类中同名函数自动隐藏式声明为virtual。可以用sizeof函数去证明vptr的存在:

class A{ 
public:
	 A() = default;
	 virtual void func(){}
	int m; };
class B{ int m; };
int main()
{
	cout << "A.size(): " << sizeof(A) << " B.size(): " << sizeof(B) << endl;
	getchar();
}

输出:

3、虚函数运用

tip1、虚函数与构造函数:

如果在构造函数中调用虚函数,能实现多态么?不能

因为vptr指针是在对象初始化的时候,由编译器初始化,所以只有当对象完全构造结束,vptr才会明确其指向,也就是虚函数表才会构造成功(因为没构造结束之前并不知道这个表里有几个虚函数或者有没有虚函数)。对象的完全构造是指,父类对象构造->一级子类对象构造->二级子类对象构造->....,虚函数的意义是动态绑定,vptr指针也只关注当前的类,不关心被谁继承。按照这样的顺序,在父类对象构造完成之前,根本就没有子类的vptr指针,父类构造完到子类构造调用之间,子类的vptr指针也是指向父类的虚函数表,这是不是很像静态链接的形式?那么在构造函数中调用虚函数没有意义。

从内存角度来理解就是,vptr指针需要调用vtable,而vtable是在对象内存空间的,构造函数执行完之前,对象内存空间还没有被初始化,所以即便该函数传子类对象,调用的也是构造完成的父类版本。

构造函数本身也不可以是虚函数:

如果按照虚函数的动态绑定意义,传子类对象只执行子类版本的构造函数,那子类继承父类的那部分就没有被初始化,就会出现未定义问题。

故而构造函数只能从基类开始一层层往下执行。

tip2、虚函数与析构函数:

析构函数可以是虚函数,析构函数执行时,vtable早就初始化好了,所以不存在冲突。

当动态创建子类对象、并以父类指针指向子类对象时,必须将父类析构函数设为virtual,如若不然,在执行delete释放该指针指向空间时候,只会执行父类析构函数,而不会执行子类,造成内存泄漏。

class BaseClass
{
public:
	BaseClass(){}
	/*virtual */~BaseClass()
	{
		std::cout << "BaseClass virtual construct." << std::endl;
	}
};

class SubClass : public BaseClass
{
public:
	SubClass(){}
	/*virtual */~SubClass()
	{
		std::cout << "SubClass virtual construct." << std::endl;
	}

};


int main()
{
	BaseClass* pObj = new SubClass();
	delete pObj;
	getchar();
	return 0;
}

output:

这里根据我的理解,由于delete是一个C++重载的操作符,实际上也就是个函数。那么当没有设置虚函数的时候,在编译期间,就确定了delete中执行的是父类的deconstructor,如果设置了虚函数,则根据pObj接收的是对象类型,执行对应的deconstructor,而根据类析构函数调用规则,调用完子类的析构,会继续析构为了创建子类而暂时创建的父类对象,所以第二步也会执行父类的析构。

4、纯虚函数与C++接口“抽象类”

纯虚函数只要将虚函数的实现部分换成“=0”即可;

含有纯虚函数的类是抽象类,不可以被实例化,因为其中的纯虚函数根本没有实现,需要子类自己去定义实现才能被实例化。

子类必须自己完成父类中的抽象类实现。

抽象类仅仅是施加对子类的约束,而不自己实现,比如动物类就是抽象类,规定了子类的必要条件,比如要有四肢或者其他,但是四肢具体实现子类自己控制就好。

规定子类要有哪些,但不规定你怎么实现。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值