结构体、枚举、联合
自定义类型,区别与C语言的自带类型(int、char、double等),由我们自己定义。
在C语言中共有3种自定义类型,分别是:结构体、枚举类型和联合体
结构体
结构体是一个集合类型,在结构体内,我们可以定义多个变量,这些变量称为成员变量。
结构体的声明:
1 2 3 4 5
| struct tag //struct + 标签 { ...... };
|
例如想要声明一个学生的类型的结构体:
1 2 3 4 5 6 7 8 9
| struct Student //学生类型的结构体 { char name[20]; int age; double height; char id[10]; };
|
特殊的声明:当我们想要创建的对象只需要使用一次,可以不写结构体名称,即:匿名结构体
1 2 3 4 5
| struct { int x; int y; }a;
|
1 2 3 4 5 6
| struct { int x; int y; int z; }arr[20],*p;
|
结构体的定义与初始化
有了类型,我们就可以定义变量,就像知道int类型就可已定义int类型的变量一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| struct Point { int x; int y; int z; }p1; struct Point p2;
struct Point p3 = {1,2,3};
struct Point p4 = {.z=3,.x=1,.y=2};
struct Student { char name[20]; int age; };
struct Student s1; s1 = (struct Student){"张三", 18};
struct Node { int data; struct Point p; struct Node* next; }n1 = {6,{1,2,3},NULL};
struct Node n2 = {6, {4,5,6}, NULL};
|
typedef重定义结构体
由于每次定义结构体变量,我们总是需要写那么一长串来定义变量,比较麻烦,这时我们就可以使用typedef重定义结构体:
1 2 3 4 5 6 7 8
| typedef struct Student { char name[20]; int age; }Stu;
Stu s1; Stu s2;
|
结构体的自引用
顾名思义,既然我们可以使用声明一个结构体后可以使用结构体类型来创建变量,那么是否可以在结构体内部使自己的类型来创建一个自己的变量呢?答案是可以的,但是并不能直接创建
实例1:
1 2 3 4 5 6 7
| struct son { char name[20]; struct son next_son; }; struct son s1;
|
这时我们创建一个变量s1,他的内部会有一个名字,并有他的儿子,而创建了他的儿子之后里面又生成了儿子的儿子的儿子…….最终无穷无尽递归下去,所以不能这么写。即便是可以,如果计算**sizeof(struct son)**会算出什么来呢?我们不得而知。
因此正确的方式是:
1 2 3 4 5 6
| struct son { char name[20]; struct son* next_son; } struct son s1;
|
在创建一个变量s1后,s1会有自己的名字,而s1内部的指针不用时可以置为空,在使用时就可以指向下一个儿子
1 2 3 4 5 6 7 8
| struct son { char name[20]; struct son* next_son; }s1,s2; s1.next_son = &s2;
s2.next_son = NULL;
|
而之后数据结构中的链表,就需要使用这种方式:
1 2 3 4 5
| struct Node { int data; struct Node* next; };
|
注意:
一个错误的写法:
1 2 3 4 5
| typedef struct { int data; Node* next; } Node;
|
在结构体定义内部,成员 Node* next
试图使用 Node
作为类型名,但此时 typedef
尚未完成对匿名结构体类型的重命名。编译器在解析结构体成员时,Node
还未被定义,因此会报错:unknown type name 'Node'
。
因此正确的方式是:
1 2 3 4 5
| typedef struct Node { int data; struct Node* next; } Node;
|
结构体成员变量的访问
使用结构体创建变量后,要访问变量内的数据要用到两个操作符:.
和->
普通变量使用.
来访问成员变量,指针类型使用->
来访问成员变量。例如:(malloc函数可以看这篇C语言动态内存管理)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include <stdio.h> #include <stdlib.h> #include <string.h>
typedef struct Student { char name[20]; int age; int id; } Student;
int main() { Student s1 = {"张三", 18, 123456}; Student s2; strcpy(s2.name, "李四"); s2.age = 18; s2.id = 654321; printf("%s-%d-%d\n", s1.name, s1.age, s1.id); printf("%s-%d-%d\n", s2.name, s2.age, s2.id); Student* s3 = (Student*)malloc(sizeof(Student)); strcpy(s3->name, "小明"); s3->age = 18; s3->id = 1345678; printf("%s-%d-%d\n", s3->name, s3->age, s3->id); free(s3); s3 = NULL;
return 0; }
|
结构体的大小
我们熟知char类型变量占1个字节,int类型变量占4个字节,long long类型变量占8个字节,那么我们自己定义的结构体类型占多大的空间呢?
这里运行
1 2 3 4 5 6 7
| struct S1 { int i; char c1; char c2; }; printf("%d\n", sizeof(struct S1));
|
1 2 3 4 5 6 7
| struct S2 { char c1; int i; char c2; }; printf("%d\n", sizeof(struct S2));
|
明明成员变量的类型都一样,运行完后我们得到了两个不同的结果:

这就涉及到结构体内存对齐:
基本对齐规则:
首成员对齐
结构体的第一个成员从偏移量0的地址开始存放
成员偏移对齐
其他成员变量与一个对齐数的整数倍的地址处对齐(其中对齐数为编译器默认对齐数与该成员大小的较小值)即:
对齐数=min(编译器默认对齐数,该成员大小)(Linux下gcc的默认对齐数为4,Windows下msvs默认对齐数为8,这些均可自己设置)
每个成员都有自己的对齐数,结构体的总大小为最大对齐数的整数倍
如果结构体嵌套了结构体,嵌套的结构体对齐到内部最大成员对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
就比如:(预编译指令#pragma pack()可以设置默认对齐数)这里以4为例
1 2 3 4 5 6 7 8 9
| #pragma pack(4) struct S1 { int i; char c1; char c2; }; printf("%d\n", sizeof(struct S1));
|

第二种:
1 2 3 4 5 6 7 8
| struct S2 { char c1; int i; char c2; }; printf("%d\n", sizeof(struct S2));
|

同理下方:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| struct S3 { double d; char c; int i; }; printf("%d\n", sizeof(struct S3));
struct S4 { char c1; struct S3 s3; double d; }; printf("%d\n", sizeof(struct S4));
|
所以下面所有运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #pragma pack(4); #include <stdio.h> int main() { struct S1 { int i; char c1; char c2; }; printf("%d\n", sizeof(struct S1)); struct S2 { char c1; int i; char c2; }; printf("%d\n", sizeof(struct S2)); struct S3 { double d; char c; int i; }; printf("%d\n", sizeof(struct S3)); struct S4 { char c1; struct S3 s3; double d; }; printf("%d\n", sizeof(struct S4)); }
|

如果把默认对齐数修改成1,就可以相当于以成员变量来计算大小,此时输出结果:

因此我们得出结论:在设计结构体的时候,应尽量让占用空间小的成员集中在一起,才能更节省空间。