- (一) 结构体
- 结构体的声明
- 结构体的自引用
- 结构体成员的初始化
- 结构体内存对齐
- 修改默认对齐数
- 结构体传参
- 位段
- (二) 枚举类型
- (三) 联合体/共用体
- 联合体的概念
- 判断大小端
- 联合体的大小
struct Book
{
char name[20];
char author[20];
int price;
}S1,S2;//全局变量
int main()
{
struct Book S3;//局部变量
return 0;
}
在声明结构体的时候,也可以选择不完全声明
struct
{
char name[20];
char author[20];
int price;
}S1,S2;//全局变量
int main()
{
//这里struct Book S3;就不可以使用了
return 0;
}
结构体的自引用
在数据结构中,存在线性表这一概念,线性表包括顺序表和链表,在内存中连续存储发方式叫线性表,串联数据的存储方式为链表,链表的在内存中的存储方式如下,一个结点包括数据域和指针域,分别存放数据和指向下一个结点。
具体代码如下,
typedef struct Node//前方定义为了后方引用
{
int data;
struct Node* next;//结构体指针,指向下一个结点
}Node;
结构体成员的初始化
struct Student
{
char name[20];
int sorce;
}S1 = { "Zhangsan",67 }, S2 = {"Lisi",78};
int main()
{
struct Student S3 = {"Wangwu",66};
return 0;
}
或者在结构体中嵌套结构体,
struct Sorce
{
float math;
float english;
}x1,x2;
struct Student
{
char name[20];
struct Sorce sorce;
}S1 = { "Zhangsan",{67.5,75.8} }, S2 = { "Lisi",{99.5,65.4} };
int main()
{
printf("%s", S1.name);
printf("%.2f %.2fn", S1.sorce.math, S1.sorce.english);
return 0;
}
结构体内存对齐
结构体在内存中对齐的规则如下,
1.结构体第一个成员放在起始位置偏移量为0的地方。
2.结构体从第二个成员开始,总是放在偏移量为一个对齐数的整数倍处。(这里的对齐数是指编译器默认的对齐数和变量自身大小)(在Linux编译器下,没有默认的对齐数,在VS环境下,对齐数是8)
3.结构体的总大小必须是各个成员的对齐数中最大对齐数的整数倍。
#includestruct S1 { char a; int b; char c; }; int main() { printf("%dn", sizeof(struct S1)); return 0; }
如上例所示,输出为12,而不是简单的各变量内存相加。以VS2019为编译环境, 结构体中的对齐数如下。
在内存中的存放情况如图所示,第一个变量为char类型的,大小占1个字节,第二个变量是int类型的,对齐数是4,所以从第四位开始,向后占4个内存空间,,最后一个是变量也是char类型的,占1个字节,开始位置是1的倍数即可,结构体的总大小要为4的整数倍,由0到11正好是12个字节,所以最终结果是12。
当结构体中嵌套结构体时,所占内存又是多少呢?这里我们在追加一条关于结构体内存对齐的规则:4.如果嵌套了结构体,那么嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍(含嵌套结构体的对齐数),我们可以来看一个例子。
#includestruct S1 { char a; double b; }; struct S2 { char c; struct S1 s; int d; }; int main() { printf("%dn", sizeof(struct S2)); return 0; }
此时,嵌套结构体对应的对齐数如下,
在内存中的存放情况如图所示,第一个char类型占一个字节,进入被嵌套的结构体,第二个也是char类型,占一个字节,然后是double类型,对齐数是8,占16个字节,随后是int类型对齐数是4,占4个字节,因为结构体中(含嵌套的结构体)最大字节数是8,所以结构体总的占内存是8的倍数,结果是32。
为什么结构体的内存要对齐,有以下普遍认为的几个原因:
1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一-次访问。
修改默认对齐数代码指令#pragma pack(数字),内涵数字为默认对齐数,#pragma pack() 为默认对齐数销毁指令。
#include#pragma pack(1)//默认对齐数为1 struct S1 { char a; int b; char c; }; #pragma pack() int main() { printf("%dn", sizeof(struct S1)); return 0; }
因为默认对齐数是1,根据上面的分析,最终打印结果为6。
关于结构体内成员的偏移量可以参考文章利用offsetof计算起始偏移量
#includestruct S1 { char a; int b[10]; }; void print(struct S1 t) { for (int i = 0;i < 10 ;i++) { printf("%d ", t.b[i]); } } int main() { struct S1 s = { 'x',{1,2,3,4,5,6,7,8,9,0} }; print(s); return 0; }
上述例子为直接传参,创建的变量t把结构体中的数据全部拷贝。这时传递的结构体如果值过大,那么在内存中压栈系统开销比较大,效率比较低,除了这个之外,我们也可以通过结构体指针的方式传参,如下,
#includestruct S1 { char a; int b[10]; }; void print(struct S1 *t) { for (int i = 0;i < 10 ;i++) { printf("%d ", t->b[i]); } } int main() { struct S1 s = { 'x',{1,2,3,4,5,6,7,8,9,0} }; print(&s); return 0; }
这样通过传递地址来传参,效率比较高。
位段 C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为"位段"。
位段的成员必须是整型家族(int,unsigned int,signed int,char)且位段的成员后面必须有一个冒号和数字,数字代表二进制位。
#includestruct S1 { int a : 2;//a占2个比特位 int b : 4;//b占4个比特位 int c : 10;//c占10个比特位 };
位段的内存分配规则如下,
1.位段的空间上按照需求以4个字节(int)或者1个字节(char)的方式来开辟的(1个字节=8个比特位)
2.位段是不跨平台的,注重可移植的程序应避免使用位段。
#includestruct S1 { char a : 2; char b : 4; char c : 5; }s; int main() { s.a = 10; s.b = 5; s.c = 12; return 0; }
如图所示,以一个字节(8个比特位)为单位开辟空间,a,b,c在内存中所占字节数和赋值后的存入情况如下,
最终的结果如下: 在内存中显示为: (二) 枚举类型可以被一一列举的对象就可以采用枚举类型,从第一个对象开始。默认值为0,往后依次加1。
enum Day
{
Mon,//默认值为0
Tue,//默认值为1
Wed,//默认值为2
Thu,//默认值为3
Fri,//默认值为4
Sat,//默认值为5
Sun//默认值为6
};
此时Day里为枚举常量,如果不想常量为默认值时,可以给其进行初始化。
enum Day
{
Mon = 3,
Tue = 5
};
枚举常量在定义时进行初始化赋值,在 主函数中不能在做更改,但是我们可以设置枚举变量,给枚举变量赋枚举常量的值。
enum Day
{
Mon,
Tue,
};
int main()
{
enum Day Thu = Mon;
enum Day Thu = 5;//error不能把int类型的赋值给枚举类型
printf("%d", Thu);
return 0;
}
(三) 联合体/共用体
联合体的概念
几个不同的变量共同占用一段内存的结构的自定义类型叫联合体(也叫共用体),定义如下。
#include判断大小端union A { int x; char y; }n; int main() { printf("%pn", n); printf("%pn", n.x); printf("%pn", n.y); //三个地址相同,共用一块内存空间 printf("%d", sizeof(n)); //输出为4 return 0; }
联合体还可以用来判断大端存储(从内存最高位字节地址上存储)还是小段存储(从内存最低位字节地址上存储),
#includeunion A { int x; char y; }n; int main() { n.x = 1; if (n.y == 0) { printf("大端"); } else { printf("小端"); } return 0; }
在内存中的存放方式如下,
联合体的大小因为联合体共用一段内存空间,所以联合体的大小至少是最大成员的大小,但是当最大成员的大小不是最大对齐数的整数倍时,需要对齐到最大对齐数的整数倍。
#includeunion A { int x; char y[5]; }n; int main() { printf("%d", sizeof(n));//输出为8 return 0; }
在联合体内部,int x占4个字节,对齐数为4;char y[5]占5个字节,最大对齐数是1,所以最终联合体的大小要是4的倍数,最终为8。



