类和对象的大小
在c语言中我们知道结构体的大小计算存在对齐规则,类是结构体在c++中的一个升级,那来想一想,计算内存的方法会不会也有升级呢。
让我们回忆一下结构体中的内存规则
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
如果按照对齐规则来计算这个内存大小是48个字节(不包含函数内的大小),那么结果是否为48字节呢。
结果就是48个字节,所以类中也是存在内存对齐规则的。我们没有计算函数的内存大小那么函数是不是不占内存呢?
成员函数实际上是存放在公共代码区的,因为每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。所以为了解决此问题,将成员函数存放在了公共代码区。
可以看到在只有函数和空的类中也会有一个字节的内存原因是编译器会给空类一个字节来唯一标识这个类,这个字节不存储有效数据,来表示对象存在。
this指针
我们可以先通过这个日期类去了解this指针
我们可以考虑一下为什么调用这个print并没有传参为什么会有完整的打印出给定的日期,这就不得不要讲到我们隐含的this指针。
- Data类中有Init和print两个函数函数体中并没有关于不同对象的区分,那么当调用Init和print函数时怎么区分是d1调用的还是其他对象调用的呢,这里就要用到c++中隐含的this指针解决这个问题了。
- 编译器编译后,类的成员函数会默认在第一个位置,增加一个当前类型的指针,叫做this指针。比如Data类的原型为,void Init(Data* const this,int year,int month ,int day)
- 类成员函数中访问成员变量,本质都是通过this指针去访问的,比如个_year赋值时。 this->_year = year;
- c++规定不能在实参和形参位置都显示写this指针,但是可以在函数体内显示使用this指针
下面程序的运行结果是 A.编译报错 B.运行崩溃 C.正常运行
答案是:C
编译是检查不出来空指针的问题的,空指针不是语法错误,空指针是运行时的错误
分析:
p虽然是空指针,但是p调用成员函数不会出现空指针访问。因为成员函数没有存在对象里面
这里会把p作为实参传给隐藏的this指针。传空指针不报错,空指针只要不去解引用是不会报错的。
这道题,并没有解引用this
下面程序的运行结果是 A.编译报错 B.运行崩溃 C.正常运行
答案是:B
这道题解引用了this,也就是解引用了空指针
类的默认成员函数
默认成员函数就是用户没有显示实现,编译器会自动生成的成员函数称为默认成员函数。一个类,我们不写的情况下编译器会默认生成六个默认成员函数,但其中最重要的是前四个我们要着重理解。
构造函数
构造函数 是一个 特殊的成员函数,虽然他的名字叫做构造,但是构造函数的主要任务并不是开空间创建对象,而是实例化时初始化对象。他的本质就是用来替代我们类中的Init函数的功能,构造函数自动调用的特点就完美的替代了Init。
其特征如下:
1. 函数名与类名相同。
2. 无返回值。(c++规定)
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
语法规定缺省参数不能和无参的构造同时出现,因为编译器会分辨不出调用哪个函数。
5. 如果类中没有显式定义构造函数,则 C++ 编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
可以看到编译器自动生成的是随机值,所以并没有什么用处。所以大多数的默认构造还是需要我们自己去实现的。
6.无参构造,全缺省构造和我们不写构造时编译器默认给定的构造函数都叫做默认构造函数。但是他们不能同时存在。无参构造和全缺省构造函数虽然构成函数重载,但是调用时还是会出现歧义。总结就是不传实参就可以调用的构造函数都是默认构造。
7.C++里面把类型分为两类:内置类型和自定义类型。内置类型 如:int,char,double,指针,内置型数组等等,自定义类型:我们是用class,struct等定义的类型,我们不写编译器默认生成的构造函数,对于内置类型不做初始化处理,对于自定义类型成员变量会去调用它的默认构造函数(不用参数就可以调的)初始化,如果没有默认构造函数就会报错。我们要初始化这个成员变量,要用初始化列表才能解决。后面会讲到。
通过监视可以发现Data类中的默认构造函数在初始化A类(自定义类的成员变量)时调用了A类的默认构造。
如果A中不存在默认构造就会报错。
析构函数
析构函数和默认构造函数的功能相反,析构函数不是对对象本身的销毁,比如局部对象是存在栈帧的,函数结束的时候栈帧销毁,他就释放了,不需要我们去管,c++规定对象在销毁时会自动调用析构函数,完成对对象中资源的清理释放工作。析构函数的功能类比我们的Destroy。而像Data并没有写Destroy,就是不用资源释放所以就不用析构函数的。
析构函数的特征:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值。
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
可以看到这里构造两次就调用两次析构完成内存的清理。但是Date类中没有需要释放的空间所以没有什么用。
但在我们栈和队列中都尤为重要。
构造与析构顺序:st1先构造,st2后构造,st2先析构,st1后析构,st1, st2是存在栈中(先进后出)的,但st1, st2指向的空间是堆上面的,堆上的资源不手动释放是不会释放的。
5.跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型不做处理,自定义类型成员会调用他的析构函数。
6.即使我们显示写析构函数,自定义类型也会调用他的析构,自定义类型无论什么情况下都会自动调用他的析构函数。
7.类如Data类没有申请资源时,析构函数可以不写,直接使用编译器的默认析构函数。如果默认生成的析构函数就可以使用,就不需要显示写析构函数,如Myqueue,如果类对象需要资源清理,才需要自己实现析构函数。析构函数生命周期到了以后,自动调用如Stack。
以上便是我对C++类和对象的初步认识,如果大家觉得有不好的点多多指教了。最后如果这个文章对你有帮助的话谢谢你的点赞哦。