C++:继承用法详解~

news2024/11/15 23:36:26

         在学完C++的类和对象,并掌握了类的核心语法与基本用法之后;我们就得去学习一下继承的语法,与继承的用法。简单概括一下,继承是C++中一种代码复用的手段,它允许我们,对已有的类,增添新的成员函数或变量,产生的新类,叫做子类。而本期博客,就是讲解继承的用法,如果这篇博客对你学习或工作上有帮助,还请多多点赞+收藏,万分感谢~ 

一、继承的基础概念(核心)

1、继承的用法展示

        在我们现实生活中,每一所学校都拥有自己的教务系统,而教务系统中,存储着学生,老师以及学校一些其它的信息。在用代码实现教务系统的时候,我们需要用一个学生类储存学生的信息,我们又需要一个老师类储存老师的信息。类的定义如下:
        这里只是简单的表示了一下:一个学生类,一个老师类,分别需要哪些属性和方法。可是我们从代码中发现,两者存在一定的代码重复,显得过于冗余。而C++中,因为继承机制的存在,我们可以对这两个类进行,下面的处理:

class Person
{
protected:
	string name = "周天津"; // 名字
	int old = 58; // 年龄
	string ID = "111081200631313111"; // 身份证号
	string tel = "11188888888"; // 手机
};

class teacher : public Person
{
public:
	void stude()
	{
		cout << "老师" << name << endl;
		cout << "年龄" << old << endl;
		cout << "ID" << ID << endl;
		cout << "电话" << tel << endl;

		cout << "职工" << work << endl;
	}
private:
	string work = "数学老师";
};

        运行一下,看看效果: 

2、继承的定义与使用

  1. 继承是C++中一种代码复用的手段,它允许我们,对已有的类,增添新的成员函数或变量,产生的新类,叫做子类。继承是一种层次结构的复用,体现了代码从简单到复杂;通过继承手段,我们可以使类得到复用。每种书的用语习惯不同,被复用的类可以称为父类也可以是基类,在此基础上产生的新类,叫做子类或派生类。
  2. 继承的使用格式:class    子类名    :    继承方式    父类名

3、继承方式

  • 在C++中,继承方式存有3种:public继承、protected继承、private继承(这里复用三个访问权限修饰符),那不同方式的继承有什么作用呢?——不同方式的继承,影响着父类中不同,访问权限的成员继承之后,在子类当中的访问权限
    1. 无论是什么继承,父类的私有成员,在子类中都是不可见的。
    2. 父类的其余成员,继承方式与访问权限限定符,选择其中权限最小的;如:保护继承,父类的公有成员变保护,保护成员还是保护。
    3. 当父类的成员,不想在类外之间访问,而想在子类中访问的时候,可以将父类的成员实现为保护成员。可见保护成员与私有成员的不同就在此处。
    4. 继承方式不显示写的时候,struct定义的类,默认是公有基础,class定义的类,默认是私有继承。
    5. 现实中,最常用的继承方式是公有继承~

三种继承方式总结:

父类成员public继承projected继承private继承
publicpublicprojectedprivate
protectedprotectedprojectedprivate
private 不可见不可见

不可见

4、隐藏规则

  • 在继承体系中⽗类和⼦类都有独⽴的作⽤域。
  • 父类与子类,若存在成员变量名相同,则构成成员变量隐藏;
  • 父类与子类,若存在成员函数名相同,则构成成员函数隐藏;
  • 注意隐藏,只需要函数名相同即可。
  • 所谓隐藏,就是子类指针或引用,去调用构成隐藏关系的成员变量或成员函数的时候,默认的是子类中的成员函数,或成员变量~
  • 若想要调用父类中成员函数,或成员变量,则可以通过指明类型。

5.子类与父类对象间的赋值兼容转换

  • 子类的实例化对象、或其指针、引用,可以赋值给父类的实例化对象、或其指针、引用。这里是一种切片的效果,所以不涉及类型转换问题,在赋值的时候,不会产生临时对象。
  • 父类的实例化对象不能赋值给子类的实例化对象,但父类的指针或引用,可通过强制类型转换,赋值给子类的指针或引用,但前提是确保安全性。

