结构体类型
为什么要有结构体
我们用c语言描述年龄时候,可以定义一个整形类型来实现:
int age;
age = 18;
printf("年龄为%d",age);
(c语言描述年龄)
由于年龄这一属性比较单一,类似性别、某游戏角色攻击力、血量都可以用c语言内置的变量来描述。
但是如果现在需要描述一本书或一个游戏装备,这种有多种属性的角色,只用一个变量看起来就不够用了,我们需要一种方式(结构体)来描述他们:
例如描述一本书,我们可以这样说:“书的名字是xxx,书的作者是xxx,书的总页数是xxx”,这时我们需要定义一个结构体类型,以描述这些属性。
结构体类型定义,初始化,使用
结构体定义
以下是定义一个用来描述书的结构体类型,模样如下:
//struct 表示结构体 book 表示结构体类型名称
struct book
{//此处为自定义结构体类型“book” 的属性们char name[20];char writer[20];int n;
}
结构体初始化
//前面定义了结构体变量类型,现在用定义好的类型创建一个结构体:
struct book doom; //"wtruct book"表示类型 “doom”表示结构体变量名//结构体初始化类似于普通变量的初始化:
int a; //“int”表示类型 “a”表示变量名doom.n = 30;//赋值结构体doom中的n(书的页数)strcpy(doom.name,"老人与海")//f赋值结构体doom中的name(书名)
结构体使用
printf("%d",doom.n);//打印书的页数
结构体对齐
为什么要有结构体对齐
假如我们现在有这样的一个结构体:
struct test//定义类型
{char a;int b;int c;int d;
}struct test A;//创建一个结构体A
我们可能想象的A在内存中是这样放的:
对于我们来说一目了然,但是对于计算机读取来说就麻烦了,假如现在有一个机器,他一次可以读四个字节,那么他访问结构体A可能需要七次才能读完:
我们可以通过某种规则让他变成方便计算机读取的存储形式:
空间换时间,用三个字节的代价,让计算机读取这个结构体用的次数减少了三次。
对齐规则
以下是结构体对齐规则的基本要点:
-
首个成员对齐:
- 结构体的第一个成员总是从结构体变量的起始地址开始存放,偏移量为0。
-
成员对齐规则:
- 后续每个成员在结构体中的偏移量需要是对齐数的整数倍,其中对齐数一般等于该成员的大小或编译器默认的对齐数(机器相关的最小对齐单位)两者中的较小值。
- 比如,如果一个结构体里有一个
int
成员,而在当前平台上int
大小为4字节且默认对齐单位也为4字节,那么该int
成员将被放在4字节对齐的位置上。
-
结构体大小对齐:
- 结构体的总大小(包括可能存在的填充字节)需要是所有成员中最大对齐数的整数倍。
- 结构体末尾可能会添加额外的字节作为填充,以满足上述对齐要求。
-
结构体自身的对齐值:
- 结构体的有效对齐值通常是其所有成员中最大对齐值的成员的对齐值,确保整个结构体实例的起始地址也符合这个最大对齐值的要求。
- 当结构体组成数组时,结构体之间的间隔也将按照结构体的有效对齐值来确定。
-
编译器控制:
- 许多编译器允许通过预编译指令(如C/C++中的
#pragma pack(n)
)来指定自定义的对齐值,这样会暂时改变当前作用域内的结构体对齐策略。
- 许多编译器允许通过预编译指令(如C/C++中的
结构体位段
为什么要有结构体位段
假如有这样的一个结构体类型:
//类型名称考试成绩
struct Scores
{int math;int english;int chinese;
}
一般来说,一个成绩只需要三位数就可以描述,也就是只需要八个机器码(比特)完全够用了,而一个int类型要占用32个机器码(比特),这样就显得很浪费了。
为了解决这个问题(节省空间),我们可以使用结构体位段。简单来说,就是让变量们挤一挤。比如上面的Scores结构体,让三科的成绩挤在一个整形int里。
这样浪费的空间就少了很多,用位段定义这个成绩结构体类型如下:
struct Scores
{int :math;int :english;int :chinese;
}
结构体位段规则(不同编译器有所差异)
结构体位段(Bit-field)是C语言中一种特殊的数据结构,允许在一个整型变量(通常为int
或unsigned int
)内划分多个命名的位区域。位段的规则主要包括:
-
声明格式: 在结构体中定义位段时,格式如下:
struct {type member_name : bit_width;// 其他位段... } variable_name;
-
其中,
type
是位段成员的类型,但实际存储时会被隐式转换为足够容纳所有位段的整型类型;member_name
是位段的名称;bit_width
是在该位段中使用的位数。 -
分配顺序: 位段成员在内存中按照声明顺序从左向右分配,但不同的编译器可能会有不同的分配策略,有的是从最低有效位(LSB)开始分配,有的是从最高有效位(MSB)开始分配。
-
位段宽度:
bit_width
指定的位数不能超过成员类型的位宽。例如,在32位系统中,一个int
类型的位段的宽度范围是1到32。 -
未指定宽度: 如果省略位宽,则大多数编译器会根据成员类型自动选择合适的位宽,直到填满包含位段的整数。
-
存储开销: 编译器会尽可能紧凑地安排位段,但可能会在位段之间加入额外的位作为填充,以便于整体对齐,并且至少会分配一个完整的基本整型变量的空间(如一个
int
)来存储所有位段。 -
跨边界行为: 不同编译器对于位段跨越一个基本整型变量边界的处理方式不同。有些编译器会在下一个整型变量中继续分配,而有些则可能不支持跨越边界。
-
初始化和赋值: 可以初始化位段,但赋值时要注意仅修改指定位段的位,不会影响同一存储单元内其他位段的值。
-
跨平台差异: 位段的具体行为依赖于编译器实现,因此在不同平台和编译器之间可能存在显著差异,编写可移植的位段代码时需特别小心。