跳转至

继承方式

继承方式是派生类定义中的核心要素,它决定了基类成员在派生类中的访问属性,进而影响了派生类与外部代码对基类成员的可见性。 理解三种继承方式的区别,是正确设计继承关系的基础。

访问控制回顾:三种访问属性

在理解继承方式之前,需要先回顾类成员的三种访问属性:

类成员的访问属性

访问属性 关键字 类内访问 派生类访问 外部访问
公有 public 取决于继承方式
保护 protected 取决于继承方式
私有 private ✗(始终不可访问)

无论采用何种继承方式,基类的 private 成员在派生类中始终不可直接访问。这是封装原则的体现——基类的实现细节对派生类也是隐藏的。

三种继承方式

C++ 提供了三种继承方式:公有继承(public)私有继承(private)保护继承(protected)。 它们共同决定了基类成员在派生类中的访问状态。不同的继承方式主要影响两个方面:

  1. 派生类成员函数对基类成员的访问权限。
  2. 派生类对象(外部代码) 对从基类继承的成员的访问权限。

继承方式与访问控制

继承方式与访问控制都使用关键字 publicprivateprotected,但其所表达的含义并不相同。 继承方式与访问控制会共同影响派生类成员的访问权限。

1. 公有继承(public inheritance)

公有继承是最常用的继承方式,它在派生类中保持了从基类中继承的成员的访问属性,最能体现 "is-a" 关系。

公有继承的访问控制规则

基类成员属性 在派生类中的访问属性
public public(保持不变)
protected protected(保持不变)
private 不可直接访问
  • 派生类成员函数:可以直接访问基类的 publicprotected 成员。
  • 外部代码(派生类对象):只能访问从基类继承的 public 成员。

公有继承示例

#include <iostream>
using namespace std;

// 基类 Point
class Point {
public:
    void initPoint(float x = 0, float y = 0) {
        this->x = x;
        this->y = y;
    }
    void move(float offX, float offY) {
        x += offX;
        y += offY;
    }
    float getX() const { return x; }
    float getY() const { return y; }
private:
    float x, y;      // 私有成员:派生类不可直接访问
};

// 派生类 Rectangle:公有继承 Point
class Rectangle : public Point {
public:
    void initRectangle(float x, float y, float w, float h) {
        initPoint(x, y);     // ✓ 可以访问基类的 public 成员函数
        this->w = w;
        this->h = h;
        // this->x = x;      // ✗ 错误!不能直接访问基类的 private 成员
    }

    float getW() const { return w; }
    float getH() const { return h; }

private:
    float w, h;      // 新增成员
};

int main() {
    Rectangle rect;

    rect.initRectangle(2, 3, 20, 10);  // ✓ 调用派生类成员函数
    rect.move(3, 2);                   // ✓ 调用继承自基类的 public 成员函数
    cout << rect.getX() << endl;       // ✓ 调用继承自基类的 public 成员函数
    // cout << rect.x << endl;         // ✗ 错误!不能直接访问基类的 private 成员

    return 0;
}

公有继承的语义

公有继承传达的是 "is-a"(是一种) 关系。如果 Rectangle 公有继承自 Point, 意味着 Rectangle 是一种 Point。这种关系对外部代码是可见的:任何需要 Point 的地方 都可以使用 Rectangle 对象(类型兼容原则)。

2. 私有继承(private inheritance)

私有继承是最严格的继承方式,它将基类的所有可访问成员都变为派生类的 private 成员。

私有继承的访问控制规则

基类成员属性 在派生类中的访问属性
public private
protected private
private 不可直接访问
  • 派生类成员函数:可以直接访问基类的 publicprotected 成员(在派生类中它们变为 private)。
  • 外部代码(派生类对象)不能访问任何从基类继承的成员(所有继承成员在派生类中都成了 private)。

私有继承示例

#include <iostream>
using namespace std;

class Point {
public:
    void initPoint(float x = 0, float y = 0) {
        this->x = x;
        this->y = y;
    }
    void move(float offX, float offY) {
        x += offX;
        y += offY;
    }
    float getX() const { return x; }
    float getY() const { return y; }
private:
    float x, y;
};

// Rectangle:私有继承 Point
class Rectangle : private Point {
public:
    void initRectangle(float x, float y, float w, float h) {
        initPoint(x, y);     // ✓ 可以访问(但在派生类中为 private)
        this->w = w;
        this->h = h;
    }