6、子类的默认成员函数

  • 子类(派生类),作为一个类,同样会存在六种默认成员函数(C++11,会存在八种);而需要我们去重点学习的有:构造函数、析构函数、拷贝构造函数、赋值运算符重载,我们需要通过以下两个方面去学习这四种函数。
    1. 编译器默认生成的,其行为是什么?
    2. 如果编译器默认生成的,不能满足我们呢的需要,我们又该如何显示实现呢?
    3. 我将通过代码+注释,将这四种成员函数的行为,以及如何显示实现,展示出来。还请区分侧重点,自行通过IDE进行逐一验证,或详细内容还请跳转至类与对象篇。
    4. 我这里只会总结,比较重要的默认成员函数中的知识点~
    5. 详细内容还请跳转至类与对象篇。

i、构造函数
  • 总结:(初始化列表,定义变量的顺序:先父类后子类,其中又符合先声明的先定义)
  • 子类默认构造函数的行为:
    1. 调用父类的默认构造函数(无参构造函数,有参但全缺省构造函数,编译器自动生成的构造函数都属于默认构造函数);若父类无默认构造函数,则编译器报错。
    2. 内置类型编译器的行为是未知的。
    3. 自定义类型,编译器调用自定义类型的默认构造函数,若无默认构造函数,则编译器报错。
    4. 可见,编译器对子类中父类的行为,和,对对象的行为基本类似。
  • 显示写子类构造函数:
    1. 父类的初始化,必须在初始化列表中进行;若不显示写父类的初始化,则调用父类的默认构造函数,若父类无默认构造函数,则编译器报错。内置类型编译器的行为是未知的。
    2. 显示调用父类构造函数:父类名(参数)
    3. 自定义类型和内置类型的初始化,和在普通类中,无区别。
class Person // 基类
{
public:
	Person() = default; // 强制编译器生成 -> 默认构造函数

	Person(const char* name, size_t age, const char* Addr) //普通构造函数
		:_name(name)
		, _age(age)
		, _Addr(Addr) 
	{}

protected:
	string _name = "庄梦涵"; // 名字
	size_t _age = 17;  // 年龄
	string _Addr = "河南龙门镇庄村"; // 籍贯
};

class student : public Person // 派生类
{
public:

	student() = default; // 强制编译器生成 -> 默认构造函数

	student(const char* name, size_t age, const char* Addr, const char* grade) // 普通构造函数
		:Person(name, age, Addr)
		, _grade(grade)
	{}

	void test()
	{
		cout << "_name:" << _name << endl;
		cout << "_age:" << _age << endl;
		cout << "_Addr:" << _Addr << endl;
		cout << "grade:" << _grade << endl;
	}
protected:
	string _grade = "大二"; 
};

int main()
{
	student zmh;
	zmh.test();
	cout << endl;
	student cmy("常梦圆",17,"神垕","大四");
	cmy.test();
	return 0;
}

ii、析构函数
  • 总结:(析构函数,析构变量的顺序要求:先子类后父类,其中又符合后声明的先析构)
  • 子类默认析构函数的行为:
    1. 对于子类的,内置类型编译器的行为是未知的。
    2. 对于子类的,自定义类型,编译器会调用它的析构函数。
    3. 将子类的成员变量都析构之后,再自动调用父类的析构函数。
  • 显示写子类析构函数:
    1. 同普通的自定义类型一样,无论我们是否显示调用其析构函数,编译器都会在其销毁之前自动调用析构函数。
    2. 所以,我们写子类的析构函数的时候,只需要负责子类新增,内置类型的成员变量指向资源的清理就行。至于自定义类型和父类,编译器会自动为我们清理。而这一目的,也是为了保证,先析构子类后析构父类。
    3. 虽然编译器会自动为我们调用父类的析构函数,可若我们偏要显示调用的时候,也不是不可以——但必须要指明类域,否则编译器会报错。
    4. 父类和子类的析构函数,构成隐藏。至于为什么,因为C++的重写,需要函数名相同。

