在面向对象程序设计(OOP)的三大核心特性 —— 封装、继承、多态中,继承是实现代码复用和构建类层次结构的关键技术。C++ 的继承机制允许开发者在已有类的基础上扩展功能、增加属性,从而快速构建新的类,既减少了代码冗余,又提升了软件的可维护性。本文将从继承的基本概念出发,逐步深入探讨继承的定义格式、访问权限控制、类间转换、作用域规则、默认成员函数、特殊继承场景及继承与组合的选择等核心内容,结合大量实战代码示例,帮助大家全面掌握 C++ 继承的精髓。下面就让我们正式开始吧!
继承(inheritance)是面向对象程序设计中代码复用的重要手段,它允许我们在保持原有类(基类)特性的基础上,通过扩展属性和方法创建新的类(派生类)。这种机制不仅体现了 "由简单到复杂" 的认知规律,更解决了传统函数复用中代码冗余的问题。
在未使用继承时,若我们需要设计Student(学生)和Teacher(教师)两个类,会发现它们存在大量重复的成员:
_name)、地址(_address)、电话(_tel)、年龄(_age)identity(),如校园二维码刷卡)同时,它们也有各自的特有成员:
Student:学号(_stuid)、学习方法(study())Teacher:职称(_title)、授课方法(teaching()) 若分别定义这两个类,重复的成员会被多次编写,不仅增加开发工作量,还会导致后续维护困难(比如在修改身份认证逻辑时需修改两个类)。而通过继承,我们可以将共同成员提取到一个基类(如Person)中,让Student和Teacher作为派生类继承该基类,从而实现代码复用。
C++ 中继承的定义格式如下:
class 派生类名 : 继承方式 基类名 {
// 派生类的成员(属性+方法)
};
Person)。Student、Teacher)。public(公有继承)、protected(保护继承)、private(私有继承)三种。 在C++ 中,基类成员的访问权限由 "访问限定符" 和 "继承方式" 共同决定。核心规则为:派生类中基类成员的访问权限 = min (基类中成员的访问限定符,继承方式),其中权限优先级为:public > protected > private。
public:公有权限,类内、类外均可访问。protected:保护权限,类内可访问,类外不可访问,但派生类可访问。private:私有权限,仅类内可访问,类外和派生类均不可访问。在不同继承方式下,基类成员在派生类中的访问权限变化如下表所示:
基类成员类型 | public 继承 | protected 继承 | private 继承 |
|---|---|---|---|
public 成员 | 派生类 public 成员 | 派生类 protected 成员 | 派生类 private 成员 |
protected 成员 | 派生类 protected 成员 | 派生类 protected 成员 | 派生类 private 成员 |
private 成员 | 派生类中不可见 | 派生类中不可见 | 派生类中不可见 |


