类对象模型
类对象的大小
结构体内存对齐规则
首先我们先复习以下结构体的内存对齐规则:
1 第一个成员在于结构体偏移量为0的地址处
2 其他成员变量要对其某个数字(对齐数)的整数倍的地址处。注意的是,对齐数=编译器的默认值与该成员大小的较小值。
3 结构体总体大小为最大对齐数的最小整数倍。最大对齐数=结构体所有变量类型的大小的最大值与编译器默认数字的较小值
对于普通的类来说,我们根据规则可以计算出B的大小是8个字节。
但是如果一个类里面有函数的化,那么它的大小是多少呢?
比如A里面有一个函数,那么它的大小是多少呢?
它的大小也是8。
这是为什么呢?
类对象的存储方式
在类对象中,只会保存成员变量,成员函数存放在公共的代码段。
也就是说,一个类对象的大小,实际就是该类中成员变量之和的大小,但是要注意内存对齐。
注意,空类的大小是一个字节。编译器给空类一个字节来标识这个类的对象。
this指针
this指针的引出
我们定一个类 Date
问题来了:
Date 定义了init和print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用init函数时,该函数是怎么知道要设置的是d1的参数,而不是d2的参数呢?
C++中引用了this指针来解决这个问题。
C++编译器会给每个“非静态的成员函数”增加一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针来操作。只不过所有的操作对用户来说是透明的,用户不需要传递,编译器自己完成的。这个隐藏的指针就是this指针。
this指针的特性
1 this指针的类型是: 类的类型 * const,即成员函数中,不能给this指针赋值。
2 只能在“成员函数”内部使用。
3 this指针本质上是“成员函数”的形参,当调用成员函数时,将对象地址作为实参传递给this形参。因此,对象中不存储this指针。
4 this指针时“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
this指针的存储
this指针不在对象中存储,那么,this指针在哪里存储呢?
this指针在栈里面存储。
为什么呢?
前面是不是介绍过 this 指针是一个形参?形参存储在哪里?形参存储在栈上。函数在调用时会创建栈帧,栈里面会存储形参和局部变量,在调用结束后会销毁栈帧。
类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显示实现,编译器会生成的成员函数为默认成员函数。
构造函数
概念
构造函数是一个特殊的成员函数,名字和类的名字相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个声明周期内只调用一次。
语法和特性
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
语法和特性如下:
1 函数名与类名相同。
2 无返回值(返回值不是void,而是真的没有返回值)。
3 对象实例化时编译器自动调用对应的构造函数。
4 构造函数可以重载。
5 如果类中没有显示定义构造函数,则编译器会自动生成一个无参数的默认构造函数,一旦用户显示定义,编译器将不再生成。
6 无参数的构造函数和全缺省构造函数都成为默认构造函数,并且默认构造函数只有一个。注意:无参构造函数、全缺省构造函数和编译器默认生成的构造函数都属于默认构造函数。
上图圈红框的就是一个构造函数。
析构函数
概念
析构函数:与构造函数相反,析构函数不是完成对对象本身的销毁,局部对象的销毁是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作,防止内存泄漏。
语法和特性
析构函数也是特殊的成员函数。
语法和特性如下:
1 析构函数是在类名前加上字符~。
2 无参数无返回值类型。
3 一个类只能由一个析构函数,若未显示定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
4 对象生命周期结束时,C++编译系统自动调用析构函数。
5 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数。有资源申请时,一定要写,否则会造成内存泄漏。