iii、拷贝构造函数
  • 总结:(默认生成的拷贝构造函数是一种浅拷贝;若需要资源的申请与释放则需要自己实现)
  • 子类默认拷贝构造函数的行为:
    1. 需要知道的是,拷贝构造函数是构造函数的重载形式之一;
    2. 对父类的处理:首先会去调用父类的拷贝构造函数,
    3. 对自定义类型的处理:会去调用自定义类型的拷贝构造函数。
    4. 对内置类型的处理:编译器直接进行值拷贝~
  • 显示写子类拷贝构造函数:
    1. 拷贝构造函数的函数名为类名相同;第一个参数必须是类名的引用(通常是const 引用,也不能有缺省值);并且没有返回类型(void也 没有);拷贝构造函数可以有其他参数(必须有缺省值),但这不是常见做法。
    2. 对父类的处理:在初始化列表中,显示调用父类的拷贝构造函数。
    3. 对自定义类型的处理:在初始化列表中,显示调用自定义类型的拷贝构造函数。
    4. 对内置类型的处理:完成正确的初始化与赋值,可以是在构造函数的函数体中,也可以是初始化列表。

iiii、赋值运算符重载

        总结:赋值运算符重载函数的行为,无论是编译器自动生成的,还是人为实现的,都与拷贝构造函数相似。只不过前者是对已有对象进行赋值,后者是对正在创建的对象的初始化。

        注:父类与子类的赋值运算符重载函数,构成隐藏关系,通过子类、子类指针或引用,显示调用父类的赋值运算符时,还请标明类域。

二、继承进阶(细节)

1、实现一个不能被继承的类

  1. ⽗类的构造函数私有,⼦类的构成必须调⽤⽗类的构造函数,但是⽗类的构成函数私有化以后,⼦类看不⻅就不能调⽤了,那么⼦类就⽆法实例化出对象。
  2. C++11新增了⼀个final关键字,final修改⽗类,⼦类就不能继承了。

2、继承与友元

        父类的友元函数,不能被子类继承;若想要该函数同时能访问父类和子类,则需要都进行友元声明。

3、继承与静态成员

        父类被子类继承后,父类的静态成员在子类中,与父类共用,下面则是一个静态成员count,记录当前已实例化对象的个数。

class WK
{
public:
	WK()
	{
		cout << "当前已创建ZMH/WK类个数:" << ++count << endl;
	}
	~WK()
	{
		cout << "当前已创建ZMH/WK类个数:" << --count << endl;
	}
protected:
	static int count;
};

class ZMH :public WK {};

int WK::count = 0;

int main()
{
	WK d1,d2,d3;

	{
		ZMH d4, d5, d6;
	}
	
	WK d7;

	return 0;
}

 

4、单继承与多继承

i、继承模型
  • 单继承:⼀个⼦类只有⼀个直接⽗类时称这个继承关系为单继承。
  • 多继承:⼀个⼦类有两个或以上直接⽗类时称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的⽗类在前⾯,后⾯继承的⽗类在后⾯,⼦类成员在放到最后⾯。
  • 菱形继承:菱形继承是多继承的⼀种特殊情况菱形继承有数据冗余和⼆义性的问题。⽀持多继承就⼀定会有菱形继承,所以实践中我们也是不建议设计出菱形继承这样的模型的。

5、继承与组合的比较

  • public继承是⼀种is-a的关系。也就是说每个⼦类对象都是⼀个⽗类对象。
  • 组合是⼀种has-a的关系。假设B组合了A,每个B对象中都有⼀个A对象。
  • 继承允许你根据⽗类的实现来定义⼦类的实现。这种通过⽣成⼦类的复⽤通常被称为⽩箱复⽤(white-box reuse)。术语“⽩箱”是相对可视性⽽⾔:在继承⽅式中,⽗类的内部细节对⼦类可⻅继承⼀定程度破坏了⽗类的封装,⽗类的改变,对⼦类有很⼤的影响。⼦类和⽗类间的依赖关系很强,耦合度⾼。
  • 对象组合是类继承之外的另⼀种复⽤选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接⼝。这种复⽤⻛格被称为⿊箱复⽤(black-box reuse),因为对象的内部细节是不可⻅的。对象只以“⿊箱”的形式出现。 组合类之间没有很强的依赖关系,耦合度低。优先使⽤对象组合有助于你保持每个类被封装。
  • 总结:在实现一个类中,若继承和组合的复用手段都可以使用,则更推荐使用组合。

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

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

