【C++】详细讲解继承(下)

news2025/1/27 20:59:31

        本篇来继续说说继承。上篇可移步至【C++】详细讲解继承(上) 

1.继承与友元

友元关系不能继承 ,也就是说基类友元不能访问派⽣类私有和保护成员。
class Student;//前置声明

class Same //基类
{
public:
	friend void Fun(const Same& p, const Student& s);//友元声明
protected:
	string _name; 
};

class Student : public Same //派生类
{
protected:
	int _stuid;
};
void Fun(const Same& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuid << endl;
}

像上面的代码,Fun函数只能访问Same基类的成员变量_name,_stuid是访问不到的。

 解决方法就是在派生类Student里面也加上友元声明,就可以了。

class Student : public Same //派生类
{
	friend void Fun(const Same& p, const Student& s);//友元声明
protected:
	int _stuid;
};

2.继承与静态成员

基类定义了static静态成员,则整个继承体系⾥⾯ 只有⼀个这样的成员 。⽆论派⽣出多少个派⽣类,都只有⼀个static成员实例。
class Same //基类
{
public:
	string _name;
	static int _count;//静态成员变量
};

int Same::_count = 0;//静态成员变量初始化

class Student : public Same //派生类
{
protected:
	int _stuNum;
};

 我们观察一下_name的地址和_count的地址。

int main()
{
	Same p;
	Student s;
	cout << &p._name << endl;
	cout << &s._name << endl;

	cout << &p._count << endl;
	cout << &s._count << endl;

	return 0;
}

这⾥的运⾏结果可以看到:⾮静态成员_name的地址是不⼀样的,说明派⽣类继承下来了,基类和派⽣类对象各有⼀份静态成员_count的地址是⼀样的,说明派⽣类和基类共⽤同⼀份静态成员。

公有情况下,基类和派生类指定类域都可以访问静态成员变量。

cout << Same::_count << endl;
cout << Student::_count << endl;

也可以通过对象访问。

 

 改变其中一个,另一个也改变,因为这就是同一个。

cout << Same::_count << endl;
cout << Student::_count << endl;
Same::_count++; //改变_count
cout << p._count << endl;
cout << s._count << endl;

3.多继承以及菱形继承

3.1 继承模型

  • 单继承:⼀个派⽣类只有⼀个直接基类时称这个继承关系为单继承
  • 多继承:⼀个派⽣类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。
  • 菱形继承:菱形继承是多继承的⼀种特殊情况,如下。

菱形继承有数据冗余⼆义性(存在歧义)的问题,在Assistant的对象中Person成员会有两份。所以在实践中并不提倡设计出菱形继承的模型。

 二义性问题可以通过指定访问哪个基类的成员来解决,但是数据冗余问题是不能得到解决的。

如果在特定场景下,一定需要设计菱形继承,怎么办?虚继承就出场了。

3.2 虚继承

新增了一个关键字virtual。放在会造成数据冗余和二义性的那些类上

这里就是在Student和Teacher 加上virtual,都要加,只加一个都没用。

class Person
{
public:
	string _name; // 姓名
	
};
// 使⽤虚继承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; // 主修课程
};

 加了virtual后person在Assistant里继承的数据就只有一份了,数据冗余和二义性就得到了解决。

3.3 相关小测试

假如现在有一个菱形继承关系如下, virtual应该加在哪里?

加在B类和C类上。因为E里面会有数据冗余和二义性,而这些冗余的数据是因为A有两份,导致继承A的是B和C,所以要加在B和C上,不是D和C。

 最后,除非万不得已,不要设计出菱形继承。菱形继承以及virtual的底层是特别复杂的。

3.4 多继承中指针偏移问题

先看题。

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;
}

说法正确的是?

A :p1 == p2 == p3    B :p1 < p2 < p3    C :p1 == p3 != p2    D :p1 != p2 != p3
答案 C :p1 == p3 != p2 
先继承的在前面 ,先声明的在前面,所以Base1和Base2的底层位置如下。
p3是Derive的指针,指向开头。
p1是Base1的指针,Base1是基类,p1指向的范围是派生类Derive切出来的Base1的那部分,最开始指向开头,和p3一个位置。
p2与p1同理,指向的范围是Derive切出来的Base2的那部分,最开始指向Base2开头。
所以答案是 p1 == p3 != p2 
借上面那个题,说了多继承中的指针偏移问题。

