目录

一、为什么需要继承?

二、三种继承方式的语法

基类的准备

不同继承方式的效果

三、公有继承(public)——真正的“is-a”

语法与效果

公有继承的含义

四、保护继承(protected)——“is-a”但对外隐藏

语法与效果

保护继承的含义

五、私有继承(private)——“is-implemented-in-terms-of”

语法与效果

私有继承的含义

六、一张表总结三种继承

七、完整例子:不同继承方式的效果对比

八、三个常见错误

1. 搞混继承方式和成员访问级别

2. 忘记写继承方式(默认为private)

3. 滥用私有继承

九、这一篇的收获


一、为什么需要继承?

假设你要写三个类:Student(学生)、Teacher(老师)、Staff(行政人员)。

他们都有人类共有的属性:姓名、年龄、身份证号。如果没有继承,你得在三个类里各写一遍:

cpp

class Student {
    string name;
    int age;
    string id;
    string studentId;   // 学号
    void study();
};

class Teacher {
    string name;
    int age;
    string id;
    string teacherId;   // 工号
    void teach();
};

class Staff {
    string name;
    int age;
    string id;
    string staffId;
    void work();
};

三个类有大量重复代码。更糟糕的是:如果要加一个“性别”字段,三个类都要改。

用继承,把这些公共部分提取到一个基类(Person)中,然后让StudentTeacherStaff继承它:

cpp

class Person {
public:
    string name;
    int age;
    string id;
};

class Student : public Person {  // 公有继承
public:
    string studentId;
    void study();
};

class Teacher : public Person {
public:
    string teacherId;
    void teach();
};

现在Student自动拥有了nameageid,不需要重复声明。这就是继承最朴素的价值——复用代码


二、三种继承方式的语法

C++的继承语法:

cpp

class Derived : access-specifier Base {
    // 派生类成员
};

access-specifier可以是:

  • public:公有继承

  • protected:保护继承

  • private:私有继承

基类的准备

为了演示可见性变化,定义一个包含三种成员的基类:

cpp

class Base {
public:
    int pub;
protected:
    int prot;
private:
    int priv;
};

不同继承方式的效果

基类成员类型 public继承 protected继承 private继承
public成员 在派生类中是 public 在派生类中是 protected 在派生类中是 private
protected成员 在派生类中是 protected 在派生类中是 protected 在派生类中是 private
private成员 派生类不可访问 派生类不可访问 派生类不可访问
外部通过派生类访问public成员 ✅ 可以 ❌ 不可以 ❌ 不可以

核心规律:继承方式不改变基类private成员的“不可访问性”,只改变public和protected成员在派生类中的访问级别。且继承方式只会收紧访问权限,不会放宽。


三、公有继承(public)——真正的“is-a”

语法与效果

cpp

class Derived : public Base {
    void func() {
        pub = 10;    // ✅ 可以访问(仍然是public)
        prot = 20;   // ✅ 可以访问(仍然是protected)
        // priv = 30; // ❌ 不可访问(基类private)
    }
};

int main() {
    Derived d;
    d.pub = 100;     // ✅ 外部可以访问(因为是public继承)
    // d.prot = 200; // ❌ 外部不能访问(prot现在还是protected)
}

公有继承的含义

公有继承表达的是“is-a”关系(是一种):

  • Student 是一种 Person

  • Dog 是一种 Animal

  • Circle 是一种 Shape

这意味着:任何可以使用Base的地方,都可以使用Derived

cpp

void printPerson(Person& p) {
    cout << p.name << endl;
}

Student s;
printPerson(s);  // Student可以当作Person来用

这是面向对象多态的基础(后面会详细讲)。95%以上的继承场景都用公有继承


四、保护继承(protected)——“is-a”但对外隐藏

语法与效果

cpp

class Derived : protected Base {
    void func() {
        pub = 10;    // ✅ 在派生类内部可以访问(变成protected)
        prot = 20;   // ✅ 可以访问(还是protected)
        // priv = 30; // ❌ 不可访问
    }
};

int main() {
    Derived d;
    // d.pub = 100;  // ❌ 外部不能访问(pub变成了protected)
}

保护继承的含义

保护继承仍然表达“is-a”关系,但对外部代码隐藏了基类的public接口。只有在派生类及其子类中才能访问。

使用场景极其罕见。一般只在以下情况出现:

  • 基类的public接口过于宽泛,不想暴露给外部

  • 作为实现继承的一种手段(而不是接口继承)

现实项目中,保护继承几乎不用。


五、私有继承(private)——“is-implemented-in-terms-of”

语法与效果

cpp

class Derived : private Base {
    void func() {
        pub = 10;    // ✅ 在派生类内部可以访问(变成private)
        prot = 20;   // ✅ 可以访问(变成private)
        // priv = 30; // ❌ 不可访问
    }
};

class GrandChild : public Derived {
    void func2() {
        // pub = 10;  // ❌ 不可访问!在Derived中pub已变成private
        // prot = 20; // ❌ 也不可访问
    }
};

int main() {
    Derived d;
    // d.pub = 100;  // ❌ 外部不能访问
}

私有继承的含义

私有继承不表达“is-a”关系。它表达的是一种“根据某物实现”(is-implemented-in-terms-of)的关系。

