结构体详解
重点讲解结构体的内存对齐
结构体是什么
结构体(struct)是C语言中一种复合数据类型,它允许将多个不同类型的数据项组合成一个单一的数据类型。结构体非常适合用来存储和表示具有多个属性的实体,如学生的姓名、学号、年龄等。
结构体的基本结构
**注释
struct 是结构体的关键词
str是结构体的名字,是自定义的
char int float 是成员的数据类型
num name这些则是成员名
str1 str2是结构体变量 且是全局变量
上面的结构体则是定义了两个具有4个成员的结构体变量str1,str2
定义结构体时,结构体名字–str和变量str1,str2可有可无
但结构体结尾必须需加上英文分号
结构体定义本身不会分配内存,它只是一个模板。当创建结构体变量时,才会为其分配内存空间
在主函数使用结构体时,结构体的声明必须在调用之上,否则会报错
**
结构体的使用
结构体的初始化
打印结果
只要printf打印的内容 如名字是:%s 与str1.name一一对应,其他也如此,即使顺序不同,也可以得到正确打印
结构体的直接访问
在上面的中,我们看到了结构体的访问形式如下
<结构体类型的变量名>.<成员名> (使用中<>去掉)
其中这里面的.是结构体直接访问的操作符
结构体的间接访问
是->操作符 用于间接访问结构体指针指向的对象的成员
如果我们有一个指向结构体的指针,而不是结构体变量本身,此时就需要使用间接访问操作符
struct S
{
int arr[5];
int a;
float b;
};
void print(struct S* ps)
{
for (int i = 0; i < 5; i++)
printf("%d ", ps->arr[i]);
printf("\n");
printf("%d\n", ps->a);
printf("%.2f", ps->b);
}
int main()
{
struct S s = { {1,2,3,4,5},10,3.14f };
print(&s);//此处对s进行取地址,是为了得到结构体的地址.从而使指针指向结构体
return 0;
}
结果
总结,具体使用哪种访问,要看具体情况,这才能达到最佳效果
typedef重命名
type是类型 def是定义
:对于复杂的变量声明,typedef 可以显著提高代码的可读性。
typedef struct S
{
int num;
char name[20];
int age;
float high;
}s1,s2,s3; //此处即命名了三个结构体 typedef struct S s1 typedef struct S s2 typedef strucr S s3
在其他地方的应用
int main()
{
typedef int INT;
INT num = 0;//等价于int num 此处将int 命名为了 INT
typedef char ch;
ch s = 'a';
typedef int A[3];
A arr[3];
return 0;
}
结构体的内存对齐
引入
#include <stdio.h>
struct S
{
char a;
char b;
int c;
char d;
};
int main()
{
struct S s = { 0 };
printf("%zd", sizeof(s));
return 0;
}
大家觉得这打印结果应为多少
相信不少人认为a , b, d,e各1个字节 d4各字节, d所以总和应为7个字节
但结果却并非如此
打印结果
出现这种情况的原因是结构体的内存对齐,接下来让我们一起了解内存对齐
正言
结构体的对齐规则:
对于该规则的讲述,拿上面引言的讲解为例,
讲vs中内存抽象为图像

