多态 虚函数

#include<iostream>
using namespace std;




class A
{
public:
	virtual void print() 
	{
		cout<<"A"<<endl;
	}
private:
	int value;
};


class B:public A
{
public:
	virtual void print()
	{
		cout<<"B"<<endl;
	}
private:
	int value;
};


class D:public A
{
public:
	virtual void print()
	{
		cout<<"D"<<endl;
	}
private:
	int value;
};


void main()
{
	A * pA;
	pA=new B();
	pA->print();


	pA=new D();
	pA->print();
}


上述是一个多态的例子,输出结果为:

B
D

如果上例中A类中print()类非virtual函数则输出A,这与B类中print()函数是否为虚类无关(除非涉及到B类的子类)。亦即,virtual是针对父类而言的。

下面为转载部分:

大概意思理解为(自己的理解):

1. 父类A和子类B都有相同的函数,如果不用虚函数的话,A * pA=new B() ,在调用函数F( )时会调用A类的F函数;

                                                        如果用虚函数的话,每个类都有自己的虚函数表,如果子类有继承的话,子类会把相应的虚函数重写,这样在调用时就变成自己的虚函数。

2. 父类里函数为virtual类型时,执行其子类中的函数;父类里函数为非virtual类型时,执行父类中的函数。

3. 析构函数略有不同。如果父类中析构函数为非虚函数,此类的行为是未定义的,可能会出现意料之外的错误。父类中的资源被释放,而子类中的资源则不一定。

如果父类中析构函数为虚函数时,首先会调用子类中的析构函数,然后依次往上层调用。所以所有的资源都可被释放。

关于虚函数

       C++是支持动态绑定(也即多态)的,不过它不像Java那样天生的就是多态,不仅要在成员函数的前面加上关键字virtual还要通过指针或者引用才可以。

1.虚函数是多态的基础,而且只有非静态非友元非内联的成员函数才能声明为虚函数。

2.virtual只能用在函数声明的时候,不能用在函数实现的时候。

3.当父类中声明虚函数时,子类自动继承该虚函数,无论子类是否声明,同原型的函数都自动为虚函数。这里的同原型指的是函数名,返回类型,参数列表都完全一样,只有函数的实现不一样。

二、虚函数实现机制

  简单的说是“旧瓶装新酒”。

  当编译器发现类有虚函数,则在对象建立的时候偷偷地加了一个成员,它是一个void类型的指向指针的指针vptr,该指针指向了一个虚函数表vtable,表的每一项都是函数的地址。图示如下:


当通过指针或者引用调用虚函数时,编译器会根据索引到vtable中取得相应的函数入口地址,即为要调用的具有多态性质的函数。这种机制实际是将找谁变成了到哪里去找,也即编译器只知道到某个指定的索引去取函数地址,但它不知道这个函数地址是基类中的函数还是子类中重写的“具有”多态性质的函数。

比如p->z();//z为虚函数

编译器根据z声明的顺序就知道它的索引(后面详述),比如为2,则通过p调用时直接到vtable[2]中取函数地址,然后调用就可以了。至于vtable[2]中的函数是什么它不管。关于细节后面有更详细的讨论。

继承族中虚函数机制的图示:



三、详述细节

我们先看最简单的多态例子:

class B
{
public:
 virtual void print()
 {
  cout<<"B"<<endl;
 }
 virtual void move()
 {}
private:
 int b;
};
class D : public B
{
public:
 virtual void print()
 {
  cout<<"D"<<endl;
 }
private:
 int d;
};

void main()  
{  
 D dd;
 B *pB = &dd;
 pB->print();

 system("pause");
}  
virtual table布局如下:


说明:

1.只有虚函数才能进入虚函数表,而且根据声明的顺序依次排列,如上图。
2.当 pB->print();执行时,编译器将发生转换,将上句转换如下:


对于有参数的则形如:

(*pB->vtable[1])(pB,Arg_list);

3.如果子类中没有重写父类中的虚函数,则形如class D中的B::move见上图,函数地址不改变。

四、多重继承下的虚函数

形如:

class B1;

class B2;

class D : public B1,public B2;

则在D的布局中有两个vptr有两个vtable,其他与单继承相似。多态调用的时候也发生转换,不同的是有可能发生指针的调整,即B2 *pB2 = new D;这种情况下。新的D对象的地址必须调整,以指向其B2 subobject,编译时期会产生这样的代码:

D *tmp = new D;

B2 *pB2 = tmp ? tmp + sizeof(B1) : 0;

关于this指针调整的大小,也即上面的这段代码必须在编译器的某个地方插入,问题是,在哪个地方???

Bjarne在cfront编译器中采用的增大virtual table,将offset也存储起来;比较有效率的解决方法是利用所谓的thunk技术,也即thunk是一小段assembly码,可能看起来是如下的样子:

pB2_methodname_thunk:

this += sizeof(B1);

pB2->virtual_method(this);

虚函数的实现机制比较复杂,细节问题很多,这里只是冰山一角,欢迎讨论。

 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值