继承方式不决定基类成员能否使用,而是决定其在派生类中的访问级别。公有继承保持接口与类型关系(is-a),支持多态;保护继承隐藏外部接口但允许家族内访问;私有继承实现复用且断绝上下级关系(has-a)。基类private成员始终不可访问。组合优于私有继承,除非需重写虚函数或访问protected成员。显式声明继承方式,避免默认规则陷阱。
一句话定调:继承方式不决定派生类"能不能用"基类成员,而是决定"基类成员在派生类里被降级到什么访问级别"。
基类成员本身有访问级别:public / protected / private
继承方式也有级别:public / protected / private
最终在派生类中呈现的访问性,取两者中更严格的那个:
public > protected > private
一、核心矩阵
class Base {
public:
int pub;
protected:
int prot;
private:
int priv;
};
| 基类成员 \ 继承方式 | public 继承 | protected 继承 | private 继承 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可访问 | 不可访问 | 不可访问 |
基类
private成员在任何继承方式下,派生类都不能直接访问------它存在但编译器不让碰。
二、公有继承:is-a,接口继承
class Derived : public Base {
void internal() {
pub = 1; // public → public,类内可访问
prot = 1; // protected → protected,派生类内部可访问
// priv = 1; // 编译错误:基类 private 成员,派生类永远不能直接碰
}
};
类外:
Derived d;
d.pub = 1; // public 成员,类外可访问
// d.prot = 1; // 编译错误:protected 成员,类外禁止访问(protected 语义本身限制,与继承方式无关)
语义:Derived is a Base
Base* p = new Derived; // 只有 public 继承支持向上转型
设计含义:基类定义接口契约,派生类提供特化实现,这是多态的基础。
三、保护继承:对外关门,家族内部留接口
class Derived : protected Base {
void internal() {
pub = 1; // 降级为 protected,派生类内部可访问
prot = 1; // protected → protected
// priv = 1; // 编译错误:基类 private,不可访问
}
};
类外:
Derived d;
// d.pub = 1; // 编译错误:已降级为 protected,类外不可访问
子孙类仍然可用:
class MoreDerived : public Derived {
void internal() {
pub = 1; // 在 Derived 中是 protected,子孙类可访问
prot = 1; // protected,子孙类可访问
}
};
向上转型:
// Base* p = new Derived; // 编译错误:protected 继承,类外不允许向上转型
语义:复用 Base 能力,但不承认自己是一个 Base。
适用场景极少,多见于库或框架的中间层基类。
四、私有继承:实现复用,断绝对外和对下的接口暴露
class Derived : private Base {
void internal() {
pub = 1; // 降级为 private,派生类内部可访问
prot = 1; // 降级为 private
// priv = 1; // 编译错误:基类 private,不可访问
}
};
类外:
Derived d;
// d.pub = 1; // 编译错误:已降级为 private,类外不可访问
子孙类也拿不到:
class MoreDerived : public Derived {
void internal() {
// pub = 1; // 编译错误:在 Derived 中已是 private,子孙类不可访问
}
};
向上转型:
// Base* p = new Derived; // 编译错误:private 继承,类外不允许向上转型
语义:Derived is implemented in terms of Base------不是 is-a,而是"用 Base 实现 Derived"。
五、三种继承方式对照
| 继承方式 | 建模关系 | 类外可访问基类 public | 子孙类可访问基类接口 | 向上转型 Base* |
|---|---|---|---|---|
| public | is-a | 可以 | 可以 | 允许 |
| protected | 家族实现复用 | 不可以(降级为 protected) | 可以 | 不允许 |
| private | 实现复用 | 不可以(降级为 private) | 不可以 | 不允许 |
经验法则:
90% 情况用 public 继承。需要 has-a 或实现复用时,先考虑组合,再考虑 private 继承。protected 继承基本只在框架内部出现。
六、组合 vs 私有继承
私有继承能做的事,组合通常也能做且耦合更低:
class Stopwatch {
Timer timer; // 组合:不引入继承关系,可持有多个,不影响派生类体系
public:
void begin() { timer.start(); }
void end() { timer.stop(); }
};
私有继承仅在以下情况更有价值:
-
需要重写基类虚函数
-
需要访问基类 protected 成员
-
需要 EBO(空基类优化)
七、using 可以有选择地"放出来"
class Derived : private Base {
public:
using Base::pub; // 将 Base::pub 重新提升为 Derived 的 public 成员
};
限制:只能提升到继承方式允许的级别范围内,不能突破基类成员本身的访问级别。
八、默认继承方式(易踩坑)
class Derived : Base {}; // 默认 private 继承
struct Derived : Base {}; // 默认 public 继承
工程规范:始终显式写明继承方式,不依赖默认规则。
九、一句话总结
-
public 继承:保接口、保类型关系,is-a
-
protected 继承:封外部、留家族,半隐藏
-
private 继承:封外部、断子孙,纯实现复用
-
基类 private 成员:派生类永远不能直接访问
-
继承方式 = 基类接口在派生类中的再封装策略