10.1使用继承构建类
文章目录
1.扩展类
当使用C++编写类定义时,可以告诉编译器,该类继承、派生或扩展了一个已有的类。通过这种方式,该类将自动包含原始类的数据成员和方法;原始类称为父类(parent class)、基类或超类(superclass)。扩展已有类可以使该类(现在称为派生类或子类)只描述与父类不同的那部分内容。
1.1客户对继承的看法
指向某个对象的指针或引用可以指向声明类的对象,也可以指向其任意派生类的对象。
1.2从派生类的角度分析继承
如果类将数据成员或方法声明为 protected,派生类就可以访问它们;如果声明为private,派生类就不能访问。
private 访问说明符可以控制派生类与基类的交互方式。建议将所有数据成员都默认声明为private,如果希望任何代码都可以访问这些数据成员,可以提供 public 的getter 和 setter。如果仅希望派生类访问它们,就可以提供 protected 的getter和 setter。把数据成员默认设置为 private 的原因是,这会提供最高级别的封装,这意味着可以改变数据的表示方式,而保持 public 或 protected 的接口不变。另外,不直接访问数据成员,也可以在 public 或 protected的setter中方便地添加对输入数据的检查。方法也应默认设置为private,只有需要公开的方法才设置为 public,只有派生类需要访问的方法才设置为protected。
1.3禁用继承
C++允许将类标记为 final
,这意味着继承这个类会导致编译错误。将类标记为 final
的方法是直接在类名的后面使用 final
关键字。
2.重写方法
从某个类继承的主要原因是为了添加或替换功能。这个方法在派生类中的行为与在基类中的相同。在许多情况下,可能需要替换或重写某个方法来修改类的行为。
2.1关键字virtual
只有基类中声明为virtual
的方法才能被派生类正确地重写。关键字 virtual
出现在方法声明的开头.
派生类也是如此。如果想进一步在派生类中重写它们,相应的方法也应该标记为 virtual。
2.2重写方法的语法
为了重写某个方法,需要在派生类的定义中重新声明这个方法,就像在基类中声明的那样,但是需要添加关键字override
,并从派生类中删除关键字 virtual
。
一旦将方法或析构函数标记为 virtual,它们在所有派生类中就一直是 virtual,即使在派生类中删除了virtual关键字,也同样如此。
2.3客户对重写方法的看法
指针或引用可指向某个类或其派生类的对象。对象本身"知道"自己所属的类,因此只要这个方法声明为 virtual,就会自动调用对应的方法。
即使基类的引用或指针知道这实际上是一个派生类,也无法访问没有在基类中定义的,定义在派生类中的方法或成员。
2.4override关键字
overide 关键字的使用是可选的,但强烈推荐使用。如果没有 override 关键字,可能会偶然创建一个新的虚方法,而不是重写基类的方法。
2.5virtual的真相
如果方法不是virtual,也可以试着重写这个方法,但是这样做会导致微妙的错误。
隐藏而不是重写
试图重写非虚方法将会"隐藏"基类定义的方法,并且重写的这个方法只能在派生类环境中使用。
如何实现virtual
为理解如何避免隐藏方法,需要了解virtual关键字的真正作用。C++在编译类时,会创建一个包含类中所有方法的二进制对象。**在非虚情况下,将控制交给正确方法的代码是硬编码,此时会根据编译期的类型调用对应的方法。**这称为静态绑定(static binding),也称为早绑定(early binding)。
如果方法声明为virtual,会使用名为虚表(vtable)的特定内存区域来调用正确的实现。**每个具有一个或多个虚方法的类都有一张虚表,这种类的每个对象都包含指向虚表的指针,这个虚表包含指向虚方法实现的指针。通过这种方法,当使用某个对象调用方法时,指针也进入虚表,然后根据实际的对象类型在运行期执行正确版本的方法。**这称为动态绑定(dynamic binding)或晚绑定(late binding)。
class Base
{
public:
virtual void func1();
virtual void func2();
void nonVirtualFunc();
};
class Derived : public Base
{
public:
void func2() overrid;
void nonVirtualFunc();
};
对于这个示例, 考虑下面的两个实例:
Base myBase;
Derived myDerived;
使用virtual的理由
略
虚构函数的需求
析构函数应该都声明为 virtual。原因是,如果析构函数未声明为 virtual,很容易在销毁对象时不释放内存。唯一允许不把析构函数声明为 virtual的例外情况是,类被标记为 final。
2.6禁用重写
除了将实体类标记为 final,C++还允许将方法标记为 final。这意味着无法在派生类中重写这个方法。