相关文章

28 TreeView组件

Tkinter ttk.Treeview 组件使用指南 ttk.Treeview 是 Tkinter 的一个高级控件&#xff0c;用于显示和管理层次化数据。它类似于电子表格或列表视图&#xff0c;但提供了更丰富的功能&#xff0c;如可展开的节点、多列显示等。ttk 模块是 Tkinter 的一个扩展&#xff0c;提供了…

NVM安装及配置

一&#xff1a;下载nvm安装包 https://github.com/coreybutler/nvm-windows/releases 二&#xff1a;安装步骤 三&#xff1a;检查环境变量 &#xff08;1&#xff09;、检查用户变量和系统变量中是否有NVM_HOME和NVM_SYMLINK。一般情况下&#xff0c;安装nvm后&#xff0c;系…

Java分布式架构知识体系及知识体系图

Java分布式架构整体知识体系是一个庞大而复杂的领域&#xff0c;它涵盖了多个方面&#xff0c;旨在帮助开发者构建高性能、高可用、可扩展的分布式系统。以下是对Java分布式架构整体知识体系的概述&#xff1a; 一、分布式理论基础 CAP理论&#xff1a; 一致性&#xff08;Con…

GUI编程04:课堂练习及总结

本节内容视频链接&#xff1a;6、课堂练习讲解及总结_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1DJ411B75F?p6&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 根据前三节学习到的Frame、Panel、Button知识&#xff0c;画出一下窗口界面&#xff1a; 实现代码如下…

避坑之:深信服AC跨三层取MAC(核心交换机是锐捷S7808C_RGOS 11.0(4)B2P1)

今天碰到一个奇怪的现象&#xff0c;深信服AC对接锐捷交换机做跨三层取MAC&#xff0c;怎么都获取不到。 一、坑1&#xff1a;交换机不回应snmp报文 1.1 排查锐捷交换机配置 配置上看着没有问题&#xff0c;重新配置了community 1.2 查看snmp报文是否通畅 我的笔记本是win10…

选对文档版本管理软件:10款工具详解

本篇文章中提到的工具包括&#xff1a;1.PingCode&#xff1b;2.Worktile&#xff1b;3.联想Filez&#xff1b;4.蓝凌云&#xff1b;5.阿里云盘&#xff1b;6.360亿方云&#xff1b;7.无忧企业文档&#xff1b;8.DocStar ECM&#xff1b;9.Dropbox Business&#xff1b;10.Shar…

APP 数据抓取 - Charles 抓包工具的使用(Charles 端口配置、CA 证书配置、Charles Android 模拟器配置)

前言说明 此文章是我在学习 Charles APP 抓包时编写&#xff0c;内容都是亲测有效&#xff0c;文章内容也有参考其他人&#xff0c;参考文章如下&#xff1a; Android 手机使用 charles 抓 https 请求&#xff08;保姆级教程&#xff09;网易 mumu 模拟器安装下载 charles 的…

JAVAEE初阶第二节——多线程基础(上)

系列文章目录 JAVAEE初阶第二节——多线程基础(上) 计算机的工作原理 认识线程&#xff08;Thread&#xff09;Thread 类及常见方法线程的状态 文章目录 系列文章目录JAVAEE初阶第二节——多线程基础(上) 计算机的工作原理 一.认识线程&#xff08;Thread&#xff09;1.概念 …

Leetcode面试经典150题-28.找出字符串第一个匹配项的下标

解法都在代码里&#xff0c;不懂就留言或者私信&#xff0c;比第一题稍微难点 用KMP解这个题简直就像大炮打蚂蚁&#xff0c;但是没办法&#xff0c;现在都是这么卷 package dataStructure.bigFactory;public class _28Strstr {public static int strStr(String s1, String s…

EasyCode实现完整CRUD + 分页封装

文章目录 1.创建一个表sys-user2.EasyCode 模板配置1.entity.java.vm2.dao.java.vm3.mapper.xml.vm4.service.java.vm5.serviceImpl.java.vm6.controller.java.vm7.PageInfo.java.vm8.PageResult.java.vm9.SunPageHelper.java.vm 3.EasyCode生成CRUD1.右键表&#xff0c;选择Ge…

