目录
结构体
结构体类型的声明
结构的自引用
结构体变量的定义和初始化
结构体内存对齐
结构体传参
结构体实现位段(位段的填充&可移植性)
枚举
枚举的优点
枚举的使用
联合
联合类型的定义
联合大小的计算
联合的特点
结构体
结构体类型的声明
结构体的各种数据类型的集合,这些数据被称为结构体成员。创建结构体变量时,每个成员各自占有空间。
声明格式:
struct tag { member - list ; } variable - list ; 或 声明匿名结构体 struct { int a ; char b ; float c ; } a [ 20 ], * p ; 在声明时同时创建结构体指针变量p等,便于使用。结构的自引用
struct
Node
{
int
data
;
struct
Node next
;
};
结构体引用自身作为成员是错误的,原因是结构体的大小将是未知的
正确的做法是将结构体指针作为成员,如:
struct
Node
{
int
data
;
struct
Node* next
;
};
注意typedef后的类型在结构体中还不能使用,例如
typedef struct
{
int
data
;
Node
*
next
;//编译器还不认识Node
}
Node
;
正确的做法是:
typedef struct Node
{
int
data
;
struct Node
*
next
;
}
Node
;
结构体变量的定义和初始化
定义结构体变量有多种方式,例如
struct Point{ int x; int y; }p1;//定义结构体变量p1 或者 struct Point p2;//定义结构体变量p2 初始化结构体变量 struct Point p3={3,5}; 倘若是这样定义的结构体,还需嵌套初始化 struct Node { int data ; struct Point p ; struct Node * next ; } n1 = { 10 , { 4 , 5 }, NULL }; // 结构体嵌套初始化n1,用{}初始化结构体成员结构体变量 struct Node n2 = { 20 , { 5 , 6 }, NULL }; // 结构体嵌套初始化n2结构体内存对齐
在计算结构体大小是需要考虑结构体内存对齐,结构体内存对齐存在的原因如下:
1.平台原因:某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:在访问未对齐的内存,处理器需要访问两次,而对齐的内存只要访问一次
故数据结构(栈)应该尽可能在自然边界上对齐。
内存对齐规则:
1.第一个成员对齐到与结构体变量偏移为0的位置
2.其他成员对齐到各自对齐数的整数倍(对齐数是编译器默认对齐数与成员大小(字节数)的较小值)
3.结构体总大小为各个成员最大对齐数的整数倍
4.如果嵌套结构体,嵌套结构体对齐到自己成员最大对齐数的整数倍,结构体总大小是所有成员最大对齐数(含嵌套结构体的成员)的整数倍
VS2022编译器默认的对齐数是8,用#pragma pack(4)可以修改编译器的默认对齐数为4
再用#pragma pack()可以还原编译器的默认对齐数 用宏offsetof可以求结构体成员变量相对结构体首地址的偏移量 offsetof宏的实现 #define offsetof(StructName,MemberName) (size_t)&(((StructName*)0)->MemberName)结构体传参
结构体传参通常传结构体的地址,即结构体指针,这样做节省内存的开销,因为结构体传参参数需要压栈,结构体较大时系统时间和空间开销大。
结构体实现位段(位段的填充&可移植性)
位段是结构体中的一种特殊的类型,(位段不具跨平台性,注重可移植程序避免使用)位段声明规则如下:
1.位段的成员必须是整形(int,char)。 2.位段的成员名后边有一个冒号和一个数字(数字表示开辟的bit位)。 位段的内存分配: 位段的空间上是按需以 4 个字节( int )或者 1 个字节( char )的方式来开辟的(空间不够则直接重新开辟字节) 例如:struct S {
char a:3;
char b:4;
char c:5;
char d:4;
};
struct S s = {0};
s.a = 5; s.b = 12; s.c = 3; s.d = 4;
在VS下,在内存中布局如下(图有点草,c=3上面是00011打错了):
可见位段在字节中是从右向左开辟,不够位段成员大小则重新在更高地址开辟一个字节
在编译器下查看结构体s的内存内容发现确实如此:
注意:位段的跨平台问题(基于以下原因,位段不支持跨平台性)
1.int 位段不知道被当成signed int还是unsigned int。 2. 位段中最大位的数目不确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题) 3. 位段中的成员在内存中分配顺序标准未定义。(VS2022是从右向左) 4. 当后一个位段成员无法容纳于第一个位段成员剩余的位时,不确定是否舍弃剩余位(VS2022下是舍弃的)枚举
枚举是指将可能取值一一列举的类型,例如
enum Color { RED , GREEN , BLUE }; {} 中的内容是枚举类型的可能取值,也叫 枚举常量 。 这些可能取值都是有值的,默认从 0 开始,一次递增 1 ,当然在定义的时候也可以赋初值,未被赋值的变量的值将是上一个枚举常量+1(第一个枚举常量是0)。枚举的优点
相比define定义的常量,枚举有类型检查更加的严谨。
定义枚举类型一次定义多个常量快捷方便,枚举的常量有各自的名称且是同一个类型,增加了代码的可读性便于维护。
枚举的使用
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异
像clr=4;这样赋值是不合理的因为enum color类型和int 类型不同。
虽然enum Color和int类型不同,但编译器却能正常运作,求枚举类型的变量的大小发现是4个字节
联合
联合也是一种特殊的自定义类型,这种类型定义的变量包含一系列的成员,与结构体不同的是,这些成员公用一块空间,所以联合也叫共用体。
联合类型的定义
联合类型的声明
union
Un
{
char
c
;
int
i
;
};
定义联合变量
union
Un un
;
联合大小的计算
因为联合的成员是共用同一块内存空间的,这样一个联合体类型变量的大小,至少是最大成员的大小,但这不是联合体类型变量的最终大小,因为联合体也存在内存对齐
规则是,联合体大小至少是最大成员的大小;若其不是联合体成员最大对齐数的整数倍,则还要对齐到整数倍。(数组对齐数是数组成员类型的对齐数)
例如:
首先VS2022下编译器的默认对齐数是8,c的对齐数是1,i的对齐数是4,数组a的对齐数是1,
这样联合un的大小至少是5,然后对齐到4的整数倍8
为什么至少是5而不是4呢,数组a算一个成员还是数组中各个成员都算做联合体的一个成员?
下面的例子说明了:
可见整个数组a是联合体的成员,否则结果就是8而不是12了。
联合的特点
因为联合体成员公用一块空间,联合的特点自然是内存的存储上与结构体有所不同。
例如:
i的大小刚好是整个联合体的大小,c则是一部分,
故i=0x22334455(4字节)时,c就等于0x55(1字节),从内存中也很好理解。



