继承概述
继承(Inheritance)是 C++ 面向对象程序设计实现代码复用的最重要手段。它允许我们在保持原有类特性的基础上进行扩展,增加新的方法(成员函数)和属性(成员变量),从而产生新的类,称为派生类。
为什么需要继承?
想象一下,如果我们要定义 Student 和 Teacher 两个类。这两个类里面都有姓名、地址、年龄、电话等成员变量,都有身份认证的成员函数。如果不使用继承,设计时就会出现大量冗余。
#include <iostream>
#include <string>
using namespace std;
class Student {
public:
// 进入校园/图书馆/实验室刷二维码等身份认证
void identity() {
cout << "Student Identity" << endl;
}
// 学习
void study() {
// ...
}
protected:
string _name = "peter"; // 姓名
string _address; // 地址
string _tel; // 电话
int _age = 18; // 年龄
int _stuid; // 学号
};
class Teacher {
public:
void identity() {
cout << "Teacher Identity" << endl;
}
// 授课
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 << "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;
}
继承的定义与术语
在术语上,Person 被称为基类(Base Class)或父类,而 Student 和 Teacher 被称为派生类(Derived Class)或子类。这种命名差异主要源于翻译习惯,两者指代同一概念。


继承方式对访问权限的影响
继承方式决定了基类成员在派生类中的可见性。下表总结了不同继承方式下,基类成员的访问权限变化:
| 类成员/继承方式 | 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继承。因为后两者继承下来的成员只能在派生类的类里面使用,实际扩展维护性不强。
模板继承示例
标准库中的容器往往也体现了继承的思想。例如 stack 和 vector 的关系,既符合 is-a 关系,也符合 has-a 关系。
#include <iostream>
#include <vector>
using namespace std;
namespace jiang {
template<class T>
class stack : public 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;
}
基类和派生类之间的转换
在继承体系中,对象之间的赋值和转换有严格的规则:
- 向上转换(Upcasting):
public继承的派生类对象可以赋值给基类的指针或引用。有个形象的说法叫'切片'或者'切割',寓意把派生类中基类那部分切出来,基类指针或引用指向的是派生类中切出来的基类那部分。 - 向下转换(Downcasting):基类对象不能直接赋值给派生类对象。基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用,但必须是基类的指针指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用 RTTI(Run-Time Type Information)的
dynamic_cast来进行识别后进行安全转换。

#include <iostream>
#include <string>
using namespace std;
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;
}