private成员并非未被继承,而是派生类无法通过语法访问(包括类内和类外)。这些成员仍存在于派生类对象中,只是被编译器屏蔽。protected。例如,Person类的_name设为protected,则Student可访问该成员,而 main 函数中无法直接访问Student对象的_name。class关键字定义派生类时,默认继承方式为private。struct关键字定义派生类时,默认继承方式为public。建议显式指定继承方式,提高代码可读性。public继承,protected和private继承因扩展性差(派生类成员仅能在类内使用),极少被推荐。下面通过代码验证不同继承方式下的访问权限:
// 基类Person
class Person {
public:
void publicFunc() { cout << "基类public成员函数" << endl; }
protected:
string _protectedVar = "基类protected成员变量";
private:
string _privateVar = "基类private成员变量";
};
// 公有继承
class PublicDerived : public Person {
public:
void accessBaseMember() {
publicFunc(); // 可访问基类public成员
cout << _protectedVar << endl; // 可访问基类protected成员
// cout << _privateVar << endl; // 编译报错:基类private成员不可见
}
};
// 保护继承
class ProtectedDerived : protected Person {
public:
void accessBaseMember() {
publicFunc(); // 基类public成员变为protected,类内可访问
cout << _protectedVar << endl; // 基类protected成员仍为protected,类内可访问
// cout << _privateVar << endl; // 编译报错
}
};
// 私有继承
class PrivateDerived : private Person {
public:
void accessBaseMember() {
publicFunc(); // 基类public成员变为private,类内可访问
cout << _protectedVar << endl; // 基类protected成员变为private,类内可访问
// cout << _privateVar << endl; // 编译报错
}
};
int main() {
PublicDerived pd;
pd.publicFunc(); // 可访问(基类public成员继承后仍为public)
// pd._protectedVar; // 编译报错:protected成员类外不可访问
ProtectedDerived pd2;
// pd2.publicFunc(); // 编译报错:基类public成员经protected继承后变为protected,类外不可访问
PrivateDerived pd3;
// pd3.publicFunc(); // 编译报错:基类public成员经private继承后变为private,类外不可访问
return 0;
} 在public继承(仅 public 继承支持)中,基类和派生类的对象之间存在特定的转换规则,称为 "切片" 或 "切割"(slicing)。
基类对象不能赋值给派生类对象,因为基类不包含派生类的特有成员,无法完成完整初始化。
class Person {
protected:
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person {
public:
int _stuNo; // 学号(特有成员)
};
int main() {
Student sobj; // 派生类对象
// 1. 派生类对象赋值给基类指针
Person* pPtr = &sobj;
// pPtr->_stuNo; // 编译报错:基类指针无法访问派生类特有成员
// 2. 派生类对象赋值给基类引用
Person& pRef = sobj;
// pRef->_stuNo; // 编译报错:基类引用无法访问派生类特有成员
// 3. 派生类对象赋值给基类对象(调用基类拷贝构造)
Person pObj = sobj;
// 4. 基类对象赋值给派生类对象:编译报错
// sobj = pObj;
return 0;
}
基类指针可以通过强制类型转换赋值给派生类指针,但仅当基类指针原本指向派生类对象时才安全。若基类是多态类型(析构函数加virtual),建议使用dynamic_cast进行安全转换(依赖 RTTI 运行时类型信息)。
class Person {
public:
virtual ~Person() {} // 多态类型标志
};
class Student : public Person {
public:
int _stuNo;
};
int main() {
Person* pPtr = new Student(); // 基类指针指向派生类对象
// 安全转换:使用dynamic_cast
Student* sPtr = dynamic_cast<Student*>(pPtr);
if (sPtr != nullptr) {
sPtr->_stuNo = 1001; // 安全访问派生类特有成员
}
delete pPtr;
return 0;
}基类和派生类各自拥有独立的作用域,当两者出现同名成员时,会触发 "隐藏规则"(也称为 "重定义")。
基类名::成员名。class Person {
protected:
string _name = "小李子";
int _num = 111; // 基类成员:身份证号
};
class Student : public Person {
public:
void Print() {
cout << "姓名:" << _name << endl;
cout << "身份证号:" << Person::_num << endl; // 显式访问基类同名成员
cout << "学号:" << _num << endl; // 访问派生类同名成员
}
protected:
int _num = 999; // 派生类成员:学号(隐藏基类的_num)
};
int main() {
Student s;
s.Print();
// 输出结果:
// 姓名:小李子
// 身份证号:111
// 学号:999
return 0;
}class A {
public:
void func() {
cout << "A::func()" << endl;
}
};
class B : public A {
public:
// 函数名相同,参数列表不同,构成隐藏(非重载)
void func(int i) {
cout << "B::func(int i):" << i << endl;
}
};
int main() {
B b;
b.func(10); // 调用派生类函数:B::func(int i)
// b.func(); // 编译报错:基类func被隐藏,需显式指定作用域
b.A::func(); // 显式调用基类函数:A::func()
return 0;
}请分析以下代码的编译运行结果:
class A {
public:
void func() { cout << "A::func()" << endl; }
};
class B : public A {
public:
void func(int i) { cout << "B::func(int i):" << i << endl; }
};
int main() {
B b;
b.func(10); // 正常运行:输出B::func(int i):10
b.func(); // 编译报错:基类func被隐藏
return 0;
} 答案:编译报错。原因是B::func(int)隐藏了A::func(),b.func()无法找到无参版本的func,需显式调用b.A::func()。
C++ 中每个类都有 6 个默认成员函数(若用户未显式定义,编译器会自动生成):构造函数、拷贝构造函数、赋值运算符重载、析构函数、取地址运算符重载、const 取地址运算符重载。其中前 4 个在继承体系中有特殊的生成规则。


destructor(),因此基类析构函数不加virtual时,派生类析构函数会隐藏基类的。#include <iostream>
#include <string>
using namespace std;
class Person {
public:
// 基类构造函数
Person(const char* name = "peter") : _name(name) {
cout << "Person(const char* name)" << endl;
}
// 基类拷贝构造函数
Person(const Person& p) : _name(p._name) {
cout << "Person(const Person& p)" << endl;
}
// 基类赋值运算符重载
Person& operator=(const Person& p) {
cout << "Person& operator=(const Person& p)" << endl;
if (this != &p) {
_name = p._name;
}
return *this;
}
// 基类析构函数
~Person() {
cout << "~Person()" << endl;
}
protected:
string _name; // 姓名
};
class Student : public Person {
public:
// 派生类构造函数:初始化列表中显式调用基类构造函数
Student(const char* name, int num) : Person(name), _num(num) {
cout << "Student(const char* name, int num)" << endl;
}
// 派生类拷贝构造函数:初始化列表中显式调用基类拷贝构造
Student(const Student& s) : Person(s), _num(s._num) {
cout << "Student(const Student& s)" << endl;
}
// 派生类赋值运算符重载:显式调用基类赋值运算符
Student& operator=(const Student& s) {
cout << "Student& operator=(const Student& s)" << endl;
if (this != &s) {
Person::operator=(s); // 显式调用基类赋值运算符(避免隐藏)
_num = s._num;
}
return *this;
}
// 派生类析构函数:编译器自动调用基类析构
~Student() {
cout << "~Student()" << endl;
}
protected:
int _num; // 学号
};
int main() {
// 测试构造与析构顺序
Student s1("jack", 1001);
// 输出:
// Person(const char* name)
// Student(const char* name, int num)
// 测试拷贝构造
Student s2(s1);
// 输出:
// Person(const Person& p)
// Student(const Student& s)
// 测试赋值运算符
Student s3("rose", 1002);
s1 = s3;
// 输出:
// Student& operator=(const Student& s)
// Person& operator=(const Person& p)
// 测试析构顺序(先派生后基类)
// 输出:
// ~Student()
// ~Person()
// ~Student()
// ~Person()
// ~Student()
// ~Person()
return 0;
}有时我们需要设计一个无法被继承的类(如工具类),C++ 提供了两种实现方式:
派生类的构造必须调用基类构造函数,若基类构造函数私有化,派生类无法访问,从而无法实例化:
class NonInheritable {
private:
// 私有化构造函数
NonInheritable() {}
};
// class Derived : public NonInheritable {}; // 编译报错:无法访问基类构造函数final关键字 final关键字可修饰基类,表明该类禁止被继承:
class NonInheritable final { // final修饰,禁止继承
public:
void func() { cout << "不能被继承的类" << endl; }
};
// class Derived : public NonInheritable {}; // 编译报错:无法继承final类友元关系是单向的、非传递的,且不能被继承。即基类的友元无法访问派生类的私有 / 保护成员,派生类的友元也无法访问基类的私有 / 保护成员。
class Student; // 前向声明
class Person {
public:
// 声明Display为Person的友元
friend void Display(const Person& p, const Student& s);
protected:
string _name = "张三"; // Person的保护成员
};
class Student : public Person {
protected:
int _stuNum = 1001; // Student的保护成员
};
// Display是Person的友元,但不是Student的友元
void Display(const Person& p, const Student& s) {
cout << p._name << endl; // 可访问Person的保护成员
// cout << s._stuNum << endl; // 编译报错:无法访问Student的保护成员
}
// 解决方案:同时将Display声明为Student的友元
class Student {
friend void Display(const Person& p, const Student& s);
// ...
}; 基类定义的静态成员(static修饰),在整个继承体系中仅存在一份实例,所有派生类共享该成员。
class Person {
public:
static int _count; // 静态成员:计数(统计继承体系中对象总数)
protected:
string _name;
};
// 静态成员类外初始化
int Person::_count = 0;
class Student : public Person {
protected:
int _stuNum;
};
class Teacher : public Person {
protected:
int _teaId;
};
int main() {
Person p;
Student s;
Teacher t;
// 所有对象共享同一个_count
p._count++;
s._count++;
t._count++;
cout << Person::_count << endl; // 输出3
cout << Student::_count << endl; // 输出3
cout << Teacher::_count << endl; // 输出3
// 验证静态成员地址相同(共享一份)
cout << &Person::_count << endl;
cout << &Student::_count << endl;
cout << &Teacher::_count << endl; // 三个地址完全一致
return 0;
}Student : public Person),结构清晰,无歧义。Assistant : public Student, public Teacher),功能强大但存在风险。

