⭐本篇文章为C++学习第4章,主要了解类对象大小和this指针
⭐本人C++代码仓库:yzc的c++学习: 小川c++的学习记录 - Gitee.com
目录
一. 类对象模型
1.1 类成员函数和成员变量的分布
1.2 如何计算类的大小?(结构体内存对齐)
二. this指针
2.1 了解this指针
2.2 this指针特性
三. 类和this指针相关面试题
3.1 结构体怎么对齐? 为什么要进行内存对齐?
3.2 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?
3.3 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景
3.4 this指针存在地址空间哪里?
3.5 this指针可以为空吗?
一. 类对象模型
1.1 类成员函数和成员变量的分布
类中既有成员变量,又有成员函数,一个类实体包含了哪些内容??
可以通过获取不同类型的类大小进行分析
#include<iostream>
using namespace std;
//1.既有成员变量,也有成员函数
class A
{
int a;
int b;
void f1()
{}
void f2()
{}
};
//2.只有成员变量
class B
{
int a;
int b;
};
//3.只有成员函数
class C
{
void f1()
{}
void f2()
{}
};
//4.什么都没有
class D
{};
int main()
{
cout << "A:" << sizeof(A) << endl;
cout << "B:" << sizeof(B) << endl;
cout << "C:" << sizeof(C) << endl;
cout << "D:" << sizeof(D) << endl;
return 0;
}
运行结果如下:
可以看到:
1. 拥有成员变量的A和B大小是8,而没有成员变量的C和D是1
2.拥有成员函数的C和没有成员函数的D都是1
所以:类只保存成员变量,将成员函数放在公共代码段。由于每一个类实体都有类成员函数,而成员函数在每一个类实体的作用是一样的,如果给每一个类实体都分配内存存储成员函数,就会占用大量过多的内存空间
注意:空类和只有成员函数类的大小是1,因为编译器要给一个标识符来标识这个类
1.2 如何计算类的大小?(结构体内存对齐)
使用sizeof可以轻松计算出一个类的大小,它是如何计算的??
需要使用结构体内存对齐规则:
1. 第一个成员变量在结构体偏移量为0的地址处
2. 其他成员变量对齐到某个数字(对齐数)的整数倍地址处
对齐数=min(编译器系统默认对齐数,该成员大小),VS默认对齐数是8,各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数
3. 结构体大小为:最大对齐数的整数倍,最大对齐数为所有变量类型的最大者与默认对齐数的较小值
4. 如果嵌套了结构体,嵌套的结构体对齐到自己最大对齐数的整数倍,这个数也能成为对齐数(要和默认对齐数比较)
如:
#include<iostream>
using namespace std;
class A
{
int a; //偏移量为 0~4
int b; //偏移量为 4~8
char c; //偏移量为 8~9
};
int main()
{
//总偏移量是9,最大对齐数=min(max(1,4),8)=4
//结构体的大小为最大对齐数的整数倍,4 8 都小于9,所以大小为12
cout << sizeof(A);
return 0;
}
变量分布图如下:
运行结果:
如:
#include<iostream>
using namespace std;
//1.既有成员变量,也有成员函数
class A
{
char arr[12]; // 0~12
double b; //大小为8,只能从8,16,24开始分配 16~24
int c; // 24~28
};
int main()
{
//总偏移量是28,最大对齐数=min(max(1,8,4),8)=8
//结构体的大小为最大对齐数的整数倍,为 4*8=32
cout << sizeof(A);
return 0;
}
变量分布图如下:
在c/c++中可以修改默认的对齐数
#pragma (4) //将默认对齐数修改为4
二. this指针
2.1 了解this指针
为了方便了解this类,我们先创建一个日期类:
d1.Set(2024, 9, 21);
//本质
this=&d1
d1.Set(this, 2024, 9, 21);
Set函数内部
this->_year = year;
this->_month = month;
this->_day = day;
我们创建了d1实体,并且调用Set函数,问题是Set函数中没有传入d1的地址,Set函数是如何找到d1对象的呢?
实际上,Set函数包含了隐藏的this指针,当有一个实体调用这个函数的时候,就会把这个对象传递给this,再通过指针找到相应的变量
d1.Set(2024, 9, 21);
//本质
this=&d1
d1.Set(this, 2024, 9, 21);
Set函数内部
this->_year = year;
this->_month = month;
this->_day = day;
2.2 this指针特性
1. this被const修饰,在成员函数中,无法给this赋值
2. 只能在成员函数内部使用
3. this指针本质是成员函数的形式参数,当对象调用成员函数的时候,将对象地址的实参传递给this形参,所以对象中不存储this指针
4. this指针是”成员函数“的第一个隐含的指针参数,一般由编译器通过ecx寄存器进行自动传递,不需要用户传递,使用方便
三. 类和this指针相关面试题
3.1 结构体怎么对齐? 为什么要进行内存对齐?
对齐方式:见本章1.2
为什么要进行内存对齐:
1. 可以提高CPU读取数据的效率,如果不进行内存对齐,CPU需要执行更多的指令获取完整数据
2. 硬件要求:有些处理器要求特定数据在特定地址上对齐,否则会导致错误
3.2 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?
使用 #pragma(指定对齐数),即可将默认对齐数改为指定对齐数
3.3 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景
大小端是数据在内存存储方式。
大端:将数据的高位放在地址的低地址处,将数据的低位放在地址的高地址处。
小端:将数据的低位放在地址的低地址处,将数据的高位放在地址的高地址处。
如:0x12345678
小端存储:
大端存储:
使用高位截断法判断机器存储方式
#include<iostream>
using namespace std;
//0x1234
//低地址-------------->高地址
//小端:12 34
//大端:34 12
int main()
{
int a = 0x1234; //整形占用4个字节,12是数据高位,34是数据低位
char b = (char)a; //字符型占用两个字节,强制转化会把高位截断
if (b == 0x12)
{
//截断了34,说明34放在地址高位,数据低位放在高地址处,为大端存储
cout << "小端存储!" << endl;
}
if (b == 0x34)
{
//截断了12,说明12放在地址高位,数据高位放在高地址处,为小端存储
cout << "小端存储" << endl;
}
return 0;
}
运行结果:
3.4 this指针存在地址空间哪里?
this指针存在栈中,因为它是一个形式参数,函数参数都存在内存区域的栈中(linux)
在vs下,使用exc这个寄存器来传递,由于this指针需要经常用,寄存器速度较快
3.5 this指针可以为空吗?
this指针可以为空,这个时候在成员函数内部不能调用任何成员变量
#include<iostream>
using namespace std;
class A
{
public:
void f1()
{
cout << "?????" << endl;
}
void f2()
{
cout << _a << endl;
}
private:
int _a = 123;
};
int main()
{
A* p = nullptr;
p->f1();//正常运行
return 0;
}
运行结果:
如果使用 p 去调用f2函数就会崩溃
调试结果,由于this是nullptr,造成崩溃