【C++】类和对象(初阶认识)#上篇#

目录

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

类的引入

封装 和 类的访问限定符

所以祖师爷在类中还引入了访问权限

用类定义变量

类的理解和对象的实例化

 

sizeof 计算类对象的大小

类对象的成员函数在公共代码区

this 指针


  1.  对面向过程和面向对象的初步认识

什么,是面向过程?

那生活中的洗衣服来做栗子:比如手搓洗衣服这件事,这个过程中,包含了

 拿衣服 → 拿洗衣粉 → 放水浸泡 → 用洗衣粉手搓 → 涤第二遍 → 晒衣服

面向过程在这里是更关注洗衣服每个步骤的过程,关注的是过程

但是这样的话就要我们的使用者就得比较有要求,你得会洗衣服,要有耐心等

而对应到我们编程的面向过程,关注的也是过程,这样可能很多方面关于对象(使用者等)的考虑可能就会欠佳,并且对于对象(使用者等)可能就有更高的要求

而面向对象呢?

面向对象同样也用洗衣服为引例:这里更像是用洗衣机洗衣服

 我们拿要衣服 → 放进便利我们、面向我们的洗衣机 → 晒衣服完事

我们的面向对象在这里更关注的是我们,让我们更方便、系统地洗衣

这样的话对于对象(我们使用者)来说,是很有方便,系统有效的

而对应到我们编程的面向对象,关注的是对象。更面向于使用者,这样可能会让过程更麻烦与否,但是这是面向于对象,服务于对象才是我们的目标  在人的世界中可以说面向对象是优于面向过程的

  1. 类的引入

在C语言中,相比其他语言最大的特点之一,是太自由了,我们的数据(变量等)和 方法(函数等)是分离的。比如用C语言实现的一个栈

栈栈

可以看到,不管是 StackPush 、StackPop 还是其他函数,函数是和结构体分离的,而数据一般捆绑在我们的结构体中,其结果是函数和我们的数据是分离的。这样的函数与数据分离的结果是什么?我们可以拿到任意局部的数据去进行修改,或者不修改;只要方法正确,我们可以没有限制操控地、细致地操控很多东西……这就是自由,没有过多的限制同样决定了C语言是更关注过程的一门语言,我们可以用它实现很多非常巧妙的函数(那些很优很优的算法很多都是C语言实现的)

但是世界是发展的,更多事物是服务于人,计算机也是;所以只有面向过程是不够的,我们也需要一门更加面向对象的语言,去服务于我们,或其他事物,也能让我们更加方便与规范

C是满足不了,但是祖师爷可以

                                  —— 所以 ,诞生了

类的格式:

class Stack

{

//public:

Init();          //函数这里没展开实现

Push();

Pop();

Destroy();

//private:

int *_p;

int _top;

int _capacity;

};

理解李姐:

C语言中,因为结构体中只能是捆绑数据,所以定义了一个结构体变量更相当于创建了一个集成的数据,形成一个大的数据域,它更面向于我们的函数,可以说是更面向于过程;

但是现在,我们的类是将数据和方法都捆绑到一起,函数也可以和数据捆绑起来,这样我们用类定义的一个变量更相当于一个对象,类中有的函数可以通过这个对象去调用,相当于对象在行使它所拥有的权力。假设我们用类来实现商家这一类群体,类中的数据可以是商家的个人数据,可以用我们熟悉的数据结构来进行存储商家信息作为我们的数据;类中的函数就是商家可以行使的权力,比如修改自己的信息(修改自己对应的数据部分),货品上架等,这些是类中的函数完成

—— 哦哦

定义变量且不看,先来了解面向对象的第一个特点:封装

封装 和 类的访问限定符

要了解封装,害(hai弟叁牲)得先从自由人C语言讲起

C语言的结构体中数据和方法是分离的,肥肠自由

—— 但是,自由性很高也往往不是一件好事

面向过程的自由人可以完成很多细致的操作,很多高级的函数;函数很优固然是好,但是对象是我们,我们是使用者,越细致的操控意味着使用者要注意的地方有很多,对于对象的要求也就越高:

栗子:

可能你有 必须严谨包装、在36~46度生存的化学限量版洗衣粉,效果虽然很好但是使用者一旦操作不当就会出错;

