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

C++(进阶) 第1章 继承

C++(进阶) 第1章 继承


文章目录

  • 前言
  • 一、继承
    • 1.什么是继承
    • 2.继承的使用
  • 二、继承方式
    • 1.private成员变量的(3种继承方式)继承
    • 2. private继承方式
    • 3.继承基类成员访问⽅式的变化
  • 三、基类和派生类间的转换
    • 1.切片
  • 四、 继承中的作⽤域
    • 1.隐藏规则:
    • 2.考察继承作⽤域相关选择题
  • 五、派⽣类的默认成员函数
    • 1.默认成员函数
    • 2.派生类的默认成员函数
      • 1.构造函数
      • 2.析构函数
      • 3.拷贝构造
      • 4.赋值
  • 六、继承与友元
    • 1.友元函数不会被继承
  • 七、继承与静态成员
  • 八、多继承
    • 1.什么叫多继承
    • 2.多继承的坑
    • 3、虚继承
  • 总结


前言

在初级篇提过面向对象的三大特性:封装继承多态,在初阶篇可以非常直观的感受到封装是什么那么继承到底是什么呢?


一、继承

1.什么是继承

继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段,它允许我们在保持原有类特性的基础上进⾏扩展,增加⽅法(成员函数)和属性(成员变量),这样产⽣新的类,称派⽣类。继承呈现了⾯向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触函数层次的复⽤,继承是类设计层次的复⽤。


举个例子:
假如我现在有三个类分别是:骑手 商家 用户 ,这三个类都有下面这些基础信息,这些基础信息太过于冗余重复,这个时候就可以定义一个公共类然后让这三个类去继承
在这里插入图片描述


就像这样
在这里插入图片描述

2.继承的使用

class person
{
public:void print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "张三";int _age = 18;
};
class Student: public  person
{
protected:int _stuid;
};

假如现在有上面这俩个类,可以看到student
类里面是没有print函数的
在这里插入图片描述
可以看到这里s也是可以去调用print函数的


假如现在我们成员变量修改成public
在这里插入图片描述

在这里插入图片描述
这里可以看到俩个_name并不是一个,但是这里的成员函数是一个,可见父类和子类并不是同一个成员

但是成员函数是同一个,因为函数并不是存在一个对象里面,他们都是公用的

总结:继承本质上其实也是一种复用

二、继承方式

在这里插入图片描述
继承方式有三种和类里面的访问限定符是一样的


他们之间可以这样9组组合
在这里插入图片描述


这里可以把这表格堪称俩个部分
在这里插入图片描述
下面这个蓝色的可以看见都是不可见的,那么不可见是什么意思呢?


1.private成员变量的(3种继承方式)继承

private三种继承方式都是不可见这里只演示一个

class person
{
public:void print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
//private:string _name = "张三";int _age = 18;
};
class Student: public  person
{
public:void func(){cout << _age << endl;}
protected:int _stuid;
};
int main()
{Student s;s.func();return 0;
}

比如说上面的代码,是可见的,运行以后就是这样,可以去访问
在这里插入图片描述


现在我把private加上,这里就不让继承的类用了,这个就叫不可见
在这里插入图片描述


这里有一个容易混淆的概念,不可见不代表没有继承
在这里插入图片描述

可以看到上面还是给继承了的,但是就是无法直接调用,但是这里可以简直的去调用它,这里就可以直接去调用父类的成员函数实现间接的去调用
在这里插入图片描述


2. private继承方式

在1的代码的基础上,我把public的基础方式改成private
在这里插入图片描述

在这里插入图片描述

可以看到原本的public的成员函数也会变成private

3.继承基类成员访问⽅式的变化

通过上面的例子可以发现访问权限和继承权限他们是取权限小的那个
public < protected < private

现在在回头看原来的那个表格
在这里插入图片描述
可以看到最后的结果全部都是取到了最小的权限哪里


三、基类和派生类间的转换

1.切片

现在假如有一个父类和一个子类

class person
{
public:void print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "张三";int _age = 18;};
class Student: public person
{protected:int _stuid;
};

现在思考这样几个问题

  1. 能否把子类赋值给父类呢?
  2. 子类的指针能否赋值给父类?
  3. 子类的引用能否赋值给父类?

在这里插入图片描述
答案是可以的

这里引入一个新的概念赋值兼容转换


为了方便理解可以看下面这个图片
在这里插入图片描述
一般情况下父类的东西会比子类的少,因为父类一般都是放基本信息的,子类一般都是继承了父类信息的基础上在添加了一些东西就会像上面这样

#define _CRT_SECURE_NO_WARNINGS 
#include<bits/stdc++.h>
using namespace std;class person
{
public:void print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}string _name = "张三";
protected:int _age = 18;};
class Student: public person
{protected:int _stuid;
};
int main()
{person p;Student s;p = s;person* ptr = &s;person& ref = s;return 0;
}

