C++_深究继承

news2025/1/20 1:44:50

文章目录

  • 1. 继承的概念和定义
    • 1.1 继承的概念
    • 1.2 继承定义
      • 1.2.1定义格式
      • 1.2.2 继承关系和访问限定符
  • 2. 基类和派生类对象赋值转换
  • 3.继承中的作用域
  • 4. 派生类的默认成员函数
  • 5. 继承和友元
  • 6. 继承与静态成员
  • 7. 菱形继承即菱形虚拟继承
      • 菱形虚拟继承
  • 8. 继承的总结与反思

1. 继承的概念和定义

1.1 继承的概念

继承是面向对象程序设计使代码可以复用的最重要的手段,允许程序员在保持原有类特性的基础上进行扩展,增加功能,通过继承产生的类,称为派生类。 继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。 在继承之前我们接触的复用只是函数复用,而继承是设计层面的复用。

其用法如下:

#include <iostream>
using namespace std;

class person
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }

protected:
    string _name = "";
    int _age = 0;
};

class student : public person
{
protected:
    int _id;
};

class teacher : public person
{
    int _jobid;
};

//其中,student和teacher就属于派生类(子类),而person就是这两个类的基类
//我们可以通过监视窗口查看student和teacher对象就可以观察到变量的复用,调用print可以喊道成员函数的复用。
int main()
{
    student s;
    teacher t;
    s.Print();
    t.Print();
    return 0;
}

在这里插入图片描述

1.2 继承定义

1.2.1定义格式

从下面就可以看到person是父类,也称为基类,而student是子类,也称为派生类。

在这里插入图片描述

1.2.2 继承关系和访问限定符

在c++中,有三种继承方式,分别是:public继承, protected继承,private继承

而访问限定符也有三种,public访问,protected访问,private访问

这两个一组合就导致了继承的机制极为复杂!

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

总结:

  1. 基类private成员在派生类外不管是什么方式继承都是不可见的,这里的不可见是指基类成员虽然还是被继承到了派生类中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它
  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在 派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的
  3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他 成员在子类的访问方式 == min(成员在基类的访问限定符,继承方式),public > protected > private
  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过 最好显示的写出继承方式。
  5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡 使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里 面使用,实际中扩展维护性不强

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

  • 派生类对象可以赋值给基类的对象/指针/引用。形象的说法可以说是切片或切割,意思是把派生类中父类那部分切过来赋值过去。

需要注意的是在赋值的时候并没有调用拷贝构造,而是直接将里面的内容拷贝过去。

  • 基类对象不能赋值给派生类对象

这也很容易理解,如果能赋值过去,那么子类对象多的成员不会知道应该赋值成什么

  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但必须是基类的指针指向派生类对象时才安全。
  • 在这里插入图片描述

3.继承中的作用域

  1. 在继承体系中基类和派生类都有独立的作用域
  2. 子类和父类中有同名成员,派生类将屏蔽基类对同名成员的直接访问,这种情况叫做**隐藏(也叫做重定义)。**如果在定义有重名成员变量或函数的时候想要访问基类的成员,可以使用访问限定符来显示访问。
class person
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }

protected:
    string _name = "";
    int _age = 0;
};

class student : public person
{
protected:
	string _name;
    int _id = 0;
public:
	void print()
	{
		cout << _id << endl;
	}
};
int main()
{
	//在这种情况下想要访问基类的print()函数,应该使用访问限定符
	student s;
	s.person::print();
	//所以在设计继承时最好不要定义同名成员,否则会出现二义性问题
}
  1. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  2. 注意在实际使用中继承体系里面最好不要定义同名成员

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

在学习c++类和对象的时候,我们知道对于类来说有六个我们不写也会自动生成的默认成员函数,那么在派生类中,这些默认成员函数是如何生成的呢?

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。(也就是说基类的成员变量只能通过基类构造函数构造,如果没有显示调用,则编译器会自动调用默认构造函数)。如果基类没有默认构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的初始化
  3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员(也就是不需要显示调用基类的析构函数)。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
  5. 派生类对象初始化先调用基类构造再调派生类构造。
  6. 编译器会对析构函数名进行特殊处理,处理程destrutor(),便于后面的重写做铺垫(注意,重写和重定义是两个不同的概念!!),因此父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。

