C++类与对象—上

news2024/9/23 5:20:30

本期我们来学习类与对象

目录

面向过程和面向对象初步认识

类的引入

 访问限定符

类的定义

封装

类的作用域

类的实例化

this指针

C语言和C++实现Stack的对比 


面向过程和面向对象初步认识

C 语言是 面向过程 的, 关注 的是 过程 ,分析出求解问题的步骤,通过函数调用逐步解决问题。
 比如我们洗衣服

 这是C语言的的实现,我们要按步骤,一步一步来求解

C++ 基于面向对象 的, 关注 的是 对象 ,将一件事情拆分成不同的对象,靠对象之间的交互完成。

 再比如我们要设计一个外卖系统

如果是面向过程的话,就是上架,点餐,送餐,结算等等,关注的是步骤

而如果是面向对象的话,就是商家,骑手,客户三者之间的相互关系了,关注的是对象与对象之间关系和交互,就像在虚拟的世界里描述现实世界一样,是将现实世界的类和对象映射到计算机里

大家可以认为面向对象是一种更高级的开发方式,是语言的进化方向,现在新的语言几乎都是面向对象的,所以这也是我们学习的方向

类的引入

我们之前说过,C++里将结构体升级为了类

s1是我们以前创建栈的方式,因为现在的栈是一个类了,所以我们可以直接用类名来创建栈,如上面的s2 

我们栈里面的a,capacity,top都叫做成员变量

 此外,C++还可以在类里面定义函数,这叫做成员函数(方法)

我们之前为了区分,在各种函数前加上了前缀,比如StackInit,QueueInit,现在可以在类里面定义函数,我们就不需要加前缀了

两个函数都叫Init,但是因为域不同,所以不会干扰,C++里{ } 定义的都是域

我们随便写点函数

这里成员变量并不一定要在函数后面,在前面也是可以的,甚至写在中间也没问题,因为类域是一个整体

我们再调用函数就非常方便了,用 . 的方式即可,我们写起来是比C语言舒服的

上面结构体的定义, C++ 中更喜欢用 class 来代替

但我们换成class后又编译不通过了,这就涉及到了权限的问题

 访问限定符

