虚函数的的原理及应用

本文详细介绍了C++中的虚函数,包括虚函数的概念、多态性、虚函数的实现机制(虚函数表和虚函数表指针)、虚析构函数的必要性、构造函数不能为虚函数的原因以及纯虚函数的作用。通过实例解析了虚函数在继承和多态中的应用,强调了虚函数在实现动态联编和接口规范中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1,虚函数的概念

虚函数是在类中被声明为virtual的成员函数,当编译器看到通过指针或引用调用此类函数时,对其执行晚绑定,即通过指针(或引用)指向的类的类型信息来决定该函数是哪个类的。通常此类指针或引用都声明为基类的,它可以指向基类或派生类的对象。


2,多态的概念

多态指同一个方法根据其所属的不同对象可以有不同的行为。

(1)校长说放假的例子,不同的人有不同的行为。

(2)小王毕业后,赚钱,买了一辆宝马开。



这种实现不科学





3,虚函数的实现机制(虚函数表和虚函数表指针)

一个类对象占多少个字节。


说到虚函数的实现方法,我们就不得不说到动态联编(dynamic binding)和静态联编(static binding)。静态联编意味着编译器能够直接将标识符和存储的物理地址联系在一起。每一个函数都有一个唯一的物理地址,当编译器遇到一个函数调用时,它将用一个机械语言说明来替代函数调用,用来告诉CPU跳至这个函数的地址,然后对此函数进行操作。这个过程是在编译过程中完成的(注:调用的函数在编译时必须能够确定),所以静态联编也叫前期联编(early binding)。但是,如果使用哪个函数不能在编译时确定,则需要采用动态联编的方式,在程序运行时在调用函数,所以动态联编也叫后期联编(late binding)。

在C++继承多态中,如若要在派生类中重新定义基类的方法,则要把它声明为虚函数,并且用指针或者引用去调用它的方法,实现动态联编,否则编译器默认的将是静态联编。

class A

{

public:

    virtual void f()   { cout << "A’s f()" <<endl; } //f()被声明为虚函数

    virtual void g()   { cout << "A’s g()"<< endl;} //g()被声明为虚函数

};

 

class B : publicA

{   

public:

    void f()   { cout << "B’s f()" << endl; }

};

 

class C : publicA

{

public:

    void g()   { cout << "C’s g()" << endl; }

};

int main (void)

{

    A     *pa;

    B     b;

    C     c;

    pa = &b;

    pa -> f();

    pa -> g();

    pa = &c;

    pa -> f();

    pa -> g();

    return 0;

}


但是,调用一个虚函数比调用一个非虚函数的速度要慢一些,原因:首先,我们必须使用*__vptr去获得合适的virtual table,然后通过这张virtual table的索引才可以找到正确的调用函数,只有这样我们才可以调用这个函数。使用虚函数在内存方面也有一定的成本,即每个对象都讲增大,增大量为存储地址的空间。

 

每个类都有一个自己的vtable,每个对象都有自己的*__vptr(继承的)

C++<span times="" new="" ';="" mso-hansi-font-family:="" 'times="" "="">的编译器是保证虚函数表的指针存在于对象实例中最前面的位置

多重继承的虚函数结构,看下面这篇博客

http://haoel.blog.51cto.com/313033/124595/

 

4,为什么要用虚析构函数

输出结果是:
Do something in class ClxDerived!

这个结果是很危险的,因为ClxDerived的析构函数没有被调用到。


 答案:这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。

注意:当然,并不是要把所有类的析构函数都写成虚函数。
因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

 

5,为什么构造函数不能是虚函数

当一个构造函数被调用时,它做的首要的事情之一是初始化它的V P T R

所以它使用的V P T R必须是对于这个类的V TA B L E。而且,只要它是最后的构造函数调用,那么在这个对象的生命期内, V P T R将保持被初始化为指向这个V TA B L E,但如果接着还有一个更晚派生的构造函数被调用,这个构造函数又将设置V P T R指向它的 V TA B L E,等.直到最后的构造函数结束。V P T R的状态是由被最后调用的构造函数确定的。这就是为什么构造函数调用是从基类到更加派生类顺序的另一个理由。

(1)虚函数是对应一个vtable的,虚函数的调用是通过虚函数表的指针来调用的。而虚函数表的指针是在构造函数中生成的,可是对象还没有实例化,虚函数表的指针还没有指向对应类的虚函数表,所以也无法调用虚的构造函数。

(2)虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象是自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。

 

6,纯虚函数

 class <类名>
    {

       virtual <类型><函数名>(<参数表>)=0;
       …

   };

带有纯虚函数的类称为抽象类,不能被实例化。

 

我是一个抽象类,不要把我实例化,纯虚函数的作用是用来规范派生类的行为,实际上就是所谓的“接口”,它告诉使用者,我的派生类都会有这个函数。

 

以下载模块为例子

下载模块是一个单独的模块

下载中有三种状态,下载失败,下载中(进度更新),下载完成,需要将这三种状态告诉UI层。

有三种方式:

(1)cocos2d-x中的观察者模式

(2)在下载模块中定义一个ui层的指针,在初始化下载模块时,将ui层指针传进来。

(3)写一个纯虚基类CDelegate,定义好三个接口,然后在下载模块中定义一个CDelegate的指针,哪个ui层用这个下载模块,哪个ui层就继承这个CDelegate虚基类,然后在初始化下载模块时将自己的指针传进来。这样就可以达到下载模块的公用。

#include "stdafx.h"
#include <iostream>
using namespace std;
class CDelegate
{
public:
	virtual void OnDownSuccess()=0;
	virtual void OnDownFail()=0;
	virtual void OnDownProgress(double progress)=0;
};

class CDownLoad
{
public:
	CDownLoad(CDelegate* delegate){ m_delegate = delegate; }
	~CDownLoad(){}
public:
	void start()
	{ 
		cout<<"do down things"<<endl;
		OnEvent();
	}
	void OnEvent()
	{
		double process = 0.5;;
		m_delegate->OnDownFail();
		m_delegate->OnDownSuccess();
		m_delegate->OnDownProgress(process);
	}
private:
	CDelegate* m_delegate;
};

class UILayer1 : public CDelegate
{
public:
	virtual void OnDownSuccess(){ cout<<"UILayer1::OnDownSuccess()"<<endl; }
	virtual void OnDownFail(){ cout<<"UILayer1::OnDownFail()"<<endl; }
	virtual void OnDownProgress(double progress){ cout<<"UILayer1::OnDownProgress()"<<endl; }
public:
	void start_download()
	{
		CDownLoad*  down_load = new CDownLoad(this);
		down_load->start();
	}
};

class UILayer2 : public CDelegate
{
public:
	virtual void OnDownSuccess(){ cout<<"UILayer2::OnDownSuccess()"<<endl; }
	virtual void OnDownFail(){ cout<<"UILayer2::OnDownFail()"<<endl; }
	virtual void OnDownProgress(double progress){ cout<<"UILayer2::OnDownProgress()"<<endl; }
public:
	void start_download()
	{
		CDownLoad*  down_load = new CDownLoad(this);
		down_load->start();
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	UILayer1* layer1 = new UILayer1();
	layer1->start_download();

	UILayer2* layer2 = new UILayer2();
	layer2->start_download();
	system("pause");
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值