【C++】类、静态、枚举、重载、多态、继承、重写、虚函数

五、类

面向对象编程是一个巨大的编程范式。C++中的类class就是基于对象的程序设计。
我们可以用类来定义一个新的类型,这些新类型就可以像内置类型一样使用。
内置类型颗粒度太太小,现实需求又非常复杂,这就需要我们把内置类型适度的进行拼搭,拼搭成一个能描述现实问题的大粒度颗粒,来解决现实问题。
C++的数据类型有:常量、变量、布尔类型、指针类型、字符串类型、引用类型、枚举类型、数组类型、vector容器类型、复数类型、pair类型、类类型。所以类也是一种数据类型。

你可以把类看成一个新的数据类型,或者说是应用程序中的一种设施,这种设施是把数据和函数封装在一起的设施

类的重点内容有:   
a、怎样定义一个类?通过共有类接口私有类接口实现。也就是信息隐藏(information hiding)的概念。
b、怎样定义和操纵类的对象实例?类域、嵌套类、做为名字空间成员的类、局部类...
c、类对象的初始化、析构、赋值如何实现?特殊成员函数:构造函数constructor、析构函数destructor、拷贝赋值操作符copy assignment operator...

这里重点强调一组特殊成员函数:转换函数conversion functions,就是将class类型定义一组标准转换。当类对象被用作函数实参、或作为内置或重载操作符的操作数时,这些转换函数就由编译器隐式的调用。

e、操作符重载的概念和设计,使我们能够使用内置操作符来操作class类型的操作数,使class类型对象的用法像内置类型对象的用法一样直观。 所以,我们把像赋值、下标、调用、new和delete等重载操作符声明为一个类的友元friend,使其拥有特殊的访问权限。

1、类和结构体的联系和区别
关于结构体的内容可以参考我的另外一篇博文:【C语言学习笔记】八、结构体-CSDN博客
C++之所以兼容结构体,是因为希望和C保持兼容性。结构体是C的语法,但C中没有类。
类和结构体可以说没什么区别。唯一的区别就是结构体中的变量默认都是共有的,类中的变量没有public声明就都默认是私有的。
public表示可以在类以外的任何地方访问这些变量

2、什么时候用类什么时候用结构体?
虽然类和结构体没有太大的区别,但是它们还是各自有各自的应用场景,不是随便通用的。比如当我们想管理很多变量时,那你用结构体,你的代码就非常清晰。如果你还想写一些函数,甚至是使用继承,建议还是写成类的形式,因为这会涉及到更多的内容。比如当一个结构体继承一个类的时候,编译器就会警告,虽然程序还是可以运行,但还是有一些语义上的区别的。总之:结构体的定位是数据的结构,就是只用结构体表示一些数据;如果你还实现更复杂的功能,那就用类。

3、类定义、类声明、数据成员、成员函数、可见性

4、写一个最简单的类
(1)梳理需求
我打算写一个类,类的功能是实现日志信息管理。
由于日志系统可大可小,可简可繁,不仅是打印信息到控制台,还可以打印不同的颜色、或者通过网络输出日志信息到一个文件。所以一个log系统可十行代码也可上万行。现在我就写一个最最简单的Log系统,实现向控制台写入文本的能力,并且区分日志级别(错误、警告、信息或跟踪)的功能即可

(2)分解需求
把日志信息分3个级别:错误、警告、信息或跟踪
当我把级别设置为"信息",就打印信息、警告、错误3种日志;
当我把级别设置为"警告",就打印警告、错误2种日志;
当我把级别设置为"错误",就打印错误1种日志;

(3)代码写作过程:

上面的Log类中的公共变量我用了两次public是因为:我喜欢把类中不同的部分分开来写,比如,public方法写在一部分,public变量又放另一部分,public静态变量又会放其他一部分。这只是每个人的编程风格而已,只是为了更清晰一点而已。

说明:上面的步骤只是展示了如何逻辑清晰的写一个类,但事实上上面的代码是非常糟糕的,后面我们将使用更多的概念来改进这个类,使其达到专业生产级水平的代码。

