分类: C/C++
2007-07-25 09:35:15
1. 虚函数基础
虚函数通俗地讲就是那些被virtual关键字修饰的成员函数。
虚函数的作用是用来实现多态性(Polymorphism)。
多态性是将接口与实现进行分离。
指向基类的指针和引用在操作它的派生类对象时,会根据不同的类对象,调用对应的函数。
虚函数的定义在基类中进行,在需要定义为虚函数的成员函数的声明前冠以关键字 virtual。
基类中的某个成员函数被声明为虚函数后,此虚函数就可以在一个或多个派生类中被重新定义。
在派生类中重新定义时,其函数原型,包括返回类型,函数名,参数个数,参数类型及参数的先后顺序,都必须与基类中的原型完全相同。
虚函数是重载的一种表现形式,是一种动态重载方式。
2. 为什么使用虚函数
#include
using namespace std;
class Base
{
public:
void out ()
{
cout << "base class!" << endl;
}
};
class Derive1:public Base
{
public:
void out ()
{
cout << "derive1 class!" << endl;
}
};
class Derive2:public Base
{
public:
void out ()
{
cout << "derive2 class!" << endl;
}
};
int
main ()
{
Derive1 object1;
Derive2 object2;
Base object3;
Base *object = &object1;
object->out ();
object = &object2;
object->out ();
object = &object3;
object->out ();
object1.out ();
object2.out ();
object3.out ();
return 0;
}
运行结果:
base class!
base class!
base class!
derive1 class!
derive2 class!
base class!
通过对象指针进行的普通成员函数调用,仅仅与指针的类型有关,而与此刻正指向什么对象无关。
#include
using namespace std;
class Base
{
public:
virtual void out ()
{
cout << "base class!" << endl;
}
};
class Derive1:public Base
{
public:
void out ()
{
cout << "derive1 class!" << endl;
}
};
class Derive2:public Base
{
public:
void out ()
{
cout << "derive2 class!" << endl;
}
};
int
main ()
{
Derive1 object1;
Derive2 object2;
Base object3;
Base *object = &object1;
object->out ();
object = &object2;
object->out ();
object = &object3;
object->out ();
object1.out ();
object2.out ();
object3.out ();
return 0;
}
运行结果:
derive1 class!
derive2 class!
base class!
derive1 class!
derive2 class!
base class!
可见当将基类中对应的成员函数定义为虚函数,实现了当指针指向不同对象时执行不同的策略。
3. 虚函数是如何做到因对象的不同而调用其相应的函数的
非虚成员函数是静态确定的。即,非虚成员成员函数(在编译时)被静态地选择,该选择基于指向对象的指针(或引用)的类型。
虚成员函数是动态确定的(在运行时),也就是说,成员函数(在运行时)被动态地选择,该选择基于对象的类型,而不是指向该对象的指针/引用的类型。这被称作“动态绑定”。大多数的编译器使用以下技术来实现:如果对象有一个或多个虚函数,编译器将一个隐藏的指针放入对象,该指针称为“virtual-pointor”或“v-pointer”。这个v-pointer指向一个全局表,该表称为“虚函数表(virtural-table)”或“v-table”。
编译器为每个含有至少一个虚函数的类创建一个v-table。例如,如果Base类有虚函数out()、in() 和 save(),那么将有且只有一个和Base类相关的v-table,即使有一大堆Base对象。并且每个Base对象的 v-poiner将指向 Base的这个 v-table。该 v-table自己有指向类的各个虚函数的指针。例如,Circle 的v-table 会有三个指针:一个指向Circle::out(),一个指向 Circle::in(),一个指向Circle::save()。
在调用一个虚函数时,系统通过对象的 v-pointer找到类的 v-table,然后通过查找v-table得到方法的地址。
上述技术会导致空间开销和时间开销:
每个对象一个额外的指针(仅仅对于需要动态绑定的对象),加上每个方法一个额外的指针(仅仅对于虚方法),这会增加空间开销。
和普通函数调用比较,虚函数调用需要两个额外的步骤(得到v-pointer的值,得到方法的地址)。
这些开销不会发生在非虚函数上。
4. 虚函数与重载函数的关系
一般的重载函数,函数的返回类型及所带的参数必须至少有一样不完全相同,只需函数名相同即可。
基类中定义的虚函数在派生类中重新定义时,其函数原型,包括返回类型,函数名,参数个数,参数类型及参数的先后顺序,都必须与基类中的原型完全相同。
重载虚函数时,若与基类中的函数原型出现不同,系统将根据不同情况分别处理:
(1)仅仅返回类型不同,其余相同,系统会当作出错处理;
(2)函数原型不同,仅仅函数名相同,系统会认为是一般的函数重载,将丢失虚特性。