或者说刚刚的栈,栈本身是后来入栈的元素在栈顶上,但是自由度高有的对象就有可乘之机,挪栈中全部元素空出栈底,把后来的元素放到栈底,本来你应该在栈顶的呀;如果说这样在一些情况下可以有利,那没事;但是如果是一个不能插队的排队系统呢,有人这样干呢!!后面的元素会想刀人的,关键在代码实现层面没有有效的办法去管控它(可以但是可能会异常繁琐)

再举一个刚刚的例子(栗子),刚刚实现的栈中,栈的数据部分捆绑在一个结构体中,结构体中的top有两种初始化方式:

top = 0; 表示 top 指向入栈位置

top = -1; 每次入栈时, top要先自增,再进行入栈元素的赋值:相当于top指向的是入栈位置的前一个位置

现在在看到我们的入栈函数,其核心代码也就只有一行两行;但是有的人看到了说直接在要使用的时候直接 arr[++top] = 入栈元素;   但是一运行发现出错了,最后调试发现 top 初始化为 0,就指向入栈位置,应该这样写:arr[top++] = 入栈元素;     这也是一种不规范的行为

这里我们可以看到:如果数据和方法分离(比如刚刚的不调用函数直接入栈),我们自己要控制的也越多,有些人也不会讲规范,很容易出错。对于个人是很讲究代码规范的,看到刚刚的直接入栈那种不规范操作更是嫉恶如仇

我们看到,C语言这样数据和分离的方法在面向过程中是非常牛的,很多很多问题最优解也是用C语言写的;

但是数据和分离的这种方法在对于对象来说,很多时候对于对象的要求就要求会很高,有些人会很不规范,对于不规范操作我是嫉恶如仇,当然祖师爷也是

祖师爷想,既然C语言那么多人会代码不规范,提高每个人的素质比较困难,但是可以改变语法

—— 《既然改变不了世界,那就改变自己篇》

所以祖师爷在类中还引入了访问权限

开始了开始了

 

祖师爷设计了三种访问权限:

public:公有的。类中成员的属性为 public 表示该成员可以在类的外部直接被访问

protected:被保护的。类中成员的属性为 protected 不能在类的外部直接访问该成员

private:私有的。类中成员的属性为 private ,表示该成员不能在类的外部直接被访问

用法简单,如下:

属性为 public 的成员可以在类外面直接访问,被 private 修饰的成员不能被直接访问(这里错误展示就不写啦,有点小锉锉,不是理由)

一般我们把要给对象用的成员函数属性设置为 public ,而不想让对象改动的数据设置为私有 private ,这样对象在类外就只能调用你规定好的函数,让使用者更加规范地、方便地、系统地去访问数据等操作,将不规范扼杀在了摇篮里

这样设计带来的规范和方便时给人的,所以这里也体现了 面向对象(关注的是对象)

其实了解了这个,大概想想就可以猜到封装的意思了

类的封装:将数据和方法有机结合,不提供对象的属性和细节,只提供对象成员函数的接口,以此来进行和对象的交互

(不提供 对象 成员函数的实现细节 和 各数据 ,只给你调用成员函数来对对象的数据等进行修改)

这样很大程度上提高了我们的代码的规范性,用起来好方便,呜呜祖师爷真好,祖师爷以德报怨,爱死祖师爷了

现在类的基本格式和定义搞清楚了,来看看怎么用类来定义变量

用类定义变量

和C语言的结构体不同,结构体在定义变量时要加上 struct,不能直接是结构体名字来定义变量

而在类中,是直接使用:类名 + 变量名   来定义变量 (etc.     Stack S; )

(大概是祖师爷也觉得每次定义要加struct太麻烦了,直接改掉了)

etc.   //与上面格式代码对应

Stack S;

对的,just这样方便

但是有人发现在C++中使用 struct 定义的类型也可以直接用 类型 + 变量名  来定义变量

那是因为在C++中,struct 定义的类型也被升级成了类;也就是说,C++使用 struct 和 class 定义的都是类,定义的不是结构体哦

那有人说,struct 定义出来的类和 class 定义出来的类没有任何差异吗?有,但不多

就一个地方:

struct 定义出来的类,其成员的属性默认为:public

class  定义出来的类,其成员的属性默认为:private

就一个公私有的问题啦,不过日常我们的数据不愿意被外部直接访问到,如果忘记手动修饰成员的属性,class 会更安全健壮点

