继承方式
继承方式是派生类定义中的核心要素,它决定了基类成员在派生类中的访问属性,进而影响了派生类与外部代码对基类成员的可见性。
理解三种继承方式的区别,是正确设计继承关系的基础。
访问控制回顾:三种访问属性
在理解继承方式之前,需要先回顾类成员的三种访问属性:
类成员的访问属性
| 访问属性 |
关键字 |
类内访问 |
派生类访问 |
外部访问 |
| 公有 |
public |
✓ |
取决于继承方式 |
✓ |
| 保护 |
protected |
✓ |
取决于继承方式 |
✗ |
| 私有 |
private |
✓ |
✗(始终不可访问) |
✗ |
无论采用何种继承方式,基类的 private 成员在派生类中始终不可直接访问。这是封装原则的体现——基类的实现细节对派生类也是隐藏的。
三种继承方式
C++ 提供了三种继承方式:公有继承(public)、私有继承(private) 和 保护继承(protected)。
它们共同决定了基类成员在派生类中的访问状态。不同的继承方式主要影响两个方面:
- 派生类成员函数对基类成员的访问权限。
- 派生类对象(外部代码) 对从基类继承的成员的访问权限。
继承方式与访问控制
继承方式与访问控制都使用关键字 public、private 和 protected,但其所表达的含义并不相同。
继承方式与访问控制会共同影响派生类成员的访问权限。
1. 公有继承(public inheritance)
公有继承是最常用的继承方式,它在派生类中保持了从基类中继承的成员的访问属性,最能体现 "is-a" 关系。
公有继承的访问控制规则
| 基类成员属性 |
在派生类中的访问属性 |
public |
public(保持不变) |
protected |
protected(保持不变) |
private |
不可直接访问 |
- 派生类成员函数:可以直接访问基类的
public 和 protected 成员。
- 外部代码(派生类对象):只能访问从基类继承的
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 |
不可直接访问 |
- 派生类成员函数:可以直接访问基类的
public 和 protected 成员(在派生类中它们变为 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 |
不可直接访问 |
- 派生类成员函数:可以直接访问基类的
public 和 protected 成员(在派生类中它们为 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()
小结
-
派生类定义语法:class 派生类名 : 继承方式 基类名 { ... };
-
三种继承方式对比:
| 继承方式 |
public 成员 |
protected 成员 |
private 成员 |
| 公有继承 |
→ public |
→ protected |
不可访问 |
| 私有继承 |
→ private |
→ private |
不可访问 |
| 保护继承 |
→ protected |
→ protected |
不可访问 |
-
选择指南:
- 公有继承:表达 "is-a" 关系,最常用。
- 私有继承:表达 "用...来实现",可用组合替代。
- 保护继承:仅用于继承树内部共享,较少使用。
-
同名隐藏:派生类中新成员会隐藏基类的同名成员,可通过 基类名::成员名 访问被隐藏的成员。
继承方式是派生类设计的"权限开关",选择合适的继承方式是正确表达类型关系的关键。公有继承是最自然、最常用的继承方式,它让 "is-a" 关系对外可见,为多态打下基础。