处理C++继承从概念到实践指南这类问题时,先确认目标场景,再按步骤核对配置或玩法细节。
继承(Inheritance)是面向对象程序设计(OOP)中实现代码复用的核心机制。它允许我们在保持原有类特性的基础上进行扩展,增加新的成员变量和成员函数,从而产生新的类,称为派生类(Derived Class)。继承体现了面向对象程序设计的层次结构,反映了从简单到复杂的认知过程。

在没有继承机制时,如果多个类有共同的属性和行为,我们只能在每个类中重复定义这些成员,导致代码冗余和维护困难。
例如,考虑学生(Student)和教师(Teacher)两个类:
// 没有继承的设计 - 存在大量冗余class Student {public: void identity() { /* 身份认证 */ } void study() { /* 学习 */ }protected: string _name; string _address; string _tel; int _age; int _stuid; // 学号};class Teacher {public: void identity() { /* 身份认证 */ } void teaching() { /* 授课 */ }protected: string _name; string _address; string _tel; int _age; string _title; // 职称};可以看到,两个类中都重复定义了姓名、地址、电话、年龄等成员变量,以及 identity() 成员函数。通过继承,我们可以将这些公共成员提取到基类中:
// 使用继承的设计class Person {public: void identity() { cout << "身份认证:" << _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; // 职称};继承的基本语法如下:
class 派生类名 : 继承方式 基类名 { // 派生类新增成员};其中:
基类:也称为父类,是被继承的类
派生类:也称为子类,是继承基类的类
继承方式:public、protected、private,控制基类成员在派生类中的访问权限
继承方式会影响基类成员在派生类中的访问权限:
| 基类成员访问限定符 | public继承 | protected继承 | private继承 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可见 | 不可见 | 不可见 |
重要规则:
在 public 继承中,存在以下转换规则:
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; // 正确 // 2. 派生类对象可以赋值给基类对象(调用基类的拷贝构造) Person pobj = sobj; // 正确 // 3. 基类对象不能赋值给派生类对象 // sobj = pobj; // 错误:编译报错 // 4. 基类指针/引用可以通过强制类型转换赋值给派生类指针/引用 // 但必须是基类指针指向派生类对象时才安全 Person* p = &sobj Student* ps = (Student*)p; // 强制转换,需要谨慎 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 s1; s1.Print(); // 输出:姓名:小李子 // 身份证号:111 // 学号:999 return 0;}注意:
如果是成员函数的隐藏,只需要函数名相同就构成隐藏(参数不同也构成隐藏)
实际开发中应避免在继承体系中定义同名成员
可以使用基类::成员名显式访问被隐藏的基类成员
派生类的6个默认成员函数有其特殊性:
派生类的构造函数必须调用基类的构造函数来初始化基类部分:
class Person {public: Person(const char* name = "peter") : _name(name) { cout << "Person()" << endl; }protected: string _name;};class Student : public Person {public: Student(const char* name, int num) : Person(name) // 必须调用基类构造函数 , _num(num) { cout << "Student()" << endl; }protected: int _num;};注意:_name不属于子类student成员,属于父类person的成员
初始化列表只能初始化当前类自己定义的成员变量,不能直接初始化父类的成员。
- _num :是 Student 自己的成员,所以可以写 _num(num)
- _name :定义在 Person 父类,Student 无权在自己的初始化列表直接初始化它
2、正确处理父类成员的唯一方式
只能通过调用父类构造函数,由父类自己的初始化列表完成 _name 赋值:
Student(const Student& s) : Person(s) // 调用基类的拷贝构造 , _num(s._num) { cout << "Student(const Student& s)" << endl;}Student& operator=(const Student& s) { if (this != &s) { Person::operator=(s); // 显式调用基类的operator=,这里需要使用父类的类域,因为名字相同,构成隐藏,访问时需要指明父类域 _num = s._num; } return *this;}派生类析构函数会在调用完成后自动调用基类的析构函数:
~Student() { cout << "~Student()" << endl; // 此处会自动调用 ~Person()}构造和析构顺序:
友元关系不能继承:基类的友元不能访问派生类的私有和保护成员。
class Student;class Person {public: friend void Display(const Person& p, const Student& s);protected: string _name;};class Student : public Person {protected: int _stuNum;};void Display(const Person& p, const Student& s) { cout << p._name << endl; // 正确:可以访问Person的protected成员 // cout << s._stuNum << endl; // 错误:不能访问Student的protected成员 // 需要将Display也声明为Student的友元}基类定义的静态成员在整个继承体系中只有一份:
class Person {public: string _name; static int _count; // 静态成员};int Person::_count = 0;//静态成员变量需要在类外进行初始化值class Student : public Person {protected: int _stuNum;};int main() { Person p; Student s; cout << &p._name << endl; // 地址不同 - 非静态成员各有副本 cout << &s._name << endl; cout << &p._count << endl; // 地址相同 - 静态成员共享 cout << &s._count << endl; cout << Person::_count << endl; // 通过基类访问 cout << Student::_count << endl; // 通过派生类访问 return 0;}单继承:一个派生类只有一个直接基类
多继承:一个派生类有两个或以上直接基类
菱形继承是多继承的一种特殊情况,会导致数据冗余和二义性问题:
class Person {public: string _name;};class Student : public Person {protected: int _num;};class Teacher : public Person {protected: int _id;};class Assistant : public Student, public Teacher {protected: string _majorCourse;};int main() { Assistant a; // a._name = "peter"; // 错误:对"_name"的访问不明确 a.Student::_name = "xxx"; // 需要显式指定 a.Teacher::_name = "yyy"; // 数据冗余:两份_name return 0;}使用虚继承可以解决菱形继承的数据冗余和二义性问题:
class Person {public: string _name;};class Student : virtual public Person { // 虚继承protected: int _num;};class Teacher : virtual public Person { // 虚继承protected: int _id;};class Assistant : public Student, public Teacher {protected: string _majorCourse;};int main() { Assistant a; a._name = "peter"; // 正确:只有一份_name return 0;}注意:虽然C++支持多继承和虚继承,但实际开发中应尽量避免设计出菱形继承,因为这会增加代码复杂性和维护难度。
在面向对象设计中,除了继承还有另一种重要的代码复用方式——组合。
| 特性 | 继承(is-a) | 组合(has-a) |
|---|---|---|
| 关系 | 派生类对象是一个基类对象 | B对象中有一个A对象 |
| 复用类型 | 白箱复用(内部细节可见) | 黑箱复用(内部细节不可见) |
| 耦合度 | 高(基类改变影响派生类) | 低(类之间依赖弱) |
| 封装性 | 破坏封装(基类细节对派生类可见) | 保持封装 |
// 组合示例:Car has-a Tireclass Tire {protected: string _brand; size_t _size;};class Car {protected: string _colour; string _num; Tire _t1, _t2, _t3, _t4; // 组合关系};// 继承示例:BMW is-a Carclass BMW : public Car {public: void Drive() { cout << "好开-操控" << endl; }};// 使用final防止继承class Base final { // 这个类不能被继承public: void func() { cout << "Base::func" << endl; }};// class Derived : public Base { // 错误:Base是final的// // ...// };继承是C++面向对象编程的核心特性之一,它提供了代码复用的强大机制。通过合理使用继承,我们可以:减少代码冗余,提高代码复用性、建立清晰的类层次结构、实现多态性(配合虚函数)。