继承(C++)

继承

  • 一、初识继承
    • 概念
      • “登场”
      • 语法格式
    • 继承方式
      • 九种继承方式组合
      • 小结(对九种组合解释)
  • 二、继承的特性
    • 赋值转换 一一 切片 / 切割
    • 作用域 一一 隐藏 / 重定义
  • 三、派生类的默认成员函数
    • 派生类的默认成员函数
    • 1. 构造函数
    • 2. 拷贝构造
    • 3. 赋值运算符重载
    • 4. 析构函数
  • 四、延伸知识
    • 1. 继承与友元
    • 2. 继承与静态成员
  • 五、单继承和多继承
    • 单继承
    • 多继承
      • 菱形继承
      • 菱形虚拟继承
        • 语法
        • 原理
  • 总结
    • 拓展知识:组合

一、初识继承

概念

继承保持原有类特性的基础上进行扩展,增加功能,产生新的类。新的类就叫做派生类(子类),原有类就叫做基类(父类)。
继承的作用:继承机制是面向对象程序设计使代码可以复用的最重要的手段,继承是类设计层次的复用,呈现了面向对象程序设计的层次结构

“登场”

class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;cout << endl;}protected:string _name = "张三";int _age = 18;
};//继承后,父类的Person成员(成员函数 + 成员变量)都会成为子类一部分
class Student : public Person
{
protected:int _stuId;
};int main()
{Student s;s.Print();return 0;
}

展现继承
结论:代码体现出Student对Person的继承(复用)

语法格式

定义格式

继承方式

继承方式和访问限定符:
继承方式和访问限定符

九种继承方式组合

C++中的继承方式和访问限定符组合,形成了九种情况的继承结果

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

eg:实例演示三种继承关系下基类成员的各类型成员访问关系的变化

class Person
{
public:void Print(){cout << "名字:" << _name << endl;}
protected:string _name = "张三";
private:int _age = 18;
};//class Student : protected Person
//class Student : private Person
class Student : public Person
{
protected:int _stuId = 111;
};int main()
{Student s;s.Print();return 0;
}

关系变化

小结(对九种组合解释)

不需要全部记完,这两种是最常用的记住即可:
常用

  1. 在九种组合表中,基类的私有成员是不可见的。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符, 继承方式),(public > protected > private)。 Min:两者的较小者。
  2. 基类private成员在派生类中什么方式都不可见。不可见:基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能访问
  3. 基类成员不想在类外直接被访问,但要在派生类中能访问,就要定义为protected。保护成员限定符是因为继承才出现的
  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public。建议:显示写
  5. 实际运用一般都是public继承,扩展维护性强

二、继承的特性

赋值转换 一一 切片 / 切割

派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用。这个过程叫切片或者切割,不会产生临时变量,发生赋值兼容,就同把派生类中父类那部分切来赋值过去。

原理:(切片 / 切割)
切片/切割

eg:证明:不会产生临时变量

class Person
{
protected:string _name;string _sex;int _age;
};class Student : public Person
{
public:int _No;
};int main()
{int i = 0;//double& d = i;   //errorconst double& rd = i;   //int赋值给double类型的值,会产生临时变量,所以要+const//派生类对象可以直接赋值给基类对象,不要+const,也就证明,这个过程没有产生临时变量Student s;Person& p = s;return 0;
}

注意:派生类对象赋值给基类的对象(或者基类的指针,或者基类的引用)这个过程称为向上转换。

拓展(不作详细介绍): 基类的指针和引用可以通过强转赋值给派生类的指针或者引用,但基类的指针是指向派生类对象时才安全。-- 这个过程称为向下转换。注意:基类对象不能赋值给派生类对象。

eg:

class Person
{
//protected:
public:string _name = "peter";string _sex = "male";int _age = 18;
};class Student : public Person
{
public:int _No = 2140104111;
};int main()
{Student s;//1.派生类对象可以赋值给父类对象/指针/引用Person p = s;Person* ptrp = &s;ptrp->_age = 21;Person& rp = s;rp._name = "张三";//2.基类对象不能赋值给派生类对象//s = p;   //errorreturn 0;
}

代码分析:
代码分析

注意:使用保护继承,成员权限会发生变化

Person p = s;  //就会出现错误

这是因为保护继承下,派生类的对象只能被派生类或派生类的子类引用,而不能被基类引用。

原理: 因为非公有派生类(私有或保护派生类)不能实现基类的全部功能,例如在派生类外不能调用基类的公用成员函数访问基类的私有成员。因此,只有公有派生类才是基类真正的子类型,它完整地继承了基类的功能。

作用域 一一 隐藏 / 重定义

  1. 在继承体系中基类和派生类都有独立的作用域
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问(子类成员隐藏父类成员),这叫做隐藏(或者重定义)。 (在子类成员中,可以 基类::基类成员 显示访问。但是指定作用域,如果找不到会直接报错,不会再去访问别的域。eg:派生类成员)
  3. 成员函数隐藏:函数名相同就构成隐藏
  4. 建议:最好不要定义同名成员
    拓展:访问成员遵循就近原则(编译器既定顺序):局部域-当前类域-父类域-全局域

eg1:成员变量

class Person
{
protected:string _name = "张三";int _num = 111;
};class Student : public Person
{
public:void Print(){cout << "姓名:" << _name << endl;//指定显示访问cout << "Person::_num:" << Person::_num << endl;//默认访问子类。子类隐藏了父类cout << "_num:" << _num << endl;}protected:int _num = 999;
};int main()
{Student s;s.Print();return 0;
}//output:
//姓名:张三
//Person::_num:111
//_num : 999

eg2:成员函数

//fun不构成重载,因为不在同一作用域
//fun构成隐藏,成员函数满足函数名相同
class A
{
public:void fun(){cout << "fun()" << endl;}
};
class B : public A
{
public:void fun(int i){A::fun();cout << "fun(int i)->" << i << endl;}
};int main()
{B b;b.fun(1);//b.fun();    //参数不匹配//指定访问b.A::fun();return 0;
}//output:
//fun()
//fun(int i)->1
//fun()

三、派生类的默认成员函数

派生类的默认成员函数

演示代码:后面会分为四个部分进行拆分讲解

class Person
{
public://如果没有默认构造,必须在派生类的初始化列表显示调用//Person(const char* name)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 Student : public Person
{
public:Student(const char* name, int num):Person(name), _num(num){cout << "Student()" << endl;}Student(const Student& s):Person(s), _num(s._num){cout << "Student(const Student& s)" << endl;}Student& operator=(const Student& s){cout << "Student& operator=(const Student& s)" << endl;if (this != &s){Person::operator=(s);_num = s._num;}return *this;}~Student(){cout << "~Student()" << endl;}protected:int _num;
};int main()
{Student s1("jack", 18);   //构造函数Student s2(s1);           //拷贝构造函数Student s3("rose", 17);   s1 = s3;                  //赋值运算符重载return 0;
}

构造和析构调用和执行顺序图
调用结构

1. 构造函数

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认构造,则必须在派生类构造的初始化列表阶段显式调用
  2. 派生类对象初始化先调用基类构造再调派生类构造

eg1:(有默认构造)

class Person
{
public:Person(const char* name = "张三"):_name(name){cout << "Person()" << endl;}
protected:string _name;
};class Student : public Person
{
public:Student(const char* name, int num):_stuId(num){cout << "Student()" << endl;}
protected:int _stuId;
};int main()
{Student s("jack", 18);return 0;
}

代码F11逐语句执行过程:
代码F11逐语句执行过程

eg2:(没有默认构造)
基类没有默认构造,在派生类必须显示调用,Person先初始化,然后是_stuId。(基类先声明,所以先初始化Person)

//没有默认构造
class Person
{
public:Person(const char* name):_name(name){cout << "Person()" << endl;}
protected:string _name;
};class Student : public Person
{
public:Student(const char* name, int num):Person(name)  //在初始化列表调用基类默认构造。如同定义匿名对象, _stuId(num){cout << "Student()" << endl;}
protected:int _stuId;
};int main()
{Student s("jack", 18);return 0;
}

代码F11逐语句执行过程:代码F11逐语句执行过程

2. 拷贝构造

  1. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化

演示代码的拷贝构造部分:
拷贝构造

3. 赋值运算符重载

  1. 派生类的operator=必须调用基类的operator=完成基类的复制

赋值运算符重载

4. 析构函数

  1. 派生类的析构会在调用完成后自动调用基类的析构函数清理基类成员。原因:为了保证派生类对象,先清理派生类成员再清理基类成员的顺序
  2. 因为后续的一些场景,析构函数要构成重写,重写的条件之一就是函数名相同。所以编译器对析构函数名进行特殊处理,处理成destructor()。所以父类析构函数不+virtual,子类析构函数和父类析构函数构成隐藏关系

析构函数

四、延伸知识

1. 继承与友元

友元关系不能继承,所以基类的友元不能访问子类私有成员和保护成员

eg:

class Student;  //先声明,因为在Person中引用了Student对象
class Person
{friend void Dispaly(const Person& p, const Student& s);
protected:string _name = "张三";
};class Student : public Person
{
protected:int _stuId = 0;
};
void Dispaly(const Person& p, const Student& s)
{cout << s._name << endl;  //okcout << p._name << endl;  //okcout << s._stuId << endl;  //error   
}int main()
{Dispaly(Person(), Student());return 0;
}

注意:如果想要在Display()中调用s._stuId,要在Student类中也加上友元

2. 继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员

class Person
{
public:Person(){++_count;}
protected:string _name;
public:static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected:int stuId;
};
class Graduate : public Student
{
protected:string _seminarCourse;
};int main()
{Student s1;Student s2;Student s3;Graduate s4;cout << "人数:" << Person::_count << endl;Graduate::_count = 0;cout << "人数:" << Person::_count << endl;return 0;
}//output:
//人数:4
//人数:0

注意:静态成员属于父类和派生类,在派生类不会单独再拷贝一份,继承的是使用权。eg:上面的代码使用的始终都是一个_count

五、单继承和多继承

单继承

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

多继承

多继承

菱形继承

菱形继承:多继承的一种特殊情况。
菱形继承
菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承由数据冗余(浪费空间)和二义性(不知道访问谁) 问题,在Assistant的对象中Person成员有两份

成员模型
菱形继承代码:

class Person
{
public:string _name;
};class Student : public Person
{
protected:int _stuId;
};class Teacher : public Person
{
protected:int _workId;
};class Assistant : public Student, public Teacher
{
protected:string _course;
};int main()
{Assistant a;//这样会有二义性问题,无法明确访问的哪一个//a._name = "peter";   //error//显示指定访问那个父类的成员可以解决二义性问题,但是数据冗余问题无法解决a.Student::_name = "xxx";a.Teacher::_name = "yyy";return 0;
}

对于菱形继承解决不了的问题,出现了虚拟继承。

菱形虚拟继承

虚拟继承可以解决菱形继承的二义性和数据冗余问题。

语法

如上面菱形继承代码的继承关系,在Student和Teacher继承Person时使用虚拟继承。
eg:
虚拟继承

原理

借用简化的菱形继承体系,通过内存窗口观察对象成员的模型

菱形继承

class A
{
public:int _a;
};class B : public A
{
public:int _b;
};class C : 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;
}

菱形继承

菱形虚拟继承

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

菱形虚拟继承

注意: D中为什么B和C部分要找属于自己的A
解释:
在这里插入图片描述
菱形虚拟继承的原理解释:
菱形虚拟继承的原理解释

总结

  1. 虚拟继承的缺陷:虚拟继承会增加程序的复杂性,因为派生类需要特别处理虚基类的初始化和访问。虚拟继承还可能导致一些性能上的损失,因为派生类需要额外的指针来访问虚基类。-- 不建议使用菱形继承
  1. 继承和组合:
    • public继承是一种is-a的关系。eg:植物和花
    • 组合是一种has-a的关系。 eg:轮胎和车

拓展知识:组合

**优先使用对象组合,而不是类继承,**组合耦合度低,代码维护性好

  1. 白盒测试:知道底层
  2. 黑盒测试:不知道底层
  1. 继承,通过生成派生类的复用称为白箱复用(white-box reuse)。白箱(相对可视性而言):在继承方式中,基类内部细节对子类可见。继承一定程度破坏了基类的封装。耦合度高:基类的改变极大的影响派生类,两者关系紧密
  2. 组合,新的更复杂的功能可以通过组装或组合对象获得。被组合对象具有良好定义的接口。这种复用风格称为黑箱复用(black-box reuse),**对象内部细节不可见。**组合类之间没有很强的依赖关系,耦合度低

eg:

//继承
//Car和BMW  Car和Benz构成is-a关系
class Car
{
protected:string _color = "白色";string _num = "陕IT6666";
};class BMW : public Car
{
public:void Drive(){cout << "好开" << endl;}
};class Benz : public Car
{
public:void Drive(){cout << "好坐" << endl;}
};//组合
//Tire和Car构成has-a关系
class Tire
{
protected:string _brand = "Michelin";size_t _size = 17;
};class Car
{
protected:string _color = "白色";string _num = "陕IT6666";Tire _t;
};

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

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

相关文章

Centos7查看磁盘和CUP统计信息iostat命令

Centos7查看磁盘和CUP统计信息iostat命令 Centos7内存高|查看占用内存命令 docker实战(一):centos7 yum安装docker docker实战(二):基础命令篇 docker实战(三):docker网络模式(超详细) docker实战(四):docker架构原理 docker实战(五):docker镜像及仓库配置 docker实战(六…

Spring Clould 注册中心 - Eureka,Nacos

视频地址&#xff1a;微服务&#xff08;SpringCloudRabbitMQDockerRedis搜索分布式&#xff09; Eureka 微服务技术栈导学&#xff08;P1、P2&#xff09; 微服务涉及的的知识 认识微服务-服务架构演变&#xff08;P3、P4&#xff09; 总结&#xff1a; 认识微服务-微服务技…

9.Sentinel哨兵

1.Sentinel Sentinel&#xff08;哨兵&#xff09;是由阿里开源的一款流量控制和熔断降级框架&#xff0c;用于保护分布式系统中的应用免受流量涌入、超载和故障的影响。它可以作为微服务架构中的一部分&#xff0c;用于保护服务不被异常流量冲垮&#xff0c;从而提高系统的稳定…

多线程+隧道代理:提升爬虫速度

在进行大规模数据爬取时&#xff0c;爬虫速度往往是一个关键问题。本文将介绍一个提升爬虫速度的秘密武器&#xff1a;多线程隧道代理。通过合理地利用多线程技术和使用隧道代理&#xff0c;我们可以显著提高爬虫的效率和稳定性。本文将为你提供详细的解决方案和实际操作价值&a…

【数据库】详解数据库架构优化思路(两主架构、主从复制、冷热分离)

文章目录 1、为什么对数据库做优化2、双主架构双主架构的工作方式如下&#xff1a;双主架构的优势包括&#xff1a;但是一般不用这种架构&#xff0c;原因是&#xff1a; 3、主从复制主从复制的工作方式如下&#xff1a;主从复制的优势包括&#xff1a;主从复制的缺点 4、冷热分…

回归预测 | MATLAB实现NGO-SVM北方苍鹰算法优化支持向量机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现NGO-SVM北方苍鹰算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现NGO-SVM北方苍鹰算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一览基…

机器学习知识点总结:什么是EM(最大期望值算法)

什么是EM(最大期望值算法) 在现实生活中&#xff0c;苹果百分百是苹果&#xff0c;梨百分白是梨。 生活中还有很多事物是概率分布&#xff0c;比如有多少人结了婚&#xff0c;又有多少人有工作&#xff0c; 如果我们想要调查人群中吸大麻者的比例呢&#xff1f;敏感问题很难得…

【VR】SteamVR2.0的示例场景在哪里

&#x1f4a6;本专栏是我关于VR开发的笔记 &#x1f236;本篇是——在哪里可以找到SteamVR2.0的示例场景 SteamVR2.0的示例场景在哪里 1. 逐步打开方式2. 快速打开方式 1. 逐步打开方式 Assets——SteamVR——InteractionSystem——Samples——>Interactions_Example 2. 快…

多维时序 | MATLAB实现KOA-CNN-BiGRU-Attention多变量时间序列预测

多维时序 | MATLAB实现KOA-CNN-BiGRU-Attention多变量时间序列预测 目录 多维时序 | MATLAB实现KOA-CNN-BiGRU-Attention多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLAB实现KOA-CNN-BiGRU-Attention多变量时间序列预测&#xff0c;KOA-…

Postman项目实战一

项目简介&#xff1a;外卖任务委派系统 测试脚本&#xff1a; 登录&#xff0c;获取token创建任务&#xff0c;获取任务id根据id&#xff0c;修改任务根据id&#xff0c;查询任务根据id&#xff0c;删除任务根据id&#xff0c;验证任务已被删除 步骤&#xff1a; 1.创建Col…

工业生产全面感知!工业感知云来了

面向工业企业数字化转型需求&#xff0c;天翼物联基于感知云平台创新能力和5G工业物联数采能力&#xff0c;为客户提供工业感知云服务&#xff0c;包括工业泛协议接入、感知云工业超轻数采平台、工业感知数据治理、工业数据看板四大服务&#xff0c;构建工业感知神经系统新型数…

C++(Qt)软件调试---gdb调试入门用法(12)

gdb调试—入门用法&#xff08;1&#xff09; 文章目录 gdb调试---入门用法&#xff08;1&#xff09;1、前言1.1 什么是GDB1.2 为什么要学习GDB1.3 主要内容1.4 GDB资料 2、C/C开发调试环境准备3、gdb启动调试1.1 启动调试并传入参数1.2 附加到进程1.3 过程执行1.4 退出调试 4…

计算机竞赛 卷积神经网络手写字符识别 - 深度学习

文章目录 0 前言1 简介2 LeNet-5 模型的介绍2.1 结构解析2.2 C1层2.3 S2层S2层和C3层连接 2.4 F6与C5层 3 写数字识别算法模型的构建3.1 输入层设计3.2 激活函数的选取3.3 卷积层设计3.4 降采样层3.5 输出层设计 4 网络模型的总体结构5 部分实现代码6 在线手写识别7 最后 0 前言…

mysql------做主从复制,读写分离

1.为什么要做主从复制&#xff08;主从复制的作用&#xff09; 做数据的热备&#xff0c;作为后备数据库&#xff0c;主数据库服务器故障后&#xff0c;可切换到从数据库继续工作&#xff0c;避免数据丢失。 架构的扩展。业务量越来越大,I/O访问频率过高&#xff0c;单机无法满…

shell和Python 两种方法分别画 iostat的监控图

在服务器存储的测试中,经常需要看performance的性能曲线&#xff0c;这样最能直接观察HDD或者SSD的性能曲线。 如下这是一个针对HDD跑Fio读写的iostat监控log,下面介绍一下分别用shell 和Python3 写画iostat图的方法 1 shell脚本 环境:linux OS gnuplot工具 第一步 :解析iosta…

DETR-《End-to-End Object Detection with Transformers》论文精读笔记

DETR&#xff08;基于Transformer架构的目标检测方法开山之作&#xff09; End-to-End Object Detection with Transformers 参考&#xff1a;跟着李沐学AI-DETR 论文精读【论文精读】 摘要 在摘要部分作者&#xff0c;主要说明了如下几点&#xff1a; DETR是一个端到端&am…

测试工具coverage的高阶使用

在文章Python之单元测试使用的一点心得中&#xff0c;笔者介绍了自己在使用Python测试工具coverge的一点心得&#xff0c;包括&#xff1a; 使用coverage模块计算代码测试覆盖率使用coverage api计算代码测试覆盖率coverage配置文件的使用coverage badge的生成 本文在此基础上…

【Android】设置-显示-屏保-启用时机-去除插入基座相关(不支持该功能的话)

设置-显示-屏保-启用时机-去除插入基座相关&#xff08;不支持该功能的话&#xff09; 1-项目场景&#xff1a;2-问题描述3-解决方案&#xff1a;4-代码修改前后效果对比图&#xff1a;代码修改前&#xff1a;代码修改后&#xff1a; 1-项目场景&#xff1a; 展锐平台 2-问题描…

ctfshow-web12

0x00 前言 CTF 加解密合集CTF Web合集 0x01 题目 0x02 Write Up 国际惯例看一下返回包&#xff0c;是不是有注释 然后做一下测试&#xff0c;看是命令执行还是代码执行 通过phpinfo看到可以执行代码 然后尝试执行命令&#xff0c;无法&#xff0c;发现存在disable_function…

基于nginx禁用访问ip

一、背景 网络安全防护时&#xff0c;禁用部分访问ip,基于nginx可快速简单实现禁用。 二、操作 1、创建 conf.d文件夹 在nginx conf 目录下创建conf.d文件夹 Nginx 扩展配置文件一般在conf.d mkdir conf.d 2、新建blocksip.conf文件 在conf.d目录新建禁用ip的扩展配置文…