首页 > 其他分享 >继承的概念及定义

继承的概念及定义

时间:2024-11-15 17:17:02浏览次数:3  
标签:定义 继承 成员 基类 概念 对象 派生类 函数

以下是对上述内容的详细解释:

一、继承的概念及定义

(一)继承的概念

  1. 代码复用手段:继承是面向对象程序设计中实现代码复用的重要手段。它允许在保持原有类特性的基础上进行扩展,产生新的派生类,增加方法和属性,体现了由简单到复杂的认知过程,是类设计层次的复用,区别于函数层次的复用。
  2. 示例说明:以学生(Student)和老师(Teacher)为例,他们都有一些共同的成员变量(姓名、地址、电话、年龄)和成员函数(身份认证函数identity),如果不使用继承,这些相同的部分在两个类中会重复定义,造成冗余。使用继承后,可以将这些公共部分放到一个基类(如Person)中,学生类和老师类继承自这个基类,从而复用这些成员,避免重复定义。

(二)继承定义

  1. 定义格式
    • Person是基类(父类),Student是派生类(子类)。继承方式有public(公有继承)、protected(保护继承)、private(私有继承)。
    • 使用class时默认的继承方式是private,使用struct时默认的继承方式是public,但最好显式写出继承方式。
    • 在实际运用中一般使用public继承,其他两种继承方式使用较少且不提倡,因为其继承下来的成员只能在派生类内部使用,扩展性和维护性不强。
  2. 继承基类成员访问方式的变化
    • 基类的私有成员在派生类中无论以何种方式继承都是不可见的,虽然被继承到了派生类对象中,但语法上限制了派生类对象在类内和类外都不能访问。
    • 如果基类成员不想在类外直接被访问,但需要在派生类中能访问,可以定义为protected
    • 基类的私有成员在派生类中不可访问,基类的其他成员在派生类的访问方式取决于“成员在基类的访问限定符”和“继承方式”中的较小者(public > protected > private)。

(三)继承类模板

  1. 作用:通过继承类模板,可以直接调用基类模板的成员函数来完成特定功能。例如,通过继承vector<T>模板类来实现stack栈类,按需实例化vector的成员函数,避免重复实现栈的基本操作。
  2. 注意事项:当基类是类模板时,在派生类中调用基类的成员函数需要指定类域,否则可能会编译报错。这是因为模板是按需实例化,未实例化的成员函数可能找不到标识符。

(四)基类和派生类间的转换

  1. 派生类对象赋值给基类指针/引用public继承的派生类对象可以赋值给基类的指针或引用,这种情况被形象地称为“切片”或“切割”,即把派生类中基类那部分切出来,基类指针或引用指向的是派生出类中切出来的基类那部分。
  2. 基类对象不能赋值给派生类对象
  3. 基类指针/引用转换为派生类指针/引用:基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用,但必须是基类的指针指向派生类对象时才是安全的。如果基类是多态类型,可以使用dynamic_cast进行安全转换,这个在后面的类型转换章节会专门讲解。

(五)继承中的作用域

  1. 隐藏规则
    • 在继承体系中,基类和派生类都有独立的作用域。
    • 当派生类和基类中有同名成员时,派生类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。在派生类成员函数中,可以使用“基类::基类成员”显示访问被隐藏的基类成员。
    • 如果是成员函数的隐藏,只需要函数名相同就构成隐藏。在实际中,在继承体系里最好不要定义同名的成员。
  2. 考察继承相关选择题
    • 对于两个类中的函数构成的关系,如果只有在同一个类里函数名相同且参数不同才构成重载,在不同类中函数名相同一般构成隐藏。
    • 如果程序中没有正确处理成员函数的隐藏问题,可能会导致编译报错。