类的理解和对象的实例化

思考,只是定义好了一个类,它里面的数据有没有开空间?

早在C语言的结构体中,刚定义好的结构体类型只是声明,其中的成员变量没有分配空间,只是声明,声明这个结构体类型里有这个成员;而到了真正定义了一个结构体变量时,才根据刚刚的声明去开辟空间,按成员声明分配空间

C++中刚定义好的类也是声明,其中的变量是声明该类中有该成员,并没有真正开辟空间

注意,这里的成员变量 _pa  _top  _capacity  还没有开辟空间,只是声明,声明此类中有该成员变量

那在什么时候初始化呢?

在对象被定义的时候,也就是在对象实例化的时候,才真正在本函数栈帧中(或其他存储空间中)开辟大小为 类的大小 的空间,按成员变量的声明依次分配内存空间给它的成员变量

形象地加深理解:

我们可以看到,其实头文件中的类更相当于一张图纸,一张蓝图;而用这个类创建了对象,才是用这个图纸将物体创造出来,因为创建好对象才真正开辟了空间,一定要理解清晰,每次写之前就应该想到

用类来创建对象的时候才分配空间,所以给这个过程一个形象的名字,叫做对象的实例化

继续添加内容

sizeof对象的问题,图纸和公共代码区

 

sizeof 计算类对象的大小

要讲到计算一个类对象的大小,得先了解:

类对象的成员函数在公共代码区

和结构体计算大小不同,类中除了有数据,比结构体还多了成员函数这一东西

那计算大小时要不要算进成员函数的大小去呢?

那得看对象的栈帧里面是不是有成员函数的栈帧

先构想:如果成员函数的栈帧在对象里面,那创建的每一个对象内部都有该函数,用的时候调用它。这样固然可以,但是重复的那段函数代码是不是很冗余啊?这样设计是不是很挫啊 —— 祖师爷当然不会那样干

这就好比一个对象相当于一个房子,然后成员函数相当于日常大家都要用的足球场,现在如果在对象里面都塞一个函数的空间,相当于每个房子里都装修一个足球场

祖师爷怎么做呢?祖师爷把成员函数的栈帧空间放到了公共代码区,不在单个对象里面,而是每个对象(同一类)要使用该函数时,都去公共代码区调用该函数,相当于只要定义一个函数栈帧 大家一起用。这就相当于把足球场 修在了小区里面,每个房子的人可以使用同一个

所以成员函数的函数栈帧是不在对象里面的,而是在公共的代码区,所以计算大小时,也不用计算每个对象里面成员函数的大小

所以剩下的就只是数据了,这下和C语言结构体计算大小就一样了

  1. this 指针

类中的私密数据不是不可以在外部访问到嘛,调用成员函数才能访问私密数据,那编译器是怎么实现的呢?它的底层又是什么呢?

先来看函数对象调用成员函数的一些特点

我们看到当类的对象调用其成员函数时,格式是:对象.成员函数(传参);

调用的汇编代码如下:

  

其实对象调用成员函数的底层还会有一个 this 指针,这是属于每一个成员函数的浪漫(每一个成员函数都有这个 this 指针)

this 指针 是一个隐含的参数,比如在调用 S1 中的 Push() 函数时,this指针 是一个隐含的参数

在调用 S1.Push() 时,编译器会 取 S1的地址 给 this指针,等到了成员函数内部,就可以用这个 this指针 可以用来访问 S1 中的数据(权限够),这就是调用成员函数修改数据的实现

清晰具体的过程吧:


S1.Push(1);    

底层汇编是 lea      rcx, [S1]       意思是取S1的地址给寄存器 rcx

等到了(公共代码区的)成员函数, 寄存器 rcx 将寄存的 S1的地址 给到 this指针,要使用S1该对象里的数据时,是通过this指针来实现的

用刚刚的栈举栗子:

我们一般在成员函数内部直接写:_capacity = Init_SZ;  

这样可以访问到 S1中的 _capacity,是因为底层是这样的:this->_capacity = Init_SZ;

是通过this指针来访问到 S1 中的数据;这里又一次体现出 C嘎嘎更多是让编译器帮我们完成了操作,而不是实质性地改变C语言

 

是成员函数就有 this 指针(这是属于每一个成员函数的浪漫)

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Meiyourou.c

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值