C++进阶:详细讲解继承

现在也是结束了初阶部分的内容,今天开始就进入进阶部分了。一刻也没有为初阶的结束而哀悼,立刻赶来“战场”的是进阶部分里的继承


文章目录

  • 1.继承的概念和定义
    • 1.1继承的概念
    • 1.2继承的定义
      • 1.2.1继承的格式
      • 1.2.2再讲访问限定符(详讲protected)
      • 1.2.3**继承基类成员三种访问方式**
  • 2.基类和派生类对象赋值转换
  • 3.继承中的作用域
  • 4.派生类的默认成员函数
    • 4.1构造函数
    • 4.2重载=和析构函数
  • 5.继承与友元
  • 6.继承与静态成员
  • 7.复杂的菱形继承及菱形虚拟继承
    • 7.1单继承、多继承和菱形继承
    • 7.2解决菱形继承
    • 7.3解决菱形继承原理
  • 8.继承和组合


1.继承的概念和定义

1.1继承的概念

继承(inheritance)是一种面向对象编程的机制,允许一个类(派生类)从另一个类(基类)继承属性和行为

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

在这里插入图片描述

#include<iostream>
#include<string>
using namespace std;class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "Nero";int _age = 18;
};class Teacher :public Person//公有继承
{
protected:int _jobid;//老师的工号
};class Student :public Person
{
protected:int _stuid;//学生的学号
};int main(){Student s;Teacher t;s.Print();t.Print();return 0;
}

在这里插入图片描述

1.2继承的定义

1.2.1继承的格式

class Base {// 基类的成员和方法
};class Derived : [访问权限] Base {// 派生类的成员和方法
};
  • class Base 表示基类的定义。
  • class Derived : [访问权限] Base 表示派生类的定义,并指定基类为 Base
  • [访问权限] 可选,表示继承的访问权限,可以是 publicprotectedprivate。如果不指定,默认为 private

1.2.2再讲访问限定符(详讲protected)

  1. Public成员
    • public成员在类的内部和外部都可以被访问。
    • 类的外部代码可以直接访问对象的public成员。
  2. Protected成员
    • protected成员在类的内部可以被访问,但在类的外部不能直接访问。
    • 派生类(子类)==可以访问基类(父类)的protected==成员。
  3. Private成员
    • private成员只能在类的内部被访问,外部不能直接访问。
    • 派生类也不能访问基类private成员。

所以,protected和private的访问权限相同(针对本类内外来说),但是二者对继承的子类来说是不一样的

相对应的,访问限定符有三个。那么继承方式也是有三种继承:

在这里插入图片描述

1.2.3继承基类成员三种访问方式

类成员/继承方式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继承下来的成员都只能在派生类的类里面使用

class A
{
public:int _public;
protected:int _protected;
private:int _private;
};class B :public A
{
public:void print_public(){cout << _public << endl;}void print_protected(){cout << _protected << endl;}void print_private(){cout << _private << endl;}
};int main()
{B b;return 0;
}

这里可以看到已经报错了,因为基类的private成员在派生类中是不可见

在这里插入图片描述


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

  • 派生类对象可以赋值给基类的对象/ 基类的指针/基类的引用。有个形象的说法叫切片或者切割(走了一个特殊处理:中间不会产生一个临时变量):寓意把派生类中父类那部分切来赋值过去。

  • 基类对象不能赋值给派生类对象(目前是这样的)

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

在这里插入图片描述

class Student : public Person//学生类继承于Person类
{
public:int _No; // 学号(学生类自己的成员变量)
};
void test1()
{Student stu;//子类对象可以赋值给父类对象/指针/引用Person per = stu;//走了一个特殊处理:中间不会产生一个临时变量Person* pper = &stu;//这个指针也是直接指向对象费父类那一部分Person& rper = stu;//基类对象不能赋值给派生类对象stu = per;//基类的指针可以通过强制类型转换赋值给派生类的指针pper = &stu;Student * ps1 = (Student*)pper; // 这种情况转换时可以的。ps1->_No = 10;pper = &per;Student* ps2 = (Student*)pper; // 这种情况转换时虽然可以,但是会存在越界访问的问题ps2->_No = 10;
}int main()
{test1();return 0;
}