现在我把保护去掉

可以发现上面的代码是没有报错的

值得注意的是这里并不是之前说的隐式类型转换这里是一个新的概念赋值兼容转换
在这里插入图片描述
在这里插入图片描述


如果子类赋值给父类那么编译器就会走特殊处理,他并不是传统类型的转换 ,但是这里父类不能给子类赋值


四、 继承中的作⽤域

1.隐藏规则:

  1. 在继承体系中基类和派⽣类都有独⽴的作⽤域。
  2. 派⽣类和基类中有同名成员,派⽣类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。(在派⽣类成员函数中,可以使⽤ 基类::基类成员 显⽰访问)
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成藏。
  4. 注意在实际中在继承体系⾥⾯最好不要定义同名的成员。

假如现在我代码是这样的

class person
{
public:void print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:int _age = 18;string _name = "张三";};
class Student: public person
{protected:string _name = "李四";int _stuid;
};

我这里student继承了父类,那么子类其实这里的函数并不会直接拷贝过来,他们其实还是共用一个那么,这里再用子类去调用print函数,假如这个时候刚好子类和父类里面刚好有同名变量那么这个print函数会去调用那个呢?

首先这样写并不会报错这里会按照就近原则去查找
在这里插入图片描述

2.考察继承作⽤域相关选择题

class A
{
public:void fun(){cout << "func()" << endl;}
};
class B : public A
{
public:void fun(int i){cout << "func(int i)" <<i<<endl;}
};
int main()
{B b;b.fun(10);b.fun();return 0;
};
1. A和B类中的两个func构成什么关系(B)
A. 重载 B. 隐藏 C.没关系2.下⾯程序的编译运⾏结果是什么(A)
A. 编译报错 B. 运⾏报错 C. 正常运⾏

五、派⽣类的默认成员函数

首先第一个问题:父类的构造函数子类能不能用?答案是:能

1.默认成员函数

在这里插入图片描述
这里就只考虑前四个后面俩个没有那么重要


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

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 Studen :public Person
{
protected:int _x;string _addrss;
};

派生类在原来的基础上在加俩个成员变量一个内置类型一个自定义类型

派生类的默认成员函数可以这样看

  1. 父类成员(整体)
  2. 子类自己的自定义类型
  3. 子类自己的内置类型
    只会调用默认构造函数

有变化的就只有1 下面的俩个都和普通的类一样


可以看到这里定义了一个子类,但是这里调用的是一个父类的构造和析构

在这里插入图片描述


从上面代码可以发现子类是会继承父类的构造函数和析构函数的


走完上面的1就直接去走23,内置初始化成随机值(不同编译器处理方式不一样)自定义就去找自定义的处理方式

值得注意的是这里只会调用默认构造带参数的构造是不会掉用的

在这里插入图片描述


假如说我现在要显示的写这个构造函数

在这里插入图片描述

它是不可以这么写的,这里报错是因为_name 是父类的成员函数
这里是编译器的规定记住就行了


假如说这里非要去显示调用构造函数这里就要这么写
在这里插入图片描述

在这里插入图片描述


2.析构函数

如果不写析构函数系统就会生成默认的析构函数,但是如果要显示的写
但是这里有这一个问题这里根本就编译不过

由于多态的原因析构函数会被编译器统一处理成destructorr() ,但是如果他们俩个都叫这个那么这里就会构成隐藏,所以这里连续调用了俩次就会报错

下面全部都是错误演示
在这里插入图片描述所以这里要指定作用域在这里插入图片描述


但是过了以后这里又开始扯淡了这里直接调用了这么多
在这里插入图片描述


这里可以发现多调用了,这里不得不说一个机制就是这里编译器做了处理子类析构函数里面会自动调用父类的析构,这里就是析构特殊的地方,其他几个都是显示切片调用就只有它是隐藏


3.拷贝构造

如果我们在子类里面不写拷贝构造那么编译器就会去调用父类的拷贝构造
在这里插入图片描述
所以一般情况下子类不需要写拷贝构造除非是设计到深拷贝


子类的就要这么写,子类调用拷贝构造就要去调用父类的拷贝构造就要这样去切片,因为这里要传一个父类过去但是我们没有所以我们这里就要用到上面说的复制兼容转换转一个自己(子类)编译器就会自己去切割
在这里插入图片描述

4.赋值

这里就是一个典型的隐藏错误
这里我是要子类切片去调用父类的拷贝,然后这里触发了上面说的隐藏,这里变成了自己调用自己,所以这里要指定作用域