【访问限定符说明】
1. public 修饰的成员在类外可以直接被访问
2. protected private 修饰的成员在类外不能直接被访问 (我们暂时认为 protected private 是类似的 )
3. 访问权限 作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. 如果后面没有访问限定符,作用域就到 } 即类结束。
5. class 的默认访问权限为 private struct public( 因为 struct 要兼容 C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

 

我们加上public就可以编译通过了,pubilc是公有的,直到遇到下一个访问限定符前都是公有的,但我们一般不希望成员变量也是公有的,所以要加上private

 说明白点就是,我们想给别人用的是公有,不想给的是私有

我们切换成struct也是可以用访问限定符的,因为struct也是类,区别在于struct默认是公有,class默认是私有,所以我们之前用struct可以通过编译,而class不可以,不过我们不推荐用默认,最好显示的指定出来

类的定义

class className
{
 // 类体:由成员函数和成员变量组成
 
}; // 一定要注意后面的分号
class 定义类的 关键字, ClassName 为类的名字, {} 中为类的主体,注意 类定义结束时后面 分号不能省
类体中内容称为 类的成员: 类中的 变量 称为 类的属性 成员变量 ; 类中的 函数 称为 类的方法 或者 成员函数
类的声明和定义是可以分离的,我们来看一看

 需要注意的是,我们在定义时要加上类域,让编译器知道,这不是一个普通函数,而是一个类的成员函数

另外,如果我们在类里面直接定义函数,就默认是内联,这些函数前加不加inline都无所谓

比如我们的Push和Destroy都是内联,当然最终是否成为内联要取决于编译器

所以我们长的函数定义和声明分离,短的函数就直接定义了

关于类还有一个问题

这里的year是无法区分的,所以我们喜欢在成员变量前加上下划线

后面加下划线也是可以,也有人喜欢加m

其他方式也可以,大家根据喜好选择就行 

封装

面向对象的三大特性: 封装、继承、多态
封装是将数据和方法放在一起,不想给你看的变成私有,想给你看的变成公有,封装的本质是一种更好的管理
比如电脑,我们最后使用的只有鼠标,键盘,显示器等等,但电脑真正工作的是cpu,显卡,内存等等一些硬件元件,我们并不需要关系主板上的线路有些什么,cpu是如何工作的等等
我们举个例子,我们去参观一些景点,博物馆时,有些东西会被保护起来,被围起来,如果不将他们保护起来,我们可能就会看到那些地方刻着xxx到此一游,xxx love xxx等等,有了封装,我们按照规则来游玩,就是一种更好的管理
C语言的数据和方法是分离的,C++是没有分离的,我们在C语言时写的栈,Top函数哪怕只有一行,我们也要写成函数

我们正常用是这样用的

但是如果有人这样写呢?

一会调用函数,一会自己访问

更严重一点的,如果这样搞,就可能会出现随机值了,你怎么知道top是栈顶元素,还是下一个位置呢?

 但是如果我们没有提供STTop函数,让用的人自己去想top是什么,就会出现各种各样的问题

所以C++为了解决这些问题,为了杜绝这种乱写代码的情况产生,就有了封装

我们只能按照规则访问,不遵循规则就报错

类的作用域

类定义了一个新的作用域 ,类的所有成员都在类的作用域中 在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
class Person
{
public:
 void PrintPersonInfo();
private:
 char _name[20];
 char _gender[3];
 int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
 cout << _name << " "<< _gender << " " << _age << endl;
}
另外,局部域和全局域会影响生命周期,而类域和命名空间不会

类的实例化

我们先看这样一个问题

我们在类里面的成员变量是属于声明还是定义? 

答案是声明,对于变量而言,声明和定义的真正区别在于开不开空间

 而且对于类,是整体定义,而不是一个一个的定义

我们这里创建的s,叫做类的实例化对象,或者对象的定义

类就像房子的设计图一样,而对象就是我们根据设计图设计出的房子

类里面能不能存数据?不能,就像图纸里面不能住人

 所以这种错误就是错误的,因为它没有空间,图纸不能住人

我们再看一个问题

 这里应该输出多少呢?是只算成员呢,还是也要算函数呢?

我们看运行结果

 结论:对象的大小只算成员变量(要结构体对齐),不算成员函数

另外,sizeof对象和sizeof类计算出的结果是一样的,就和sizeof(int)是一样的

我们举个例子

我们先把成员变量设置为公有的,我们来看

s1.top和s2.top,并不是同一块空间,就像两栋房子,都有厨房,都有卧室

但是两个Push,是同一个函数,就像我们在小区里,我们的篮球场,篮球场不需要每家每户都建一个,而是在公共的地方建一个,大家一起来使用,篮球场也可以在每家每户建一个,但是没必要,太浪费了,所以调用函数,就是去公共的区域调用

我们再看这个问题

 为什么这里是1,而不是0呢?

举个例子就是我们在小区里建房子时,有一栋房子我们还没规划好,但是需要给这栋房子留个位置,不然之后就无法建造

 这一个字节就是用来占位的,表示对象存在,不然取地址就没办法了

this指针

我们先看这样一段代码

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
	int a;
};
int main() {
	Date d1, d2;
	d1.Init(2022, 1, 2);
	d2.Init(2023, 2,3);
	d1.Print();
	d2.Print();
}

 我们知道d1,d2是两个不同的对象,但是Print函数是在公共区域的,为什么输出的结果却不同呢?

这里面就有一个隐藏的this指针,编译器会将函数处理为

 同时,函数的调用也会被处理为

 这些都是编译器的暗箱操作

另外,还规定了我们不能在形参和实参上显示的去写,所以上面都是报错

但是可以显示的去用,比如

 我们也不能将this改为空指针

 因为this指针的类型是 类类型* const

补充两种错误的写法

 

 上面的是声明,即我们不能在图纸里住,第二个是 :: 这个符号是域作用限定符