(六)派生类的默认成员函数

  1. 生成规则
    • 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显式调用。
    • 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
    • 派生类的operator=必须要调用基类的operator=完成基类的复制,且由于派生类的operator=隐藏了基类的operator=,所以需要显式调用基类的operator=并指定基类作用域。
    • 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员,以保证派生类对象先清理派生类成员再清理基类成员的顺序。因为多态中一些场景析构函数需要构成重写,编译器会对析构函数名进行特殊处理,处理成destructor(),所以基类析构函数不加virtual的情况下,派⽣类析构函数和基类析构函数构成隐藏关系。
  2. 示例说明:以PersonStudent类为例,展示了派生类在构造、拷贝构造、赋值操作和析构过程中对基类相应函数的调用。

(七)实现一个不能被继承的类

  1. 方法一:将基类的构造函数设为私有,派生类必须调用基类的构造函数才能实例化对象,但基类构造函数私有化后,派生类无法调用,也就无法实例化对象。
  2. 方法二:在 C++11 中,可以使用final关键字修饰基类,使得派生类不能继承该基类。

(八)继承与友元

  1. 友元关系不继承:基类的友元不能访问派生类的私有和保护成员。例如,DisplayPerson的友元,但不是Student的友元,所以无法访问Student的成员。
  2. 解决方法:如果需要让Display访问Student的成员,可以将Display也变成Student的友元。

(九)继承与静态成员

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

(十)多继承及其菱形继承问题

  1. 继承模型
    • 单继承:一个派生类只有一个直接基类。
    • 多继承:一个派生类有两个或以上直接基类,多继承对象在内存中的模型是先继承的基类在前,后继承的基类在后,派生类成员在最后。
  2. 菱形继承
    • 菱形继承是多继承的一种特殊情况,存在数据冗余和二义性问题。例如,在Assistant类中,由于StudentTeacher分别继承了Person,导致Assistant类中有两份Person的成员变量和成员函数。
    • 解决二义性问题可以显式指定访问哪个基类的成员,但数据冗余问题无法解决。
  3. 虚继承
    • 虚继承可以解决数据冗余和二义性问题,但底层实现复杂,性能会有一些损失。使用虚继承后,基类会单独提取出来,避免了多个派生类中重复存储基类成员。

(十一)多继承中指针偏移问题

  1. 问题分析:在多继承中,对象的内存布局是先声明的基类在前,后声明的基类在后,派生类成员在最后。对于Derive类继承自Base1Base2,当分别用Base1*Base2*Derive*指针指向Derive对象时,Base1*Derive*指针指向的地址相同,而Base2*指针指向的地址与它们不同。
  2. 答案解释:所以答案是p1 == p3!= p2,即选项 C。

(十二)继承和组合

  1. 关系描述
    • public继承是一种“is - a”的关系,每个派生类对象都是一个基类对象。
    • 组合是一种“has - a”的关系,一个类组合了另一个类,就意味着这个类的对象中包含另一个类的对象。
  2. 复用方式
    • 继承是白箱复用,基类的内部细节对派生类可见,一定程度破坏了基类的封装,基类的改变对派生类影响大,派生类和基类间依赖关系强,耦合度高。
    • 对象组合是黑箱复用,被组合的对象内部细节不可见,组合类之间依赖关系弱,耦合度低,优先使用对象组合有助于保持每个类的封装。
  3. 实践建议:实践当中建议优先使用组合,而不是继承。但如果类之间的关系适合继承(“is - a”)或者要实现多态,则需要使用继承。如果类之间的关系既适合继承又适合组合,也优先使用组合。

二、总结

继承是面向对象编程中的重要概念,它允许代码复用,但也带来了一些复杂性,如作用域隐藏、多继承的问题等。在实际编程中,需要根据具体情况合理选择继承和组合,以提高代码的可维护性和扩展性。同时,要注意避免设计出复杂的继承关系,尤其是菱形继承,以免带来数据冗余和二义性等问题。

标签:定义,继承,成员,基类,概念,对象,派生类,函数
From: https://blog.csdn.net/weixin_64368818/article/details/143804111

