Chapter 14: Inheritance

继续允许一个类获取其它类的成员。在下面的示例中,Square继续自Rectangle。这是通过在类名后使用冒号后跟public关键字以及要继续的类名来指定的。

class Rectangle
{
public:
    int x, y;
    int getArea() { return x * y; }
};
class Square : public Rectangle {};

这里Rectangle变为Square的基类,而Square变为Rectangle的派生类。除了其自己的成员,Square获得了对Rectangle中成员的所有访问,除了其构造器与析构器。

向上转换

一个对象可以向上转换为其基类,因为它包含其基类所包含的所有内容。向上转换是通过将对象赋值给基类类型的指针或引用来实现的。在下面的示例中,Square对象被向上转换为Rectangle。当使用Rectangle的接口时,Square对象将会被看作一个Rectangle,从而仅Rectangle的成员可以被访问。

Square s;
Rectangle& r = s; // reference upcast
Rectangle* p = &s; // pointer upcast

派生类可以用于需要基类的任意位置。例如,Square对象可以作为参数传递给需要Rectangle对象的函数。然后派生对象将会被隐式地向上转换为其基类类型。

void setXY(Rectangle& r)
{
    r.x = 2;
    r.y = 3;
}
int main()
{
    Square s;
    setXY(s);
}

向下转换

指向Square对象的Rectangle引用或指针可以向下转换为Square对象。向下转换需要显式执行,因为将一个实际的Rectangle向下转换为Square是不允许的,并且可能会在运行时破坏程序。

Square& a = static_cast<Square&>(r); // reference downcast
Square* b = static_cast<Square*>(p); // pointer downcast

构造器继承

为确保基类中的域被正确地初始化,当创建派生类的对象时,基类的无参数构造器会被自动调用。

#include <iostream>
using namespace std;
class B1
{
public:
    int x;
    B1() : x(5) {}
};
class D1 : public B1 {};
int main()
{
    D1 d; // calls parameterless constructors of D1 and B1
    cout << d.x; // "5"
}

如果在基类中没有默认的构造器,派生类必须调用正确的基类构造器。通过将基类的构造器放置在派生类的构造器的初始化器列表中,由派生类构造器可以显式调用基类构造器。这允许将参数传递给基类构造器。

class B2
{
public:
    int x;
    B2(int a) : x(a) {}
};

class D2 : public B2
{
public:
    D2(int i) : B2(i) {} // call base constructor
};

此种情况的另一种解决方案是继承构造器。在C++11中,这可以使用using语句实现。

class D2 : public B2
{
public:
    using B2::B2; // inherit all constructors from B2
    int y { 0 };
};
int main()
{
    D2 d(3); // call inherited B2 constructor
    cout << d.x; // "3"
}

注意,基类构造器不能初始化派生类中的域。所以,派生类中的声明的域应初始化其自身。这里是使用统一初始化来实现的。

多继承

C++允许由多个基类继承。这被称为多继承。此时基类是通过逗号分隔的列表来指定的。

class Person {};
class Employee {};
class Teacher: public Person, public Employee {};

多继承通常并不使用,因为通过单继承可以更好地描述大部分真实世界中的关系。同时它也会极大地增加代码的复杂性。