5. 继承和友元

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

通俗一点理解,父亲的朋友不是你的朋友。

class student;
class person
{
    friend void display(const person& p, const student& s);
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }

protected:
    string _name = "";
    int _age = 0;
};

class student : public person
{
protected:
    int _id = 0;
};

class teacher : public person
{
    int _jobid = 0;
};

void display(const person& p, const student& s)
{
    cout << p._name << endl;
    cout << s._id << endl;
}

在这里插入图片描述

6. 继承与静态成员

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

#include <iostream>
using namespace std;
class person
{
public:
    static int _num;
    person() { ++_num; }
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "";
    int _age = 0;
};

int person::_num = 0;

class student : public person
{
protected:
    int _id = 0;
};

class teacher : public person
{
    int _jobid = 0;
};


int main()
{
    student s;
    teacher t;
    person p;
    cout << person::_num << endl;
    return 0;
}

在这里插入图片描述

7. 菱形继承即菱形虚拟继承

**单继承:**一个子类只有一个直接父类时称这个继承关系为单继承。

在这里插入图片描述

多继承:一个子类友两个或以上直接父类时称这个继承关系为多继承(只要保证父类中都没有同名成员就不会二义性问题)。
在这里插入图片描述

**菱形继承:**菱形继承时多继承的一种特殊情况

在这里插入图片描述

这种继承方式存在的问题是:可能会存在数据冗余和二义性的问题!看下图:
在这里插入图片描述

class person
{
public:
	string _name;
	int _age;
};

class student: public person
{
protected:
	int _id;
};

class teacher : public person
{
protected:
	int _num;
};

class assistant : public student, public teacher
{
protected:
	string _course;
};

int main()
{
	assistant s;
	cout << s._name << endl;

}

在这里插入图片描述
然后我们可以通过观看对象模型就可以看到问题出现在哪
在这里插入图片描述

如果想要解决二义性问题,可以使用指定访问,如s.student::_name,s.teacher::_name,但是,这样的方法仍然不能解决数据冗余的问题,想要解决这个问题,就需要使用虚拟继承。

菱形虚拟继承

虚拟继承可以解决虚拟继承的二义性和数据冗余的问题。如上面的继承关系,在student和teacher继承person时使用虚拟继承(在继承体系的腰部),即可解决问题。

class person
{
public:
	int _age = 0;
};

class student: virtual public person
{
public:
	int _id = 0;
};

class teacher : virtual public person
{
public:
	int _num = 0;
};

class assistant : public student, public teacher
{
public:
	int _t = 0;
};

int main()
{
	assistant s;
	cout << s._age << endl;
	return 0;

}

通过使用虚继承,就可以使得assistant对象中只存在一个person成员,但是虚拟继承是如何做到的呢?我们可以通过借助内存窗口观察对象模型得知。
在这里插入图片描述
通过分析可以看到,assistant对象将person对象的数据放到了对象组成的最下面单独出来,而本来student成员和teacher成员里面的person成员则变成了两个指针,而这两个指针指向的是什么呢?同样我们通过监视窗口观察一下。
在这里插入图片描述
通过观察我们就可以发现,这两个指针指向的地方存的下一个位置储着一个数据指向从该地方到虚继承成员的数据存储地址,存的是偏移量。通过测试,我们就知道了虚继承是如何解决数据冗余的问题的了。

虚拟继承中student和teacher对象模型中的这两个指针叫做虚基表指针,这两个表叫做虚基表。虚基表中存的是偏移量,通过偏移量找到下面的person。
下面是对菱形虚拟继承的原理解释:
在这里插入图片描述

8. 继承的总结与反思

  1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱 形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。
  2. 多继承可以认为是C++的缺陷之一,很多后来的OO(object oritanted)语言都没有多继承,如Java
  3. 继承和组合
  • public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
  • 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
  • 优先使用对象组合,而不是类继承
  • 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用称为白箱复用。术语“白箱”是相对可视性而言:在继承关系中,基类的大部分细节对派生类可见。继承一定程度的破坏了基类的封装,基类的改变对派生类的影响有可能很大,派生类和基类之间的依赖关系较强,耦合度很高
  • 对象组合是继承之外的另一种复用选择。新的更复杂的功能可以通过组装或者组合对象来获得。对象组合要求被组合的对象具有良好的接口。这种复用风格被称为黑箱复用,因为对象的内部细节是不可见的。对象只以“黑箱”的方式呈现。组合类之间的依赖关系较弱,耦合度第。优先使用组合有助于保持每个类的独立性。
  • 实际中应尽量多的取用组合。组合的耦合度低,代码维护性好。不过继承也有其用处,并且多态也只能通过继承实现。简单来说,只有组合无法完成的任务我们才使用继承。

