2、重载成员函数
从一个类继承的主要原因是添加或替换功能。Derived的定义通过添加了另外的成员函数someOtherFunction()增加了父类的功能。其它的成员函数someFunction(),从Base继承,在继承类中的表现与在基类中完全一致。在许多情况下,你会想通过修改,或者说重载成员函数来修改类的行为。
2.1、virtual关键字
简单地从基类的继承类中定义一个成员函数并不会恰当地重载该成员函数。为了正确地重载一个成员函数,我们需要一个新的c++关键字叫做virtual。只有在基类中声明成员函数为virtual才能恰当地被继承类重载。该关键字会在成员函数声明的开始,修改后的Base类展示如下:
class Base
{
public:virtual void someFunction();// Remainder omitted for brevity.
};
对于Derived类也一样。如果继续在继承类中进行重载的话也要将成员函数标记为virtual:
class Derived : public Base
{
public:virtual void someOtherFunction();
};
virtual关键字不会在成员函数定义前重复,例如:
void Base::someFunction()
{println("This is Base's version of someFunction().");
}
警告:尝试重载基类中的non-virtual成员函数会隐藏基类定义,只会在继承类的上下文中使用。
2.2、重载成员函数语法
为了重载成员函数,与在基类中的声明一样重新在继承类中再声明一次,但是要加上override关键字并且 移除virtural关键字。例如,如果想要在Derived类中提供一个新的someFunction()的定义,必须首先将它添加到Derived类定义中,如下:
class Derived : public Base
{
public:void someFunction() override; // Overrides Base's someFunction()virtual void someOtherFunction();
};
新的someFunction()的定义给出了与Derived的成员函数一致的其余部分。与virtual关键字一样,不需要在成员函数中重复override关键字:
void Derived::someFunction()
{println("This is Derived's version of someFunction().");
}
如果你想的话,是允许在重载的成员函数前添加virtual关键字的,但是它是冗余的。举例如下:
class Derived : public Base
{
public:virtual void someFunction() override;
};
一旦成员函数或析构函数被标记为virtual,对于所有的继承类它都是virtual的,即使在继承类中移除virtual关键字也改变不了。
2.3、重载成员函数的客户观点
有了前面的修改,其它代码仍然用以前同样的方式调用someFunction()。与以前一样,Base类的对象或者Derived类的对象上也可以调用成员函数。然而,现在someFunction()的行为基于对象的类的不同而不同了。
例如,下面的代码与以前一样,调用了Base的someFunction()版本:
Base myBase;
myBase.someFunction();
// Calls Base's version of someFunction().
代码的输出如下:
This is Base's version of someFunction().
如果代码声明了一个Derived类的对象,另外一个版本自动调用:
Derived myDerived;
myDerived.someFunction();
// Calls Derived's version of someFunction()
这次输出如下:
This is Derived's version of someFunction().
Derived类的对象的另外的所有东东保持不变。其他可能从Base继承的成员函数仍然是Base提供的定义,除非在Derived中显式地进行了重载。
我们前面学到过,指针与引用可以指向一个类的对象或者任何继承的类。对象自身是“知道”实际是哪个类的成员,所以只要它被声明成virtual,就会调用恰当的成员函数。例如,如果你有一个Base引用指向了一个实际是Derived的对象,调用了someFunction()实际上调用了继承类的版本,下面会展示。如果在基类中省略掉virtual关键字重载的特性就不会好好工作了。
Derived myDerived;
Base& ref { myDerived };
ref.someFunction();
// Calls Derived's version of someFunction()
记住即使Base引用或指针知道它是指向的一个Derived实例,你也不能访问没有在Base中定义的Derived类成员。下面的代码编译不成功,因为Base引用没有叫做someOtherFunction()的成员函数:
Derived myDerived;
Base& ref { myDerived };
myDerived.someOtherFunction();// This is fine.
ref.someOtherFunction();// Error
而这个继承类的知识特点对于非指针或非引用的对象是不正确的。歪打正着一次,还想着一直歪下去啊!可以转换或者将Derived赋值给Base,因为Derived是Base。然而,对象在这一点上丢掉了Derived类的所有知识。
Derived myDerived;
Base assignedObject { myDerived };// Assigns a Derived to a Base.
assignedObject.someFunction();// Calls Base's version of someFunction()
记住这个看起来奇怪的行为的一个方法就是想像一下对象在内存中是什么样的。把Base对象想像成一个占用了一定量内存的盒子。Derived对象是一个稍微大一点的盒子,因为它有Base的一切还加了一点儿东西。不管你是有一个Derived或Base引用或者是指向Derived的指针,盒子是不会变的--只是有了一个新的访问的方式。然而,当你将Derived转换成Base时,你会把Derived的所有“独特性”扔掉来满足小的盒子。
注意:当用基类的指针或引用访问时,继承类保持所有的数据成员与成员函数。当转换成基类对象时会丢掉它们的独特性。继承类的数据成员与成员函数的丢失叫做分片。