this指针是存在哪里的?对象,栈,堆,静态区还是常量区?

答案是在栈里面,this指针是形参,形参是在栈里的

 vs下对this指针的传递进行优化,对象的地址是放在ecx,ecx存储this指针(ecx是寄存器)

注意,这是vs下面的,不是所有的编译器

我们再看一个问题

 答案为第一段代码选C,第二段代码选B

因为第一段代码p调用Print不会解引用,Print的地址不在对象里,不过p会作为实参传递给this指针

 传递空指针并不会报错

第二段代码同样p调用Print不会解引用,但是this传递过去后,this是空指针,函数内部访问了_a,就是空指针解引用

总结一下

1. this 指针的类型:类类型 * const ,即成员函数中,不能给 this 指针赋值。
2. 只能在 成员函数 的内部使用
3. this 指针本质上是 成员函数 的形参 ,当对象调用成员函数时,将对象地址作为实参传递给 this 形参。所以对象中不存储 this 指针
4. this 指针是 成员函数 第一个隐含的指针形参,一般情况由编译器通过 ecx 寄存器自动传递,不需要用 户传递

C语言和C++实现Stack的对比 

C语言实现

 我们发现用C语言实现栈(这里我只截了开头和结尾,相信大家都知道C实现的栈是什么样子)