可以看到,有些地方是报错了的

在这里插入图片描述


3.继承中的作用域

  1. 在继承体系中基类派生类都有独立的作用域

  2. 子类和父类中有同名成员子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员来显示访问父类的)

  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同(返回值和参数数量、类型可以不同)就构成隐藏。

  4. 注意在实际中在继承体系里面最好不要定义同名的成员

class Person
{
protected:string _name = "Neor"; // 姓名int _id = 111;//身份证号
};class Student : public Person
{
public:void Print(){cout << "学号:" << _id << endl;cout << "身份证号" << Person::_id << endl;}
protected:int _id = 888; // 学号
};void test1()
{Student s1;s1.Print();
};int main()
{test1();return 0;
}

在这里插入图片描述


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

在这里插入图片描述

4.1构造函数

  • 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用(顺序是先父后子)。

  • 派生类对象初始化先调用基类构造再调派生类构造

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

4.2重载=和析构函数

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

  2. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序(先子后父)

    • 派生类对象析构清理先调用派生类析构再调基类的析构
    • 编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系
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 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);//直接operator =(s)会因为隐藏,一直调用_num = s._num;}return *this;}~Student(){cout << "~Student()" << endl;}
protected:int _num; //学号
};void test2()
{Student s1("Nero", 18);
}int main()
{test2();return 0;
}

在这里插入图片描述

派生这些默认成员函数规则,其实跟以前类似,
唯一不同的是,不管是构造初始化/拷贝/析构,多了父类那一部分。原则: 父类那部分调用父类的对应函数完成


5.继承与友元

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

class A
{
public:int _pa;friend void print(B& bb);//这个print是A的友元
protected:int _a;
};class B :public A
{
public:int _pb;
protected:int _b;
};void print(B& bb)
{cout << "公有的_pb" << bb._pa;cout << "私有的_b" << bb._b;//cout << "公有的_pb" << bb._pb;//cout << "私有的_b" << bb._b;
}

在这里插入图片描述

class A
{
public:int _pa;friend void print(B& bb);//这个print是A的友元
protected:int _a;
};class B :public A
{
public:int _pb;friend void print(B& bb);
protected:int _b;
};void print(B& bb)
{cout << "公有的_pb" << bb._pa;cout << "私有的_b" << bb._b;//cout << "公有的_pb" << bb._pb;//cout << "私有的_b" << bb._b;
}

这样才对


6.继承与静态成员

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

class Person
{
public:Person() {++_count; }
protected:string _name; // 姓名
public:static int _count; // 统计人的个数。
};int Person::_count = 0;class Student : public Person
{
protected:int _stuNum; // 学号
};int main()
{Person p1;Person p2;Student s2;cout << Person::_count << endl;cout << Student::_count << endl;return 0;
}

在这里插入图片描述


7.复杂的菱形继承及菱形虚拟继承

7.1单继承、多继承和菱形继承

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

在这里插入图片描述

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

在这里插入图片描述

就有可能出现一种特殊情况:菱形继承

在这里插入图片描述

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在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; // 主修课程
};void test3()
{Assistant a;a._name = "Nero";
}int main()
{test3();return 0;
}

在这里插入图片描述

7.2解决菱形继承

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承(在:后面加上virtual),即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。

  • 我们要知道是哪几个继承时导致了二义性,把那些继承变为虚拟继承就行了,上面这个例子就是Student和Teacher的继承
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 _majorCourse; // 主修课程
};void test3()
{Assistant a;a._name = "Nero";//此时就可以了
}int main()
{test3();return 0;
}

7.3解决菱形继承原理

这里为了探讨这个问题,先给出下列代码:

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对象中将A放到的了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A


8.继承和组合

  • public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象 ; 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

  • 优先使用对象组合,而不是类继承(继承的耦合度高) 。

  • 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。

  • 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。

  • 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合