以上就是关于c++继承的主要内容了,如果大家对本内容还有什么疑惑或者博主哪里说法有误的话,欢迎大家在评论区指出!

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

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

相关文章

scope(三)

前面两节讲了没有scoped的可以直接修改,现在讨论下把scoped这个属性去掉会是怎么样 1.准备的两个页面 放置的两个时间属性。 2.有hash值的页面 3.对比:另外的页面 可以看出只修改了当前页面的值 4.去掉scoped对比 两个页面多发生了更改,scoped对当前的页面起到一个保护的作用…

C++ primer 3.1节 课后练习

练习1.9 编写程序&#xff0c;使用while循环将50到100的整数相加。 #include <iostream> using namespace std; int main(){int i50,sum0;while (i < 100) {sum sum i;i;}cout << sum;return 0; } 练习1.10: 除了运算符将运算对象的值增加1之外&#xff0c…

mac 可以进行单片机(stm32)的开发吗?

当涉及到在Mac上进行单片机开发时&#xff0c;是完全可行的。以下是为什么Mac适合单片机开发的解释&#xff1a;开发工具&#xff1a;针对STM32单片机&#xff0c;你可以使用多种开发工具。一个常用的选择是Segger Embedded Studio&#xff0c;它是一个功能强大的集成开发环境&…

Windows系统提权(一)

权限提升概述 windows系统常见的权限&#xff1a; 用户权限 管理员权限 系统权限 访客权限 什么是提权 权限提升&#xff08;privilege escalation&#xff09;&#xff1a;攻击者通过安全漏洞把获取到的受限制的低权限用户突破限制&#xff0c;提权到高权限的管理员用户&…

opentcs初次运行

openTCS简介 openTCS是一个开放的交通控制系统&#xff0c;可以实现机器人的任务分配、路径规划、调度等一系列的功能&#xff0c;也有相应的仿真平台&#xff0c;既可以使用该软件及仿真平台进行基本的任务分配、路径规划等操作&#xff0c;也可以基于该平台进行二次开发&…

机器学习重要内容:特征工程之特征抽取

目录 1、简介 2、⭐为什么需要特征工程 3、特征抽取 3.1、简介 3.2、特征提取主要内容 3.3、字典特征提取 3.4、"one-hot"编码 3.5、文本特征提取 3.5.1、英文文本 3.5.2、结巴分词 3.5.3、中文文本 3.5.4、Tf-idf ⭐所属专栏&#xff1a;人工智能 文中提…

2023“钉耙编程”联赛 Day 3 L 题 Inference 题解

原题描述 给定 m m m 个特征&#xff0c;你想基于大量的数据,再通过 Alice \text{Alice} Alice 的前 m − 1 m−1 m−1 个特征的值推断出她的最后一个特征的值。 特征之间的关系可以表示为一个有向无环图&#xff0c;其中一个节点 A A A 指向一个节点 B B B 表示 B B B…

Python基础语法入门(第二十天)——文件操作

一、基础内容 在Python中&#xff0c;路径可以以不同的表现形式进行表示。以下是一些常用的路径表现形式&#xff1a; 1. 绝对路径&#xff1a;它是完整的路径&#xff0c;从根目录开始直到要操作的文件或文件夹。在Windows系统中&#xff0c;绝对路径以盘符开始&#xff0c;…

Linux系列讲解 —— FTP协议的应用

简单介绍一下FTP文件传输协议在linux系统中的应用。 目录 0. 基本概念1. FTP Server1.1 安装FTP Server1.2 FTP Server开启和关闭1.3 查看FTP Server是否开启1.4 FTP服务器配置 2. FTP Client2.1 lftp2.2 ftp2.3 sftp2.4 文件资源管理器集成的ftp和sftp 3. ftp常用命令 0. 基本…

