1.结构体的声明
要知道怎么计算结构体大小我们首先要了解结构体
1.1什么是结构体
结构是一些值的集合,这些值称为成员变量.结构的每个成员可以是不同类型的变量.
1.2结构体的声明和定义
struct tag
{
member-list;
}variable-list;
member-list;
- 此处是结构体成员
- 可以有多个成员变量
- 成员类型可以是不同类型的变量
variable-list;
- } 之后的此处是定义的变量列表,在结构体创建初同时定义了一个结构体变量
- 此处定义的变量是全局变量
- 也可以在函数中 使用 struct tag abc; (tag为结构体名,abc为变量名) 方式来定义变量(局部变量)
例如此处描述一个学生:
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}s4,s5; //分号不能丢
int main()
{
struct Stu s1;
struct Stu s2;
struct Stu s3;
return 0;
}
此处的Stu就是一个结构体,内部成员描述学生的属性,s1,s2,s3,s4,s5都是定义的结构体变量.s4,s5是全局变量,s1,s2,s3是局部变量.
1.3匿名结构体
对于上述结构体的声明 有一种特殊的存在
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}*p;
细心的你应该发现了,上述的声明方式没有名称,这种类型叫做匿名结构体,由于没有名称,所以只会在声明结构体的时候,同时定义需要的结构体变量.
需要注意的是上述的两种结构体虽然成员内容相同,但是在内存中完全是不同的两个结构体.
所以基于上述代码 p = &x; 这样的代码是不合法的.
1.4结构体的自引用
struct Node
{
int data;
//1.struct Node next;
//2.struct Node* next;
};
如果结构体需要在成员中构建自己的变量的时候,应该使用 方式1 还是 方式2 呢. 如果你也觉得迷茫.那么请你看这么一段代码.
sizeof(struct Node);
如果使用方式1的话,那上述代码算出来的结果应该是多少呢. 我们发现会进入无限循环,无法算出结果.这在计算机种有限资源的容器中是不允许出现的.所以方式2才会正确的方式.也就是下述代码.
struct Node
{
int data;
struct Node* next;
};
1.5结构体改名
结构体也是可以改名的(typedef关键字)
//结构体改名
typedef struct Student
{
int id;
char name[20];
}Stu;
int main()
{
Stu s1;
return 0;
}
我们可以发现在改名之后,我们可以更方便的创建结构体变量.
改名后的自引用
知道结构体可以改名后,肯定有人要问 那我可不可以,使用改名之后的结构体名称来进行自引用.也就是下述代码.
XXX
//匿名
typedef struct
{
int data;
Node* next;
}Node;
//非匿名
typedef struct Node
{
int data;
Node* next;
}Node;
简洁明了,不可以.这是不允许的.想要自引用,只能使用原名称的指针类型来引用.
//这是正确的引用方式
typedef struct Node
{
int data;
struct Node* next;
}Node;
此块代码是正确的引用方式
2.结构体的初始化
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {4, 5};
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
int main()
{
struct Node head;
//定义后初始化
head.data= 0;
head.p = p3;
head.next = NULL;
Node h2 = {2,NULL,NULL};//定义时初始化
}
结构体初始化有两种
- 在定义的同时初始化
- 在定义之初不初始化,之后在对成员分别进行初始化
而在定义之后,对成员的操作方式也是有两种
- "." 操作符:针对普通类型
- "->"操作符:针对指针类型
struct date // 声明一个结构体类型
{
int month;
int day;
int year;
}
struct student
{
int num;
char name[20];
char sex;
int age;
struct date birthday;
char addr[30];
}student1, *student2;
int main()
{
//直接使用"."操作符进行操作.
student1.num = 10;
//bitthday成员也是一个结构体,可以嵌套使用"."操作符
student1.birthday.month = 5;
student1.age = 20;
student1.age ++;
//student2是指针类型,使用"->"操作符
student2->age = 18;
//由于优先级问题需要带括号
(&student2).num = 31;
}
当然指针类型也是可以使用"."操作符的.只需要解引用即可,但是注意需要带括号,因为"."操作符优先级高于解引用操作符.
3.结构体大小的计算
了解了结构体以及基本操作,那我们来看看结构体的大小应该怎么计算
3.1结构体对齐
结构体对齐规则
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。(最大对齐数不包含vs默认值)
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
3.2结构体大小的计算
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
灰色为浪费掉的空间. 根据上述规则
- 首先比较c1的大小和默认对齐数的大小. 显然c1的小以为c1的大小为1.然后根据1和当前偏移量对比,如果偏移量为0,则从当前位置开始存入数据,如果偏移量不为0,将偏移值移动到1的倍数处.存入c1的值,0位置.
- 然后比较下一个,也就是 i 的大小和默认值得大小的最小值,得到的值为4.然后根据4和当前偏移量比较,不为0则移动到4的倍数处.存入i的值,也就是4位置.
- 然后比较下一个,c2的大小和默认值得大小的最小值,得到的值为1.然后根据1和当前偏移量比较,不为0则移动到1的倍数处.存入c2的值,也就是9位置.
- 最后计算总大小.成员的最大对齐数为4(不包含vs的默认值),当前偏移量为9,需要对齐为4的倍数.也就是12.所以结构体大小就是0到对齐位置之前的内存大小,也就是12.
所以结构体最终大小为12.
3.3补充
可以使用#pragma pack(8)来更改默认对齐数
//在创建结构体之前设置
#pragma pack(1)//设置默认对齐数为1
//使用完后可以使用无参数的来还原为默认
#pragma pack()//取消设置的默认对齐数,还原为默认