4.继承和组合

  • public继承是⼀种is-a的关系。也就是说每个派⽣类对象都⼀个基类对象。
  • 组合是⼀种has-a的关系。假设B组合了A,每个B对象中都⼀个A对象。
  • 继承允许你根据基类的实现来定义派⽣类的实现。这种通过⽣成派⽣类的复⽤通常被称为⽩箱复⽤(white-box reuse)。在继承⽅式中,基类的内部细节对派⽣类可⻅ 。继承⼀定程度破坏了基类的封装,基类的改变,对派⽣类有很⼤的影响。派⽣类和基类间的依赖关系很强,耦合度⾼
  • 对象组合是类继承之外的另⼀种复⽤选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接⼝。这种复⽤⻛格被称为⿊箱复⽤(black-box reuse),因为对象的内部细节是不可⻅的。对象只以“⿊箱”的形式出现。 组合类之间没有很强的依赖关系,耦合度低
  • 优先使⽤组合,⽽不是继承。实际尽量多去⽤组合,组合的耦合度低,代码维护性好,但这也不是绝对的,要根据实际选择使用。

比如下面的代码,就能很好解释继承和组合。

class Tire //轮胎
{
protected:
	string _brand; // 品牌
	size_t _size; // 尺⼨
};
class Car //车
{
protected:
	string _colour; // 颜⾊
	string _num; // ⻋牌号
	Tire _t1; // 轮胎
	Tire _t2; // 轮胎
	Tire _t3; // 轮胎
	Tire _t4; // 轮胎
};

轮胎和车就比较符合 has-a 的关系,车轮胎,用的组合

class BMW : public Car //继承
{
public:
	void Drive() { cout << "BMW" << endl; }
};

class Benz : public Car //继承
{
public:
	void Drive() { cout << "Benz" << endl; }
};

上面的代码是继承,BMW 和 Benz 是品牌,比较符合 is-a 的关系,BMW和Benz车。

本次分享就到这里了,我们下篇见~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2283209.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录刷题day14(2)|(链表篇)02.07. 链表相交(疑点)

目录 一、链表理论基础 二、链表相交求解思路 三、相关算法题目 四、疑点 一、链表理论基础 代码随想录 二、链表相交求解思路 链表相交时&#xff0c;是结点的位置&#xff0c;也就是指针相同&#xff0c;不是结点的数值相同&#xff1b; 思路&#xff1a;定义两个指针…

【学习笔记】计算机网络(二)

第2章 物理层 文章目录 第2章 物理层2.1物理层的基本概念2.2 数据通信的基础知识2.2.1 数据通信系统的模型2.2.2 有关信道的几个基本概念2.2.3 信道的极限容量 2.3物理层下面的传输媒体2.3.1 导引型传输媒体2.3.2 非导引型传输媒体 2.4 信道复用技术2.4.1 频分复用、时分复用和…

2024年个人成长、工作总结

一、2024年个人成长、工作总结 1.博客文章 在这一年的创作中&#xff0c;共发布95篇文章&#xff0c;其中&#xff1a; Scrum敏捷项目管理&#xff1a; Scrum敏捷项目管理 前端技术vue jquery&#xff1a; jQuery&#xff08;一&#xff09;jQuery基本语法 分布式事务&…

Arduino Uno 和 1.44 英寸 TFT 屏幕(SPI 接口)初体验

在嵌入式项目中&#xff0c;1.44 英寸 TFT 屏幕&#xff08;SPI 接口&#xff09;是一种非常实用的显示设备&#xff0c;适合用于显示文本、图形和简单动画。本文将详细介绍如何使用 Arduino Uno 和 1.44 英寸 TFT 屏幕进行基本的显示操作&#xff0c;包括显示文本、绘制图形和…

在 Windows 系统上,将 Ubuntu 从 C 盘 迁移到 D 盘

在 Windows 系统上&#xff0c;如果你使用的是 WSL&#xff08;Windows Subsystem for Linux&#xff09;并安装了 Ubuntu&#xff0c;你可以将 Ubuntu 从 C 盘 迁移到 D 盘。迁移过程涉及导出当前的 Ubuntu 发行版&#xff0c;然后将其导入到 D 盘的目标目录。以下是详细的步骤…

Kimi 1.5解读:国产AI大模型的创新突破与多模态推理能力(内含论文地址)

文章目录 一、Kimi 1.5的核心技术创新&#xff08;一&#xff09;长上下文扩展&#xff08;Long Context Scaling&#xff09;&#xff08;二&#xff09;改进的策略优化&#xff08;Improved Policy Optimization&#xff09;&#xff08;三&#xff09;简化框架&#xff08;S…

AIGC数智化赋能:创新地方文旅内容生产传播模式

随着人工智能技术的迅猛发展&#xff0c;AI的应用领域日益扩大。当前&#xff0c;如何将AI这一新质生产力转化为新质传播力和影响力&#xff0c;进而为城市文化和旅游产业的内容创造、传播及消费模式带来全面革新&#xff0c;已成为数字化文旅发展的关键议题。 AI宣传——提升…

