在 C++ 里写面向对象,继承是绕不开的话题。说白了就是一个类可以把另一个类的属性和方法拿过来用,省得重复写。
继承主要干两件事:代码复用和功能扩展。拿现实来说,学生和老师都属于'人',都有姓名、年龄,也都会吃饭睡觉。先定一个 Person 基类,再让 Student 和 Teacher 去继承它,各自的专属属性(比如学号、工号)就能分开加了。
基本语法
语法很直白:
class 派生类名 : 继承方式 基类名 {
// 派生类的成员
};
- 继承方式 管着基类成员在派生类里的访问权限,有 public、protected、private 三种。
- 基类名 就是被继承的那个,也叫父类。
- 派生类名 就是子类。
来个最基本的例子——Person 是基类,Student 公有继承它:
#include <iostream>
#include <string>
using namespace std;
// 基类:人
class Person {
public:
string name;
int age;
void eat() {
cout << name << " 正在吃饭" << endl;
}
void sleep() {
cout << name << " 正在睡觉" << endl;
}
};
// 派生类:学生,公有继承 Person
class Student : public Person {
public:
// 派生类新增属性:学号
int studentID;
// 派生类新增方法:学习
void study() {
cout << name << " 正在学习,学号:" << studentID << endl;
}
};
int main() {
// 创建派生类对象 Student s
Student s;
// 访问从基类继承的属性
s.name = "张三";
s.age = 18;
// 访问派生类自身的属性
s.studentID = 2024001;
// 调用从基类继承的方法
s.eat();
s.sleep();
// 调用派生类自身的方法
s.study();
return 0;
}
输出:
张三 正在吃饭
张三 正在睡觉
张三 正在学习,学号:2024001
三种继承方式:访问权限到底怎么变
继承方式影响基类成员在派生类里的可见性,但有一条基本规则:继承方式只会限制或保持原有权限,不会提升。具体看这张表:
| 基类成员权限 | public 继承 | protected 继承 | private 继承 | | --- | --- | --- | | public | public | protected | private | | protected | protected | protected | private | | private | 不可访问 | 不可访问 | 不可访问 |
几点实用提醒:
- 基类的 private 成员,派生类永远不能直接碰,只能靠基类的 public/protected 方法间接访问。
- 继承方式的限制是单向的,派生类没办法突破基类设的权限。
- 绝大多数场景下,public 继承就够了,protected 和 private 继承只在极少数设计里出现。
下面用代码演示三种继承的效果。
public 继承
#include <iostream>
using namespace std;
class Base {
public:
int pub;
protected:
int pro;
private:
int pri;
};
// 公有继承
class PubDerived : public Base {
public:
void show() {
pub = 10; // 合法:public 继承后仍为 public
pro = 20; // 合法:public 继承后为 protected
// pri = 30; // 非法:基类 private 成员不可访问
}
};
int main() {
PubDerived pd;
pd.pub = 100; // 合法:类外可访问 public 成员
// pd.pro = 200; // 非法:protected 成员类外不可访问
return 0;
}
protected 继承
class ProDerived : protected Base {
public:
void show() {
pub = 10; // 合法:protected 继承后为 protected
pro = 20; // 合法:protected 继承后仍为 protected
// pri = 30; // 非法:基类 private 成员不可访问
}
};
int main() {
ProDerived prd;
// prd.pub = 100; // 非法:protected 继承后 pub 变为 protected,类外不可访问
return 0;
}
private 继承
class PriDerived : private Base {
public:
void show() {
pub = 10; // 合法:private 继承后为 private
pro = 20; // 合法:private 继承后为 private
// pri = 30; // 非法:基类 private 成员不可访问
}
};
int main() {
PriDerived prd;
// prd.pub = 100; // 非法:private 继承后 pub 变为 private,类外不可访问
return 0;
}
构造与析构的固定顺序
创建派生类对象时,构造和析构的调用顺序是固定的:
- 构造:先基类,再派生类。
- 析构:先派生类,再基类。
简单记:构造先基后派,析构先派后基。
下面这个例子会清楚打印出来:
#include <iostream>
using namespace std;
class Person {
public:
Person() {
cout << "Person 基类构造函数被调用" << endl;
}
~Person() {
cout << "Person 基类析构函数被调用" << endl;
}
};
class Student : public Person {
public:
Student() {
cout << "Student 派生类构造函数被调用" << endl;
}
~Student() {
cout << "Student 派生类析构函数被调用" << endl;
}
};
int main() {
// 创建派生类对象 Student s
Student s;
// 函数结束时对象销毁,自动调用析构函数
return 0;
}
运行结果:
Person 基类构造函数被调用
Student 派生类构造函数被调用
Student 派生类析构函数被调用
Person 基类析构函数被调用
如果基类只有带参数的构造函数怎么办?派生类必须在初始化列表里显式调用基类构造,不然编译报错。
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
string name;
int age;
// 基类带参数构造函数
Person(string n, int a) {
name = n;
age = a;
cout << "Person 带参构造函数被调用" << endl;
}
};
class Student : public Person {
public:
int studentID;
// 派生类构造函数:初始化列表中调用基类构造
Student(string n, int a, int id) : Person(n, a) {
studentID = id;
cout << "Student 带参构造函数被调用" << endl;
}
void show() {
cout << "姓名:" << name << " 年龄:" << age << " 学号:" << studentID << endl;
}
};
int main() {
Student s("李四", 20, 2024002);
s.show();
return 0;
}
实战:一个简单的员工管理系统
用继承搭一个员工管理系统,需求很朴素:有普通员工 Employee 和经理 Manager,经理多一个部门属性和管理方法。
- 基类 Employee:姓名、工号、工资,还有 work()。
- 派生类 Manager:公有继承自 Employee,新增 department,改写 work(),再加 manage()。
- 所有属性通过构造函数初始化,保证数据完整。
完整代码:
#include <iostream>
#include <string>
using namespace std;
// 基类:普通员工
class Employee {
protected:
string name;
int empID;
double salary;
public:
// 带参构造函数
Employee(string n, int id, double sal) {
name = n;
empID = id;
salary = sal;
cout << "Employee 构造函数被调用" << endl;
}
// 工作方法
void work() {
cout << "员工 " << name << "(工号:" << empID << ")正在工作,月薪:" << salary << endl;
}
~Employee() {
cout << "Employee 析构函数被调用" << endl;
}
};
// 派生类:经理,公有继承普通员工
class Manager : public Employee {
private:
// 新增属性:管理部门
string department;
public:
// 构造函数:初始化列表调用基类构造
Manager(string n, int id, double sal, string dep) : Employee(n, id, sal) {
department = dep;
cout << "Manager 构造函数被调用" << endl;
}
// 新增方法:管理
void manage() {
cout << "经理 " << name << " 管理 " << department << " 部门" << endl;
}
// 重写基类的 work 方法(方法重写)
void work() {
cout << "经理 " << name << "(工号:" << empID << ")正在管理工作,月薪:" << salary << endl;
}
~Manager() {
cout << "Manager 析构函数被调用" << endl;
}
};
int main() {
// 创建普通员工对象
Employee emp("王五", 1001, 5000.0);
emp.work();
cout << "-------------------------" << endl;
// 创建经理对象
Manager mgr("赵六", 9001, 15000.0, "技术部");
mgr.work(); // 调用重写后的 work 方法
mgr.manage(); // 调用新增的 manage 方法
return 0;
}
输出:
Employee 构造函数被调用
员工 王五(工号:1001)正在工作,月薪:5000
-------------------------
Employee 构造函数被调用
Manager 构造函数被调用
经理 赵六(工号:9001)正在管理工作,月薪:15000
经理 赵六 管理 技术部 部门
Manager 析构函数被调用
Employee 析构函数被调用
Employee 析构函数被调用
常见坑与解决思路
派生类访问不到基类的 private 成员
两种办法:
- 把基类里要被派生类访问的成员改成 protected。
- 在基类提供 public 的 get/set 接口。
基类没有默认构造函数,派生类编译报错
在派生类构造的初始化列表里显式调用基类的带参构造函数就行,前面已经演示过。
多重继承的二义性(钻石问题)
一个派生类同时继承多个基类,如果两个基类有同名成员,直接调用就会冲突。
解决:用作用域解析符 :: 明确指定用哪个基类的版本。
class A {
public:
void func() {
cout << "A 的 func 方法" << endl;
}
};
class B {
public:
void func() {
cout << "B 的 func 方法" << endl;
}
};
class C : public A, public B {};
int main() {
C c;
c.A::func(); // 明确调用 A 类的 func 方法
c.B::func(); // 明确调用 B 类的 func 方法
return 0;
}
继承解决的就是代码复用和扩展的问题,但用起来得留心权限控制、构造析构顺序这些细节。多重继承更是容易出坑,非必要不推荐。