    // 派生类为从 Point 继承的成员提供公共访问接口,因为从 Point 继承的成员在派生类中为 private
    void move(float offX, float offY) {
        Point::move(offX, offY);   // 通过基类名限定调用
    }

    float getX() const { return Point::getX(); }
    float getY() const { return Point::getY(); }

    float getW() const { return w; }
    float getH() const { return h; }

private:
    float w, h;
};

int main() {
    Rectangle rect;
    rect.initRectangle(2, 3, 20, 10);

    rect.move(3, 2);             // ✓ move() 在派生类中为 private
    rect.Rectangle::move(3, 2);  // ✓ 也可通过派生类名限定(但很少这样做)

    // 必须通过派生类提供的公有接口访问
    cout << rect.getX() << endl;   // ✓

    // cout << rect.Point::getX(); // 因为 Point::getX 在派生类中为 private,外部不能访问

    return 0;
}

私有继承的语义

私有继承表达的是 "is-implemented-in-terms-of"(用...来实现) 关系。它主要用于代码实现层面的复用,而不是接口层面的继承。外部代码不应知道派生类与基类之间的关系。

3. 保护继承(protected inheritance)

保护继承介于公有继承和私有继承之间,它将基类的可访问成员变为派生类的 protected 成员。

保护继承的访问控制规则

基类成员属性 在派生类中的访问属性
public protected
protected protected(保持不变)
private 不可直接访问
  • 派生类成员函数:可以直接访问基类的 publicprotected 成员(在派生类中它们为 protected)。
  • 外部代码(派生类对象)不能访问任何从基类继承的成员。

保护继承示例

class Base {
public:
    int pub = 1;
protected:
    int prot = 2;
private:
    int priv = 3;     // 派生类不可访问
};

// 保护继承 Base
class Derived : protected Base {
public:
    void show() {
        cout << pub << endl;   // ✓ pub 在派生类中为 protected
        cout << prot << endl;  // ✓ prot 在派生类中仍为 protected
        // cout << priv;       // ✗ 不能访问基类的 private 成员
    }
};

// 进一步的派生类
class GrandDerived : public Derived {
public:
    void showMore() {
        cout << pub << endl;   // ✓ 仍然可以访问(GrandDerived 中仍为 protected)
        cout << prot << endl;  // ✓ 仍然可以访问
    }
};

int main() {
    Derived d;
    // cout << d.pub;          // ✗ 外部不能访问 protected 成员

    GrandDerived gd;
    // cout << gd.pub;         // ✗ 外部也不能访问

    return 0;
}

保护成员的特点

protected 成员在不同上下文中有不同的表现:

上下文 对 protected 成员的访问权限
类本身(成员函数) ✓ 可访问
派生类(成员函数) ✓ 可访问
外部代码(通过对象) ✗ 不可访问

这种设计既实现了数据隐藏,又方便了派生类的扩展。

三种继承方式的对比

对比总览

基类成员 继承方式 在派生类中的访问属性 派生类成员函数访问 派生类对象(外部)访问
public 公有继承 public
protected 公有继承 protected
private 公有继承 不可访问
public 私有继承 private
protected 私有继承 private
private 私有继承 不可访问
public 保护继承 protected
protected 保护继承 protected
private 保护继承 不可访问

继承方式选择指南

如何选择合适的继承方式

继承方式 适用场景 语义
公有继承 默认选择,绝大数情况使用 "is-a"(是一种)
私有继承 实现层面的代码复用,不想暴露基类接口 "is-implemented-in-terms-of"(用...来实现)
保护继承 希望只在继承体系内部传递基类功能 介于公有和私有之间

设计原则

  • 优先使用公有继承:除非有明确理由,否则应使用公有继承。它最能表达 "is-a" 关系,也是最易于理解的继承方式。
  • 私有继承优先考虑组合:在很多情况下,私有继承可以用"包含一个对象"(组合)来替代,而组合更清晰、更灵活。
  • 保护继承较少使用:保护继承主要用于构建"继承树内部的共享"。

访问基类被隐藏的成员

当派生类中新增的成员与基类成员同名时,基类的同名成员会被"隐藏"。在派生类中可以通过基类名限定来访问被隐藏的成员。

访问被隐藏的基类成员

#include <iostream>
using namespace std;

class Base {
public:
    int value = 10;
    void show() {
        cout << "Base::show()" << endl;
    }
};

