C++中的虚函数
引言
C++中的虚函数,是实现C++多态的一个重要手段。这里会介绍一下虚函数和RTTI。
一、虚函数表和虚函数表的指针
要了解C++是如何实现虚函数这功能,首先我们就要了解一个重要概念:虚函数表(Virtual Tables,之后简称vtbls)。
虚函数表是类额外的一个静态数组。其中存放该类的虚函数信息等。
在编译的时候,编译器同时也会为基类创建一个成员变量:用于存放类对象的虚拟表的位置(virtual Table Pointers,之后简称vptrs)。
下面举一个例子来解释虚函数表。
class CBase
{
public:
virtual void f1();
virtual void f2(int i);
void f3(const char * p);
};
class CSub1 : publicCBase
{
public:
virtual void f1();
};
class CSub2 : publicCBase
{
public:
virtual void f2(int i);
};
int _tmain(int argc,_TCHAR* argv[])
{
CBase * p1 = newCSub1();
p1->f1();
p1->f2(1);
delete p1;
CBase * p2 = newCSub2();
p2->f1();
p2->f2(1);
delete p2;
return 0;
}
在程序中,new CSub1(),系统为这个new出来的对象的创建一个数组,存放了CSub1对象的虚函数信息,并且将这个数组的地址赋值给该对象的vptrs。
接下来将CSub1对象的地址付给了指针p1。而这个指针的类型是CBase*,但是它所指向的对象却是CSub1。
当执行语句p1->f1()的时候,利用指针找到内存中的CSub1对象,随后再通过该对象的vptrs找到CSub1对象的虚函数表,在表中找到了虚函数f1实现的地址,找到真正的f1函数。可以看图1.1,CSub1的f1所指向的是CSub1自己的类成员函数f1。
当执行语句p1->f2(1)的时候,和上面的步骤一样,最终找到了CBase的f2。
同样p2指针也类似,这里就不做累述。
二、RTTI
Run-Time Type Information,在程序运行时,能够判断一个对象的类型。承接上一节提到的静态类型和动态类型。
void deal(CBase * p);
……
deal(new CSub1());
这里作为函数deal的参数p的静态类型是CBase*,这是在编译器进行编译的时候,能够判断的类型。但是,如果我们将CBase的子类CSub1的对象的指针作为参数给p赋值后,p的动态类型便是CSub1。所谓动态类型便是在程序运行的时候,对象实际的类型。这就是RTTI要做得事。
对于RTTI,其中介绍两个个操作。
(1)dynamic_cast,用于多态类型的转换。经常被用来将基类对象的指针转换成子类对象的指针。
比如:
CBase * p = newCSub1();
CSub1 * ps1 = p;//1
CSub1 * ps1 =dynamic_cast<CSub1 *>(p); //2
上面第一种转换,编译器是无法通过的,但是第二种转换编译是没有问题的。
(2)typeid,用于确定对象的精确类型。记住这里是对象的类型,传入一个指针,它只会去判断这个指针的类型,而不是这个指针指向对象的类型。
CBase * p1 = newCSub1();
CBase * p2 = newCSub2();
CBase * p = newCBase();
cout<<"typeid(p).name():"<<typeid(p).name()<<endl;
cout<<"typeid(p1).name():"<<typeid(p1).name()<<endl;
cout<<"typeid(p2).name():"<<typeid(p2).name()<<endl;
cout<<"typeid(*p).name():"<<typeid(*p).name()<<endl;
cout<<"typeid(*p1).name():"<<typeid(*p1).name()<<endl;
cout<<"typeid(*p2).name():"<<typeid(*p2).name()<<endl;
delete p1;
delete p2;
delete p;
这里的输出便是:
其实typeid这个操作返回的类型是type_info(这里就不再做介绍了)。
三、总结
在C++中利用虚函数,可以实现很多面向对象的模式。可以在设计模式(Design Pattern)中的各处找到虚函数的影子,所以要怎么用好虚函数,可以去参考设计模式。