菱形继承是多继承的特殊情况,指派生类的两个直接基类继承自同一个间接基类,形成菱形结构:

菱形继承主要存在以下两个问题:
Assistant对象中有两个Person的_name成员)。a._name无法确定是Student继承的还是Teacher继承的)。class Person {
public:
string _name; // 姓名
};
class Student : public Person {
protected:
int _stuNum; // 学号
};
class Teacher : public Person {
protected:
int _teaId; // 教师编号
};
// 菱形继承:Assistant继承Student和Teacher
class Assistant : public Student, public Teacher {
protected:
string _major; // 主修课程
};
int main() {
Assistant a;
// a._name = "peter"; // 编译报错:访问不明确(二义性)
// 显式指定作用域解决二义性,但无法解决数据冗余
a.Student::_name = "张三";
a.Teacher::_name = "李四";
return 0;
}
C++ 引入虚继承(virtual inheritance)机制,通过在直接基类继承间接基类时添加virtual关键字,使间接基类在派生类中仅保留一份实例,从而解决数据冗余和二义性。
class 直接基类 : virtual 继承方式 间接基类 {
// 成员定义
};用虚继承解决菱形继承问题的代码示例如下:
class Person {
public:
string _name; // 姓名
};
// 虚继承:Student虚继承Person
class Student : virtual public Person {
protected:
int _stuNum; // 学号
};
// 虚继承:Teacher虚继承Person
class Teacher : virtual public Person {
protected:
int _teaId; // 教师编号
};
// 菱形继承:Assistant继承Student和Teacher
class Assistant : public Student, public Teacher {
protected:
string _major; // 主修课程
};
int main() {
Assistant a;
a._name = "peter"; // 正常访问,无二义性(仅一份_name)
return 0;
}注意事项:
Student、Teacher)继承间接基类(Person)时添加virtual,派生类(Assistant)无需添加。多继承中,不同基类指针指向同一派生类对象时,地址会存在偏移,原因是派生类对象在内存中按继承顺序存储各基类成员。如下所示:
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main() {
Derive d;
Base1* p1 = &d; // 指向派生类中Base1部分的起始地址
Base2* p2 = &d; // 指向派生类中Base2部分的起始地址
Derive* p3 = &d; // 指向派生类对象的起始地址
cout << "p1: " << p1 << endl;
cout << "p2: " << p2 << endl;
cout << "p3: " << p3 << endl;
// 输出结果:p1 == p3 != p2(Base2部分在内存中位于Base1之后)
return 0;
}C++ 标准库的 IO 类体系采用菱形虚拟继承,解决了多继承的数据冗余问题:

核心代码片段如下所示:
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_istream : virtual public std::basic_ios<CharT, Traits> {
// 输入流相关实现
};
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_ostream : virtual public std::basic_ios<CharT, Traits> {
// 输出流相关实现
};
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_iostream : public basic_istream<CharT, Traits>,
public basic_ostream<CharT, Traits> {
// 输入输出流相关实现
};在代码复用中,除了继承,还有另一种重要方式 —— 组合(Composition)。两者的选择是 OOP 设计的关键问题,核心原则是:优先使用组合,而非继承。
Car(汽车)和 Tire(轮胎)是 has-a 关系,应使用组合:
// 轮胎类
class Tire {
protected:
string _brand = "米其林"; // 品牌
int _size = 17; // 尺寸
};
// 汽车类:组合轮胎类
class Car {
protected:
string _color = "白色"; // 颜色
string _license = "陕AB1234"; // 车牌号
Tire _t1, _t2, _t3, _t4; // 组合4个轮胎对象
};
// 宝马车:继承Car(is-a关系)
class BMW : public Car {
public:
void Drive() { cout << "宝马:操控精准" << endl; }
};BMW(宝马)是 Car(汽车)的一种,应使用继承:
class Car {
public:
virtual void Drive() { cout << "汽车:可以行驶" << endl; }
};
class BMW : public Car {
public:
void Drive() override { cout << "宝马:操控精准" << endl; }
};
class Benz : public Car {
public:
void Drive() override { cout << "奔驰:乘坐舒适" << endl; }
};stack(栈)和 vector(向量)的关系既符合 is-a(栈是一种特殊的向量,仅在尾部操作),也符合 has-a(栈包含一个向量用于存储数据)。此时优先选择组合:
// 组合实现(推荐)
template<class T>
class Stack {
public:
void push(const T& x) { _v.push_back(x); }
void pop() { _v.pop_back(); }
const T& top() { return _v.back(); }
private:
vector<T> _v; // 组合vector,隐藏其内部细节
};
// 继承实现(不推荐)
template<class T>
class Stack : public vector<T> {
public:
void push(const T& x) { this->push_back(x); }
void pop() { this->pop_back(); }
const T& top() { return this->back(); }
};insert、erase),仅暴露栈的核心接口(push、pop、top),这符合栈的抽象定义。insert破坏栈的结构,会违背栈的 "先进后出" 特性。A→B→C→D→E),会导致代码可读性和维护性下降。因此继承层次最好不要超过 3 层。virtual,delete 基类指针会导致派生类析构函数不被调用,造成内存泄漏。protected,破坏基类封装性。仅在派生类需访问的成员时使用protected。public继承,避免protected和private继承。virtual(多态场景),确保派生类对象正确析构。struct,也显式写出public继承,提高代码可读性。继承作为多态的基础,它通过构建类层次结构实现了代码复用和扩展。后续我还会结合多态、虚函数等知识点进一步深化。建议大家通过大量实践,巩固本文所学内容,真正掌握 C++ 继承的精髓。谢谢大家的支持!