继承讲完了,那下次肯定就是多态啦!!!感谢大家支持!!!

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

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

相关文章

NFT交易市场开发(一)

实现的基本功能 &#xff08;一&#xff09; 发行一个符合ERC20标准的测试Token&#xff0c;要求如下&#xff1a; 总量&#xff1a;&#xff1a;1亿精度&#xff1a;18名称&#xff1a;Fake USDT in CBI简称&#xff1a;cUSDT &#xff08;二&#xff09; 发行一个符合ERC72…

数据结构——算法的空间复杂度

【本节内容】 1.空间复杂度 2.常见空间复杂度 1.空间复杂度 空间复杂度也是一个数学表达式&#xff0c;是对一个算法在运行过程中临时占用额外存储空间大小的量度。 空间复杂度不是程序占用了多少bytes的空间&#xff0c;因为这个也没太大意义&#xff0c;所以空间复杂度算…

random标准模块

一、概述 在 Python 中&#xff0c;random 是一个内置模块&#xff0c;用于生成随机数。它提供了各种用于生成随机数的函数&#xff0c;包括伪随机数生成器、随机序列操作等。 1、需要导包 不会自动导入&#xff0c;需要显示的将random模块导入 import random2、源码分析&…

课时59:流程控制_if条件控制_语法解读

2.2.1 语法解读 学习目标 这一节&#xff0c;我们从 基础知识、简单实践、小结 三个方面来学习。 基础知识 简介 条件结构能够根据某个特定的条件&#xff0c;结合内置的测试表达式功能&#xff0c;结合测试的结果状态值对于条件进行判断&#xff0c;然后选择执行合适的任务…

【Greenhills】MULTIIDE集成第三方的编辑器进行源文件编辑工作

【更多软件使用问题请点击亿道电子官方网站查询】 1、 文档目标 在使用GHS进行工作的时候&#xff0c;可以集成第三方的编辑器进行源文件编辑工作 2、 问题场景 用于解决在GHS中进行项目开发时&#xff0c;对于GHS的编辑器使用不习惯&#xff0c;想要切换到其他第三方的编辑…

为什么在镀膜时要测薄膜折射率?

在芯片制造中&#xff0c;镀膜工序&#xff08;PVD,CVD&#xff09;是必不可少的关键环节&#xff0c;薄膜的质量直接影响了芯片的性能。对这些薄膜的精细控制又离不开对其折射率的深入理解和精确测量。今天将对芯片制造中薄膜折射率的概念、测量方法&#xff0c;以及它在整个制…

Arcgis小技巧【19】——更改字段顺序

一、问题分析 一般情况下&#xff0c;一个合格且严谨的要素或表数据&#xff0c;它的字段顺序都是固定的。 比如三调数据&#xff0c;正常的字段数据如下&#xff08;截取部分字段&#xff09;&#xff1a; 数据经过处理&#xff0c;如增删字段&#xff0c;可能会出现字段顺序…

SAM模型

SAM与过去分割对比 根据以下Demo,我们可以发现&#xff0c;通过在图像中指定要分割的内容提示&#xff0c;SAM可以实现各种分割任务&#xff0c;且无需额外的训练、做到零样本泛化&#xff0c;即SAM学会了辨别物体、具备图像理解力、对不熟悉的图像和物体能进行零样本概括&…

论文阅读《FENET: FOCUSING ENHANCED NETWORK FOR LANE DETECTION》

ABSTRACT 受人类驾驶专注力的启发&#xff0c;这项研究开创性地利用聚焦采样&#xff08;Focusing Sampling&#xff09;、部分视野评估&#xff08;Partial Field of View Evaluation&#xff09;、增强型 FPN 架构和定向 IoU 损失&#xff08;Directional IoU Loss&#xff…

STM32单片机示例:ETH_LAN8742_DHCP_NonOS_Poll_H743