Studen& operator=(const Studen& st)
{if (this != &st){operator=(st);_x = st._x;_addrss = st._addrss;}return *this;
}
Studen& operator=(const Studen& st)
{if (this != &st){Person:: operator=(st);_x = st._x;_addrss = st._addrss;}return *this;
}

在这里插入图片描述


六、继承与友元

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;cout << s._stuNum << endl;
}

假如现在有这个代码编译器报错在这里插入图片描述

这里就很简单友元函数不会给继承

所以想要解决这个问题就要在子类也写一个友元函数

class Student;
class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};class Student : public Person
{
public:friend void Display(const Person& p, const Student& s);
protected:int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl; cout << s._stuNum << endl;
}

七、继承与静态成员

class Person
{
public:string _name;static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected:int _stuNum;
};

这里可以思考一下这里的继承的_count是用一个吗?


这里的答案是他们是同一个静态成员函数是不会在继承一份的,这里可以打印地址看出来
在这里插入图片描述


八、多继承

1.什么叫多继承

在这里插入图片描述
如果一个类像上面这样连续继承这还不算多继承


像这样继承了俩个类就叫多继承,它的语法就是加个逗号继续继承

在这里插入图片描述

2.多继承的坑

在这里插入图片描述
assistant继承了student 和 teacher 但是这俩个类又继承了person也就是assistant有俩个person,这里就会出现
数据冗余和二义性
这里访问就会有二义性

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

但是即使可以解决赋值的问题访问也还会有问题,为了解决这个问题就需要下面这个

3、虚继承

很多⼈说C++语法复杂,其实多继承就是⼀个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂,性能也会有⼀些损失,所以最好不要设计出菱形继承。多继承可以认为是C++的缺陷之⼀,后来的⼀些编程语⾔都没有多继承,如Java。


总结

构造的顺序是先父后子,析构的顺序是先子后父,这里编译器做了强制处理

相关文章:

C++(进阶) 第1章 继承

C&#xff08;进阶) 第1章 继承 文章目录 前言一、继承1.什么是继承2.继承的使用 二、继承方式1.private成员变量的&#xff08;3种继承方式&#xff09;继承2. private继承方式3.继承基类成员访问⽅式的变化 三、基类和派生类间的转换1.切片 四、 继承中的作⽤域1.隐藏规则&am…...

获国家权威机构认可 亚信安全荣获CNVD技术组支撑单位认证

近日&#xff0c;国家信息安全漏洞共享平台&#xff08;CNVD&#xff09;依据《CNVD管理办法》及《CNVD支撑单位能力要求》&#xff0c;对申请加入考察期的单位进行了全面而严格的能力评估。经过层层筛选与审核&#xff0c;亚信安全凭借卓越的技术实力与专业的服务能力&#xf…...

2. Autogen官网教程 (Terminating Conversations Between Agents)

在这一章中&#xff0c;我们将探讨如何结束自动生成代理之间的对话。 导入必要的库 import osfrom autogen import ConversableAgent配置智能体 我们需要配置智能体使用的语言模型&#xff08;LLM&#xff09;。以下是一个配置示例&#xff1a; llm_config {"config_…...

java 排序 详解

Java 提供了多种方式对数据进行排序&#xff0c;包括数组和集合的排序。排序在日常开发中非常常见&#xff0c;以下将从排序算法的基本原理、Java 中的内置排序方法以及自定义排序三方面进行详解。 1. 排序的基本概念 排序是将一组数据按特定顺序排列的过程&#xff0c;常见顺…...

【数据集】城市通量塔站点观测数据

