目录
类的实例化
概念
类和对象的区别
计算一个类
不同的类的大小
类的存储模型
内存对齐
让结构体按照指定的默认对齐数进行对齐
确定偏移量
大小端字节序
This指针
概念
this指针四大特性
一些关于this指针的问题
总结
之前学过了类,但在编程中类无法直接使用,需要将类进行实例化后才能使用,现在就来看看类的实例化是什么?
类的实例化
概念
用类创建对象的过程叫做类的实例化,类可以抽象为虚拟的图纸,对象是按照图纸创建好的类,创建的过程就是类的实例化。
类和对象的区别
对象是按照类创建的实体,所以对象的功能是按照类的设计,但两者之间还有很多不同点
类 | 对象 |
虚拟 | 实体 |
不占用内存空间 | 占用内存空间 |
不参与程序运行 | 参与程序运行 |
不存在生命周期 | 生命周期具体看所在是全局域还是局部域 |
计算一个类
不同的类的大小
有内容的类:
看一下下面的代码,它的大小应该是多少?
class Bit
{
public:int A(char x, int y){return (a + x) - (b + y);}
private:char a;int b;
};
看一下其中的元素,一个成员函数,两个成员变量,32位系统的情况下,成员函数的地址是4个字节,char类型是1字节,int类型是4字节,按照内存对齐的规则,这个类应该是12字节的大小,但实际的运算结果是8字节。
无内容的类:
再看一下下面的代码,它的大小又是多少?
class Bit
{};
什么都没有,那它的字节应该是0,但实际上它的字节数是1。
类的计算结果这么奇怪,中间起决定性因素的是类的存储模型
类的存储模型
C++之父在创建类的模型是充分考虑了类被实例化的情况,所以设计了一个极为简单的存储模型,那就是将类中的成员函数放到常量区,而类中只保留成员变量。
下面的图表是一个类,在实例化后,对象中的成员函数被放到一个公共区域中,对象中只存放成员变量,所以计算对象的结果为8个字节,同时这个特性也被保留在了类中,所以sizeof(Bit)(注意这里计算的只是类,不是对象)得到的结果也是8个字节。
对于空类,空类没有内容,但考虑到类会实例化为多个对象,所以会分配给类1个字节作为占位符,占位符中部存储有效内容,仅仅表示类的存在。
类中的成员函数保存到常量区,不用担心找不到,是因为整个过程由编译器完成。
内存对齐
内存对齐的概念
现代计算机都是按照Byte作为划分单位的,从理论上讲,访问任何类型的变量都可以从任意的位置开始,但在实际操作的过程中,特殊类型的变量需要放在特殊的位置,这句需要特殊类型的变量按照一定的规则存放在内存中,而不是一个接一个的放在一起,这就是内存对齐。
为什么需要内存对齐
内存对齐主要有两个原因:
一是不同架构的cpu对数据存储及读取的方式不同,在某些CPU上任意位置读取可能会发生错误。
二是为了提升效率,假设一个32位系统下的int变量,它占据4个字节,一些平台读取是从偶地址开始读取,int变量放在偶地址开始时,只要一个读周期就能完成数据读取,但放在奇地址需要两个读周期,才能将数据读完,之后还要将数据高低字节拼凑才能得到完整的数据。
内存对齐的规则
内存对齐一共四点规则
一、特殊变量(这里指结构体,类,联合体等自定义变量)放入内存时,第一个数据存放在标记为offset为0的地址,之后的数据按照数据的大小或者数据的子数据(结构体中嵌套结构)的大小的整数倍。
以该类为例class BIT
{char a;int b;}
在32位系统下,char类型变量a占据1个字节,a放在偏移量为0的位置,int类型变量b占据4个字节,按照规则将放在数据的整数倍的位置,所以放在偏移量为4的位置。
二、自定义类型变量,按照其最大子类型的成员作为标准,从最大成员的整数倍开始储存。
看一下这个类,类Y中嵌套了类Xclass X
{int a;char b;double c;
};class Y
{char i;int j;short d;X k;
};
在偏移量为0的位置放入i,在偏移量为4的位置放入4,偏移量为8的位置放入d,此时的偏移量为10变量K中最大的类型是double占据8个字节,10不能整除8,偏移量后移,直到偏移量为16时,放入a,之后放入b,在偏移量为24的位置放入c,最终大小为32个字节。
三、类的总大小是最大成员类型的整数倍,如果不是,需要在成员后继续填补,直到达到最大成员类型的整数倍。
看一下下面的代码class BIT
{int a;char b;
}
这个类中数据占据5个字节,数据之间没有填补,但类的总大小是8个字节,按照规则三,类的大小是最大元素的整数倍,最大类型是4个字节,不能被5整除,所以在变量b之后还要继续填补三个字节。
四、如果程序中有#pragma pack(n)定义了字节,那么编译器会用n与数据大小做比较,两者中选择小的为偏移量。
类的大小的影响因素
一,系统是32位还是64位
数据类型 | 32位 | 64位 |
char | 1 | 1 |
char* | 4 | 8 |
short int | 2 | 2 |
int | 4 | 4 |
unsigned int | 4 | 4 |
float | 4 | 4 |
double | 8 | 8 |
long | 4 | 8 |
long long | 8 | 8 |
unsigned long | 4 | 8 |
二、成员变量的排列顺序
class A
{char a;char b;int c;
}class B
{char a;int c;char b;
}
同样的数据,顺序不同A的大小为8字节,B的大小为12字节。
让结构体按照指定的默认对齐数进行对齐
VS中,可以通过#pragma pack(n)
预处理指令定义字节。
确定偏移量
大小端字节序
内存中每个字节作为一个存储单元对应一个唯一的编号,这个编号就是地址也就是指针,地址分为低地址与高地址,编号小的为低地址,大的为高地址。
数据的值分为高位和低位
大端存储模式:高位值放入低地址位
小端存储模式:低位值放入低地址位
当然需要注意的是,如果多个数据 0X12345678 0X11223344,不同的数据存储与大小端没有关系。
不同的处理器,大小端字节序不同,判断如何判断采用的是大端还是小端?
看一下下面的图不难发现,将int类型的1放入内存,只需要提取内存中0X00000000的数据,就可以判断是大端还是小端。
This指针
概念
先看一下下面代码:
class Date
{
public:void Init(int year, int month, int day);
private:int _year;int _month;int _day;
};
void Date::Init(int year, int month, int day)
{_year = year;_month = month;_day = day;printf("%d-%d-%d\n", _year, _month, _day);
}
int main()
{Date A;Date B;A.Init(2023, 10, 12);B.Init(2022, 10, 9);system("pause");return 0;
}
类中的函数存在于(常量区)只有一份,上面的代码中对象A和B分别调用了Init函数,输出的结果不同,这是因为在类中存在一个隐形的指针,this指针。
为了让成员函数知道操作哪一个对象的成员变量,C++增加了一个隐藏的指针this指针,当该指针指向了某个对象时,通过成员函数操作的内容就是该类的成员变量。
this指针四大特性
一、成员函数内部使用
this指针只能在成员函数内部使用,C++定义时将this指针默认定义成了成员函数的第一个形参。
void Date::Init(Date* this,int year, int month, int day)
{this->_year = year;this->_month = month;this->_day = day;printf("%d-%d-%d\n", _year, _month, _day);
}
本质上this指针只是个形参,接收实参(类的地址),完成相应的操作。
二、隐式指针
this指针作为形参不能显式(手动)写,C++自动完成的,一般通过ecx寄存器自动完成,实际在成员函数中可以在成员变量前填写this指针,不过没有必要。
三、类型
为了保护this指针的内容,C++定义它的类型为 类类型*const this,const修饰this指针,this指针的值不可被修改。
const int *pa = &a;
int const *pb = &a;
int *const pc = &a;*pa = 20; (不可修改指针指向的变量的值)
*pb = 30; (不可修改指针指向的变量的值)
*pc = 40; (可修改指针指向的变量的值)pa = &b; (可修改指针的值)
pb = &b; (可修改指针的值)
pc = &b; (不可修改指针的值)
四、存储位置
每一个对象,都有this指针,实际上就是它的地址,一般情况下this指针存储的在栈区中,VS编译器this指针存在ecx寄存器中。
一些关于this指针的问题
1、this指针的问题,下面两个题分别选择什么
题目一的选项是C、正常运行,p的指针是nullptr,print(p)调用后不发生解引用,直接打印print(),不发生解引用编译运行时不会崩溃。
题目二的选项时B、运行崩溃,P的指针是在传递后在cout<<this->_a<<endl处,发生了解引用,所以程序运行崩溃。
二、this指针放在哪里
this指针放在栈区中,VS编译器下this指针存放在ecx编译器中。
三、this指针可以为空吗
this指针可以为空,在不解引用时能正常使用,解引用时程序会崩溃。