文章目录 目的基础说明关键配置关键代码示例链接总结 目的 以太网是比较常用到的功能&#xff0c;STM32系列单片机使用CubeMX配置使用以太网功能比非常方便。不过对于H7系列来说需要使能 DCache 才能启用LwIP&#xff0c;启用Cache后又会带来一些需要特别注意的事情。这篇文章…

AI智能应用百科立即落地实操课

该课程旨在教授学员如何将AI智能应用于实际场景。通过深入的案例研究和实操练习&#xff0c;学员将学会应用机器学习、自然语言处理等技术&#xff0c;快速解决现实问题。课程强调实际操作&#xff0c;帮助学员快速运用AI技术解决工作中的挑战。 课程大小&#xff1a;3.3G 课…

10 | MySQL为什么有时候会选错索引?

前面我们介绍过索引&#xff0c;你已经知道了在 MySQL 中一张表其实是可以支持多个索引的。但是&#xff0c;你写 SQL 语句的时候&#xff0c;并没有主动指定使用哪个索引。也就是说&#xff0c;使用哪个索引是由 MySQL 来确定的。 不知道你有没有碰到过这种情况&#xff0c;一…

【Selenium】selenium介绍及工作原理

一、Selenium介绍 用于Web应用程序测试的工具&#xff0c;Selenium是开源并且免费的&#xff0c;覆盖IE、Chrome、FireFox、Safari等主流浏览器&#xff0c;通过在不同浏览器中运行自动化测试。支持Java、Python、Net、Perl等编程语言进行自动化测试脚本编写。 官网地址&…

学会Web UI框架--Bootstrap,快速搭建出漂亮的前端界面

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属的专栏&#xff1a;前端泛海 景天的主页&#xff1a;景天科技苑 文章目录 Bootstrap1.Bootstrap介绍2.简单使用3.布局容器4.Bootstrap实现轮播…

二分与前缀和

789. 数的范围 - AcWing题库 import java.util.*;public class Main{static int N 100010;static int[] a new int[N];public static void main(String[] args){Scanner sc new Scanner(System.in);int n sc.nextInt();int m sc.nextInt();for(int i 0; i < n; i ){…

《互联网的世界》第五讲-信任和安全(第一趴:物理世界的非对称加密装置)

信任和安全的话题过于庞大&#xff0c;涉及很多数学知识&#xff0c;直接涉及 “正事” 反而不利于理解问题的本质&#xff0c;因此需要先讲一个前置作为 part 1。 part 1 主要描述物理世界的信任和安全&#xff0c;千万不要觉得数字世界是脱离物理世界的另一天堂&#xff0c;…

【C++庖丁解牛】实现string容器的增删查改 | string容器的基本接口使用

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 目录 前言&#x1f4d6;pu…

MACBOOK PRO M2 MAX 安装Stable Diffusion及文生图实例

以前偶尔会使用Midjourney生成一些图片&#xff0c;现在使用的头像就是当时花钱在Midjourney上生成的。前段时间从某鱼上拍了一台性价比还不错的macbook&#xff0c;想着不如自己部署Stable Diffusion&#xff08;以下简称SD&#xff09;尝试一下。 网上有很多教程&#xff0c…

Apache POI 解析和处理Excel

摘要&#xff1a;由于开发需要批量导入Excel中的数据&#xff0c;使用了Apache POI库&#xff0c;记录下使用过程 1. 背景 Java 中操作 Excel 文件的库常用的有Apache POI 和阿里巴巴的 EasyExcel 。Apache POI 是一个功能比较全面的 Java 库&#xff0c;适合处理复杂的 Offi…

qt-C++笔记之使用Cmake来组织和构建QWidget工程项目

qt-C笔记之使用Cmake来组织和构建QWidget工程项目 —— 杭州 2024-03-10 code review! 文章目录 qt-C笔记之使用Cmake来组织和构建QWidget工程项目1.运行2.文件结构3.CMakeLists.txt4.main.cpp5.widget.h6.widget.cpp7.widget.ui 1.运行 2.文件结构 3.CMakeLists.txt 代码 c…