【C++】——类和对象(上)
一类
1、类的定义
类和前面我们在C语言中学习到的结构体有点类似,其最大的区别就是我们的类可以定义函数,类的定义需要使用到关键字class。
其定义如下:
class是定义类的关键字,text是类的名称,{}中为类的主体,要注意的是最后的那个分号不可以省略。
类中的内容称为类的成员,类中的变量称为类的属性或者成员变量,类中的函数被称为方法或者成员函数。
C++中其也兼容C语言的大部分语法,所以我们的struct在C++中也升级成了类,其和C语言中的struct的最大区别就是其可以定义函数了。
那么我们的类具体有和作用呢?
前面我们学习数据结构的时候,我们会发现,我们的链表,顺序表,栈,队列这些数据结构,其都会有增删改查的功能,那么我们前面对于这些函数的取名都需要和这个数据结构有关系,那么我们现在有了类,取名就可以简单一点了。
如下:
我们上面定义了一个Stack的类,然后我们将栈的结构,和函数都放在这个类中,那么这个类里面的方法或者成员函数,其在命名的时候我们直接按其功能来命名即可了。
那么我们不同的类,其函数命名一样会不会冲突呢?
答案是不会,在C++中分为四个作用域:局部域,全局域,命名空间域和类域。那么就是说其形成了一块专属的空间,各个类之间是不冲突的。
我们在前面的时候,使用struct的时候,因为函数和变量是独立定义的,那么我们使用函数的时候需要将变量传给函数,但是在类中,我们的函数是可以直接访问同一个类的成员变量的。所以我们在函数的定义的时候就可以少设置个参数了。
为了区分成员变量,⼀般习惯上成员变量会加⼀个特殊标识,如成员变量前⾯或者后⾯加_或者m 开头,注意C++中这个并不是强制的,只是⼀些惯例,具体看公司的要求。
2、访问限定符
C++⼀种实现封装的⽅式,⽤类将对象的属性与⽅法结合在⼀块,让对象更加完善,通过访问权限 选择性的将其接提供给外部的⽤⼾使⽤。
public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访 问,protected和private是⼀样的,以后继承章节才能体现出他们的区别。
访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有 访问限定符,作⽤域就到}即类结束。
class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
⼀般成员变量都会被限制为private/protected,需要给别⼈使⽤的成员函数会放为public。
如下图所示:
3、类域
通过前面的学习我们知道编译器查找某个对象的优先顺序是先去局部域找再去全局域找,它是不会自己去命名空间域和类域中寻找的。所以如果我们在实现函数进行声明和定义分离时,必须指定类域。编译器查找某个对象的优先顺序是先去局部域找再去全局域找,它是不会自己去命名空间域和类域中寻找的。所以如果我们在实现函数进行声明和定义分离时,必须指定类域。
不过对于一些代码较少的就不建议声明和定义分离了,这样编译器才可将其当成inline函数,减少内存的使用,提高效率。
二、对象
类和对象,上面我们讲到了类的部分知识,那么下面我们来了解一下对象的内容吧。
1、实例化
使用类类型在物理内存中创建对象的过程,称为类的实例化出对象
类是对象进行一种抽象描述,是一个模型一样的东西,其限定了类有哪些成员变量,那些成员变量只是声明,没有分配空间,用类实例化出对象的时候才会分配空间。
⼀个类可以实例化出多个对象,实例化出的对象占⽤实际的物理空间,存储类成员变量。打个⽐ ⽅:类实例化出对象就像现实中使⽤建筑设计图建造出房⼦,类就像是设计图,设计图规划了有多 少个房间,房间⼤⼩功能等,但是并没有实体的建筑存在,也不能住⼈,⽤设计图修建出房⼦,房 ⼦才能住⼈。同样类就像设计图⼀样,不能存储数据,实例化出的对象分配物理内存存储数据。
2、对象的大小
我们学习C语言的时候,我们的结构体的大小如何计算的我们已经学习过了,那么我们的对象的大小是否也是这样计算呢?
我们的类的定义中,其就比C语言中的结构体多了一个函数,那么主要是这个函数的大小如何计算呢?还是说函数的话其有另外的方式存储。
⾸先函数被编译后是⼀段指令,对象中没办法存储,这些指令 存储在⼀个单独的区域(代码段),那么对象中⾮要存储的话,只能是成员函数的指针。再分析⼀下,对 象中是否有存储指针的必要呢,Date实例化d1和d2两个对象,d1和d2都有各⾃独⽴的成员变量 _year/_month/_day存储各⾃的数据,但是d1和d2的成员函数Init/Print指针却是⼀样的,存储在对象 中就浪费了。如果⽤Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。这⾥需 要再额外哆嗦⼀下,其实函数指针是不需要存储的,函数指针是⼀个地址,调⽤函数被编译成汇编指 令[call地址],其实编译器在编译链接时,就要找到函数的地址,不是在运⾏时找,只有动态多态是在运⾏时找,就需要存储函数地址,这个我们以后会讲解。
所以我们的对象的大小和结构体是一样的,也是使用的内存对齐规则:
内存对齐规则:
第一个成员在与结构体偏移量为 0 的地址处。
其他变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS 中默认的对齐数为 8。
结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
如果嵌套了结构体,那么嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
可以看到这个对象的大小就刚刚好是三个从成员变量使用对齐规则计算的结果。
那么要是我们的类的定义的时候就只有函数或者就是一个空类,没有成员变量吗,那么我们的对象的大小又是多少呢?
其创建出来的大小为1,其作用是占位,告诉编译器这个对象是存在的。
三、this指针
我们先看下面的一段代码:
我们看上面的调用函数部分,那么函数的调用其是如何精准的找到我们要操作的对象的呢?
实际上是依靠的this指针。
Date类中有Init与Print两个成员函数,函数体中没有关于不同对象的区分,那当d1调⽤Init和 Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这⾥就要看到C++给了 ⼀个隐含的this指针解决这⾥的问题。
例如lnit的原型就为:lnit(Date*const this,int year,int month,int day);
类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值, this- >_year = year;
C++规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理),但是可以在函数体内显 ⽰使⽤this指针。
我们还注意到其原型上的this指针使用了const来修饰,那么我们补充一下这个的用法:
1、int* const p
:const 在 * 的右边,修饰的是指针,表示指针本身不可变,并且这种定义下的指针必须要初始化。
2、const int*p
:const 在 * 的左边,修饰的是值,表示指针所指的内容不可变。
所以说这里隐含的 this 指针所加的 const 意思就是 this 指针本身不能改变。