2015年(7)
分类: C/C++
2015-02-12 23:59:25
该程序的运行结果,我们已经标注在程序之中。因为指针的类型决定调用那一个成员函数,所以,一个vehicle*调用vehicle成员函数,即使它指向派生类的对象。同样,一个car *也调用car 的成员函数。我们把这称为早期联编或静态联编,因为指针要调用那一个函数是在编译时就确定的。
那么,当vehicle*指向派生类对象时,我们能不能通过该指针来调用派生类的成员函数呢?在C++中,我们是可以作到的,这要用到C++的多态特性。 也就是说,基类指针是调用基类的成员函数,还是调用派生类的成员函数,不是由指针的类型决定的,而是由指针指向的对象的类型决定的。
2. 什么是多态多态也称为动态联编或迟后联编,因为到底调用哪一个函数,在编译时不能确定,而要推迟到运行时确定。也就是说,要等到程序运行时,确定了指针所指向的对象的类型时,才能够确定。在C++中,动态联编是通过虚函数来实现的。
我们知道,函数调用是通过相应的函数名来实现的。对于源程序进行编译后,存放在内存中的可执行程序,函数实际上是一段机器代码,它是通过首地址进行标识和调用的。例如,假定定义一个函数:
void func(){call是汇编语句中的一条指令,意思是调用一个函数。实际操作过程是:保存当前地址、保护现场,跳转到xxxxx地址执行。正是基于这个原因,在C/C++中的函数名是一个指针,该指针指向该函数段代码在内存中的首地址。如何将源程序中的函数调用和函数体(也就是在内存中该函数的机器代码)联系起来呢?这件工作是由编译器和连接程序来完成的。
在C/C++语言中,函数调用在程序运行之前就已经和函数体(函数的首地址)联系起来。编译器把函数体翻译成机器代码,并记录了函数的首地址。 在对函数调用的源程序段进行编译的时候,编译器知道这个函数名的首地址在那里(它可以从生成的标识符表中查到这个函数名对应的首地址),然后将这个首地址替换函数名,一并翻译成机器码。这种编译方法称为早期或静态联编。
那么,当vehicle*指向派生类对象时,我们能不能通过该指针来调用派生类的成员函数呢?从这种编译方法来看,是不可能的。因为编译器只会寻找vehicle*的成员函数。如何实现这个功能:当用基类指针调用成员函数时,是调用基类的成员函数,还是调用派生类的成员函数,不由指针的类型决定,而由指针指向的对象的类型决定呢?也就是说,如果基类指针指向基类对象,就调用基类的成员函数,如果基类指针指向派生类对象,就调用派生类的成员函数。这就要用到另外一种方法,称为动态联编或迟后联编。到底调用哪一个函数,在编译时不能确定,而要推迟到运行时确定。在C++中,动态联编是通过虚函数来实现的。下面我们先介绍虚函数,然后讨论动态联编实现的原理。
3. 为什么使用虚函数
虚函数的定义很简单,只要在成员函数原型前加一个关键字virtual即可。如果一个基类的成员函数定义为虚函数,那么,它在所有派生类中也保持为虚函数,即使在派生类中省略了virtual关键字。需要注意的是:要达到动态联编的效果,基类和派生类的对应函数不仅名字相同,而且返回类型、参数个数和类型也必须相同。
基类vehicle的成员函数message被定义为虚函数,虽然其派生类中的message成员函数定义时,没有virtual关键字,但都是虚函数。如果派生类中有与基类对应的方法,并且基类指针指向派生类的对象,那么基类指针调用的方法是派生类的方法。
如果将vechicle中的message()函数定义为虚函数,那么main函数中四个相同的语句“unicycle->message();”,它们的结果并不相同,结果依次是:vechicle message, car message, car message, vechicle message, boat message。哪一个类的message成员函数被调用,不是在编译时确定的,而是根据运行时unicycle指针指向的对象的类型确定的。由于类truck没有覆盖基类的message成员函数,系统调用基类的message成员函数。
一个有意思的情况:如果在类car中,将虚函数void message(void)声明为private,那么,car sedan将无法调用message()函数(这是因为message()是car类的私有函数,实例不能调用),而vehicle *unicycle = &sedan,却可以调用message()函数,且调用的是car类中的message()函数。
5. 什么是纯虚函数及纯虚函数的作用
纯虚函数是一种特殊的虚函数。纯虚函数的定义如下: