继承(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.rhkb.cn/news/101893.html

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

相关文章

【编织时空三:探究顺序表与链表的数据之旅】

本章重点 链表OJ题 1. 删除链表中等于给定值 val 的所有结点。 OJ链接 思路一&#xff1a;删除头结点时另做考虑&#xff08;由于头结点没有前一个结点&#xff09; struct ListNode* removeElements(struct ListNode* head, int val) {assert(head);struct ListNode* cur h…

Go:测试框架GoConvey 简介

快速开始 GoConvey是一个完全兼容官方Go Test的测试框架&#xff0c;一般来说这种第三方库都比官方的功能要强大、更加易于使用、开发效率更高&#xff0c;闲话少说&#xff0c;先看一个example&#xff1a; package utils import (. "github.com/smartystreets/goconvey…

【JVM】运行时数据区域

文章目录 说明程序计数器虚拟机栈本地方法栈Java堆方法区运行时常量池直接内存 说明 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途&#xff0c;以及创建和销毁的时间&#xff0c;有的区域随着虚拟机进程的启动而一直…

【广州华锐互动】牲畜养殖VR模拟实操系统为传统教育注入新的生命力

随着科技的不断发展&#xff0c;虚拟现实(VR)技术已经逐渐走进我们的生活。在农业领域&#xff0c;VR技术的应用也日益广泛&#xff0c;为现代农业人才培养提供了新的途径。 由广州华锐互动开发的“牲畜养殖VR模拟实操系统”引起了广泛关注&#xff0c;系统包含了鸡、猪、牛、马…

产品流程图是什么?怎么做?

产品流程图是什么&#xff1f; 产品流程图是一种图形化的表达方式&#xff0c;用于描述产品开发、制造、销售、使用等各个阶段中涉及的流程、步骤和关系。它通过图形符号、箭头、文本等元素&#xff0c;展示了产品的各个环节之间的关联和顺序&#xff0c;通常被用于可视化产…

STM32 F103C8T6学习笔记12:红外遥控—红外解码-位带操作

今日学习一下红外遥控的解码使用&#xff0c;红外遥控在日常生活必不可少&#xff0c;它的解码与使用也是学习单片机的一个小过程&#xff0c;我们将通过实践来实现它。 文章提供源码、测试工程下载、测试效果图。 目录 红外遥控原理&#xff1a; 红外遥控特点&#xff1a; …

Qt+C++串口调试接收发送数据曲线图

程序示例精选 QtC串口调试接收发送数据曲线图 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<QtC串口调试接收发送数据曲线图>>编写代码&#xff0c;代码整洁&#xff0c;规则&…

探索GreatADM:图形化部署MGR的全新体验

摘要&#xff1a; 在DBA的日常工作中&#xff0c;快速部署数据库高可用架构&#xff0c;且标准化地入网部署数据库是一项重要的基础任务。本文将介绍常见的部署MGR的方式&#xff0c;并重点介绍万里数据库的GreatADM数据库管理平台进行图形化、可视化、标准化的部署过程&#x…

vue 学习笔记 简单实验

1.代码(html) <script src"https://unpkg.com/vuenext" rel"external nofollow" ></script> <div id"counter">Counter: {{ counter }} </div> <script> const Counter {data() {return {counter: 5}} } Vue.cr…

二、pikachu之SQL注入(2)

文章目录 1、delete注入2、http header注入3、布尔盲注4、时间盲注 4、宽字节注入 1、delete注入 &#xff08;1&#xff09;寻找传参页面&#xff0c;在删除留言的时候&#xff0c;发现是get传参&#xff1b; &#xff08;2&#xff09;判断是否存在注入点&#xff0c;命令&…

Shell语法揭秘:深入探讨常见Linux Shell之间的语法转换

深入探讨常见Linux Shell之间的语法转换 一、引言二、Linux常用Shell&#xff1a;Bash、Zsh、Ksh、Csh、Tcsh和Fish的简介2.1、Bash、Zsh、Ksh、Csh、Tcsh和Fish的特点和用途2.2、语法差异是常见Shell之间的主要区别 三、变量和环境设置的语法差异3.1、变量定义和使用的不同语法…

Redis——set类型详解

概要 Set&#xff08;集合&#xff09;&#xff0c;将一些有关联的数据放到一起&#xff0c;集合中的元素是无序的&#xff0c;并且集合中的元素是不能重复的 之前介绍的list就是有序的&#xff0c;对于列表来说[1, 2, 3] 和 [2, 1, 3]是两个不同的列表&#xff0c;而对于集合…

GraphScope,开源图数据分析引擎的领航者

文章首发地址 GraphScope是一个开源的大规模图数据分析引擎&#xff0c;由Aliyun、阿里巴巴集团和华为公司共同开发。GraphScope旨在为大规模图数据处理和分析提供高性能、高效率的解决方案。 Github地址&#xff1a; https://github.com/alibaba/GraphScope GraphScope 的重…

开发第一个gPRC的开发

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

网络综合布线实训室方案(2023版)

综合布线实训室概述 随着智慧城市的蓬勃发展,人工智能、物联网、云计算、大数据等新兴行业也随之崛起,网络布线系统作为现代智慧城市、智慧社区、智能建筑、智能家居、智能工厂和现代服务业的基础设施和神经网络,发挥着重要作用。实践表明,网络系统故障的70%发生在布线系统,直接…

苹果手机桌面APP带云图标有个箭头,过一段时间经常要下载才能使用APP

环境&#xff1a; IPhone 11 IOS13.0 问题描述&#xff1a; 苹果手机桌面APP带云图标有个箭头&#xff0c;过一段时间经常要下载才能使用APP 解决方案&#xff1a; 1.打开设置&#xff0c;往下找到iTunes Store与App Store 2.找到下面卸载未使用的APP 关闭按钮

最优的家电设备交互方式是什么?详解家电设备交互的演进之旅

家电&#xff0c;在人们的日常生活中扮演着不可或缺的角色&#xff0c;也是提升人们幸福感的重要组成部分&#xff0c;那你了解家电的发展史吗&#xff1f; 70年代 结婚流行“四大件”&#xff1a;手表、自行车、缝纫机&#xff0c;收音机&#xff0c;合成“三转一响”。 80年…

问题描述:在Windows下没有预装ImageMagick工具

问题描述:在Windows下没有预装ImageMagick工具 # WInR输入cmd回车进入命令行,执行以下命令查看版本信息 magick --version没有预装ImageMagick工具 解决方案&#xff1a;下载安装ImageMagick 官网下载:ImageMagick-7.1.1-15-Q16-x64-dll.exe 下载之后&#xff0c;一路下一步…

MySQL表的约束

MySQL表的约束 约束的概念空属性默认值列描述zerofill主键自增长唯一键外键 约束的概念 在正式谈MySQL表的约束之前&#xff0c;我们先来简单理解一下约束这个概念; 约束&#xff1a;意思是指带有束缚、限制、管束等意思&#xff1b; 大白话就是说:规定了那些事情你不能干&…

如何大幅提高遥感影像分辨率(Python+MATLAB)

前言&#xff1a; 算法&#xff1a;NSCT算法&#xff08;非下采样变换&#xff09; 数据&#xff1a;Landsat8 OLI 遥感图像数据 编程平台&#xff1a;MATLABPython 论文参考&#xff1a;毛克.一种快速的全色和多光谱图像融合算法[J].测绘科学,2016,41(01):151-15398.DOI:10.1…