5、静态static
C++中的静态static关键字有3个意思:
一是,当你在类或者结构体外部使用static关键字时,表示你static的符号,其链接只能在内部,也就是你static定义的符号只能在翻译单元可见。
二是,当你在类或结构体内部使用static关键字时,表示该符号将与类的所有实例共享内存。也就是说该静态变量是该类类型的所有对象共享访问的。同样的效果也适用于静态方法。
三是,函数内部使用static关键字时,就类似python中的闭包效果。

(1)static关键字在类或结构体外部时:

上图s_Variable是定义一个静态变量,s表示这个变量是静态的,意思是这个变量只会在这个翻译单元内部链接。
静态变量或静态函数意味着,当需要将这些变量或函数与实际定义的符号链接时,链接器不会在这个翻译单元的作用域之外,寻找那个符号定义。

(2)static关键字在类或结构体内部时:
如果我创建一个名字叫Entity的类,我不断创建Entity的实例,但是static变量或函数永远只有一个,所有实例共享这一个static变量或函数。
所以,如果某个实例更改了static变量或函数,那所有实例的这个变量和函数都会跟着改变。所以,我们一般都不会通过实例更改static变量或函数,没意义嘛。

所以设置static变量的目的只有一个:就是所有实例可以共享这个变量。而不是通过实例去改变这个变量,这样做毫无意义。
比如银行账户,每个账户的姓名和余额都不一样,但每个账户的利率是一样的。如果我们给每个账户都单独设置一个利率变量,是不是就非常浪费。所以使用static变量是有意义的。

静态方法无法访问类的实例。所以在静态方法内部是不能写引用到类实例的代码。静态方法也不能访问非静态变量。
在类中的每个非静态方法都是要获取当前类的一个实例作为其参数的。
静态方法可以被类实例调用、也可以通过命名空间调用。

总之,关键字static就是一个作用域的功能。static变量或函数是类外的变量和函数,虽然它也写在类内部;非static变量或函数是类内的变量和函数。类外的函数访问类外的变量,肯定访问不了类内的变量了。

(3)static在函数内部时:
static在函数内部时,也叫局部静态 local static
局部静态变量允许我们声明一个变量,这个变量的生存期相当于整个程序的生存期,但是作用域确实这个函数内部的。这一下就让我想起python中的闭包!是不是,你有没有同感!

小结:函数可以访问外部变量,也可以在函数体内更改外部变量。但是函数体内的变量只能在函数体内改变。但是如果函数体内的变量是static的,那这个变量就相当于是全局变量,但是这个全局变量不能在函数体外更改,只能被该函数更改。

6、枚举类
枚举ENUM,enumeration的缩写。枚举就是一个数值集合。就是给一个名字赋值,但不是平时我们说的就赋一个值的那种,是另外一种赋值方法。

7、构造函数
类在每次实例化对象时,都会运行一个特殊的方法就是构造函数。

构造函数的名称必须与类的名称相同、必须没有返回值、可以有参数也可以没有

所以构造函数就是类中的一种特殊方法,就是每次实例化一个实例的时候,就自动调用的一个方法。上图是我们手动写了一个构造函数,也就是我们自己指定了一个构造函数。如果我们在类中不写构造函数,那就会有一个已经写好的构造函数默认让你用了,就是默认就已经执行了一个构造函数。
如果不实例化对象,那构造函数就不会被运行。
总之,构造函数就是创建类实例时运行。有人知道是为什么吗?
有心的小伙伴估计都已经猜出来了:比如上图你现在写的类Entity,即使你不写Entity这个特殊成员函数————构造函数,其实你还是要执行一个初始化的构造函数的!因为即使你的Entity类不是继承的子类,但只要你写类,肯定就默认有一个父类,就是默认你已经继承了一个父类,这个父类就是所有类的父亲,不管啥类都是这个父类的子类,所以当我们把Entity类写完后,初始化Entity时,其实首先运行的代码就是父类的代码(在上面小标题3-可见性的最后一个小例子就有演示!),而父类的代码中就肯定有一个父类的构造函数,所以父类的这个构造函数首先被执行,所以你实例化Entity时,其实构造函数就已经被在你看不见的地方被执行了。