【数据集】城市通量塔站点观测数据 数据概述数据下载参考数据概述 数据集简介:Harmonized gap-filled dataset from 20 urban flux tower sites 数据集名称:Harmonized gap-filled dataset from 20 urban flux tower sites (用于 Urban-PLUMBER 项目的 20 个城市通量塔站点…...

scau编译原理综合性实验

一、题目要求 题目&#xff1a; 选择部分C语言的语法成分&#xff0c;设计其词法分析程序、语法语义分析程序。 要求&#xff1a; 设计并实现一个一遍扫描的词法语法语义分析程序&#xff0c;将部分C语言的语法成分&#xff08;包含赋值语句、if语句、while循环语句&#xf…...

ETAS工具导入DBC生成Com协议栈

文章目录 前言DBC配置关键属性Cobra参数配置Cobra使用isolar工程配置总结前言 ETAS工具导入DBC主要也是生成arxml用的,ETAS推荐使用Cobra导入,本文介绍导入过程及注意事项 DBC配置关键属性 对于普通Com报文,配置为周期发送,及其周期,NmMessage配置为No,示例如下: 对…...

表单校验规则

这里简单记录下vue使用表单时候&#xff0c;给表单添加校验规则&#xff0c;直接上代码 <script setup>import { ref } from vue// 定义表单对象const form ref({account: ,password: ,agree: true})// 定义表单验证规则const rules {account: [{required: true, mess…...

接口的扩展

1. 接口中新增的方法 JDK7之前接口中只能定义抽象方法。 JDK8的新特性&#xff1a;接口中可以定义有方法体的方法。&#xff08;默认、静态&#xff09; JDK9的新特性&#xff1a;接口中可以定义有私有方法体的方法。 有方法体的方法&#xff1a;接口升级时&#xff0c;为了兼容…...

新能源电机轴承电腐蚀,如何破?

近年来&#xff0c;随着全球范围内对可再生能源的重视与推动&#xff0c;新能源电机作为新能源汽车、风力发电和太阳能发电等系统的重要组成部分&#xff0c;得到了迅猛的发展。然而&#xff0c;在实际应用中&#xff0c;新能源电机的维护与管理越来越受到关注&#xff0c;其中…...

Java中的File和IO流

File对象 File对象本质是一个文件或文件夹&#xff0c;用于写入和读取文件内容 注意&#xff1a;对于相对路径而言&#xff0c;在单元测试方法中的File是相对于Module&#xff0c;在main中的File是相对于Project 构造器 File(String pathname)File file1 new File("D:…...

ls命令实操笔记

ls命令&#xff1a;全称list&#xff0c;显示文件的文件名与相关属性。&#xff08;目前工作目录所含之文件及子目录&#xff09; 4567 45678 7891 a1b2 a2b3c abcd Abcd acde aD7E bcde 通过ls浏览上述文件所在的目录&#xff0c;实现以下需求&#xff1a; 浏览含…...

线段数--算法

线段树是常用来维护 区间信息 的数据结构 线段树可以在 O(logN) 的时间复杂度内实现 单点修改区间修改区间查询 区间求和求区间最大值求区间最小值 简单介绍一下线段树 线段树是一个将区间内的数不断细分的一种数据结构&#xff0c;也就是一个完全二叉树&#xff0c;用每一…...

JS的DOM操作和事件监听综合练习 (具备三种功能的轮播图案例)

下面是是对dom操作的一个综合练习 下面代码是html的基本骨架&#xff08;没有任何的功能&#xff09;&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" c…...

低温存储开关机问题

问题&#xff1a; 某消费电子产品在进行可靠性实验室&#xff0c;在低温-30C存储两个小时后&#xff0c;上电不开机。在常温25C时&#xff0c;开关机正常。 分析&#xff1a; 1、接串口抓log信息&#xff0c;从打印信息可以看出uboot可以起来&#xff0c;在跑kernel时&#x…...

mysql系列1—mysql架构和协议介绍

背景&#xff1a; 本文开始整理mysql相关的文章&#xff0c;用于收集数据库相关内容&#xff1b;包括mysql架构和存储方式、索引结构和查询优化、数据库锁等内容。思考如何根据具体的业务给出最优的分表规划和表设计、字段选择和索引设计、优化的SQL语句&#xff0c;以及数据库…...

设计模式——模板模式

定义与基本概念 模板模式&#xff08;Template Pattern&#xff09;是一种行为设计模式。它在一个抽象类中定义了一个操作的算法骨架&#xff0c;将一些步骤的实现延迟到具体子类中。这个抽象类就像是一个模板&#xff0c;定义了执行某个流程的基本框架&#xff0c;而具体的细…...

CV22_语义分割基础

1. 常见的分割类型 在计算机视觉领域&#xff0c;根据不同的应用场景和需求&#xff0c;分割任务可以分为几种主要类型。以下是几种常见的分割类型&#xff1a; 语义分割&#xff08;Semantic Segmentation&#xff09;&#xff1a; 语义分割的目标是将图像中的每个像素分配到…...

Dubbo源码解析-Dubbo的线程模型(九)

一、Dubbo线程模型 首先明确一个基本概念&#xff1a;IO 线程和业务线程的区别 IO 线程&#xff1a;配置在netty 连接点的用于处理网络数据的线程&#xff0c;主要处理编解码等直接与网络数据 打交道的事件。 业务线程&#xff1a;用于处理具体业务逻辑的线程&#xff0c;可以…...

【Canvas与标志】圆角三角形生化危险警示标志

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>圆角三角形生化危险警示标志 Draft1</title><style type&qu…...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间&#xff0c; 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点&#xff0c;不需要开启数据库闪回。…...

【第二十一章 SDIO接口(SDIO)】

第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

【C++】纯虚函数类外可以写实现吗?

1. 答案 先说答案&#xff0c;可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...

LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)

在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...

FFmpeg avformat_open_input函数分析

函数内部的总体流程如下&#xff1a; avformat_open_input 精简后的代码如下&#xff1a; int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...