每个函数的第一个参数都是 Stack*
函数中必须要对第一个参数检测,因为该参数可能会为 NULL
函数中都是通过 Stack* 参数操作栈的
调用时必须传递 Stack 结构体变量的地址
结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即 数据和操作数据的方式是分 离开的 ,而且实现上相当复杂一点,涉及到大量指针操作,稍不注意可能就会出错。
C++实现
typedef int DataType;
class Stack
{
public:
	void Init()
	{
		_array = (DataType*)malloc(sizeof(DataType) * 3);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = 3;
		_size = 0;
	}
	void Push(DataType data)
	{
		CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	void Pop()
	{
		if (Empty())
			return;
		_size--;
	}
	DataType Top() { return _array[_size - 1]; }
	int Empty() { return 0 == _size; }
	int Size() { return _size; }
	void Destroy()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	void CheckCapacity()
	{
		if (_size == _capacity)
		{
			int newcapacity = _capacity * 2;
			DataType* temp = (DataType*)realloc(_array, newcapacity *
				sizeof(DataType));
			if (temp == NULL)
			{
				perror("realloc申请空间失败!!!");
				return;
			}
			_array = temp;
			_capacity = newcapacity;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};
int main()
{
	Stack s;
	s.Init();
	s.Push(1);
	s.Push(2);
	s.Push(3);
	s.Push(4);

	printf("%d\n", s.Top());
	printf("%d\n", s.Size());
	s.Pop();
	s.Pop();
	printf("%d\n", s.Top());
	printf("%d\n", s.Size());
	s.Destroy();
	return 0;
}
C++ 中通过类可以将数据 以及 操作数据的方法进行完美结合,通过访问权限可以控制那些方法在类外可以被 调用,即封装 ,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。而且每个方法不需要传 递Stack* 的参数了,编译器编译之后该参数会自动还原,即 C++ Stack * 参数是编译器维护的, C 语言中需 用用户自己维护
我们可以理解为C和C++的区别就是手动挡和自动挡的区别
C就是手段当,需要自己控制变速箱,C++是自动挡,电脑程序控制变速箱
以上即为本期全部内容,希望大家可以有所收获
如有错误,还请指正

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

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

相关文章

研究生考试 之 计算机网络第七版(谢希仁) 第一章 课后答案

研究生考试 之 计算机网络第七版(谢希仁) 第一章 课后答案 目录 研究生考试 之 计算机网络第七版(谢希仁) 第一章 课后答案 一、简单介绍 二、计算机网络第七版(谢希仁) 第一章 课后答案 1、 计算机网络向用户可以提供哪些服务&#xff1f; 2、 试简述分组交换的要点。 3…

人工神经网络

&#x1f351; CV专栏 1. 单个神经元 &#x1f351; 神经网络 即 模型 &#x1f364; 输入 四个参数 --> 结果 &#x1f351; 模型训练(学习) 例子 &#x1f351; 模型的输入x 乘 权值ω 减去阈值θ --> 激活函数 f &#x1f351; 输出 yi &#xff08;向下传递 或 直…

一步步带你学习Python编程:从零开始的查缺补漏

在快节奏的生活中&#xff0c;很难找到时间来学习新的技能。但有时候&#xff0c;我们会突然发现自己有一些空闲时间&#xff0c;而又不想虚度光阴。无聊的时候&#xff0c;我们可以选择学习一项新技能来充实自己。最近&#xff0c;我就因为有些无聊&#xff0c;决定重新学习Py…

linux实现网络程序

1️⃣ 在linux下&#xff0c;通过套接字实现服务器和客户端的通信。 2️⃣ 实现单线程、多线程通信。或者实现线程池来通信。 3️⃣ 优化通信&#xff0c;增加守护进程。 有情提醒&#xff0c;类里面默认的函数是内联。内联函数在调用的地方展开&#xff0c;没有函数地址&…

【Springboot系列】Springboot整合Swagger3不简单

1、缘由 Swagger是一个根据代码注解生成接口文档的工具&#xff0c;减少和前端之间的沟通&#xff0c;前端同学看着文档就可以开发了&#xff0c;提升了效率&#xff0c;之前很少写swagger&#xff0c;这次自己动手写&#xff0c;还是有点麻烦&#xff0c;不怎么懂&#xff0c;…

外网SSH远程连接linux服务器「cpolar内网穿透」

文章目录 视频教程1. Linux CentOS安装cpolar2. 创建TCP隧道3. 随机地址公网远程连接4. 固定TCP地址5. 使用固定公网TCP地址SSH远程 转载自内网穿透工具的文章&#xff1a;无公网IP&#xff0c;SSH远程连接Linux CentOS服务器【内网穿透】 本次教程我们来实现如何在外公网环境下…

OpenGL入门教程之 纹理

引言 我们已经了解到&#xff0c;我们可以为每个顶点添加颜色来增加图形的细节&#xff0c;从而创建出有趣的图像。但是&#xff0c;如果想让图形看起来更真实&#xff0c;我们就必须有足够多的顶点&#xff0c;从而指定足够多的颜色。这将会产生很多额外开销&#xff0c;因为每…

【ArcGIS Pro二次开发】(23):用地编码和用地名称的规范性检查

在国空或村庄规划的编制过程中&#xff0c;随着规划用地的调整&#xff0c;经常会手动修改用地编码和用地名称&#xff0c;不可避免的会出现错误&#xff0c;如果单靠人工校对&#xff0c;累人又不能保证准确性。这个工具的目的就是检查用地编码和用地名称是否规范&#xff0c;…

最短路径Floyd与区间DP

floyd算法是求最短路径的算法&#xff0c;算法复杂度为n(o^3),其优点在于能够一次求解所有点到其他点的最短路径&#xff0c;不需要其他运算&#xff0c;使用二维数组存储。其三层循环自外向内分别为&#xff1a;中间点&#xff0c;起始点和终点。状态方程为&#xff1a; num[…

【社区图书馆】《网络工程师的Python之路:网络运维自动化实战(第2版)》

文章目录 图书前言图书简介图书作者、简介图书好评图书目录总结&#xff1a;本人选择此图书的意义 图书前言 光阴似箭&#xff0c;岁月如梭。转眼之间&#xff0c;距离本书最早的电子书出版已经过去了三年之久。承蒙广大读者的厚爱&#xff0c;电子书和第1版纸质书的发行量远远…

【云原生】Java 应用程序在 Kubernetes 上棘手的内存管理

文章目录 引言JVM 内存模型简介非 Heap 内存Heap 堆内存Kubernetes 内存管理JVM 和 Kubernetes场景 1 — Java Out Of Memory 错误场景 2 — Pod 超出内存 limit 限制场景 3 — Pod 超出节点的可用内存场景 4 — 参数配置良好&#xff0c;应用程序运行良好 结语 引言 如何结合…

PCIe-DMA多通道/高性能/超低延时/超低抖动视频采集显示V4L2驱动

1 介绍 基于PCI ExpressIntegrated Block&#xff0c;Multi-Channel PCIe QDMA Subsystem实现了使用DMA地址队列的独立多通道、高性能Continous或Scather GatherDMA&#xff0c;提供fifo/AXI4-Stream用户接口。 基于PCI ExpressIntegrated Block&#xff0c;Multi-Channel PCIe…

大型体检管理系统源码:适用于大中型医院或独立体检中心

一套专业的体检管理系统源码&#xff0c;是医院、体检中心等单位开展体检业务的得力助手。它将以往人工操作的健康体检过程所得到信息转换成全信息化的电脑管理&#xff0c;使体检过程更为流畅、更有条理&#xff0c;更加便于管理&#xff0c;从而实现体检业务管理的自动化、信…

2023年报考CSM敏捷教练认证好不好?含金量高吗?

CSM&#xff0c;Certified Scrum Master&#xff0c;是Scrum联盟发起的Scrum认证。帮助个人从自身、团队和组织层面&#xff0c;学习技能和工具来扩展实践的层面&#xff0c;帮助团队正确使用Scrum&#xff0c;从而提高项目整体成功的可能性。 认证收益 职业能力提升 推动企业…

AIGC技术周报|为文生图模型提供“参考”;交互式prompt系统:让文生图模型更懂你

AIGC通过借鉴现有的、人类创造的内容来快速完成内容创作。ChatGPT、Bard等AI聊天机器人以及DallE 2、Stable Diffusion等文生图模型都属于AIGC的典型案例。「AIGC技术周报」将为你带来最新的paper、博客等前瞻性研究。 交互式prompt系统&#xff1a;让文生图模型更懂你 文生图…

JVM性能监测工具-JConsole

JVM性能监测工具-JConsole JConsole工具是JDK自带的图形化性能监控工具。并通过JConsole工具&#xff0c; 可以查看Java应用程序的运行概况&#xff0c; 监控堆信息、 元空间使用情况及类的加载情况等。 JConsole程序在%JAVA_HOM E%/bin目录下 或者你可以直接在命令行对他进…

【Java代码】MP3、flac歌曲批量生成同名的“xxx.lrc”歌词文件导入索尼黑砖二代

目录 1、准备条件2、实现方式3、代码环境和maven依赖4、Java代码5、示例1结果6、示例2结果7、一个小问题8、“音乐标签”下载地址 1、准备条件 网易云下载的MP3、flac后缀的歌曲若干首&#xff08;ncm后缀的歌曲需要还原格式&#xff0c;不然会随着VIP过期而无法听&#xff09…

《CTFshow-Web入门》06. Web 51~60

Web 51~60 web51题解 web52知识点题解 web53知识点题解 web54知识点题解 web55知识点题解 web56知识点题解 web57知识点题解 web58知识点题解 web59题解 web60题解 ctf - web入门 web51 题解 相比上一题多过滤了 tac 命令。那换一个即可。 题解&#xff1a; url ?cnl<f…

go/java/C++覆盖率工具原理汇总学习记录

go–goc goc采用的是插桩源码的形式&#xff0c;而不是待二进制执行时再去设置breakpoints。这就导致了当前go的测试覆盖率收集技术&#xff0c;一定是侵入式的&#xff0c;会修改目标程序源码。直接看案例 package mainimport "fmt"func main() {test2(3)fmt.Prin…

Vue项目基于driverjs实现新用户导航

引导页就是当用户第一次或者手动进行触发的时候&#xff0c;提示给用户当前系统的模块介绍&#xff0c;比如哪里是退出&#xff0c;哪里是菜单等等相应的操作。 无论是开发 APP 还是 web 应用&#xff0c;新手引导都是一个很常见的需求&#xff0c;一般在这2个方面需要新手引导…