由第一个规则知起始地点为0,即起始位置(此时偏移量为0)
又因为第一个元素为char类型 大小为1个字节,所以占据一个格子
第二个数据类型同样为char 大小一个字节,且vs的默认值为8
所以最小对齐数为char的大小—1
同时第二个数据存入内存的起始地址为1,是对齐数的整数倍,无需改变起始位置占用完内存后变为
第三个数据类型为int 大小为4,而vs默认值为8,所以对齐数为4
由规则1知,起始位置要是对齐数的整数倍,
大于2且是4的最小整数倍为4
所以需浪费两个格子的内存
此时变为
int 为4个字节大小,所以再次变化
第四个数据类型为char 大小为1
而起始位置8是1的整数倍,所以无需浪费内存
变化后为
由第3条规则
上面数据的最大对齐数为4,而终止位置9并非是4的整数倍,所以要继续浪费内存来终止
而大于9且为4的最小整数倍为12
所以再浪费3个格子的内存,从而终止
相信有上面的讲解,你对此有一定了解
结下来试试这个
答案 此处交给你们自行解答,后面还有练习进行补充
结构体的内存对齐的最后一条规则
#include <stdio.h>
struct S3
{
double d;
char c;
int i;
};
struct S2
{
char c1;
struct S3 s3;
double d;
};
int main()
{
struct S2 s2 = { 0 };
printf("%d", sizeof(s2));
return 0;
}
讲解
按顺序,先是s2.c1 ,该为字符,大小为1个字节,<8,
所以对齐数为1
又因为起始位置偏移量为0
所以
接着就是嵌套结构体了,开始进入结构体s3
s3.d为double类型,大小为8个字节 == 8,所以对齐数为8
又因为起始位置要为对齐数的整数倍
所以最终为
接着s3.c 数据类型为char与上面类似,不再细说,看图
接着是s3.i 数据类型为int <8 ,所以对齐数为4
又因为起始位置要是对齐数的整数倍
所以最终为
最后再返回s2.d 为double类型,大小为8个字节 == 8 所以最小对齐数为8
由对齐规则的第一条规则知,起始位置刚好为对齐数的整数倍,所以不需舍去内存
此处32是最大对齐数8(包含嵌套结构体的最大对齐数)的整数倍,所以停止
打印结果
同理,对于大于等于2的结构体嵌套数,一样,都是一个一个元素排
为什么存在结构对齐及优解
重点:是拿空间来换取时间的做法
取优
此时聪明的你们,应该发现了,即使一个结构体的成员名及数据类型相同,但在不同顺序下排列,最后所占的内存大小不一样
此处建议:在设计结构体为了即满足对齐,又节省空间:
可让占用空间小的成员尽量集中在一起
修改默认对齐数
#pragna这个预处理指令,可以改变编译器的默认对齐数
#include <stdio.h>
#pragma pack(1)//设置默认对齐数为1
struct s
{
char a;
char b;
int c;
char d;
};
#pragma pack()//取消设置的对齐数,还原为默认
int main()
{
printf("%d", sizeof(struct s));
return 0;
}
结果,可以看到此处的结构体完全与我们的第一个例子一样,但因为默认对齐数的不同,最后结果不同
练习
答案为:12
答案 16 12
结构体实现位段
什么是位段
段的定义形式类似于结构体,但其成员后需跟一个冒号和数字,表示该成员占用的位数
struct bs {
unsigned int a : 3; // 占用3位bit位
unsigned int b : 5; // 占用5位bit位
unsigned int c : 8; // 占用8位bit位
};
**注意事项:
1.其中 bs就是一个位段类型
2. 有些程序员的风格喜欢在数据类型跟命名之间加上下划线
例如:unsigned int _a : 3;(这仅仅是个人习惯,不会影响最终结果)
3.跟结构体相比,位段成员必须 int、unsigned int、signed int 或 char 类型
位段的内存分配
段的内存分配涉及以下几个原则:
位段的成员可以是 int、unsigned int、signed int 或 char 类型。
位段的空间按照需要以4个字节(int)或1个字节(char)的方式来开辟。
位段的成员在内存中从右向左分配空间,如果遇到不足位段成员大小的空间,则直接丢弃,重新开辟一个字节的空间分配。
直接举例说明:
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 6;
};
int main()
{
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
return 0;
}
接下来画图说明
10 转为二进制为00000000000000000000000000001010
12 转为二进制为00000000000000000000000000001100
3转为二进制为0000000000000000000000000000000011
4转为二进制为0000000000000000000000000000001100
读数都是从低位向高位读
而char a : 3;则只占3个bit位即
char b : 4;占4个bit位
char c : 5;占5个bit位 而上面这一个字节只剩1个bit位,不足则舍去,再开一个新的字节
char d : 6;占6个bit位而上面这个字节只剩3个bit位,所以再开一个新的字节
位段跨平台问题
解决方法:根据不同的平台写不同的代码
位段使用的注意事项
错误做法
正确做法 先对一个变量进行赋值,再令位段的成员等于该变量