一、结构体、位段、枚举、联合的初识
1.1 什么是结构体
结构体同数组一样属于聚合数据类型,只不过数组里面保存的是同类型的元素的集合,访问具体元素或以下表形式访问,或以指针形式访问;但结构中各个成员可能具有不同的类型,他们需要通过名字去访问,每个成员都有自己的名字。
结构体声明:
struct tag
{
member-list;//成员列表
}variable-list;//变量列表
struct tag
整体是一个自定义类型名,tag
只是一个标签
variable-list
是在定义结构体时便创建变量,个数自定,形式自定(结构体变量/结构体指针变量)
也可以在定义结构体之后采用以下方式创建
struct tag variable;
但需注意的是定义的同时创建变量对于匿名结构体尤为重要,因为编译器会把两个成员完全相同的匿名结构体当作两种不同的类型
既然是聚合数据类型,在初始化的时候就需要初始化每一个成员,如同数组一般,存在完全初始化和不完全初始化两种形式
struct Point
{
int x;
int y;
};
struct Node
{
int data;
struct Point p;//结构体中嵌套结构体
struct Node *next;//结构体的自引用必须定义结构体指针,定义结构体变量会出错(结构体大小不能确定)
}n1 = {10, {4,5}, NULL};//定义的同时创建变量并初始化
struct Node n2 = {20, {5,6}, NULL};//创建变量并初始化
struct Node n3 = {0};//不完全初始化
1.2 什么是位段
为了在结构体的基础上能够更好的节约空间,位段产生了。C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为”位段”或称”位域”。
定义的时候与结构题不同的两点:
(1)位段的成员必须是int、unsigned int、signed int或者是char
(2)位段的成员名后边有一个冒号和一个数字,数字便是在创建该位段时分配的位数
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
struct S
{
char _a:3;
char _b:4;
char _c:5;
char _d:4;
};
struct A和struct S就是两个位段类型
1.3 什么是枚举
枚举是一个被命名的整型常数的集合,枚举的说明与结构和联合相似, 其形式为:
enum 枚举名{
标识符[=整型常数],
标识符[=整型常数],
...
标识符[=整型常数]
} 枚举变量;
注意:
枚举中每个成员(标识符)结束符
是",", 不是";"
, 最后一个成员可省略","
。初始化时可以赋负数, 以后的标识符仍依次加1。
枚举变量只能取枚举说明结构中的某个标识符常量。
1.4 什么是联合
联合是一种特殊的自定义类型,这种类型定义的变量包含一系列共用同一块空间的成员,所以联合也叫做共用体
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
//共用同一空间所以以下成员地址相同
printf("%d\n", &(un.i));
printf("%d\n", &(un.c));
二、进一步了解结构体、位段、枚举、联合
2.1 结构体
(1)结构体成员访问操作符. ->
.
直接成员访问操作符,对应于结构体变量->
间接成员访问操作符,对应于结构体指针
struct Stu
{
char name[20];
int age;
}s,*ps;
s = {"zhangsan",20};
ps = &s;
printf("%s %d", s.name, s.age);
printf("%s %d", ps->name, ps->age);
(2)结构体在定义的时候你中有我,我中有你的问题,需要通过不完整声明来解决
struct B;//不完整声明
struct A
{
int _a;
struct B* pb;
};
struct B
{
int _b;
struct A* pa;
};
(3)结构体在函数传参的时候要传地址,目的是为了避免参数压栈时的系统开销,提高性能
2.2 位段的弊端
位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
(1)int位段被当成有符号数还是无符号数是不确定的
(2)位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出现问题)
(3)位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义
(4)当一个结构体包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位段时,不确定是弃剩余的位还是利用
2.3 枚举的优点
同样是定义标识符,为何有了宏还需要枚举?
(1)增加代码的可读性和可维护性
(2)和#define定义的标识符比较枚举又类型检查,更加严谨
#include <stdio.h>
int main()
{
typedef enum Color
{
RED=1,
GREEN,
BLUE=4
}Color;
Color clr = GREEN;//GREEN=2
clr = 2;//只能打枚举变量给枚举变量赋值,才不会出现类型差异
printf("%d\n", GREEN);
return 0;
}
(3)防止了命名污染
(4)便于调试
(5)使用方便,一次可以定义多个变量
2.4 联合的两处应用
(1)判断当前计算机的大小端存储
#include <stdio.h>
int main()
{
union Un
{
int i;
char c;
};
union Un un;
un.i = 1;
if (un.c==1)
{
printf("小端存储\n");
}
else
{
printf("大端存储\n");
}
return 0;
}
(2)将long类型的IP地址转换为点分十进制的表示形式
union ip_addr
{
unsigned long addr,
struct
{
unsigned char c1;
unsigned char c2;
unsigned char c3;
unsigned char c4;
}ip;
};
union ip_addr my_ip;
my_ip.addr = 176238749;
printf("%d.%d.%d.%d\n",my_ip.ip.c1,my_ip.ip.c2,my_ip.ip.c3,my_ip.ip.c4);
三、结构体、位段、枚举、联合的大小计算
3.1 结构体
结构体内存对齐规则:
第一个成员在与结构体变量偏移量为0的地址处
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = min {编译器默认对齐数,该成员的大小}
//vs中默认对齐数为8,Linux中的默认对齐数为4
//默认对齐数还可以用以下方式修改
#include <stdio.h>
#pragma pack(1)//设置默认对齐数为1
struct S1
{
double d;
char c;
int i;
};
#pragma pack()//取消默认对齐数
int main()
{
printf("%d\n", sizeof(struct S1));//13
return 0;
}
结构体总大小为所有成员变量中最大对齐数的整数倍
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
#include <stdio.h>
struct S1
{
double d;
char c;
int i;
};
struct S2
{
char c1;
struct S1 s1;
char c2;
};
int main()
{
printf("%d\n", sizeof(struct S2));//32
return 0;
}
S2的最大对齐数 = max {d,c,i,c1,c2}
为8
但是一般设计结构体为了节省空间,都让占用空间小的成员尽量集中一些
所以下面的结构体设计更为合理:
#include <stdio.h>
struct S1
{
double d;
char c;
int i;
};
struct S2
{
struct S1 s1;
char c1;//空间小的成员尽量集中
char c2;
};
int main()
{
printf("%d\n", sizeof(struct S2));//24
return 0;
}
3.2 位段
因位段内存分配时,对于剩余空间的处理未定义,所以在vs编译环境下通过以下代码测试:
#include <stdio.h>
struct S
{
char c1:7;
char c2:7;
char c3:7;
char c4:7;
char c5:7;
char c6:7;
char c7:7;
char c8:7;
};
int main()
{
printf("%d\n", sizeof(struct S));//8
return 0;
}
所以在vs中对于剩余空间不足以容纳第二个位段时,采用丢弃的原则
对于下面自定义类型的文段大小计算:
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
以位为单位存放,_a、_b、_c
三位段占用17bit,剩余的15bit不足以容纳_d
,所以舍弃15bit,再开辟4byte(32bit)用于存放_d
所以:
printf("%d\n", sizeof(struct A));//8
3.3 枚举
枚举变量在定义时候虽然列出了所有可能出现的变量,但创建的枚举变量值是唯一的,是一个整形大小,永远只有4byte
3.4 联合
联合的大小至少是最大成员的大小
当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍
#include <stdio.h>
struct S1
{
double d;
char c;
int i;
};
union Un
{
struct S1 s1;//16
int arr[5];//20
};
int main()
{
printf("%d\n", sizeof(union Un));//24
return 0;
}
union Un联合的最大对齐数 = max {d、c、i、arr[0]}
最大成员空间是数组创建的20byte,但最大对齐数是8,所以要提升至24byte