医学图像分析工具09.1:Brainstorm安装教程

1. 安装前准备 **官方安装包和数据&#xff1a;**https://neuroimage.usc.edu/bst/download.php **官方安装教程&#xff1a;**https://neuroimage.usc.edu/brainstorm/Installation Matlab 版本要求&#xff1a; 有 Matlab&#xff1a; R2009b (7.9) 或更高版本没有 Matlab&…

网络(三) 协议

目录 1. IP协议; 2. 以太网协议; 3. DNS协议, ICMP协议, NAT技术. 1. IP协议: 1.1 介绍: 网际互连协议, 网络层是进行数据真正传输的一层, 进行数据从一个主机传输到另一个主机. 网络层可以将数据主机进行传送, 那么传输层保证数据可靠性, 一起就是TCP/IP协议. 路径选择: 确…

7-Zip高危漏洞CVE-2025-0411:解析与修复

7-Zip高危漏洞CVE-2025-0411&#xff1a;解析与修复 免责声明 本系列工具仅供安全专业人员进行已授权环境使用&#xff0c;此工具所提供的功能只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利…

make controller vibrate and 判断是否grab

我自己的例子&#xff0c;新建cube上挂载oculus交互的代码&#xff0c;如下 然后加载自己写的代码到cube上就可以了 using Oculus.Interaction.HandGrab; using System.Collections; using System.Collections.Generic; using UnityEngine;public class Vibtation : MonoBehav…

43 继承

目录 一、继承的概念与定义 &#xff08;一&#xff09;继承的概念 &#xff08;二&#xff09;继承定义 1、定义格式 2、继承基类成员访问的变化 &#xff08;三&#xff09;继承类模板 二、基类和派生类间的转换 三、继承中的作用域 四、派生类的默认成员函数 &…

程序员转型测试:解锁漏洞挖掘新旅程

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 程序…

C++内存分布与进程地址空间

C内存分布与进程地址空间 1.C/C内存分布2.进程地址空间&#xff08;补充&#xff09; &#x1f31f;&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f;&#x1f31f; &#x1f680;&#x1f680;系列专栏&#xff1a;【Linux的学习】 &#x1f4dd;&#x1f…

软件测试 —— jmeter(2)

软件测试 —— jmeter&#xff08;2&#xff09; HTTP默认请求头&#xff08;元件&#xff09;元件作用域和取样器作用域HTTP Cookie管理器同步定时器jmeter插件梯度压测线程组&#xff08;Stepping Thread Group&#xff09;参数解析总结 Response Times over TimeActive Thre…

设计新的 Kibana 仪表板布局以支持可折叠部分等

作者&#xff1a;来自 Elastic Teresa Alvarez Soler, Hannah Mudge 及 Nathaniel Reese 在 Kibana 中构建可折叠仪表板部分需要彻底改造嵌入式系统并创建自定义布局引擎。这些更新改进了状态管理、层次结构和性能&#xff0c;同时为新的高级仪表板功能奠定了基础。 我们正在开…

用Python和Tkinter标准模块建立密码管理器

用Python和Tkinter标准模块建立密码管理器 创建一个简单的密码管理器应用程序&#xff0c;帮助用户存储和管理他们的密码。使用Python的tkinter模块来创建一个图形用户界面&#xff08;GUI&#xff09;。 本程序支持 添加、查看、搜索、复制、修改、删除 功能。 本程序使用 …

day1代码练习

输出3-100以内的完美数&#xff0c;(完美数&#xff1a;因子和(因子不包含自身)数本身) #include <stdio.h>// 判断一个数是否为完美数的函数 int panduan(int n) {if (n < 2) {return 0; // 小于2的数不可能是完美数}int sum 1; // 因子和初始化为1&#xff08;因…

为什么redis会开小差?Redis 频繁异常的深度剖析与解决方案

文章目录 导读为什么redis会开小差&#xff1f;1.连接数过多2.bigkey3.慢命令操作4.内存策略不合理5.外部数据双写一致性6.保护机制未开启7. 数据集中过期8. CPU饱和9. 持久化阻塞10. 网络问题结论 导读 提起分布式缓存&#xff0c;想必大多数同学脑海中都会浮出redis这个名字…

C# Interlocked 类使用详解

总目录 前言 在多线程编程中&#xff0c;确保多个线程对共享资源的安全访问是一个关键挑战。C# 提供了多种同步机制来处理并发问题&#xff0c;其中 System.Threading.Interlocked 类提供了一种轻量级的方法来进行原子操作。它允许您执行一些常见的增量、减量、交换等操作&…