0. 前言
大家好,我是anduin。今天为大家带来的是结构体的详细讲解。在C语言中,结构体可谓是很重要的一块内容,特别是在学习数据结构时,结构体更发挥了极大的作用。而本篇博客,我们将对结构体的基础知识和结构体内存对齐等知识作出详细讲解。话不多说,我们这就开始。
1. 结构的基础知识
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
2. 结构的声明
struct tag//结构体标签(自定义) { member-list;//成员列表 }variable-list;//变量列表(全局变量)
例如描述一个学生:
struct Stu { char name[20];//名字 int age;//年龄 char sex[5];//性别 char id[20];//学号 };
这里和上方结构的声明不太一样,少了变量列表,这个和我们本身创建变量有关,在变量列表中创建变量,就为全局变量,在主函数中创建变量就是局部变量,变量列表是可以省略的。
3 结构体创建变量
struct Book { char book_name[20]; char author[20]; int price; char id[15]; }s3,s4; //sb3 sb4也是struct Book类型的结构体变量 //是全局变量 int main() { struct Book s1; //局部变量 struct Book s2; //局部变量 return 0; }
在主函数和结构体末尾创建的结构体变量作用域不同。
4. 特殊的声明
4.1 匿名结构体
在声明结构时,可以不完全的声明,例如:
struct { char name[20]; float price; char id[12]; }sw;
我们可以看到这个结构体并没有名字,所以称它为匿名结构体。但是它如何使用?
匿名结构体只能在变量列表处,也就是结构体结尾的分号前创建变量,且这个变量为全局变量,相当于一次性使用。不能在别处创建变量。
4.2 小细节
当两个成员列表相同的匿名结构体,它们的类型是否相同?
struct { int a; char b; float c; }*p;//结构体指针,指向结构体 struct { int a; char b; float c; }ss; //匿名结构体的成员如果一样,在编译器看来也是不同类型的结构体 int main() { p = &ss; return 0; }
两个匿名结构体成员列表相同,那么将匿名结构体创建的ss变量的地址赋给一个结构体指针,是否可行?当我们编译过后发现它们的类型并不一样,以下是编译结果:
分析:
如果以我们平常的想法,它们的结果应该相同。但是编译过后,发现两个匿名结构体变量类型并不相同,依此我们可以得出结论:匿名结构体的成员如果一样,在编译器看来也是不同类型的结构体。
总的来说,当一个结构体只想使用一次时,就可以使用匿名结构体。
5. 结构体的自引用
5.1 错误的自引用方式
例如在结构体中包含一个类型为该结构体本身的成员是否可以?例如一个链表的节点的结构体,如果想要让这个节点能找到下一个节点,于是在里面放置下一个节点的结构体,可行吗?
//代码1 struct Node { int data; struct Node next; };
分析:
这样是不行的,如果在结构体中放置下一个节点,那么sizeof(struct Node)的大小为多少?Node里面整形的大小为4个字节,但是剩下一个元素也就是另一个Node是不可知的,这样无限套娃就无法计算大小,所以这样是不可行的。
5.2 正确的自引用方式
要找到下一个节点,那么可以通过地址,也就是通过结构体指针来访问,可以在本次节点中存放下一个节点的地址,这样便可以知道在一个节点中另一个成员的大小:
struct Node { int data; struct Node* next;//下一个节点的地址 }; int main() { struct Node n;//创建结构体变量 return 0; }
但是如果按照这样写,在创建一个节点的结构体变量时,写结构体的类型时十分繁琐,能不能把struct省略呢?我们知道直接省略肯定是错误的,那应该使用什么"工具"?
6. typedef
6.1 如何重命名
typedef为类型重命名,通过它便可以对复杂的类型进行重命名,比如重命名当前结构体:
typedef struct Node//typdef对struct Node 进行类型重命名 { int data; struct Node* next; }Node;//重命名后名字为Node int main() { Node n;//创建结构体变量 return 0; }
只要在typedef后写上对应的类型,然后再结构体分号前写上命名名称,便可完成重命名!
经过重命名后,这样是不是创建变量更加简单了?但是思考一个问题,我们能否这么写:
typedef struct Node//typdef对struct Node 进行类型重命名 { int data; Node* next; }Node;//重命名后名字为Node
这样写是万万不可的,结构体中使用的是重命名之后的类型,这时Node还未重命名呢。所以不能在重命名之前使用重命名后的名字!!!
编译结果:
6.2 小细节
如果我在类型重命名时这么写呢?
typedef struct Node//typdef对struct Node 进行类型重命名 { int data; struct Node* next; }Node,hello, world; int main() { Node n; hello h; world j; return 0; }
我一下写了三个重命名的类型名,这样的话我所创建的3个变量的类型是什么?
发现三个类型重命名后都是Node类型,它们的类型是由什么决定的?
分析:
这是一种"变态"的做法,因为在实际使用中,这样做后面的变量类型就混乱了。变量的类型就是typedef重定的类型来决定的,它会根据重定义来解析到对应的结构类型。也就是说这三个变量的内存空间分布本质上还是这个结构体的内存分布,但是就上层使用上来说,编译器有可能会认为这三个变量不是同一种类型。
但是很巧的是我当前使用的vs编译器是都解析到Node类型的,但是如果给用户来看,那么肯定是难受的。我们通常类型重命名时就定义一个名字!!!
6.3 匿名结构体的重命名
人说:”匿名结构体很可怜,我们是否也能给它一个名字,让它在主函数中创建局部结构体变量?“
神说:“满足你!”
typedef struct { int data; struct Node* next; }name; int main() { name q1;//匿名结构体重命名后创建变量 return 0; }
匿名结构体很开心,它也有了名字。
一个小插曲,活跃一下气氛但是匿名结构体用`typedef`重命名是没问题的哈
7. 结构体变量的定义和初始化
7.1 变量的定义
有了结构体类型,就可以定义结构体变量:
struct Book { char book_name[20]; char author[20]; int price; char id[15]; }sb3,sb4;//全局变量 struct Book sb5; //全局变量 int main() { struct Book sb1; //局部变量 return 0; }
注:不要以为全局的结构体变量只能在结构体末尾定义,只要不在{}中定义的变量均为全局变量,所以像sb5也是全局变量。
7.2 变量的初始化
7.2.1 结构体有序初始化
一般来说,我们通常按照顺序初始化变量:
struct Book { char book_name[20]; char author[20]; int price; char id[15]; }sb3 = {"bc","scw",26,"cw10001"}, sb4; struct Book sb5 = {"nsjl","111",34,"ns10001"}; int main() { struct Book sb1 = {"clanguage","thq",89,"hq10001"}; return 0; }
但是这种初始化结构体成员必须全部初始化,否则初始化时数据会混乱。比如:
struct S { char c; int a; float f; }; int main() { struct S s = { 10,3.14f };//c成员未初始化 printf("%c %d %f\n", s.c, s.a, s.f); return 0; }
运行结果:
7.2.2 结构体无序初始化
但有时我们只想初始化部分成员或乱序初始化时,可以用这种写法:
#include <stdio.h> struct S { char c; int a; float f; }; int main() { struct S s = { 'w',10,3.14f }; printf("%c %d %f\n", s.c, s.a, s.f); struct S s2 = { .f = 3.14f, .c = 'w'}; printf("%c %d %f\n", s2.c, s2.a, s2.f); return 0; }
分析:
当我们使用这种方法初始化时,可以进行乱序初始化,也可以初始化部分值。例如当前我就初始化了f, c,未初始化的a会被默认初始化为0。
运行结果:
7.2.3 结构体嵌套初始化
结构体类型如果嵌套定义的话,在初始化时就需要加上{ },并且采用对应的初始化方式,对嵌套的结构体内容进行初始化。
有序:
struct Point { int x; int y; }; struct S { char c; int a; struct Point p; }; int main() { struct S s = { 'w',10,{4,6} }; printf("%c %d %c %d\n", s.c, s.a, s.p.x, s.p.y); return 0; }
运行结果:
无序:
struct Point { int x; int y; }; struct S { char c; int a; struct Point p; }; int main() { struct S s = { 'w',10,{4,6} }; printf("%c %d %c %d\n", s.c, s.a, s.p.x, s.p.y); return 0; }
这里无序初始化只初始化部分内容时,其他的元素也是初始化为0。
运行结果:






