本篇主要介绍了类的引入及其定义、类的访问限定符及封装、类的作用域和实例化、类的对象大小计算等内容。初心是为了让读者能够理解并熟悉类与对象的相关内容。
一、类的引入
类,就是类型。它是c++中的一个概念,是把c中的结构体进行了升级同时有兼容c中结构体的用法,一个类进行实例化会有n个对象,很像c中的结构体里面包含的各种类型的变量(对象)。具体怎么升级呢?以前的结构体是里面可以存放数据,升级后不仅可以存放数据,也可以存放函数,不需要传参。比如我们之前所了解的栈或队列,它们的初始化函数都是先创立一个结构体,然后在其外面创立一个初始化函数然后把结构体指针作为参数传进函数实现初始化。而升级了以后,初始化函数就可以直接卸载里面,且不需要传参(这时候就可以考虑缺省参数的应用了,用于更合适的开辟空间大小)。
此外,升级后,结构体的名称就可以代表该类型(省去了typedef,且结构体内的也可以做这种省略操作)。
但我们实际发现,c++更多的用class定义类。怎么用呢?只要把struct换成class即可。4
二、访问限定符
我们假设创建一个类stack并创建一个初始化函数,并去创建一个变量并把它初始化
结果运行时报出不可访问的错误,为什么不可访问呢?
c++在进行类的封装时,用类将对象的属性与方法结合一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
访问限定符分三种:public(公有)、protected(保护)、private(私有)。
公有修饰的成员在类外面可以直接被访问,在上图中显然不可直接访问,因此不是public,保护和私有只能在类里面访问。class默认是私有,struct默认是公有,因此才会报错。想让其公有,只需在前面加public修饰即可。
这个public的范围是从他开始到下一个访问限定符,如果没有遇到访问限定符就一直管到结束。通常情况下,成员变量设为私有,函数设为公有。
三、类中成员函数的声明和定义分离
我们还是以创建一个栈为例:
这是之前我们所创建的一个类以及它的初始化函数,但是在实际中,我们往往会将声明和定义放在不同位置(.h放分离.cpp放定义),这就需要一些要求,因为一旦声明和定义分离,定义就在类的外,就无法直接访问这个成员函数(class默认的类都是private)所以我们按之前的套路来定义函数是行不通的,需要加一些改进。(此时有人会说,把那些函数设置为public类型的,但是,如果我在这个程序中再创建一个队列,也创建一个同名的初始化函数,那么就会出现重名的问题,但并不冲突)
方法,在函数的定义处指定我的类域(有点像前面的域作用操作符)
四、封装
面向对象的三大特性:封装、继承、多态(不止三个特性)
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来 和对象进行交互。
封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用 户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日 常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。
五、类的实例化
概念:用类类型创建对象的过程
我们在创建栈的时候
定义了三个变量,而这三个变量此时并没有创建空间去存储,只有具体创建出类的变量才会创建空间
这便是类的实例化,类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象 才能实际存储数据,占用物理空间。
六、类的大小的计算
一个类中,有成员变量,也有成员函数,本质上这个类与c中的结构体对接,计算类的大小其实是运动到结构体内存对齐原理。直接上结论:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐,成员函数不计入类的大小,而是单独拿出一块空间去存放,需要用的时候再调出来避免浪费空间。
如果一个类是空类呢?其大小又是多少呢?
运行结果我们发现a3的大小是1而不是0,这1个字节不存储有效数据,而是表明了标识对象被定义出来。因为成员函数不计入类的大小因此a2的大小也是1。
在这里补充一下结构体内存对齐规则:
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
七、this指针
我们先来看看他的实际场景,现在我们创建一个有关日期的类
前面我们提到,类中的函数并不算入类的大小,而是放在一个共同的空间,所以d1和d2的init函数是相同的,但运行的结果如下
按道理说,相同的函数相同的结果啊,怎么会这样,我们看main函数中的内容,或许会对init函数的传参产生怀疑,但其原理并不在这里,而是this指针的作用
在我们运行的过程中,编译器会把对象(d1d2)的地址传过去并用指针变量去接受,这个指针就是this指针(注释部分就是解释其原理)
这样我们就能理解为什么结果不同了(d1打印时候走的是d1的this,d2打印走的是d2的this)
this指针其实是隐含的this指针,我们的实参和形参的位置不能显示着写,编译器自己会加(如果运行注释版本会报错)但是可以在类里面用。this的指针是*const类型(无法修改this但可修改this的指向)
到目前为止我们发现,C++中通过类可以将数据以及操作数据的方法进行完美结合,通过访问权限可以控制那些方法在类外可以被调用,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。 而且每个方法不需要传递参数了,编译器编译之后该参数会自动还原,即C++中参数是编译器维护的,C语言中需用用户自己维护。