但是这里还有一个道道就是,如果我在Entity中也写了我自己的构造函数,那此时如果你的构造函数和父类的构造函数是同名同参,那就不会执行父类的构造函数了,只执行Entity的构造函数,这叫函数重载。而且这也是允许的,所以不仅仅是构造函数,任何函数都适用。如果你决得父类中哪个方法不顺眼,那你可以在子类中再写一个同名的方法,那子类中的方法就自动替换了父类中方法。当然对于构造函数,它比较特殊,因为它要和类名同名,所以一般情况是,父类的构造函数一般和子类的构造函数不同名(因为我们写类不可能写一个和父类一样名字的类),那此时子类实例化对象时,其实是执行了2次初始化,一次是父类的初始化代码,一次是子类的初始化代码。

或者这么说吧,我们显式的看到你写的类Entity只有类体中的那些代码,其实当你实例化这个类时,系统给这个类分配的内存是大于这个类本身拥有的变量的长度的,就是编译器自动给你写的类拷贝了父类中的代码,所以你现在写的Entity类编译完毕,其实前半部分是其父类的代码,后半部分才是Entity的代码。而前半部分父类的代码中又有父类的同名构造函数,所以是不是就先执行了父类的构造函数,这是第一次初始化。然后运行到后半部分代码,也就是Entity代码部分,Entity中又有和Entity同名的函数,也就是Entity自己的构造函数,此时就要执行Entity函数了,也就是第二次初始化了。

说明:此处如果你使用的是类的静态方法那就没法实现了,因为类的静态方法无法访问类中的非静态变量。
以后还会讨论堆内存的分配问题。当我们使用new关键字创建一个对象实例时,也会调用构造函数。
也就是因为这种特殊函数,或者说因为这种重载特性,除了用于初始化的构造函数外,我们还可以写一些,比如,删除构造函数、复制构造函数、移动构造函数等等。。。

8、重载、多态
本来讲完构造函数就应该开始讲析构函数了,因为它们是一对儿的。但是前面频频提到重载和多态,所以这里把重载和多态先讲了。

重载也叫函数重载,意思就是你写类的时候,即使你没有继承任何父类,其实底层也是默认你继承了一个元类,就是类的祖宗。所以你写的类编译后的代码其实是把祖宗的代码也复制了一份后的代码,也就是加入了祖宗的代码指令。当然如果你写类的时候有继承,那你的代码在编译时,编译器就拷贝了祖宗的代码+你继承的类的代码。这才是你的类的全部指令。

所以,这里就出现一个问题,当你看祖宗或者你父类中哪个方法不顺眼时,你可以写一个同名同参的方法,此时你自己的方法就替换了祖宗或者父类中的同名方法,这就叫做函数重载。C++的类是支持函数重载的。

如果你不写类,你直接写两个同名同参的函数,那编译器会毫不客气地给你报错,不给你编译。但是你在类中写两个同名同参的方法,ok,没问题,后面的方法直接覆盖前面的方法。一切都顺利。

那么多态是什么呢?就是有相同的函数(方法)名,但有不同的参数的不同函数(方法)版本。前面我们说如果你写两个同名同参函数,编译器会报错,但是如果你写的是两个同名不同参的函数,那是没关系的,不会报错。当函数调用时,编译器会根据参数判断调用哪个函数。这就是多态。同理,如果同名不同参的函数写在了类里面,那就是两个同名不同参的方法,编译器也是不会报错的,更不会像同名同参那样覆盖的!也是根据参数判断调用哪个方法。

下面一个小例子演示一下什么是重载和多态:

可能大家有些晕了,这里再来一波小结:

同名同参函数只能写到类里面,否则编译器就报错。写到类里面的同名同参函数(方法)肯定是一个在父类里面,一个在子类里面,因为子类看不惯父类的这个函数的功能,所以自己再写了一个同名同参的,替换了父类中的。这就叫重载。就是自己在子类中写的同名同参函数把父类中的函数替换了,或者说截胡了,所以叫重载。

