06-派生类和基类之间的特殊关系
13.1.4 派生类和基类之间的特殊关系
派生类与基类之间有一些特殊关系。其中之一是派生类对象可以使用基类的方法,条件是方法不是私有的:
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
rplayer1.Name(); // derived object uses base method
另外两个重要的关系是:基类指针可以在不进行显式类型转换的情况下指向派生类对象;基类引用可以在不进行显式类型转换的情况下引用派生类对象:
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
TableTennisPlayer & rt = rplayer;
TableTennisPlayer * pt = &rplayer;
rt.Name(); // invoke Name() with reference
pt->Name(); // invoke Name() with pointer
然而,基类指针或引用只能用于调用基类方法,因此,不能使用rt或pt来调用派生类的ResetRanking方法。
通常,C++要求引用和指针类型与赋给的类型匹配,但这一规则对继承来说是例外。然而,这种例外只是单向的,不可以将基类对象和地址赋给派生类引用和指针:
TableTennisPlayer player("Betsy", "Bloop", true);
RatedPlayer & rr = player; // NOT ALLOWED
RatedPlayer * pr = player; // NOT ALLOWED
上述规则是有道理的。例如,如果允许基类引用隐式地引用派生类对象,则可以使用基类引用为派生类对象调用基类的方法。因为派生类继承了基类的方法,所以这样做不会出现问题。如果可以将基类对象赋给派生类引用,将发生什么情况呢?派生类引用能够为基对象调用派生类方法,这样做将出现问题。例如,将RatedPlayer::Rating()方法用于TableTennisPlayer对象是没有意义的,因为TableTennisPlayer对象没有rating成员。
如果基类引用和指针可以指向派生类对象,将出现一些很有趣的结果。其中之一是基类引用定义的函数或指针参数可用于基类对象或派生类对象。例如,在下面的函数中:
void Show(const TableTennisPlayer & rt)
{
using std::cout;
cout << "Name: ";
rt.Name();
cout << "\nTable: ";
if (rt.HasTable())
cout << "yes\n";
else
cout << "no\n";
}
形参rt是一个基类引用,它可以指向基类对象或派生类对象,所以可以在Show()中使用TableTennis参数或Ratedplayer参数:
TableTennisPlayer player1("Tara", "Boomdea", false);
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
Show(player1); // works with TableTennisPlayer argument
Show(rplayer1); // works with RatedPlayer argument
对于形参为指向基类的指针的函数,也存在相似的关系。它可以使用基类对象的地址或派生类对象的地址作为实参:
void Wohs(const TableTennisPlayer * pt); // function with pointer parameter
...
TableTennisPlayer player1("Tara", "Boomdea", false);
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
Wohs(&player1); // works with TableTennisPlayer * argument
Wohs(&rplayer1); // works with RatedPlayer * argument
引用兼容性属性也让您能够将基类对象初始化为派生类对象,尽管不那么直接。假设有这样的代码:
RatedPlayer olaf1(1840, "Olaf", "Loaf", true);
TableTennisPlayer olaf2(olaf1);
要初始化olaf2,匹配的构造函数的原型如下:
TableTennisPlayer(const RatedPlayer &); // doesn't exist
类定义中没有这样的构造函数,但存在隐式复制构造函数:
// implicit copy constructor
TableTennisPlayer(const TableTennisPlayer &);
形参是基类引用,因此它可以引用派生类。这样,将olaf2初始化为olaf1时,将要使用该构造函数,它复制firstname、lastname和hasTable成员。换句话来说,它将olaf2初始化为嵌套在RatedPlayer对象olaf1中的TableTennisPlayer对象。
同样,也可以将派生对象赋给基类对象:
RatedPlayer olaf1(1840, "Olaf", "Loaf", true);
TableTennisPlayer winner;
winner = olaf1; // assign derived to base object
在这种情况下,程序将使用隐式重载赋值运算符:
TableTennisPlayer & operator=(const TableTennisPlayer &) const;
基类引用指向的也是派生类对象,因此olaf1的基类部分被复制给winner。