比如:你要实现一个Queue(队列),可以用list来实现。但Queue不是一种list,不应该继承list的public接口。这时可以用私有继承。

cpp

class Queue : private list<int> {
public:
    void push(int x) { push_back(x); }
    void pop() { pop_front(); }
    int front() { return list<int>::front(); }
    bool empty() { return list<int>::empty(); }
};

外部只能通过Queue的public接口操作,不能直接使用list的方法。

现实中的选择:私有继承的功能几乎都可以用组合(has-a)替代。上面的队列例子,用组合写会更清晰:

cpp

class Queue {
private:
    list<int> data;   // 组合,不是继承
public:
    void push(int x) { data.push_back(x); }
    // ...
};

除非需要访问基类的protected成员,或需要重写虚函数,否则优先用组合。


六、一张表总结三种继承

继承方式 基类public在派生类中变为 基类protected在派生类中变为 外部能否通过派生类访问基类public 表达的关系
public public protected ✅ 能 is-a(是一种)
protected protected protected ❌ 不能 受限的is-a
private private private ❌ 不能 is-implemented-in-terms-of

记忆口诀

  • public:最小限制,基类public出去还是public

  • protected:收紧一层,public变protected

  • private:全部收紧为private


七、完整例子:不同继承方式的效果对比

cpp

#include <iostream>
using namespace std;

class Base {
public:
    int pub = 1;
protected:
    int prot = 2;
private:
    int priv = 3;
};

// 公有继承
class PubDerived : public Base {
public:
    void show() {
        cout << "PubDerived内部: pub=" << pub 
             << ", prot=" << prot << endl;
        // cout << priv; // 错误:不能访问基类private
    }
};

// 保护继承
class ProtDerived : protected Base {
public:
    void show() {
        cout << "ProtDerived内部: pub=" << pub 
             << ", prot=" << prot << endl;
    }
};

// 私有继承
class PrivDerived : private Base {
public:
    void show() {
        cout << "PrivDerived内部: pub=" << pub 
             << ", prot=" << prot << endl;
    }
};

// 二级派生类
class GrandChild : public PubDerived {
public:
    void showGrand() {
        cout << "GrandChild内部: pub=" << pub 
             << ", prot=" << prot << endl;  // ✅ 仍然可以访问
    }
};

class GrandChildProt : public ProtDerived {
public:
    void showGrand() {
        // cout << pub;  // ❌ 错误!pub在ProtDerived中是protected
        // 但通过ProtDerived访问Base的pub?实际上不能直接访问
        // 需要调用ProtDerived的接口
    }
};

int main() {
    cout << "=== 外部访问测试 ===" << endl;
    
    PubDerived pubObj;
    pubObj.pub = 100;   // ✅ 可以
    // pubObj.prot = 200; // ❌ 不能
    cout << "pubObj.pub = " << pubObj.pub << endl;
    
    ProtDerived protObj;
    // protObj.pub = 100;   // ❌ 不能,pub变成了protected
    
    PrivDerived privObj;
    // privObj.pub = 100;    // ❌ 不能,pub变成了private
    
    cout << "\n=== 内部访问测试 ===" << endl;
    pubObj.show();
    protObj.show();
    privObj.show();
    
    cout << "\n=== 二级派生类测试 ===" << endl;
    GrandChild gc;
    gc.showGrand();
    
    return 0;
}

输出:

text

=== 外部访问测试 ===
pubObj.pub = 100

=== 内部访问测试 ===
PubDerived内部: pub=100, prot=2
ProtDerived内部: pub=1, prot=2
PrivDerived内部: pub=1, prot=2

=== 二级派生类测试 ===
GrandChild内部: pub=100, prot=2

八、三个常见错误

1. 搞混继承方式和成员访问级别

cpp

class Base {
    int x;  // 默认private
};

class Derived : public Base {
    void func() {
        // x = 10;  // ❌ 错误!基类的private成员在任何情况下都不可访问
    }
};

基类的private成员永远不可访问,无论用什么方式继承。

2. 忘记写继承方式(默认为private)

cpp

class Derived : Base {   // 忘了写public
    // 这是private继承!
};

C++中class的继承默认是privatestruct的继承默认是public。这是一个常见的坑。

cpp

class A {};
struct B : A {};    // public继承
class C : A {};     // private继承(!)

3. 滥用私有继承

需要实现“根据某物实现”时,先考虑组合。私有继承只在以下少数情况优于组合:

  • 需要访问基类的protected成员

  • 需要重写基类的虚函数

  • 对空间有极致要求(组合会增加一个对象的大小,私有继承可能利用空基类优化)


九、这一篇的收获

你现在应该能:

  • 区分三种继承方式的语法和可见性变化

  • 理解公有继承表达“is-a”关系,是最常用的继承方式

  • 知道保护继承和私有继承很少用,私有继承可用组合替代

  • 记住基类的private成员永远不可直接访问

💡 小作业:设计三个类:Shape(形状)、Circle(圆形)、Rectangle(矩形)。Shapecolor属性和getArea()虚函数。用公有继承实现CircleRectangle,并写一个函数打印任意形状的面积。


下一篇预告:第12篇《继承(二):构造与析构顺序,继承中的构造函数》——派生类对象构造时,先构造基类还是先构造派生类?派生类的构造函数如何向基类构造函数传参?下篇讲清楚继承中的生命周期管理。

更多推荐