在这里插入图片描述


引言

继承(Inheritance)是C++面向对象编程的三大核心特性之一(另外两个是封装和多态)。它允许我们创建一个新类(派生类),从一个已有的类(基类)中获取其属性和方法,从而实现代码复用和层次化设计。

然而,在继承过程中,基类成员在派生类中的可访问性并非一成不变,而是由两个关键因素共同决定:

  1. 基类中成员的访问说明符 (public, protected, private)。
  2. 派生类继承基类时所采用的继承方式 (public, protected, private)。

本文将用详细的说明和例子帮助你深入理解这几种继承方式下不同权限成员在派生类中权限的差异,如果觉得本文对你有帮助,点个赞和关注吧,谢谢!!!

一、 基础回顾:类的访问权限

在深入继承之前,我们必须先牢记一个类内部三种访问说明符的含义:

  • public (公有):成员可以在任何地方被访问,包括类的内部、派生类以及类的外部(对象实例)。
  • protected (保护):成员只能在类的内部及其派生类中被访问。类的外部无法直接访问。
  • private (私有):成员只能在类的内部被访问。即使是派生类也无法直接访问。
class Base {
public:
    int m_public;    // 可以在任何地方访问
protected:
    int m_protected; // 只能在本类及派生类中访问
private:
    int m_private;   // 只能在本类中访问
};

二、 不同继承方式下的成员权限变化

请牢记这条核心法则,它统御着继承中所有的权限变化:

派生类中成员的最终访问权限,取【其在基类中的访问权限】和【继承方式】中,权限更严格(更小)的那一个。

权限的严格程度(范围从小到大)为:private > protected > public

为了更直观地理解,我们可以用一个表格来总结这个法则:

基类成员权限 public 继承 protected 继承 private 继承
public public protected private
protected protected protected private
private 不可访问 不可访问 不可访问

特别注意
基类的 private 成员虽然被派生类继承(占用了派生类对象的内存空间),但无论采用何种继承方式,它们在派生类中都是永远不可直接访问的。这是 private 权限的根本性质。

三、 详解三种继承方式

接下来,我们将通过具体的代码示例来逐一解析每种继承方式。

首先,我们定义一个统一的基类 Base

#include <iostream>

class Base {
public:
    void public_func() { std::cout << "Base::public_func()" << std::endl; }
protected:
    void protected_func() { std::cout << "Base::protected_func()" << std::endl; }
private:
    void private_func() { std::cout << "Base::private_func()" << std::endl; }
};

3.1 public 继承

public 继承表达的是一种 “is-a” (是一个) 的关系,它完全接受基类的公有接口。这是最纯粹、最常见的继承形式。

  • 权限变化规则
    • public -> public
    • protected -> protected
    • private -> 不可访问
class PublicDerived : public Base {
public:
    void access_base_members() {
        public_func();    // 正确: 基类的 public 成员在派生类中是 public
        protected_func(); // 正确: 基类的 protected 成员在派生类中是 protected
        // private_func();   // 错误: 基类的 private 成员在派生类中不可访问
    }
};

int main() {
    PublicDerived d;
    d.public_func();    // 正确: 在外部可以访问从基类继承来的 public 成员
    // d.protected_func(); // 错误: protected 成员在外部不可访问
    // d.private_func();   // 错误: private 成员在外部不可访问
    d.access_base_members(); // 通过派生类的公有方法访问
    return 0;
}

小结public 继承保持了基类成员的原始访问级别(private成员除外),是实现多态的必要条件。

3.2 protected 继承

protected 继承是一种不常见的继承方式。它意味着基类的公有和保护成员,在派生类中都变成了保护成员。

  • 权限变化规则
    • public -> protected
    • protected -> protected
    • private -> 不可访问
class ProtectedDerived : protected Base {
public:
    void access_base_members() {
        public_func();    // 正确: 基类的 public 成员在派生类中是 protected
        protected_func(); // 正确: 基类的 protected 成员在派生类中是 protected
        // private_func();   // 错误: 基类的 private 成员在派生类中不可访问
    }
};

