目录
结构体
C语言已经给我们提供了内置类型即C语言自带的数据类型(如char short int double)用来描述某些变量,但这是远远不够的,当我们要描述一本书的时候,需要描述书的价格,作者,出版日期等等,描述一个学生的时候需要描述学生的姓名,学号,性别,家庭住址等等,单一的内置类型远远不够我们描述这些有多重属性的事物,所以C语言给我们提供了自定义类型用来让程序员方便描述这些变量,让程序员可以根据需要来自定义某些事物
结构体的声明
struct tag
{
member-list;
}variable-list;其中,tag是结构体的标签
member-list 是结构体的成员,可以是一个或多个
variable-list 是定义的结构体变量,可以在声明的同时定义一个或多个结构体变量
应该注意结构体声明的大括号外面必须要以分号结尾,不管有没有定义变量
//结构体类型的声明 struct Book//结构体标签 { char name[30];//书名 char author[20];//作者 int number;//书号 double price;//价格 }s1,s2;//声明的同时定义的结构体变量
结构体的变量的定义
Ps:定义在main函数外面的是全局变量,定义在main函数里里面的是局部变量
结构体的变量的初始化
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};
int main()
{
//按照结构体成员的顺序初始化
struct Stu s = { "张三", 20, "男", "20230818001" };
printf("name: %s\n", s.name);
printf("age : %d\n", s.age);
printf("sex : %s\n", s.sex);
printf("id : %s\n", s.id);
//按照指定的顺序初始化
struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "⼥ };
printf("name: %s\n", s2.name);
printf("age : %d\n", s2.age);
printf("sex : %s\n", s2.sex);
printf("id : %s\n", s2.id);
return 0;
}
结构成员访问操作符
当我们要访问结构成员的时候有两种方法,一种是直接访问,另外一种是间接访问
如果是结构成员则可以通过操作符( . )直接访问
使用方式:结构体变量 . 成员名
有时候我们得到的不是⼀个结构体变量,而是得到了⼀个指向结构体的指针,这时候通过 ( -> )来间接访问
使用方式:结构体指针 -> 成员名
结构体的特殊声明(匿名结构体)
注意:匿名结构体是结构体的一种特殊形式,它省去了结构体标签名 tag ,因此匿名结构体类型只能在声明的时候使用一次,所以匿名结构体变量只能在声明的同时定义
匿名结构体的初始化:
在定义的同时初始化
mian函数里面初始化
匿名结构体类型只能在声明的时候使用一次
编译报了一个警告,等号两边类型兼容,说明编译器把它俩当成了这个不同的类型,所以我们在使用匿名结构体类型声明的时候只能用一次
结构体重命名
结构体内存对齐
我们初步了解了结构体,那当我们想要知道结构体类型的大小,这是用sizeof求结构体的大小时,我们会惊奇的发现结构体类型的大小不等于它所有成员变量大小的总和
为什么呢?
这就与结构体在内存中的存储方式有关了
结构体在内存中存储有一个对齐规则
对齐规则:
- 结构体的第一个成员存储必须对齐到与结构体起始地址的0偏移量地址处
- 其他成员必须对齐到该成员对齐数的整数倍处
- 对齐数 == 当前成员大小与默认对齐数的较小值
- VS默认对其数 == 8 Linux中gcc无默认对齐数,对齐数为该成员大小
- 结构体总大小要对齐到所有成员对齐数的最大值的整数倍处
- 当结构体嵌套使用时,被嵌套的结构体要对齐到它的所有成员中的最大对齐数的整数倍处,结构体总大小要对齐到最大对齐数的整数倍处(包含被嵌套的结构体对齐数大小)
现在就可以分析为什么时12个字节了
为什们要存在内存对齐?
1.平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定
类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对⻬的内存,处理器需要
作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地
址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以
用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两
个8字节内存块中
总体来说:结构体的内存对齐是拿空间来换取时间的做法
怎样设计结构体使其占用内存空间大小尽量小一点?
尽可能的把占用内存空间较小的成员集中在一起
修改默认对齐数
通过 #pargma pack(n) 预处理指令来修改默认对齐数大小,n 为修改后的大小
通过 #pargma pack() 指令回复默认对齐数
结构体传参
传值调用
传址调用
所以在结构体传参时,尽量传递地址,节省时间和空间上面的开销
结构体实现位段
位段是什么
位段,位即是二进制位,是C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 利用位段能够用较少的位数存储数据
- 位段的成员只能是int unsigned int signed int 等类型,大部分平台还支持char类型
- 位段的成员名后边有⼀个冒号和⼀个数字
位段的使用
struct S
{
int _a:2;//其中冒号后面的数字代表的是该成员在内存空间中占几个比特位
int _b:5;
int _c:3;
int _d:4;
};
位段是基于结构体实现的 ,位段的成员后面要接一个冒号和一个数字,最后以分号结尾,数字代表该成员在内存空间中占几个比特位
为什们存在位段
信息的读取通常是以字节为单位,但有时某些信息的存储不足1个或多个字节时,就会造成内存空间浪费,例如表示真假,结构成员的取值范围就只有0和1,而0和1最多占用1个比特位,成员的类型用的是int ,占32个字节,就会有31比特位空间浪费掉
位段的内存分配
- 位段的空间通常是以成员的类型每次开辟1 / 4个字节大小,int 就一次开辟4个字节,char 就开辟1个字节
- VS上面,位段每次开辟的空间都是从高地址向低地址分配的
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
位段的跨平台问题
- int位段被当成有符号数还是无符号数是不确定的
- 位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
位段的应用
在网络协议中使用位段,可以有效缓解网络拥堵
位段使用的注意事项
因为位段内存分配的特殊,位段成员起始地址不一定能在某个字节的起始位置上,内存中每个字节分配一个地址,⼀个字节内部的bit位是没有地址,所以不能对位段成员进行取地址操作,scanf也就不能对某个成员进行取地址输入值,如果要给成员赋值,先把值给中间变量,在把中间变量的值赋值给位段成员
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A sa = {0};
scanf("%d", &sa._b);//这是错误的
//正确的⽰范
int b = 0;
scanf("%d", &b);
sa._b = b;
return 0;
}
标签:自定义,int,成员,位段,内存,类型,对齐,结构
From: https://blog.csdn.net/2301_80424145/article/details/136920471