目录
一.结构体
1.结构体的定义
2.结构体的声明
2.1 结构体的特殊声明
3.结构体的自引用
4.结构体变量的定义和初始化
5.结构体内存对齐(重点)
规则解释:
6.结构体传参
7.位段(位域)
位段的计算
位段的内存分配
位段的跨平台应用
二.枚举(enum)
枚举的优点
三.联合(共用体)
1.联合体的特点
2.联合体的大小计算
一.结构体
1.结构体的定义
结构是一些值的集合,这些值称为成员变量,结构体的每个成员可以是不同的类型。相同类型的集合是数组。
2.结构体的声明
struct tag { member-list; // 成员 }variable-list; // 变量例如,我们可以定义一个人的姓名,年龄,电话号等等
struct PeoInfo { char name[20]; int age; char sex[10]; char tele[12]; char addr[20]; }2.1 结构体的特殊声明
有时候我们可以看到一些结构体的声明如下:
struct { char a; char b; char c; char d; }这种结构体的声明称为匿名结构体类型,这种结构体在用的时候,只能使用一次,因为没有标签所以第二次不知道该如何调用它。
3.结构体的自引用
结构体的自引用就是结构体自己引用自己,但是在引用过程中不能出现无限死循环的调用自己,如以下代码:
struct Node { int data; struct Node next; //可变化的 };正确调用应该如下:
struct Node { int data; struct Node* next; //固定的值 };4.结构体变量的定义和初始化
结构体定义变量如下:
struct Point { int x; int y; }p1; //p1为结构体变量 ,其中前面一整体都是结构体类型 //犹如 int a; 其中int为类型,a为变量结构体初始化变量如下:
struct Point p1 = { 1, 2 };5.结构体内存对齐(重点)
其实结构体内存对齐是用来清楚知道计算结构体所占内存空间大小的规则(单位是字节),它有如下四个规则:
1.第一个成员在与结构体变量偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值
VS默认对齐数为8
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大的对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体的对齐数)的整数倍。
规则解释:
例子:有如下代码,算出S1的大小
struct S1 { char c1; int i; char c2; };(1)说在我们结构体开辟空间有如下图,其中一个单元为一个字节,说是结构体中的第一个成员在偏移量为0的地址处。在S1中,第一个成员是 char c1; 其中c1是char类型,所占空间大小为1个字节。
(2)其他成员就是除了第一个成员,第二个成员为int类型的,已知int类型的对齐数为4,其中在我们VS编译器的对齐数为8,我们取两者中的较小值作为我们对齐数,这里我们已知4的整数倍4还没有放数据,因此我们的 i 要从4开始放,如下图
我们的第三个成员为char类型,它的对齐数为1,VS编译器的对齐数为8,取1作为我们的对齐数,也就是从1的整数倍开始存放char c2.,如下图
(3)说是我们结构体的总大小为最大对齐数的整数倍,已知在这个结构体中4是我们目前的最大对齐数,4的整数倍是4,8,12等等,我们现在占了9个字节,因此还要补三个字节来作为我们结构体的大小。因此 sizeof(S1) = 12
struct S2 { char c1; struct S1 s1; int d; };(4) 说是结构体中还有一个结构体,如上代码,我们知道c1还是占偏移量为0的位置,我们知道S1结构体中最大的对齐数是4,那么我们在去找结构体的偏移位置时就去找4的倍数,如下图
S2最后一个成员为int 它的对齐数为4,找4的倍数,由于16还未被占,因此 i 从16开始存放4个字节,放到19的位置,此时总结构体的大小为我们最大对齐数4的整数倍,因为我们此时所占字节为0-19 共有20个字节,它为4的倍数,因此 sizeof(S2) = 20。
6.结构体传参
struct S { int data[1000]; int num; }; struct S s = { {0}, 1000 }; //结构体初始化 //结构体传参 void Print1(struct S s) { printf("%d", s.num); } //结构体地址传参 void Print2(struct S* ps) { printf("%d", ps->num); } int main() { Print1(s); Print2(&s); return 0; }我们知道传参的方式可以有传值和传址两种,在我们进行传值的时候,像我们结构体空间开辟时需要压栈,当我们把这么大的一块空间放在栈区,不仅会导致空间的浪费,同时也会导致时间的浪费,从而导致我们的代码整体效率下降,因此在结构体传参时,我们通常用过传地址的方式传参。
7.位段(位域)
位段是 C语言允许在一个结构体中以位(bit)为单位来指定其成员所占内存,长度。
如:
struct S { int _a : 2; int _b : 5; int _c : 10; int _d : 30; };如上形式叫做位段,位段的成员只能是整型家族
位段的计算
如上位段的大小是如何计算的? 其中我们知道1byte = 8bit 其中我们先开辟一个byte的内存,a占2个bit,还剩6个bit可以放的下b就不需要再开辟空间,下一个c需要10个bit,由于一个字节放不下,这里需要两个字节存放,下面的d需要开辟4个字节来存储,现在总共用了7个字节,说位段的总大小为我们最大位数的倍数,其中int的是4,所以这个位段的大小为8
位段的内存分配
1.位段的成员可以是int , unsigned int , signed int 或 char (属于整型家族)的类型。
2.位段的空间是按照需要以4个字节(int) 或 1个字节(char)的方式开辟。
3.位段涉及很多不确定因素,位段不具有跨平台属性,如要跨平台需要修改维护。
位段的跨平台应用
1.int位段被当成有符号还是无符号是不确定的。
2.位段中最大的位数目不能确定。(16位机器最大16,32位机器最大32,若写27,16位机器就会出现问题)
3.位段中的成员在内存中从左向右分配,还是从右向左分配标准上未定义。
4.当一个结构包含两个位段,第二个位段的成员比较大。无法容纳第一个位段剩余的位时,时舍弃剩余的位还是利用,这是不确定的。
二.枚举(enum)
枚举就是一一列举,它是有限个个数的列举方法, 如以下列举:
enum Day { Mon, Tues, Wes, Thur, Fri, Sat, Sun };枚举的值,在没有赋值时是默认从0开始赋值,依次递增1,当然也可以我们自己赋值。
枚举的优点
1.增加了代码的可读性和可维护性。
2.和#define定义的标识符比较,枚举有类型检查,更加严谨。
3.防止了命名污染(封装)。
4.使用方便,一次可以定义多个常量。
三.联合(共用体)
联合体顾名思义是联合一起,它的成员公用一块空间,因此被叫做联合体。
1.联合体的特点
联合体的成员共用一个空间,因此联合体的大小至少是最大成员的大小。
2.联合体的大小计算
1.联合体的大小至少是最大成员的大小。
2.当联合体的大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。
有以下例子:
union Un { char arr[5]; int i; };如上,其中arr[5] 相当于5个char 占了5个字节, int 占了四个字节,此时总大小是5个字节,其中最大对齐数是4,因此这个联合体的大小是4的倍数为8。



