【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表进行映射了,让代码执行跳转了。

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

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

相关文章

源码学习:文件描述符

在进程描述学习中,扯到了max_fds,接着就联想到了日常运维中常见的ulimit参数、sysctl内核参数,原来以为max_fds与这些个关联性比较强,但经过一早上折腾以后,发现其实还是有一些差距的。但是在学习过程中,却…

【C++】数组、字符串

六、数组、字符串 讨论数组离不开指针,指针基本上就是数组的一切的基础,数组和指针的相关内容参考我的C系列博文:【C语言学习笔记】四、指针_通过变量名访问内存单元中的数据缺点-CSDN博客【C语言学习笔记】三、数组-CSDN博客 1、数组就是&…

数据结构03 链表的基本操作【C++数组模拟实现】

前言:本节内容主要了解链表的基本概念及特点,以及能够通过数组模拟学会链表的几种基本操作,下一节我们将通过STL模板完成链表操作,可以通过专栏进入查看下一节哦~ 目录 单链表及其特点 完整链表构成 完整链表简述 创建单链表 …

京东云备案流程图_云主机快速ICP备案_京东云服务器备案问题解答

京东云ICP备案流程,备案包括网站和APP备案,以及备案问题解答FAQ,阿腾云以京东云网站域名备案流程为例,先填写主办单位信息,选择网站备案或APP备案,申请授权码并验证,填写并上传主办单位详细信息…

光伏仿真软件是什么?都有哪些功能?

光伏仿真软件,作为现代光伏系统设计的重要工具,正日益受到设计师、工程师和决策者的青睐。它结合了物理学、工程学和计算机科学的原理,以数字化方式模拟光伏系统的运行,帮助用户预测和优化系统的性能。本文将详细探讨光伏仿真软件…

[Go 微服务] go-micro + consul 的使用

文章目录 1.go-micro 介绍2.go-micro 的主要功能3.go-micro 安装4.go-micro 的使用4.1 创建服务端4.2 配置服务端 consul4.3 生成客户端 5.goodsinfo 服务5.1 服务端开发5.2 客户端开发 1.go-micro 介绍 Go Micro是一个简化分布式开发 的微服务生态系统,该系统为开…

Java学习【IO流:深入理解与应用(上)】

Java学习【IO流:深入理解与应用(上)】 🍃1.IO流体系结构🍃2.FileOutputStream🍁2.1FileOutputStream写数据的三种方式🍁2.2换行和续写 🍃3.FileInputStream🍁3.1每次读取…

软考高项备考经验分享

高项备考经验分享 在备考被论文卡两次后,这次终于通过了高项,分不是很高,比较幸运,对这次考试做个总结与分享,希望对同学们有所帮助。 1、备考时间 首先备考时间上不建议拉的太长,每天坚持看书3~6个月时…

《编译原理》阅读笔记:p25-p32

《编译原理》学习第 5 天,p25-p32总结,总计 8 页。 一、技术总结 1.lexical lexical这个单词后续会经常用到,所以首先要搞懂它的英文意思,不然看到中文的“词法,语法,文法”这三个词的时候就会懵了——l…

Java实现 现场评委给参赛选手打分的过程

通过评委的积极参与和公正评分,可以提高评选活动的公信力和可信度。 透明性:参赛者和观众应该清楚了解评审标准和评分过程,以便能够理解评委的评判依据。 可靠性:评委评分应该具有一致性和可靠性,不受主观因素或随机误差的影响。 编写程序,Java代码实现&#xff1…

计算机组成原理:海明校验

在上图中,对绿色的7比特数据进行海明校验,需要添加紫色的4比特校验位,总共是蓝色的11比特。紫色的校验位pi分布于蓝色的hi的1, 2, 4, 8, 16, 32, 64位,是2i-1位。绿色的数据位bi分布于剩下的位。 在下图中,b1位于h3&a…

浅谈安科瑞ACRELCLOUD-1200光伏发电系统在建筑节能中的应用

摘要:21世纪以来,随着不可再生能源的逐渐减少,人们越来越重视能源的利用率,不断开发绿色能源。通过光伏发电系统,能够提升能源利用率,减少不可再生能源的开发。同时,也能加强我国建筑节能系统的…

【React】第二个组件的一点小问题(JSX元素需要被包裹)

能看出为什么报错吗? 它告诉我们JSX元素需要被包裹,此时只需在所有元素外套一层标签(空标签也可以哦) 专业点就是要有一个根元素 注释: ctrl / 效果是 {/* */}这样 三元运算符:同CPP 循环输出数组&#x…

每日一道算法题 面试题 08.08. 有重复字符串的排列组合

题目 面试题 08.08. 有重复字符串的排列组合 - 力扣(LeetCode) Python class Solution:def permutation(self, S: str) -> List[str]:# 以索引记录字符是否用过lelen(S)idx[_ for _ in range(le) ]# 组合得到的字符串combine[]*leans[]# 递归def fu…

Go 中使用map时注意的问题

💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

面对资质申请被拒,河南企业如何调整策略再次冲刺?

当乙级风力发电资质申请未通过时,不必过于焦虑,以下是一份详细的二次申请攻略,帮助你更有条理地准备和提交申请: 一、失败原因分析与总结 查询评审意见:在收到评审结果后,首先查询并仔细阅读专家评审意见&…

利用python爬取上证指数股吧评论并保存到mongodb数据库

大家好,我是带我去滑雪! 东方财富网是中国领先的金融服务网站之一,以提供全面的金融市场数据、资讯和交易工具而闻名。其受欢迎的“股吧”论坛特别适合爬取股票评论,东方财富网的股吧聚集了大量投资者和金融分析师,他们…

vue开发网站--关于window.print()调取打印

1.vue点击按钮调取打印 点击按钮&#xff1a; 调取打印该页面&#xff1a; <div click"clickDown()">下载</div>methods: {//下载-调取打印clickDown() {window.print()}, }<style>/* 点击打印的样式 */media print {.clickDown {display: no…

推荐一款免费的GIF编辑器——【ScreenToGif编辑器】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️木道寻的主页 文章目录 &#x1f525;前言&#x1f680;素材准备&#x1f680;逐帧制作&#x1f680;保存图片⭐️⭐️⭐️总结 &#…

【吊打面试官系列-MyBatis面试题】MyBatis 框架适用场合?

大家好&#xff0c;我是锋哥。今天分享关于 【MyBatis 框架适用场合 &#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; MyBatis 框架适用场合&#xff1f; 1、MyBatis 专注于 SQL 本身&#xff0c;是一个足够灵活的 DAO 层解决方案。 2、对性能的要求很高&#…