C++:继承的概念和用法

news2025/7/13 4:37:04

文章目录

  • 继承的定义
    • 基类和派生类的对象赋值转换
    • 继承中的作用域
    • 派生类的默认成员函数
    • 几个继承小点
    • 继承理论的演示
  • 菱形继承和菱形虚拟继承
  • 虚拟继承

本篇主要总结的内容有

  1. 什么是继承
  2. 继承的一些概念
  3. 菱形继承和虚拟继承
  4. 继承的优缺点对比

继承的定义

继承是代码复用的一种重要手段,它允许程序员在一些原有的基础上进行拓展,由此增加新的类;继承体现了面向对象程序设计的层次结构,体现了由简单到复杂的设计过程

代码复用不仅仅有函数复用,在之前的学习中对于代码复用的认知都是停留在函数进行调用从而进行代码复用,而继承就是类设计层次上的复用

下面代码就是一个基础的继承实例

#include <iostream>
using namespace std;

class Person
{
public:
	Person(int age = 1, int num = 1)
		:_age(age)
		, _num(num)
	{}
	void Print()
	{
		cout << _age << " " << _num << endl;
	}
protected:
	int _age;
	int _num;
};

class Teacher :public Person
{
protected:
	int _tele;
};

int main()
{
	Person p;
	Teacher t;
	p.Print();
	t.Print();
	return 0;
}

从中就印证了前面对于继承的概念,继承后的父类的Person的成员,都会变成子类中的一部分,体现出了代码的复用

继承的定义方式其实就是前面的写法:

在这里插入图片描述

  • 继承方式主要有public继承,protected继承和private继承
  • 访问限定符有public访问,protected访问和private访问

继承基类成员的访问方式的变化

类成员/继承方式public继承protected继承private继承
基类的public成员在派生类中public成员派生类的protected成员派生类的private成员
基类的protected成员在派生类中protected成员派生类的protected成员派生类的private成员
基类的private成员在派生类中不可见在派生类中不可见在派生类中不可见
  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它
  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected,可以看出保护成员限定符是因继承才出现的

基类和派生类的对象赋值转换

这在继承中是一个比较重要的概念,简单来说就是,派生类对象可以赋值给基类的对象指针引用等,原因是在赋值的过程中,可以把对象进行一定的切割,变成基类的成员,再进行赋值即可

// 派生类赋值给基类
int main()
{
	Person p;
	Teacher t;
	p = t;
	return 0;
}

上面演示的就是将派生类赋值给基类,这是行得通的,但是将基类赋值给派生类的操作是不被允许的,因为基类成员并不能包括派生类成员:

// error
// 基类赋值给派生类
int main()
{
	Person p;
	Teacher t;
	t = p;
	return 0;
}

继承中的作用域

  • 在继承中,基类和派生类都有自己独立的作用域
  • 子类和父类中有同名成员的时候,子类成员将屏蔽父类对同名成员的直接访问,这样被称之为隐藏,也叫做重定义
  • 对于成员函数的隐藏,只要函数名相同就是隐藏

举例来解释:

#include <iostream>
using namespace std;

class Person
{
public:
	Person(int age = 1, int num = 1)
		:_age(age)
		, _num(num)
	{}
	void Print()
	{
		cout << "Person::Print" << _age << " " << _num << endl;
	}
protected:
	int _age;
	int _num;
};

class Teacher :public Person
{
public:
	Teacher(int age = 10, int tele = 20)
		:_age(age)
		, _tele(tele)
	{}
	void Print()
	{
		cout << "Teacher::Print" << _age << " " << _tele << endl;
	}
protected:
	int _age;
	int _tele;
};

int main()
{
	Person p;
	Teacher t;
	p.Print();
	t.Print();
	return 0;
}

派生类的默认成员函数

6个默认成员函数,就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
  3. 派生类的operator=必须要调用基类的operator=完成基类的复制
  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序
  5. 派生类对象初始化先调用基类构造再调派生类构造
  6. 派生类对象析构清理先调用派生类析构再调基类的析构

关于此部分的具体实例化表示在后演示

几个继承小点

1. 继承和友元关系:

继承是不会继承友元函数的,基类的友元不能访问子类的私有和保护成员

2. 继承和静态成员的关系:

基类中假如定义了一个静态成员,那么在整个继承体系中只有一个这样的成员,不管有多少个派生的子类,都只有一个static成员的实例

继承理论的演示

#include <bits/stdc++.h>
using namespace std;

class Person
{
public:
	// 继承是不会继承友元函数的,基类的友元不能访问子类的私有和保护成员
	Person(const string& name = "Tom")
		:_name("Tom")
	{
		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 (&p != this)
		{
			_name = p._name;
		}
		return *this;
	}
	~Person()
	{
		cout << "~Person()" << endl;
	}
	friend void Display(const Person& p)
	{
		cout << "friend void Display(const Person& p)" << " " << p._name << endl;
	}
protected:
	// 基类中假如定义了一个静态成员,那么在整个继承体系中只有一个这样的成员
	// 不管有多少个派生的子类,都只有一个static成员的实例
	static int _static;
	string _name;
};

