本篇博客探讨结构体的进阶应用(注意:本博客未梳理结构体基础知识【包括结构体类型的基础声明,变量的创建与初始化,结构体的输出,成员访问操作符等】,默认读者已经掌握)
一、结构体的声明
1.结构体的特殊声明:匿名结构体
struct
{
int a;
char b;
float c;
}a[20], *p;
匿名结构体的重命名:使用typedef关键字
typedef struct Node
{
int a;
char b;
float c;
}Node;
之后创建结构体变量的时候,可以直接Node s={…},前面的struct可以省略掉
注意:匿名的结构体类型,若没有对结构体类型重命名,只能使用一次
2.结构体的自引用:以链表的节点为例
struct Node
{
int data; //数据域
struct Node* next;//指针域
};
这样的代码可以无限自引用下去,并且保证了结构体的大小不会是无穷大
二、结构体内存对齐
1.对齐规则
(1)结构体第一个成员对齐到和结构体起始位置偏移量为0的地址处
(2)其它成员变量要对齐到对齐数的整数倍的地址处,对齐数=min{编译器默认的一个对齐数,该成员变量的大小(单位是字节)}
①VS中默认对齐数为8
②Linux中gcc无默认对齐数,对齐数就是成员自身的大小
(3)结构体总大小=所有成员最大对齐数的整数倍
(4)如果嵌套了结构体,嵌套的结构体成员对齐到自己成员中最大对齐数的整数倍处
结构体整体大小=最大对齐数(包括嵌套结构体中成员的对齐数)的整数倍
//例1
struct S1
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S1));//输出16
图片解析
//例2
struct S2
{
char c1;
struct S1 s1; //结构体里面嵌套结构体
double d;
};
printf("%d\n", sizeof(struct S2)); //输出32
注:#pragma这个预处理指令可以修改编译器的默认对其数,当结构体对齐方式不满足编程需要的时候我们可以自己修改对齐数
#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S //结构体的内存布局按照对齐数为1进行设置
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{
printf("%d\n", sizeof(struct S)); //6
return 0;
}
三、结构体传参:传递一个结构体对象的时候,要传结构体的地址(避免结构体过大导致压栈的系统开销过大)
例如:下面代码首选print2函数
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
四、结构体实现位段
1.位段的定义(除了以下两点外其它与结构体相同)
(1)位段的成员可以是int,unsigned int,signed int或者是char
(2)位段的成员名后面有一个冒号和数字,表示该成员占多少个bit位
struct A
{
int _a:2; //_a占2个bit位
int _b:5; //_b占5个bit位
int _c:10; //_c占10个bit位
int _d:30; //_d占30个bit位
};
2.位段的内存分配(测试环境:VS2022)
(1)位段的空间:按照需要以4个字节(int)或1个字节(char)开辟
原理解析:
a. VS2022从右向左开辟空间,本代码位段中均为char,因此一次开辟一个字节(8个 bit位)
b. 当剩下的位置不够下一个成员使用时,VS将其浪费,再开辟出新的一个字节
3.位段的跨平台问题
(1)int位段被当成有符号整型还是无符号整型不确定
(2)位段中最大位的数目不确定(比如16位机器中int占2个字节)
(3)位段中成员的内存是从左向右还是从右向左分配,C标准未定义
(4)剩余空间不够下一个成员使用,是否浪费掉剩余空间不确定
因此,位段可以达到和结构体相同的效果,并且可以很好地节省空间,但是有跨平台的问题存在
4.位段使用的注意事项:不能对位段的成员使用&操作符,因为可能拿不到地址
位段的几个成员共用一个字节,但是一个字节内部的bit位是没有地址的,所以不能用scanf,只能先输入一个值放在变量中,再赋值给位段成员。