同名不同参的函数,不管是写在类里面还是写在类外面,编译器都不会报错!!编译器是接受同名不同参的。这叫多态现象。编译器会根据参数来决定调用哪个函数。

构造函数是在类里面写的一个和类名一样的函数,但是这个函数没有返回值,就是连void也不能写!而且这个函数可带参数也可不带参数。当编译器看到这样的函数时,类实例化时,这个函数就直接一起执行了。所以实例化后的实例对象都是已经初始化过的了。

说明:后面还有虚函数,也是和这些概念搅合在一起的,建议本小部分和后面的11虚函数部分一起看,后面的虚函数也用的是这里的案例。

9、析构函数
构造函数是你创建一个新的实例对象时运行的。就是是在创建新的实例对象时,自动被调用的,通常用于设置变量或者一些初始化功能。
而析构函数则是在销毁对象时运行的。一个对象要被销毁时,析构函数就自动被调用,通常是卸载变量、清理你使用过的内存等功能。
析构函数同样适用于栈和堆分配的对象。如果你用new分配一个对象,当你调用delete时,析构函数就会被调用。如果只是一个栈对象,当作用域结束时,栈对象将被删除,此时析构函数也会被调用。

你在构造函数中初始化了一些变量,你就得在析构函数中卸载或销毁这些变量,否则就容易内存泄漏。
比如如果你在堆上手动分配了任何类型的内存,那么你得手动清理。

10、继承
重写、多态、继承是类的几个重要特点。重写和多态前面反复演示过了,所以这里讲继承也都简单多了。

类之间的继承,就是相互关联的类的层次结构,这些继承关系是一个虚表(V-table)来维护的。

继承最主要的好处就是避免代码重复编写。包含公共功能的基类--从基类中分离,从最初的父类中创建子类
我们可以把类之间的公共功能放到一个父类(基类)中,然后从父类中创建的子类就免去了相同的代码一遍遍的复制。也就是好像不用一遍遍写模板了。

11、虚函数
虚函数是面向对象编程中非常重要的概念。
虚函数和前面的重载、多态都是有联系和区别的。把前面的重载和多态都彻底弄明白了,这里也就很容易明白。

下面这个例子是对指针和类的详细拆解:

明白了指针后,我们再接上面的8(重载和多态),继续看:重载并不是覆盖,也不是删除,只是替换执行而已。所以我们用指针还是可以找到被重载的函数的:

从上例也可以看到,子类中同名同参的方法虽然可以通过重载,替换父类中的相应方法,但并不意味着父类中的那个同名同参的方法不存在了,其实还是存在的。如果想让它等同不存在,就要用虚函数,也就是使用关键字virtual和override:  

说明: 虚函数是引入了一种叫动态联编(Dynamic Dispatch)的概念,就是通过维护一个V表(虚函数表)来实现的,基类中有一个成员指针,指向V表,所以生成V表是需要额外的内存来存储V表的,是要增加一点开销的。
V表是一个表,它包含基类中所有虚函数的映射,这样在virtual方法运行时,就将它们映射到正确的覆写(voerride)的函数。也就是如果你覆写了一个基类中的函数,那就将基类中的基函数标记为虚函数virtue,然后把你写的那个函数标记为覆写override,就表示是重写了。

小结:当类中出现同名同参的方法时,就会自动启动重载机制,就是自动执行子类中的同名同参方法。但是这并不表示父类中的同名同参方法被删除了、不存在了,通过指针还是可以调用的。所以如果你永远不想用父类中的方法,那你就用虚函数,即使指针调用了父类中的同名同参方法,也会自动跳转到被覆写的子类方法上。

12、C++接口(纯虚函数)
纯虚函数是一种特殊类型的虚函数。其本质上与其他语句(如Java或C)中的抽象方法或接口相同。
纯虚函数允许我们在基类中定义一个没有实现的函数,然后强制子类去实现该函数。
其实这种做法在面向对象编程中是非常常见的,这通常被称为接口interface,其他语言有interface关键字,而C++中的接口是一个类,这个类中只包含一个未实现的方法:

说明:
一是,纯需函数必须被实现,才能创建这个类的实例。
上例中的Entity必须得重写GetClassName,因为Entity继承了Printable,而Printable中有纯需函数,所以类Entity是必须得重写得,否则无法实例化。
但是Player类是继承的Entity类,而Entity类中没有纯虚函数,所以类Player是没有像Entity类中GetClassName那样的必重写函数。但是Entity中有虚函数GetName,意思就是函数GetName可以被重写,所以Player中重写了两个GetName,这里是展示多态这个知识点。

二是,接口只是C++的一个类而已。有了这个类,我们就可以将这个类(抽象基类)作为参数(类型)放入一个通用的函数中。
上例中,如果类Player也重写了类Printable中的纯需函数GetClassName后,是不是就和Entity类一样,当然其他更多的类都同理,都可以作为Print函数的实参了。所以纯虚函数所在的类就是一个接口,它让所有重写它的类都可以变成一个统一的实参。这样类就可以当实参传递了,就可以调用这个方法或做其他的事情了。反观函数PrintName,其参数只能限制在Entity的长度,而利用Printable类中的纯虚函数GetClassName,就可以实现任意长度的实参传递了。因为通过virtual和override,也就是V表进行映射了,让代码执行跳转了。


                

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微软推出集成GPT-4o的文本转语音虚拟数字人服务

微软近日宣布,其全新的文本转语音虚拟数字人服务正式上线,并集成了GPT-4o技术。这一服务为用户提供了创建实时互动数字人的可能。通过先进的自然语言处理技术,数字人能够将文本转化为自然流畅的语音,并配以生动的虚拟形象&#xf…

C++【函数重载】【附有C语言为何不能实现函数重载的讲解】

P. S.:以下代码均在VS2019环境下测试,不代表所有编译器均可通过。 P. S.:测试代码均未展示头文件stdio.h的声明,使用时请自行添加。 博主主页:LiUEEEEE                        …

【硬件视界2】什么是CPU和GPU?有什么区别?

