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

C++ - 继承【下】

一、继承与友元

友元关系不能继承,也就是说基类友元不能访问派生类私有和保护成员 。

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;cout << s._stuNum << endl;
}int main()
{Person p;Student s;// 编译报错:error C2248: “Student::_stuNum”: 无法访问 protected 成员// 解决方案:Display也变成Student 的友元即可Display(p, s);return 0;
}

二、继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个派生类,都只有一个static成员实例。

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;// 这里的运行结果可以看到非静态成员_name的地址是不一样的// 说明派生类继承下来了,父派生类对象各有一份cout << &p._name << endl;cout << &s._name << endl;// 这里的运行结果可以看到静态成员_count的地址是一样的// 说明派生类和基类共用同一份静态成员cout << &p._count << endl;cout << &s._count << endl;// 公有的情况下,父派生类指定类域都可以访问静态成员cout << Person::_count << endl;cout << Student::_count << endl;return 0;
}

三、多继承及其菱形继承问题

3.1 继承模型

单继承:一个派生类只有一个 直接  基类时称这个继承关系为单继承

多继承:一个派生类有  两个 或 以上 直接 基类时  称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的基类在前面,后面继承的基类在后面,派生类成员在放到最后面。

 

菱形继承菱形继承是多继承的一种特殊情况。
菱形继承的问题,从下面的对象成员模型构造,可以 看出  菱形继承有  数据冗余  和  二义性  的问
在Assistant的对象中Person成员会有两份。支持多继承就 ⼀定会有菱形继承,像Java就直接不支持多继承,规避掉了这里的问题所以实践中我们也是不建议设计出菱形继承这样的模型的

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()
{// 编译报错:error C2385: 对“_name”的访问不明确Assistant a;a._name = "peter";// 需要显示指定访问哪个基类的成员可以解决二义性问题,但是数据冗余问题无法解决a.Student::_name = "xxx";a.Teacher::_name = "yyy";return 0;
}

这段代码展示了多继承中的菱形继承问题:

  • Assistant同时继承了StudentTeacher
  • StudentTeacher又都继承自Person
  • 这导致Assistant对象中会有两份Person类的成员(包括_name),造成数据冗余
  • 直接访问_name会产生二义性,需要通过类域指定访问哪个基类的成员

解决这种问题的通常方案是使用虚继承(在继承时添加 virtual关键字 ),使Person成为虚基类,确保派生类中只保留一份Person的成员。

 

3.2 虚继承

很多人说C++语法复杂,其实多继承就是⼀个体现。有了多继承,就存在菱形继承,有了菱形继承就有 菱形虚拟继承,底层实现就很复杂,性能也会有⼀些损失,所以最好不要设计出菱形继承。多继承可 以认为是C++的缺陷之一,后来的一些编程语言都没有多继承,如Java。
class Person
{
public:string _name; // 姓名/*int _tel;int _age;string _gender;string _address;*/// ...
};// 使用虚继承Person类
class Student : virtual public Person
{
protected:int _num; //学号
};// 使用虚继承Person类
class Teacher : virtual public Person
{
protected:int _id; // 职工编号
};// 教授助理
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};int main()
{// 使用虚继承,可以解决数据冗余和二义性Assistant a;a._name = "peter";return 0;
}

这段代码通过虚继承(virtual public)解决了之前的菱形继承问题:

  • StudentTeacher类都使用虚继承的方式继承Person
  • 这使得Assistant类在多重继承时,只会保留一份Person类的成员
  • 因此可以直接访问_name而不会产生二义性,同时也解决了数据冗余问题

 

我们可以设计出多继承,但是不建议设计出菱形继承因为菱形虚拟继承以后,无论是使用还是底层 都会复杂很多。当然有多继承语法支持,就一定存在会设计出菱形继承,像Java是不支持多继承的, 就避开了菱形继承。

3.3 题目

多继承中指针偏移问题?下面说法正确的是( )

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;Base2* p2 = &d;Derive* p3 = &d;return 0;
}

C

3.4 IO库中的菱形虚拟继承

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_istream : virtual public std::basic_ios<CharT, Traits>
{};

这段代码展示了 C++ 标准库中输入输出流的部分继承关系:

  • basic_ostream(基础输出流类)和basic_istream(基础输入流类)都通过虚继承的方式继承自basic_ios
  • 虚继承在这里的作用是为了避免多重继承时可能出现的菱形继承问题,特别是当basic_iostream(同时支持输入和输出的流类)继承这两个类时
  • 模板参数CharT表示字符类型,Traits表示字符特性,默认使用std::char_traits<CharT>

这种设计是 C++ 标准库中解决菱形继承问题的典型应用。

四、继承和组合

对比维度继承(is-a)组合(has-a)
关系本质派生类是一种基类一个类包含另一个类的对象
复用方式白箱复用(基类内部可见)黑箱复用(仅通过接口访问)
耦合度高(基类改,派生类可能要改)低(彼此独立,靠接口通信)
适用场景is-a 关系、多态has-a 关系、降低耦合

 