【C++11保姆级教程】auto和decltype

文章目录 前言总结一、auto1.初识auto关键字 2.auto使用二、decltype1.初识decltype2.使用decltype 总结 前言 在C11中引入了一些新的关键字和特性&#xff0c;其中包括auto和decltype。这两个关键字提供了更方便、更灵活的类型推断机制&#xff0c;使得代码编写更加简洁和可读…

TCS3200颜色识别模块

TCS3200颜色识别模块 TCS3200简介 供电电源(2.7V to 5.5V)可配置颜色滤波器和输出信号频率高分辨率光强转换到频率(工作原理) TCS3200工作原理 TCS3200是TAOS公司推出的可编程彩色光到频率的转换器&#xff0c;它把可配置的硅光电二极管与电流频率转换器集成在一个单一的CMO…

Python找出列表中出现次数最多的元素三种方式

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 方式一&#xff1a; 原理&#xff1a;创建一个新的空字典&#xff0c;用循环的方式来获取列表中的每一个元素&#xff0c;判断获取的元素是否存在字典中的key&#xff0c;如果不存在的话&#xff0c;将元素作为key&#xf…

如何保证事件的闭环处理

所谓的闭环&#xff0c;就是指告警发出、认领、协作处理、问题恢复、复盘改进的整个过程。 排班&#xff0c;专人做专事 ​这个手段听起来并不高大上&#xff0c;但确实非常有效。值班期间虽然提心吊胆的&#xff0c;生怕背锅&#xff0c;但因为是轮班制&#xff0c;心里总有…

C语言笔试训练【第12天】

文章目录 1、请阅读以下程序&#xff0c;其运行结果是&#xff08; &#xff09;2、假设编译器规定 int 和 short 类型长度分别为32位和16位&#xff0c;若有下列C语言语句&#xff0c;则 y 的机器数为&#xff08; &#xff09;3、下列程序的输出结果是什么&#xff08; &…

LC-链表的中间节点(遍历)

LC-链表的中间节点&#xff08;遍历&#xff09; 链接&#xff1a;https://leetcode.cn/problems/middle-of-the-linked-list/description/ 描述&#xff1a;给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个…

OpenCV-Python中的图像处理-图像直方图

OpenCV-Python中的图像处理-图像直方图 图像直方图统计直方图绘制直方图Matplotlib绘制灰度直方图Matplotlib绘制RGB直方图 使用掩膜统计直方图直方图均衡化Numpy图像直方图均衡化OpenCV中的直方图均衡化CLAHE 有限对比适应性直方图均衡化 2D直方图OpenCV中的2D直方图Numpy中2D…

基于Python科研论文绘制学习 - task1

绘制原则 必要性&#xff08;避免图多字少&#xff09; 易读性&#xff08;完整准确的标题、标签&#xff09; 一致性&#xff08;配图需要和上下文一致&#xff09; 尝试运行代码的时候出现了很多bug&#xff0c;基本都是围绕Scienceplots库的&#xff0c;在更新pip、pandas…

Gin安装解决国内go 与 热加载

get 方式安装超时问题&#xff0c;国内直接用官网推荐的下面这个命令大概率是安装不成功的 go get -u github.com/gin-gonic/gin 可以在你的项目目录下执行下面几个命令&#xff1a; 比如我的项目在E:\Oproject\zl cmd E:\Oproject\zl>就在目录下执行 go env -w GO111…

HCIP学习--MPLS

MPLS-多协议标签交换 标签交换 基于2.5层的标签号进行路由行为,开始传输数据包的时候需要查询两张表&#xff0c;一个路由表一个ARP表然后人们就想可不可以少查点表&#xff0c;然后MPLS就出现了&#xff0c;MPLS就是是在数据包的2.5层压入一个标签号&#xff0c;路由器基于2…

人大进仓数据库ksql命令基础

测试环境信息: 系统为银河麒麟V10 数据库为Kingbase ES V8 数据库安装目录为/opt/Kingbase/ES/V8 ksql命令位于/opt/Kingbase/ES/V8/Server/bin下 使用--help获取帮助 续上图 1.查看数据库列表 ./ksql -U system -l 2.查看数据库版本 ./ksql -V 3.连接指定的数据库tes…