当前位置: 首页 > news >正文

【C++进阶】C++中的继承

【C++进阶】C++中的继承

1. 继承的概念及定义

1.1 什么是继承?

继承是面向对象程序设计中最核心的机制之一,它允许我们在保持原有类特性的基础上进行扩展,增加新的功能,从而产生新的类(派生类)。

与之前学习的函数复用不同,继承是类设计层次的复用,体现了由简单到复杂的认知过程。

1.2 继承的基本语法

class Person {
public:void Print() {cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter";int _age = 18;
};// Student 继承自 Person
class Student : public Person {
protected:int _stuid; // 学号
};class Teacher : public Person {
protected:int _jobid; // 工号
};

2. 继承方式与访问权限

2.1 三种继承方式

类成员/继承方式public继承protected继承private继承
基类的public成员派生类的public成员派生类的protected成员派生类的private成员
基类的protected成员派生类的protected成员派生类的protected成员派生类的private成员
基类的private成员在派生类中不可见在派生类中不可见在派生类中不可见

2.2 重要规则

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的
  2. 基类成员在子类的访问方式 = Min(成员在基类的访问限定符,继承方式)
  3. class默认private继承,struct默认public继承(建议显式写出)
  4. 实际开发中主要使用public继承

3. 基类和派生类对象赋值转换

3.1 切片(切割)概念

class Person {
protected:string _name;string _sex;int _age;
};class Student : public Person {
public:int _No; // 学号
};void Test() {Student sobj;// 1. 派生类对象可以赋值给基类对象/指针/引用Person pobj = sobj;        // 切片Person* pp = &sobj;        // 切片Person& rp = sobj;         // 切片// 2. 基类对象不能赋值给派生类对象// sobj = pobj; // 错误!// 3. 基类指针可以通过强制类型转换赋值给派生类指针pp = &sobj;Student* ps1 = (Student*)pp; // 安全转换
}

核心要点

  • 派生类→基类:天然支持(切片)
  • 基类→派生类:需要强制转换,且可能不安全

4. 继承中的作用域

4.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;             // 访问自己的_num}
protected:int _num = 999; // 学号 - 与父类_num构成隐藏
};

重要

  • 子类和父类同名成员会构成隐藏
  • 函数只需要函数名相同就构成隐藏(不要求参数列表)
  • 建议:在继承体系中不要定义同名成员

5. 派生类的默认成员函数

5.1 六大默认成员函数的继承特性

class Person {
public:Person(const char* name = "peter") : _name(name) {cout << "Person()" << 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()" << 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);  // 调用基类operator=_num = s._num;}return *this;}~Student() {cout << "~Student()" << endl;// 析构函数完成后会自动调用基类析构函数}protected:int _num;
};

5.2 构造和析构顺序

  1. 构造顺序:基类→派生类
  2. 析构顺序:派生类→基类

6. 继承与友元、静态成员

6.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;     // OK:友元访问// cout << s._stuNum << endl; // 错误:友元不能继承
}

6.2 静态成员共享

class Person {
public:Person() { ++_count; }
protected:string _name;
public:static int _count; // 整个继承体系共享
};int Person::_count = 0;class Student : public Person {
protected:int _stuNum;
};

7. 复杂的菱形继承及虚拟继承

7.1 菱形继承的问题

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;
};void Test() {Assistant a;// a._name = "peter"; // 错误:二义性a.Student::_name = "xxx";  // 需要显式指定a.Teacher::_name = "yyy";  // 数据冗余!
}

问题

  • 二义性:无法确定访问哪个_name
  • 数据冗余:Person成员在Assistant中有两份

7.2 虚拟继承解决方案

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;
};void Test() {Assistant a;a._name = "peter"; // 现在没有二义性,且只有一份_name
}

7.3 虚拟继承原理

虚拟继承通过虚基表指针虚基表来实现:

  • 虚基表中存储偏移量
  • 通过偏移量找到共享的基类成员
  • 解决了数据冗余和二义性问题

8. 继承的总结与反思

8.1 继承 vs 组合

特性继承组合
关系is-ahas-a
耦合度
复用类型白箱复用黑箱复用
灵活性较低较高

8.2 使用建议

  1. 优先使用组合,而不是继承
  2. 如果关系确实是"is-a",使用public继承
  3. 避免多继承,特别是菱形继承
  4. 要实现多态,必须使用继承

8.3 代码示例

// 继承:is-a关系
class Car {
protected:string _colour = "白色";string _num = "陕ABIT00";
};class BMW : public Car {
public:void Drive() { cout << "好开-操控" << endl; }
};// 组合:has-a关系
class Tire {
protected:string _brand = "Michelin";size_t _size = 17;
};class Car {
protected:string _colour = "白色";string _num = "陕ABIT00";Tire _t; // 组合
};

9. 常见面试题

9.1 基础概念题

  1. 什么是菱形继承?菱形继承的问题是什么?

    • 菱形继承是多继承的特殊情况,形成钻石形状的继承结构
    • 问题:数据冗余和二义性
  2. 什么是菱形虚拟继承?如何解决数据冗余和二义性?

    • 在菱形继承的中间类使用virtual继承
    • 通过虚基表指针和虚基表实现共享基类成员
  3. 继承和组合的区别?什么时候用继承?什么时候用组合?

    • 继承:is-a关系,代码复用,多态实现
    • 组合:has-a关系,降低耦合度,提高灵活性
    • 优先使用组合,确需is-a关系或实现多态时使用继承
http://www.dtcms.com/a/597641.html

相关文章:

  • 【大模型量化】Qwen3-VL + Lora监督微调 + 4bit量化 | VLM模型
  • 哪个网站最好微信开发者文档小程序
  • 免费黄页网站互联网众筹网站怎样建设
  • Math for Grade 1 of junior high school
  • 卓手机建网站有没有专门找装修公司的网站
  • Goer-Docker系列-1-Dockerfile的构建速度优化
  • 【20251029】如何在Ubuntu虚拟机部署本地sql,redis
  • SQL注入之SQLMAP绕过WAF(安全狗)
  • 西安注册公司网站黄页是什么东西
  • BFF 相关学习
  • 【SAA】SpringAI Alibaba学习笔记(三):ChatModel对话记忆存储和持久化
  • Excalidraw绘图软件
  • 苹果ios系统共享的ipa文件应用app签名怎么用?
  • 潍坊网站开发培训电影网站空间配置
  • 鸿蒙ef_crypto-加密组件-SM2Sync
  • windows 下的paddle ocr 部署
  • 融合之道:电科金仓数据库的“五化一体“革命
  • 微楼书网站建设全球推广
  • AR眼镜基于上下文智能识别:电力运维高效规范操作应用方案|阿法龙XR云平台
  • Linux 文件基本属性
  • 广州做外贸网站dw网页制作成品12页
  • 甘肃省两学一做专题网站哔哩哔哩网页入口
  • PostgreSQL 监控告警实战:从 “高并发卡顿才发现” 到 “提前 1 小时预警” 的守护指南
  • 利用影视网站做cpawordpress在线安装插件在哪里
  • Java 对象分配过程深度解析
  • Java 大视界 -- Java 大数据在智能医疗影像数据压缩与传输优化中的技术应用
  • Linux 系统安装与环境配置实践
  • 潍坊seo外包平台福州seo推广优化
  • C++ 图形中间件库Magnum详细介绍
  • 电商网站开发技术难点网页设计版式布局