名人说:莫听穿林打叶声,何妨吟啸且徐行。—— 苏轼《定风波莫听穿林打叶声》 本篇笔记整理:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 1、CPU (中央处理器)①主要作用②特点 2、 GPU (图形处理…

支持纳管达梦数据库,命令存储支持对接Elasticsearch 8,JumpServer堡垒机v3.10.11 LTS版本发布

2024年6月24日,JumpServer开源堡垒机正式发布v3.10.11 LTS版本。JumpServer开源项目组将对v3.10 LTS版本提供长期的支持和优化,并定期迭代发布小版本。欢迎广大社区用户升级至v3.10 LTS最新版本,以获得更佳的使用体验。 在JumpServer v3.10.…

50-2 内网信息收集 - 内网工作环境(域相关知识)

一、工作组 工作组(Work Group)是局域网中最基本的资源管理模式,适用于小规模网络环境。 工作组的定义: 工作组是将不同功能或部门的计算机分组管理的方式。它提供了层次化的网络资源管理,使得组织内的计算机可以按照功能或部门分类。每个工作组有一个自定义的主机名称,…

短视频矩阵系统搭建APP源码开发

前言 短视频矩阵系统不仅有助于提升品牌影响力和营销效率,还能帮助企业更精准地触达目标受众,增强用户互动,并利用数据分析来持续优化营销策略。 一、短视频矩阵系统是什么? 短视频矩阵系统是一种通过多个短视频平台进行内容创作…

使用supportFragmentManager管理多个fragment切换

android studio创建的项目就没有一个简单点的框架,生成的代码都是繁琐而复杂,并且不实用。 国内的页面一般都是TAB页面的比较多,老外更喜欢侧边菜单。 如果我们使用一个activity来创建程序,来用占位符管理多个fragment切换&…

广东省钟表行业协会第十二届会员大会暨2024年钟表行业发展交流会

6月25日广东省钟表行业协会第十二届会员大会暨2024年钟表行业发展交流会在广州万富希尔顿酒店隆重召开。大会选举沙胜昔为广东省钟表行业协会第十二届理事会会长。 领导发言 新任会长 沙胜昔 首席荣誉会长 吴伟阳 新老会长交接仪式 本次大会,全国钟表大伽齐参与…

特斯拉下一代自动驾驶芯片的深度预测

引言 特斯拉一直以来都在自动驾驶技术上不断突破,随着AI大模型技术的爆发,其下一代自动驾驶芯片(HW5.0)也备受瞩目。本文将深入分析和预测特斯拉下一代自动驾驶芯片AI5的技术特点及其对行业的影响。 深入技术分析 现有自动驾驶…

Java实现ATM系统

效果: 目录结构 Account 账户类 package com.mytest;public class Account {private String cardId;private String userName;private char sex;private String password;private double balance;private double limit; //限额public String getCardId() {return cardId;}publ…

imx6ull/linux应用编程学习(5)FrameBuffer的应用编程

什么是FrameBuffer? Frame 是帧的意思, buffer 是缓冲的意思,所以 Framebuffer 就是帧缓冲, 这意味着 Framebuffer 就是一块内存,里面保存着一帧图像。帧缓冲(framebuffer)是 Linux 系统中的一种…

存储请求地址但是使用时请求的是端口

baseURL默认全局加载一次,后续直接读取缓存 解决方案:

Ubuntu qemu虚拟机 NAT网络 第一次使用,VNC访问

比如Windows 7 虚拟机 要手工设置网络

AI大模型到底有没有智能?一篇文章给你讲明明白白

生成式人工智能 (GenAI[1] ) 和大语言模型 (LLM [2] ),这两个词汇想必已在大家的耳边萦绕多时。它们如惊涛骇浪般席卷了整个科技界,登上了各大新闻头条。ChatGPT,这个神奇的对话助手,也许已成为你形影不离的良师益友。 然而&…

2024夏促steam商店加载失败、steam无法加载活动怎么办

今年的夏季促销活动终于开始了,一般夏季促销大多是去年和今年的热门游戏,不过也会有不少经典游戏参与活动,都是较低的价格出售。因为最近高考结束,考虑到会有不少新玩家前来入手游戏,为了让大家能顺利找到喜欢的游戏&a…

TIA博途WinCC通过VB脚本从 Excel中读取数据的具体方法介绍

TIA博途WinCC通过VB脚本从 Excel中读取数据的具体方法介绍 添加 一个PLC,设置PLC的IP地址,如下图所示, 添加全局DB块,新建几个变量,如下图所示, 在数据块中添加了 tag1 …… tag6 ,共 6 个浮点数类型的变量,用来接收通过 WinCC 从 Excel 文件中读取的数据。 添加 HMI…

【C++】类和对象(六)

文章目录 二、static成员概念面试题一个题目 三、友元友元函数说明 友元类 四、内部类(了解)概念:注意:特性: 五、匿名对象 书接上回: 【C】类和对象(五)隐式类型转换 二、static成员 01_31 03 12 01 概…

电脑文件kernel32.dll缺失要怎么处理?怎么才能一键修复kernel32.dll文件

关键系统文件kernel32.dll的缺失,这种情况不仅会导致系统运行不稳定,甚至可能完全无法启动某些应用程序。kernel32.dll 是一个至关重要的动态链接库文件,它与Windows操作系统的多个基本操作相关联,包括内存管理、进程和线程的控制…

制造业包括哪些?需要堡垒机吗?

制造业-国民经济的主体,是立国之本、兴国之器、强国之基,一个关系着大家吃穿住行的行业,一个与大家息息相关的行业。但大家对于制造业还有很多不了解,有小伙伴在问,制造业包括哪些?需要堡垒机吗&#xff1f…

ZABBIX-7.0LTS在线部署部署教程

ZABBIX-7.0LTS在线部署部署教程 环境: 操作系统: ubuntu 22.04zabbix-server版本: 7.0LTS系统配置[需结合监控的业务量提供配置]: 建议2C(CPU)8G(运行) 100GB(存储)架构:LNMP 第一步: 系统初始化 1.配置…