1. 继承:is-a(“是” 的关系)

也就是说每个派生类对象都是一个基类对象 。

继承表示 “派生类是一种基类”。比如:

  • 狗是一种动物(狗 → 动物)
  • 学生是一种人(学生 → 人)

这意味着派生类拥有基类的所有特征和行为还能在此基础上增加自己的特点
就像 “狗” 继承了 “动物” 的 “会呼吸、会动” 等属性,同时又新增了 “会汪汪叫” 的特性。

 

2. 组合:has-a(“有” 的关系) 

组合表示 “一个类包含另一个类的对象”。比如:

  • 汽车有发动机(汽车 → 包含 → 发动机)
  • 电脑有 CPU(电脑 → 包含 → CPU)

这里两个类是 “整体 - 部分” 的关系,整体由部分组成,但部分不属于整体的 “一种”。
比如汽车不会说 “我是一种发动机”,而是 “我包含一个发动机”。

3. 白箱复用 vs 黑箱复用:封装性的区别 

这两种关系带来的代码复用方式完全不同,核心差异在 “封装性” 上。

1. 继承:白箱复用(看得见内部)

继承时,派生类可以直接访问基类的 protected 成员(半公开的内部细节),就像 “透明的箱子”—— 基类的内部结构对派生类是可见的。

优点:派生类能直接复用基类的实现,甚至重写基类的方法(比如狗可以重写动物 “叫” 的方法)。
缺点破坏基类封装,耦合度高。如果基类改了内部实现(比如动物类加了一个新的成员变量),派生类很可能也要跟着改。

2. 组合:黑箱复用(看不见内部)

组合时,一个类只通过另一个类的 public 接口来使用它,完全看不到内部实现,就像 “不透明的黑箱子”。

优点被组合的类封装得很好,彼此依赖弱(耦合度低)。比如汽车和发动机,只要发动机的接口(比如 “启动”“停止” 方法)不变,发动机内部怎么改(比如换成电动的),汽车类都不用动。
缺点:如果需要复用的功能比较复杂,可能需要写更多代码来组合多个类。

4)什么时候用继承?什么时候用组合

优先使用组合,而不是继承。实际尽量多去用组合,组合的耦合度低,代码维护性好。不过也不太那么绝对,类之间的关系就适合继承(is-a)那就⽤继承,另外要实现多态,也必须要继承。类之间的关系既适合⽤继承(is-a)也适合组合(has-a),就用组合。

 

// Tire(轮胎)和Car(车)更符合has-a的关系
class Tire {
protected:string _brand = "Michelin"; // 品牌size_t _size = 17; // 尺寸
};
class Car {
protected:string _colour = "白色"; // 颜色string _num = "陕ABIT00"; // 车牌号Tire _t1; // 轮胎Tire _t2; // 轮胎Tire _t3; // 轮胎Tire _t4; // 轮胎
};
class BMW : public Car {
public:void Drive() { cout << "好开-操控" << endl; }
};// Car和BMW/Benz更符合is-a的关系
class Benz : public Car {
public:void Drive() { cout << "好坐-舒适" << endl; }
};template<class T>
class vector
{};// stack和vector的关系,既符合is-a,也符合has-a
template<class T>
class stack : public vector<T>
{};template<class T>
class stack
{
public:vector<T> _v;
};int main()
{return 0;
}
http://www.xdnf.cn/news/16573.html

相关文章:

  • 将 JsonArray 类型的数据导出到Excel文件里的两种方式
  • 基于黑马教程——微服务架构解析(一)
  • 设计模式(十二)结构型:享元模式详解
  • Python day26
  • 无向图的连通性问题
  • 设计模式(十三)结构型:代理模式详解
  • spring gateway 配置http和websocket路由转发规则
  • NodeJs接入腾讯云存储COS
  • Ubuntu Linux 如何配置虚拟内存 —— 一步一步配置 Ubuntu Server 的 NodeJS 服务器详细实录8
  • USB设备调试
  • 全面理解JVM虚拟机
  • RK3568 Linux驱动学习——U-Boot使用
  • 六、搭建springCloudAlibaba2021.1版本分布式微服务-admin监控中心
  • Linux 基础命令大全
  • 内存泄漏问题排查
  • Context Engineering Notes
  • 【Golang】Go语言运算符
  • 迷宫生成与路径搜索(A算法可视化)
  • Triton IR
  • Libevent(4)之使用教程(3)配置
  • 如何使用ozone调试elf文件?
  • Dify 本地化部署深度解析与实战指南
  • LangChain实现RAG
  • 力扣 hot100 Day57
  • 第四科学范式(数据密集型科学):科学发现的新范式
  • Qt C++动态库SDK在Visual Studio 2022使用(C++/C#版本)
  • IIS发布.NET9 API 常见报错汇总
  • Java面试实战:从基础到架构的全方位技术交锋
  • add新增管理员功能、BaseController类的简介--------示例OJ
  • PDF转图片实用指南:如何批量高效转换?