int Person::_static = 10;

class Student :public Person
{
public:
	// 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员
	// 如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用
	Student(const string& name = "Tom", int num = 123)
		:Person(name)
		, _num(num)
	{}
	// 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
	Student(const Student& s)
		:Person(s)
		, _num(s._num)
	{
		cout << "Student(const Student& s)" << endl;
	}
	// 派生类的operator=必须要调用基类的operator=完成基类的复制
	Student& operator=(const Student& s)
	{
		cout << "Student& operator=(const Student& s)" << endl;
		if (&s != this)
		{
			Person::operator=(s);
			_num = s._num;
		}
		return *this;
	}
	// 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员
	// 因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序
	~Student()
	{
		cout << "~Student()" << endl;
	}
protected:
	int _num;
};

菱形继承和菱形虚拟继承

菱形继承指的是一种多继承,一个子类有两个或以上直接父类时称这个继承关系为多继承

在这里插入图片描述
会造成什么问题?

这样会造成的问题有,Student类和Teacher类在创建的过程中,都会继承Person类的内容,也就是说,Student类和Teacher类当中都会有一份Person类的数据,当Assistant类继承了数据后,在Assistant类中会存在两份相同的Person类对象的内容,一份来源于Student类,一份来源于Teacher

这样会带来两个问题,首先是数据冗余,实际上在Assistant类中只需要一份Person类的数据即可,但是这里有两份,带来的是数据冗余,其次是二义性问题,当在Assistant类中调用Person类的内容数据是,会带来不知道是调用哪一个类中继承下来的Person类的问题,这是菱形继承带来的两个问题

如何解决?

对于解决二义性的问题,可以通过指定访问哪一个父类来解决二义性问题,但是数据的冗余是无法被彻底解决的,因此就要引入虚拟继承的概念

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

void Test()
{
	Assistant a;
	a._name = "Tom";
}

虚拟继承

现在定义下面这些类:

class A
{
public:
	int _a;
};

class B : virtual public A
{
public:
	int _b;
};

class C : virtual public A
{
public:
	int _c;
};

class D : public B, public C
{
public:
	int _d;
};

int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

通过内存窗口进行观察:

在这里插入图片描述
从中看出,数据冗余的情况是存在的,类B中有A的内容,类C中也有A的内容,因此就造成了数据冗余的情况出现,针对这种情况,C++也做出了一定的调整,研发出了一个虚基表的内容,简单来说,就是菱形虚拟继承的内存对象成员模型,就是在D对象中将A放到了对象组成的最下面,这个A同时属于BC,而BC中存储了两个指针,这个指针指向一个表,这两个表就是虚基表,这个指针就是虚基表指针,虚基表中存储的是偏移量,通过偏移量就可以找到A的位置进而进行访问:

在这里插入图片描述
用下图来进行菱形虚拟继承的原理解释

在这里插入图片描述
经过这样的解决也算是解决了问题,但是菱形继承带来的问题很多,在实际生产应用中最好不适用它来解题

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

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

相关文章

Redis 主从复制,哨兵,集群——(1)主从复制篇

目录 1. Redis 主从复制是什么&#xff1f; 2. Redis 主动复制能干嘛&#xff1f; 2.1 读写分离 2.2 容灾恢复 2.3 数据备份 2.4 水平扩展支撑高并发 3. Redis 主从复制配置项 3.1 配从库不配主库 3.2 权限密码配置 3.3 基本操作命令 4. 案例演示 4.1 案例说明 4.…

leetCode 647.回文子串 动态规划 + 优化空间 / 中心扩展法 + 双指针

647. 回文子串 - 力扣&#xff08;LeetCode&#xff09; 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。回文字符串 是正着读和倒过来读一样的字符串。子字符串 是字符串中的由连续字符组成的一个序列。具有不同开始位置或结束位置的子串&#x…

Springboot-MyBatisPlus-01

一 创建项目&#xff0c;选择spring boot 初始化&#xff0c;配置相关信息 第五步创建实体类 二 快速开发实体类的jar包--lombok <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.12<…

Pycharm中终端不显示虚拟环境名解决方法

文章目录 一、问题说明&#xff1a;二、解决方法&#xff1a;三、重启Pycharm 一、问题说明&#xff1a; Pycharm中打开项目配置完需要的虚拟环境后&#xff0c;在Terminal&#xff08;终端&#xff09;中无法切换及显示当前需要运行代码的虚拟环境。 比如以下一种情况&#…

TOUGH2软件教程、CO2地质封存

TOUGH系列软件是由美国劳伦斯伯克利实验室开发的&#xff0c;旨在解决非饱和带中地下水、热运移的通用模拟软件。和传统地下水模拟软件Feflow和Modflow不同&#xff0c;TOUGH系列软件采用模块化设计和有限积分差网格剖分方法&#xff0c;通过配合不同状态方程&#xff08;EOS模…

人体分割模型ACE2P与M2FP,解析人脸人体各部件属性,语义化分析

