}
};
intmain()
{
B_class*p;
B_classB_ob;
D_class*dp;
D_classD_ob;
p=&B_ob;//基类型对象的地址
//通过指针访问B_class类型的对象
p->put_author("TomClancy");
//通过基类型指针访问D_class类型的对象
p=&D_ob;
p->put_author("WilliamShakespeare");
//输出保存在对象中的每个作者姓名
B_ob.show_author();
D_ob.show_author();
cout<<"\n";
/*由于函数put_title()和show_title()不属于基类,因此不能通过基类指针p来访问它们,我们必须直接或者像下面这样通过派生类型的指针访问这两个函数。
*/
dp=&D_ob;
dp->put_title("TheTempest");
p->show_author();//这里既可以使用指针p也可以使用指针dp
dp->show_title();
return0;
}
程序输出如下:
TomClancy
WilliamShakespeare
WilliamShakespeare
Title:
TheTempest
在上面的示例中,指针p被声明为指向B_class类型的指针。
同时,它可以指向一个派生类D_class类型的对象并且可以用来访问派生类中从基类继承的成员。
但要记住,基类型指针不能访问在派生类中定义的成员。
这就是为什么程序需要通过派生类型的指针dp来访问函数show_title()的原因。
如果想通过基类型指针来访问在派生类中定义的成员,必须先把它转换为派生类型的指针。
例如,下面这行代码就可以正确地调用D_ob的成员函数show_title()。
((D_class*)p)->show_title();
其中,在类型转换运算外部的圆括号是必需的,因为我们需要转换的是指针p的类型,而不是调用函数show_title()所返回的类型。
尽管这种指针类型的转换方式在技术上没有任何错误,但最好还是避免这样做,因为这容易混淆代码(事实上,大多数C++程序员都认为这是一种不好的形式)。
需要知道的另外一点是:
基类型指针可以指向任何派生类型的对象,反之则不然。
也就是说,我们不能使用派生类型的指针来访问基类型的对象。
指针的增量运算或减量运算依赖于指针被声明的类型。
因此,当基类型指针指向派生类型的对象时,指针的增量运算或减量运算将不会使指针指向下一个派生类型的对象,相反,它将指向(指针认为是这样)下一个基类型的对象。
因此,当基类型的指针指向派生类型对象时,指针的增量运算或减量运算是无效的。
基类型指针可以指向任何派生类型的对象,这一点很重要,它也是C++的基础。
我们马上将会学到,基类型指针的这种灵活性是C++得以实现运行时多态的关键。
派生类型的引用
与刚才描述的指针行为一样,基类型的引用也可以指向派生类型的对象。
最常见的用法是在函数参数的声明和定义中。
基类型的引用参数可以接收基类型的对象或其派生类型的对象。
5.4.2虚函数
运行时多态的实现需要将两个特征结合在一起,即继承和虚函数。
在前面章节中你已经学习了继承,下面我们就来学习虚函数。
在函数声明前加virtual可以声明虚函数。
虚函数是指在基类中使用virtual声明,并且在一个或多个派生类中被重新定义的函数。
这样,每个派生类可以拥有自己的虚函数定义。
我们最感兴趣的是:
在使用基类型指针(或引用)来调用虚函数时,虚函数所发生的行为。
在这种情况下,C++根据指针指向对象的类型来决定调用虚函数的哪个定义,并且这种决定是在运行时作出的。
这样,当指针指向不同的对象时,将会执行虚函数的不同定义。
换言之,是指针指向对象的类型(而不是指针的类型)来决定虚函数的调用。
因此,如果在一个基类中包含一个虚函数,并且这个基类有两个或多个派生类,那么当一个基类指针指向不同类型的对象时,虚函数的不同定义将被执行。
同理,基类型的引用也是如此。
在基类中声明虚函数需要在函数声明前加关键字virtual。
当虚函数在派生类中重新定义时,关键字virtual不需要重复(当然重复也不是个错误)。
定义了虚函数的类被称为多态类。
包含虚函数的类被称为多态类,这个术语同样适用于从多态类继承的类。
下面的程序给出了虚函数的用法:
//使用虚函数的示例。
#include
usingnamespacestd;
classbase{
public:
在类base中,函数who()被声明为虚函数,这说明该函数可以在派生类中重新定义。
因此,在类first_d和second_d中,who()分别被重新定义。
virtualvoidwho()//声明虚函数
{
cout<<"Base\n";
}
};
classfirst_d:
publicbase{
public:
voidwho()//重新定义first_d中的who()
{
cout<<"Firstderivation\n";
}
};
classsecond_d:
publicbase
{
public:
voidwho()//重新定义second_d中的who()
{
cout<<"Secondderivation\n";
}
在main()中,声明了4个变量:
base_obj,base类型的对象;
p,指向base类型对象的指针;first_obj和second_obj,分别是两个派生类型的对象。
};
intmain()
{
basebase_obj;
base*p;
p被赋予base_obj的地址,函数who()被调用。
由于who()被声明为虚函数,在运行时,C++根据p所指向的对象类型决定who()的哪个定义将被调用。
此时,p指向一个base类型的对象,所以base中定义的who()被执行。
first_dfirst_obj;
second_dsecond_obj;
p=&base_obj;
p->who();//访问base中的who
然后,p被赋予first_obj的地址。
前面讲过,基类型的指针可以指向任何派生类型的对象。
现在,当who()又被调用,C++再次检查p指向对象的类型,然后根据该类型决定调用相应的who()。
由于p指向一个first_d类型的对象,所以who()相应的定义被调用。
同理,当把second_obj的地址赋给p时,who()在second_d中的定义被执行。
p=&first_obj;
p->who();//访问first_d中的who
p=&second_obj;
p->who();//访问second_d中的who
return0;
}
程序输出如下:
Base
Firstderivation
Secondderivation
●记住:
程序是在运行时决定调用虚函数的哪个定义。
更进一步地说,这个决定只依赖于基类型指针所指向的对象的类型。
虚函数可由标准对象通过点运算符调用。
这就是说,在前面的示例中,可以通过下面的语句来访问函数who():
first_obj.who();
但是,这种调用虚函数的方法忽略了虚函数的多态性。
只有使用基类型指针访问虚函数的时侯,运行时多态才得以体现。
在派生类中重新定义虚函数时,被称为覆盖虚函数。
初看上去,虚函数在派生类中的重新定义好像是一种特殊的函数重载形式,但实际上并不是这样。
事实上,这两种方法有着本质的不同。
首先,重载函数必须在参数的类型或数量上不同,而重新定义的虚函数在参数的类型和数量上必须相同。
虚函数的原型与它重新定义的形式必须完全相同。
如果原型不同,那么函数只能被简单地认为是重载形式,从而失去了虚函数的特征。
另一个限制是,在定义虚函数的类中,虚函数必须声明为类的成员而不能是友员,但虚函数可以被声明为其他类的友员。
同样,析构函数可以是虚函数,但构造函数则不可以。
正因为在重载一般的函数和重新定义虚函数之间有着差异和限制,我们使用术语“覆盖(overriding)”来描述虚函数的重新定义。
5.4.2.1虚函数的继承