继承
继承(Inheritance)是面向对象程序设计中实现代码复用的核心手段。它允许我们在保持基类特性的基础上进行扩展,增加新的成员函数和属性,从而生成派生类。
1. 继承的概念
假设我们有两个类 Student 和 Teacher,它们都包含姓名、地址、年龄、电话等公共成员,以及身份认证功能。如果分别定义,代码会非常冗余。
class Student {
public:
// 进入校园/图书馆刷二维码等身份认证
void identity() {
// ...
}
// 学习
void study() {
// ...
}
protected:
string _name = "peter";
string _address;
string _tel;
int _age = 18;
int _stuid;
};
class Teacher {
public:
void identity() {
// ...
}
// 授课
void teaching() {
// ...
}
protected:
string _name = "张三";
int _age = 18;
string _address;
string _tel;
string _title;
};
int main() {
return 0;
}
我们可以将公共成员提取到 Person 类中,让 Student 和 Teacher 继承 Person,这样就能复用这些成员,避免重复定义。
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
void identity() {
cout << "void identity()" << _name << endl;
}
protected:
string _name = "张三";
string _address;
string _tel;
int _age = 18;
};
class Student : public Person {
public:
void study() {
// ...
}
protected:
int _stuid;
};
class Teacher : public Person {
public:
void teaching() {
// ...
}
protected:
string title;
};
int main() {
Student s;
Teacher t;
s.identity();
t.identity();
return 0;
}
2. 继承的定义
Person 称为基类(父类),Student 称为派生类(子类)。翻译习惯上两者通用。

2.1 定义格式
class DerivedClass : access_mode BaseClass {
// ...
};
2.2 继承方式与成员访问权限
| 类成员 / 继承方式 | public 继承 | protected 继承 | private 继承 |
|---|---|---|---|
| 基类的 public 成员 | 派生类的 public 成员 | 派生类的 protected 成员 | 派生类的 private 成员 |
| 基类的 protected 成员 | 派生类的 protected 成员 | 派生类的 protected 成员 | 派生类的 private 成员 |
| 基类的 private 成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
这里需要特别注意几点:
- 私有成员的可见性:基类的 private 成员无论以何种方式继承,在派生类中都是不可见的。注意,不可见是指语法限制,对象内存中其实包含了这部分数据,只是无法直接访问。
- 保护成员的作用:如果基类成员不想在类外被直接访问,但需要在派生类中使用,应定义为 protected。这是因继承而引入的限定符。
- 访问规则总结:基类的其他成员在派生类的访问权限等于
min(基类访问限定符,继承方式)。顺序为 public > protected > private。 - 默认继承方式:使用关键字 class 时默认为 private 继承,使用 struct 时默认为 public 继承。实际开发中建议显式写出继承方式。
- 最佳实践:实际运用中一般使用 public 继承。protected/private 继承下来的成员只能在派生类内部使用,扩展性和维护性较差,不提倡滥用。
3. 模板继承示例
标准库中的 stack 容器适配器就是基于 vector 实现的典型例子,既符合 is-a 关系,也体现了 has-a 的组合思想。
namespace jiang {
template<class T>
class stack : public std::vector<T> {
public:
void push(const T& x) {
// 基类是类模板时,需要指定作用域,否则编译报错
// error C3861: 'push_back': 找不到标识符
// 因为 stack<int> 实例化时,vector<int> 也被实例化了
// 但模版按需实例化,push_back 等成员函数可能未实例化
vector<T>::push_back(x);
}
void pop() {
vector<T>::pop_back();
}
const T& top() {
return vector<T>::back();
}
bool empty() {
return vector<T>::empty();
}
};
}
int main() {
jiang::stack<int> st;
st.push(1);
st.push(2);
st.push(3);
while (!st.empty()) {
cout << st.top() << " ";
st.pop();
}
return 0;
}
4. 基类和派生类之间的转换
在涉及多态或接口设计时,理解类型转换至关重要。
- 向上转换(Upcasting):public 继承的派生类对象可以赋值给基类的指针或引用。这被称为'切片'或切割,寓意把派生类中基类那部分切出来,基类指针指向的是派生类对象中属于基类的那部分数据。
- 向下转换(Downcasting):基类对象不能直接赋值给派生类对象。基类指针或引用可以通过强制类型转换赋值给派生类指针或引用,但必须确保基类指针确实指向了派生类对象,否则不安全。
- 安全转换:如果基类是多态类型(含有虚函数),可以使用 RTTI(Run-Time Type Information)的 dynamic_cast 进行识别后再转换。

int main() {
jiang::stack<int> st;
st.push(1);
st.push(2);
st.push(3);
while (!st.empty()) {
cout << st.top() << " ";
st.pop();
}
return 0;
}
class Person {
protected:
string _name;
string _sex;
int _age;
};
class Student : public Person {
public:
int _No;
};
int main() {
Student sobj;
// 1. 派生类对象可以赋值给基类的指针/引用
Person* pp = &sobj;
Person& rp = sobj;
// 派生类对象赋值给基类对象是通过调用基类的拷贝构造完成的
Person pobj = sobj;
// 2. 基类对象不能赋值给派生类对象,这会编译报错
// sobj = pobj;
return 0;
}
在实际开发中,尽量利用向上转换带来的多态特性,谨慎处理向下转换,必要时配合 dynamic_cast 确保类型安全。