前言 在做某任务的时候&#xff0c;需要对人物图片进行预处理。 预处理的要求就是要将图片中的人物各部件分割出来&#xff0c;标识为各种不同颜色&#xff0c;比如脸部为蓝色&#xff0c;脖颈部位绿色&#xff0c;其他地方为红色 最初任务使用的PaddleSeg中基于CelebAMask-HQ…

C# RestoreFormer 图像(人脸面部)修复

效果 项目 代码 using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Windows.Forms;namespace 图像修复 {pu…

优维低代码实践:片段

优维低代码技术专栏&#xff0c;是一个全新的、技术为主的专栏&#xff0c;由优维技术委员会成员执笔&#xff0c;基于优维7年低代码技术研发及运维成果&#xff0c;主要介绍低代码相关的技术原理及架构逻辑&#xff0c;目的是给广大运维人提供一个技术交流与学习的平台。 优维…

Unity3D 基础——使用 Vector3.Distance 计算两个物体之间的距离

Vector3-Distance - Unity 脚本 APIhttps://docs.unity.cn/cn/current/ScriptReference/Vector3.Distance.html 1.在场景中新建两个 Cube 立方体&#xff0c;在 Scene 视图中将两个 Cude的位置错开。 2.新建 C# 脚本 Distance.cs&#xff08;写完记得保存&#xff09; using …

PS 学习笔记

书籍&#xff1a;Photoshop 2022从入门到精通-敬伟-微信读书 1. PS 常用快捷键 复位右侧基本工作栏&#xff1a;【窗口】-【工作区】- 【复位基本功能】 Ctrl 鼠标滚轮&#xff1a;主界面图片左右滚动Shift 鼠标滚轮&#xff1a;主界面图片上下滚动Alt 鼠标滚轮&#xff1…

StarUML的介绍与使用

文章目录 简介视图StarUML创建视图类图用例图时序图 简介 UML&#xff1a;统一建模语言&#xff0c;用模型元素组成的不同视图从各个维度来描述系统 StarUML为常用系统建模工具之一 视图 常见视图的概念可参考&#xff1a;UML常见的几种视图 包括&#xff1a;用例图、顺序图…

联想G50笔记本直接使用F键功能(F1~F12)需要在BIOS设置关闭热键功能可以这样操作!

如果开启启用热键模式按F1就会出现FnF1的效果&#xff0c;不喜欢此方式按键的用户可以进入BIOS设置界面停用热键模式即可。 停用热键模式方法如下&#xff1a; 1、重新启动笔记本电脑&#xff0c;当笔记本电脑屏幕出现Lenovo标识的时候&#xff0c;立即按FnF2进入BIOS设置界面…

ssm+vue的养老院老人健康监护平台(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的养老院老人健康监护平台&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系…

从入门到进阶 之 ElasticSearch 节点配置 集群篇

&#x1f339; 以上分享 ElasticSearch 安装部署&#xff0c;如有问题请指教写。&#x1f339;&#x1f339; 如你对技术也感兴趣&#xff0c;欢迎交流。&#x1f339;&#x1f339;&#x1f339; 如有需要&#xff0c;请&#x1f44d;点赞&#x1f496;收藏&#x1f431;‍&a…

基于SSM+Vue的咖啡销售系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

全面中文大语言模型评测来啦!香港中文大学最新研究

ChatGPT 的一声号角吹响了2023年全球大语言模型的竞赛。 2023年初以来&#xff0c;来自工业界和研究机构的各种大语言模型层出不穷&#xff0c;特别值得一提的是&#xff0c;中文大语言模型也如雨后春笋般&#xff0c;在过去的半年里不断涌现。 与此同时&#xff0c;和如何训…

Android 10 中的隐私权变更

Android 10 中的隐私权变更 重大变更外部存储访问权限范围限定为应用文件和媒体在后台运行时访问设备位置信息需要权限以 Android 9 或更低版本为目标平台时自动授予访问权限在设备升级到 Android 10 后访问针对从后台启动 Activity 的限制标识符和数据移除了联系人亲密程度信息…

Go语言入门心法(六): HTTP面向客户端|服务端编程

Go语言入门心法(一): 基础语法 Go语言入门心法(二): 结构体 Go语言入门心法(三): 接口 Go语言入门心法(四): 异常体系 Go语言入门心法(五): 函数 一:go语言面向web编程认知 Go语言的最大优势在于并发与性能,其性能可以媲美C和C,并发在网络编程中更是至关重要 使用http发送请…

hal开发之hidl/aidl支持的绑定式直通式详细讲解

为啥有hidl呢&#xff1f; 这个问题其实网络上答案比较多&#xff0c;属于android想要让厂商快速升级解耦制定的&#xff0c;即把原来系统framework和厂商耦合的hal在同一个个system.img进行剥离开&#xff0c;把厂商相关的放到vendor.img&#xff0c;aosp系统公共部分framewo…

ros_rtsp订阅Image类型topic转换为rtsp视频流

文章目录 一、安装环境二、在catkin工作空间中构建三、设置流四、推出视频流五、验证视频流1、安装vlc拉流2、安装gstreamer拉流3、安装FFmpeg拉流 一、安装环境 ROS gstreamer development libs&#xff0c;包括base、good、bad和rtspserver: sudo apt-get install libgstre…