最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
C++继承从概念到实践指南
时间:2026-08-08 07:31:50 编辑:袖梨 来源:一聚教程网
处理C++继承从概念到实践指南这类问题时,先确认目标场景,再按步骤核对配置或玩法细节。
1. 继承的概念与定义
继承(Inheritance)是面向对象程序设计(OOP)中实现代码复用的核心机制。它允许我们在保持原有类特性的基础上进行扩展,增加新的成员变量和成员函数,从而产生新的类,称为派生类(Derived Class)。继承体现了面向对象程序设计的层次结构,反映了从简单到复杂的认知过程。

1.1 为什么需要继承?
在没有继承机制时,如果多个类有共同的属性和行为,我们只能在每个类中重复定义这些成员,导致代码冗余和维护困难。
例如,考虑学生(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; // 职称};1.2 继承的定义格式
继承的基本语法如下:
class 派生类名 : 继承方式 基类名 { // 派生类新增成员};其中:
基类:也称为父类,是被继承的类
派生类:也称为子类,是继承基类的类
继承方式:public、protected、private,控制基类成员在派生类中的访问权限
1.3 继承方式与访问权限
继承方式会影响基类成员在派生类中的访问权限:
| 基类成员访问限定符 | 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 继承较少使用
2. 基类与派生类的转换
在 public 继承中,存在以下转换规则:
2.1 派生类对象赋值给基类
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;}切片:将派生类对象赋值给基类对象时,只会复制基类部分的数据,派生类特有的部分会被"切掉"。
3. 继承中的作用域
3.1 隐藏规则
在继承体系中,基类和派生类有独立的作用域。当派生类和基类有同名成员时,派生类成员会隐藏基类的同名成员。
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;}注意:
如果是成员函数的隐藏,只需要函数名相同就构成隐藏(参数不同也构成隐藏)
实际开发中应避免在继承体系中定义同名成员
可以使用基类::成员名显式访问被隐藏的基类成员
4. 派生类的默认成员函数
派生类的6个默认成员函数有其特殊性:
4.1 构造函数
派生类的构造函数必须调用基类的构造函数来初始化基类部分:
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 赋值:
4.2 拷贝构造函数
Student(const Student& s) : Person(s) // 调用基类的拷贝构造 , _num(s._num) { cout << "Student(const Student& s)" << endl;}4.3 赋值运算符重载
Student& operator=(const Student& s) { if (this != &s) { Person::operator=(s); // 显式调用基类的operator=,这里需要使用父类的类域,因为名字相同,构成隐藏,访问时需要指明父类域 _num = s._num; } return *this;}4.4 析构函数
派生类析构函数会在调用完成后自动调用基类的析构函数:
~Student() { cout << "~Student()" << endl; // 此处会自动调用 ~Person()}构造和析构顺序:
- 派生类对象构造:先调用基类构造,再调用派生类构造
- 派生类对象析构:先调用派生类析构,再调用基类析构
5. 继承与友元、静态成员
5.1 继承与友元
友元关系不能继承:基类的友元不能访问派生类的私有和保护成员。
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的友元}5.2 继承与静态成员
基类定义的静态成员在整个继承体系中只有一份:
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;}6. 多继承与菱形继承
6.1 单继承与多继承
单继承:一个派生类只有一个直接基类
多继承:一个派生类有两个或以上直接基类
6.2 菱形继承问题
菱形继承是多继承的一种特殊情况,会导致数据冗余和二义性问题:
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;}6.3 虚继承解决方案
使用虚继承可以解决菱形继承的数据冗余和二义性问题:
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++支持多继承和虚继承,但实际开发中应尽量避免设计出菱形继承,因为这会增加代码复杂性和维护难度。
7. 继承与组合
在面向对象设计中,除了继承还有另一种重要的代码复用方式——组合。
7.1 继承 vs 组合
| 特性 | 继承(is-a) | 组合(has-a) |
|---|---|---|
| 关系 | 派生类对象是一个基类对象 | B对象中有一个A对象 |
| 复用类型 | 白箱复用(内部细节可见) | 黑箱复用(内部细节不可见) |
| 耦合度 | 高(基类改变影响派生类) | 低(类之间依赖弱) |
| 封装性 | 破坏封装(基类细节对派生类可见) | 保持封装 |
7.2 设计原则
- 优先使用组合:组合的耦合度低,代码维护性好
- 适合继承时用继承:类之间确实是is-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; }};8. 继承的最佳实践
- 使用public继承:除非有特殊需求,否则使用public继承
- 避免菱形继承:多继承会增加复杂度,尽量避免设计菱形继承
- 合理使用protected:需要在派生类中访问但不在类外访问的成员设为protected
- 注意隐藏问题:避免在派生类中定义与基类同名的成员
- 正确调用基类构造函数:在派生类构造函数初始化列表中显式调用基类构造函数
- 考虑使用final:C++11的final关键字可以防止类被继承
// 使用final防止继承class Base final { // 这个类不能被继承public: void func() { cout << "Base::func" << endl; }};// class Derived : public Base { // 错误:Base是final的// // ...// };总结
继承是C++面向对象编程的核心特性之一,它提供了代码复用的强大机制。通过合理使用继承,我们可以:减少代码冗余,提高代码复用性、建立清晰的类层次结构、实现多态性(配合虚函数)。