class Derived : public Base {
public:
    int value = 20;      // 隐藏了基类的 value
    void show() {        // 隐藏了基类的 show()
        cout << "Derived::show()" << endl;
    }

    void accessBase() {
        cout << "Derived value: " << value << endl;            // 20
        cout << "Base value: " << Base::value << endl;         // 10(通过基类名限定)
        Base::show();     // 调用基类的 show()
        show();           // 调用派生类的 show()
    }
};

int main() {
    Derived d;
    d.show();               // Derived::show()
    d.Base::show();         // Base::show()(通过基类名限定访问)

    cout << d.value << endl;        // 20
    cout << d.Base::value << endl;  // 10

    return 0;
}

同名隐藏与函数重载的区别

  • 同名隐藏:派生类中的同名成员会隐藏基类中所有同名的成员(即使参数列表不同)。
  • 函数重载:在同一个作用域内,同名但参数列表不同的函数构成重载。

因此,派生类中的 fun(int) 会隐藏基类中的 fun(double),即使在基类中它们是不同的重载版本。

综合示例:三种继承方式的完整对比

继承方式综合示例

#include <iostream>
using namespace std;

// 基类
class Base {
public:
    int pub = 1;
    void pubFunc() {
        cout << "Base::pubFunc()" << endl;
    }

protected:
    int prot = 2;
    void protFunc() {
        cout << "Base::protFunc()" << endl;
    }

private:
    int priv = 3;
    void privFunc() {
        cout << "Base::privFunc()" << endl;
    }
};

// 公有继承
class PublicDerived : public Base {
public:
    void test() {
        cout << "PublicDerived test:" << endl;
        cout << pub << endl;      // ✓ 可以访问 public 成员
        cout << prot << endl;     // ✓ 可以访问 protected 成员
        // cout << priv;          // ✗ 不能访问 private 成员
        pubFunc();                // ✓
        protFunc();               // ✓
        // privFunc();            // ✗
    }
};

// 私有继承
class PrivateDerived : private Base {
public:
    void test() {
        cout << "PrivateDerived test:" << endl;
        cout << pub << endl;      // ✓ 可以访问(但在派生类中为 private)
        cout << prot << endl;     // ✓ 可以访问(但在派生类中为 private)
        pubFunc();                // ✓
        protFunc();               // ✓
    }

    // 需要手动暴露接口
    void accessPub() { pubFunc(); }
};

// 保护继承
class ProtectedDerived : protected Base {
public:
    void test() {
        cout << "ProtectedDerived test:" << endl;
        cout << pub << endl;      // ✓ 可以访问(在派生类中为 protected)
        cout << prot << endl;     // ✓ 可以访问(仍为 protected)
        pubFunc();                // ✓
        protFunc();               // ✓
    }
};

int main() {
    // 公有继承:外部可以访问 public 成员
    PublicDerived pd;
    pd.pubFunc();         // ✓
    // pd.protFunc();     // ✗ protected 不可访问
    // pd.privFunc();     // ✗ private 不可访问

    // 私有继承:外部不能访问任何基类成员
    PrivateDerived prd;
    // prd.pubFunc();     // ✗ 不可访问
    prd.accessPub();      // ✓ 通过派生类提供的接口

    // 保护继承:外部不能访问任何基类成员
    ProtectedDerived pod;
    // pod.pubFunc();     // ✗ 不可访问

    return 0;
}

运行结果分析

PublicDerived test:
1
2
Base::pubFunc()
Base::protFunc()
PrivateDerived test:
1
2
Base::pubFunc()
Base::protFunc()
ProtectedDerived test:
1
2
Base::pubFunc()
Base::protFunc()

小结

  1. 派生类定义语法class 派生类名 : 继承方式 基类名 { ... };

  2. 三种继承方式对比

    继承方式 public 成员 protected 成员 private 成员
    公有继承 public protected 不可访问
    私有继承 private private 不可访问
    保护继承 protected protected 不可访问
  3. 选择指南

    1. 公有继承:表达 "is-a" 关系,最常用。
    2. 私有继承:表达 "用...来实现",可用组合替代。
    3. 保护继承:仅用于继承树内部共享,较少使用。
  4. 同名隐藏:派生类中新成员会隐藏基类的同名成员,可通过 基类名::成员名 访问被隐藏的成员。

继承方式是派生类设计的"权限开关",选择合适的继承方式是正确表达类型关系的关键。公有继承是最自然、最常用的继承方式,它让 "is-a" 关系对外可见,为多态打下基础。