相关文章

  • C++命名空间介绍、定义、作用、是否允许嵌套
    本文章代码块默认为写了std命名空间的条件下,所以代码里面的输出直接写了cout,没写作用域什么是c++命名空间C++命名空间是一种机制,用于解决全局变量名或函数名之间的冲突问题。它可以将一组相关的变量、函数和类组织在一起,形成一个独立的命名空间,避免命名冲突。命名空间通过在......
  • 自定义注解进行数据脱敏
    前言有些时候,我们可能对输出的某些字段要做特殊的处理在输出到前端,比如:身份证号,电话等信息,在前端展示的时候我们需要进行脱敏处理,这时候通过自定义注解就非常的有用了。在Jackson中要自定义注解,我们可以通过@JacksonAnnotationsInside注解来实现,如下示例:一、自定义注解import......
  • 广义表(Generalized List)的概念和解读
    ###广义表(GeneralizedList)广义表是一种常见的非线性数据结构,用于表达多层次的嵌套关系。广义表可以看作是线性表的推广,其元素可以是单个数据项,也可以是另一个广义表。它广泛应用于符号处理、编译器设计、表达式解析等领域。---###**广义表的定义**####1.**基本定义**......
  • Windows系统日志报错:生成了一个严重警告并将其发送到远程终结点。这会导致连接终止。T
    当我们检查Windows系统日志发现有一个报错:生成了一个严重警告并将其发送到远程终结点。这会导致连接终止。TLS协议所定义的严重错误代码是10。WindowsSChannel错误状态是1203。导致报错的原因是什么?该如何处理?驰网飞飞和你分享其实这个报错和“生成以下严重警告:10。内部错误......
  • go fiber: 抛出自定义异常
    一,代码:1,自定义错误类:packageconfigimport("fmt")//定义错误代码和错误信息typeMyErrorstruct{CodeintMsgstring}//需要定义通用的Error()方法func(eMyError)Error()string{returnfmt.Sprintf("Code:%d,Msg:%s",e.Code,e.M......
  • 无线侧组网概念:信源编码、信道编码、调制、信道、空中接口
    在现代无线通信系统中,信息的传输和处理流程是一个复杂且高度精密的过程。从最初的信号生成到最终接收端的解码,每一个环节都涉及到技术手段和方法的应用。为了能够更好地理解无线通信系统的运作,本文将深入探讨无线侧组网的核心概念,包括信源编码、信道编码、调制、信道和空中......
  • C语言-指针及变量的概念与使用
    1、指针的概念计算机中所有的数据都必须放在内存中,不同类型的数据占用的字节数不一样,例如int占用4个字节,char占用1个字节。为了正确地访问这些数据,必须为每个字节都编上号码,就像门牌号、身份证号一样,每个字节的编号是唯一的,根据编号可以准确地找到某个字节。下图是4......
  • 织梦自定义图片字段报错 Call to a member function GetInnerText()
    问题:添加自定义图片字段时,前台打开当前栏目列表出现 Fatalerror:CalltoamemberfunctionGetInnerText()onstring 错误。解决方法:修改 customfields.func.php 文件:打开 /include/customfields.func.php 文件,搜索:  $fvalue=trim($ntag->GetInnerTe......
  • 什么是C++命名空间 有什么作用?如何定义使用命名空间?且交代命名空间是否允许嵌套?
    1)什么是C++命名空间有什么作用?命名空间的定义:在C++中,命名空间(Namespace)是一种将代码组织成逻辑组的机制,用于避免不同代码模块之间的命名冲突。它提供了一个声明区域,在该区域内可以定义各种类型、函数、变量等,并且这些定义的名称在该命名空间内是唯一的。命名空间的作用:......
  • ABB AC900F学习笔记331:使用ST做自定义功能块,计算最近60秒的分钟均值和最近60分钟的小
    前面自己学习了在西门子TIA使用SCL编程,施耐德Unity中使用ST编程做分钟均值和小时均值的方法,今晚在家练习了在ABBFreelance中自定义功能块使用ST语言做分钟均值和小时均值。新建项目、插入硬件、仿真器、操作站等不做介绍。新建一个用户功能块池,下面建一个功能块类。功能块类定......