Linux系统查看磁盘、内存使用情况、查看当前文件夹内文件详情:free、top、df、du、ls

Liunx系统查看内存使用情况 free&#xff1a;查看当前内存以及交换区内存使用情况&#xff0c;默认显示单位是比特&#xff0c;加上参数-h以易读的方式显示&#xff08;如KB、MB、G&#xff09;&#xff0c;在Linux系统中所有查询加上-h参数均以易读的方式显示。 资源管理器查看…

【C++ Primer Plus习题】7.5

问题: 解答: #include <iostream> using namespace std;int function(int n) {if (n 0)return 1;if (n 1)return 1;return n* function(n - 1); }int main() {int value 0;while (true){cout << "请输入数字:";cin >> value;cout << val…

【读书笔记-《30天自制操作系统》-11】Day12

从本篇内容开始讲解定时器。本篇内容比较简单&#xff0c;首先介绍定时器的概念与设置方法&#xff0c;然后介绍超时的中断处理&#xff0c;并对中断处理函数进行了优化。 1. 定时器 定时器是操作系统中十分重要的功能。它的原理很简单&#xff0c;只是每隔一段时间发送一个…

接口自动化测试框架:SoapUI

SoapUI是一个非常流行的用于Web服务测试的工具。它允许你对SOAP和RESTful Web服务进行测试。在本篇文章中&#xff0c;我们将介绍SoapUI的背景、好处以及企业实际使用该工具的干货。 一、背景 在过去的几年中&#xff0c;Web服务变得越来越流行。由于不同的应用程序可以通过W…

新材料正在加速推动压铸领域3D打印技术应用

3D打印技术&#xff0c;以其独特的逐层累加粉末材料成型方式&#xff0c;正逐步解锁模具制造的无限可能&#xff0c;尤其在实现复杂几何构型与内部结构优化方面展现出非凡潜力。这一技术革新不仅提升了模具制造的精度与效率&#xff0c;还通过随形水路、随形透气钢等创新设计&a…

利润暴涨507%的携程,做对了什么?

关于旅行&#xff0c;1500年前&#xff0c;古罗马思想家圣奥古斯丁曾在《忏悔录》这样评价&#xff1a; “世界是一本书&#xff0c;而不旅行的人只读了其中的一页。” 如今&#xff0c;旅行更是承载了人们逃离工作、抛开忧虑的祈望&#xff0c;成为了理想中的“诗和远方”。…

Google play应用老包突然被暂停和删除了,什么原因?

相信不少开发者都遇到应用突然被暂停和删除的情况&#xff0c;被谷歌判为应用存在欺骗行为&#xff0c;还会在某种程度上“明确”指出应用违规的原因&#xff1a;“您的应用包含可能使用户、用户数据或设备面临风险的代码&#xff0c;以及/或您的应用未能兑现对用户的承诺。” …

治愈系风景视频素材下载网站有哪些?令人治愈美景素材库网站分享

当我们谈到视频制作时&#xff0c;风景视频素材总是能为作品带来生动的视觉效果和震撼的感官体验。无论是用于旅游宣传、自然纪录片&#xff0c;还是日常生活记录&#xff0c;优质的风景视频素材都是不可或缺的。尽管高清美丽的风景素材并不容易获取&#xff0c;特别是那些既免…

Runway删库跑路,真的run away了!

没有任何通知&#xff0c;Runway在Hugging Face上的内容全部删除了&#xff01; 目前具体原因不明。Runway的主页只留下了一句话&#xff1a; 我们不再对HuggingFace账号进行维护。 据悉&#xff0c;Runway在Hugging Face上&#xff0c;最火的、也是争议最大的项目&#xff0c;…

成为优秀自动化测试工程师的7个步骤!

成为优秀自动化测试工程师的7个步骤 自动化测试是软件开发过程中至关重要的一环&#xff0c;它可以提高测试效率、减少人工测试的错误率&#xff0c;并且可以随时重复执行以确保软件质量。成为一名优秀的自动化测试工程师需要不断学习和提升自己的技能。以下是从0到1的详细步骤…