int main() {
    ProtectedDerived d;
    // d.public_func();    // 错误: 基类的 public 成员已变为 protected,在外部不可访问
    // d.protected_func(); // 错误: protected 成员在外部不可访问
    d.access_base_members(); // 只能通过派生类的公有方法间接访问
    return 0;
}

小结protected 继承关闭了基类对外的公有接口,但允许其后续的派生类继续访问这些接口。它相当于告诉世界:“我是基于 Base 实现的,但我的使用者不应该直接调用 Base 的功能,只有我的子类可以”。

3.3 private 继承

private 继承表达的是一种 “is-implemented-in-terms-of” (依据…来实现) 的关系,它通常被视为对象组合(Composition)的一种替代方案。

  • 权限变化规则
    • public -> private
    • protected -> private
    • private -> 不可访问
class PrivateDerived : private Base {
public:
    void access_base_members() {
        public_func();    // 正确: 基类的 public 成员在派生类中是 private
        protected_func(); // 正确: 基类的 protected 成员在派生类中是 private
        // private_func();   // 错误: 基类的 private 成员在派生类中不可访问
    }
};

class GrandChild : public PrivateDerived {
public:
    void access_grand_base_members() {
        // public_func();    // 错误: 在 PrivateDerived 中已变为 private
        // protected_func(); // 错误: 在 PrivateDerived 中已变为 private
    }
};

int main() {
    PrivateDerived d;
    // d.public_func();    // 错误: 基类的 public 成员已变为 private,在外部不可访问
    // d.protected_func(); // 错误: 基类的 protected 成员已变为 private,在外部不可访问
    d.access_base_members(); // 只能通过派生类的公有方法间接访问
    return 0;
}

小结private 继承完全切断了基类接口向外的任何暴露,包括对更下一层的派生类。派生类纯粹是“借用”了基类的实现,而不继承其接口。

四、 何时使用何种继承

作为一名经验丰富的开发者,我给出以下建议:

  1. 优先使用 public 继承
    当你能确定派生类与基类之间是明确的 “is-a” 关系时(例如,Dog is an Animal),public 继承是你的不二之选。这是C++ OOP设计中最自然、最强大的工具,也是实现多态的基础。在超过95%的场景下,你需要的都是 public 继承。

  2. 考虑用组合替代 private 继承
    private 继承的 “is-implemented-in-terms-of” 语义,通常可以通过对象组合(在一个类中包含另一个类的对象作为成员)更清晰地实现。

    // 使用组合
    class MyClass {
    private:
        Base m_base_impl; // 将Base作为成员,实现复用
    public:
        void do_something() {
            m_base_impl.public_func(); // 通过成员对象调用
        }
    };
    

    组合的耦合度更低,逻辑更清晰,因此在现代C++设计中更受推崇。只在少数特定情况(如需要访问基类的protected成员或需要重写虚函数)下,private 继承才可能是更好的选择。

  3. 谨慎使用 protected 继承
    protected 继承的使用场景非常有限,它是一种复杂的设计工具。当你需要构建一个继承层次结构,并希望中间层的基类接口只对更深层次的派生类可见,而对外部世界隐藏时,可以考虑它。但在绝大多数情况下,你可能并不需要它。

总结

掌握C++继承中的访问权限控制,关键在于理解基类成员权限继承方式共同作用的“取最严”法则。

  • public 继承是接口继承,保持接口不变,是 “is-a” 关系的体现。
  • private 继承是实现继承,完全隐藏基类接口,是 “is-implemented-in-terms-of” 关系的体现。
  • protected 继承介于两者之间,对外部隐藏接口,但对子类开放。

深刻理解这些差异,将帮助你构建出层次清晰、封装良好、易于维护的C++应用程序。希望这篇详尽的解析能为你扫清障碍,让你在C++的道路上走得更远。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