• 掌握结构体的概念和用法
  • 掌握结构体数组和结构体指针
  • 掌握结构体的结构体
  • 掌握结构体搭建链表的方法
  • 掌握结构体及链表在产品上的应用

一般的数据类型只能构造同一个类型的数据,但是在不同情况下,我们需要将不同的数据类型组合成一种新的数据结构,比如说像学生的信息包含学生姓名、学号、性别、年龄等信息。那么这些参数里可能有些是数组型、字符型、整型甚至是结构体类型的数据,基于这种需求,结构体就诞生了。结构体就是所谓“面向对象”的编程思想,将某个物体视为一个对象,把这个对象的特性封装在结构体中,用到哪个特性就将其从对象中提取出来。

定义语法

结构体有以下两种定义方式,在中大型产品中往往使用typedef版本,因为这样可以大大提高代码可读性。

标准语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
struct Date
{
unsigned short year;
unsigned short mon;
unsigned short day;
};

int main()
{
struct Date Today;
Today.year = 2023;
Today.mon = 5;
Today.day = 18;
printf("%d-%d-%d",Today.year,Today.mon,Today.day);
}

typedef版语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// struct.cpp
#include <stdio.h>
typedef struct
{
unsigned short year;
unsigned short mon;
unsigned short day;
}Date;

int main()
{
Date Today;
Today.year = 2023;
Today.mon = 5;
Today.day = 18;
printf("%d-%d-%d",Today.year,Today.mon,Today.day);
}

结构体数组

当我们要定义10个整型变量时,我们可以通过定义成数组的方式。结构体自然也能定义成数组的方式,比如要定义3个不同日期数据时,就会用到结构体数组。
有好几种定义方法,但有两种最为常用。

直接用struct声明结构体

直接用struct声明一个结构体,然后再定义结构体数组,格式如下:

1
struct 结构体名称 数组名[数组大小]

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//  structArray.cpp
#include <stdio.h>
struct Date
{
unsigned short year;
unsigned short mon;
unsigned short day;
};

int main()
{
struct Date date[3] = {{2023,5,17},{2023,5,18},{2023,5,19}};
for (unsigned short i = 0; i < 3; i++)
{
printf("%d-%d-%d\r\n",date[i].year,date[i].mon,date[i].day);
}
return 0;
}

用typedef struct声明结构体

用typedef struct声明一个结构体,并且为结构体重命名,通过重命名的方式定义结构体数组。定义格式如下:

1
结构体重命名 数组名[数组大小]

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
typedef struct
{
unsigned short year;
unsigned short mon;
unsigned short day;
}Date;

int main()
{
Date date[3] = {{2023,5,17},{2023,5,18},{2023,5,19}};
for (unsigned short i = 0; i < 3; i++)
{
printf("%d-%d-%d\r\n",date[i].year,date[i].mon,date[i].day);
}
return 0;
}

包含结构体的结构体

结构体的成员不仅可以是字符型、整型、数组型数据类型,也可以是结构体。
例如存放学生信息的结构体包括姓名、学号性别、出生日期等数据,而出生日期又包含年月日这3个成员,所以出生日期可以单独声明一个结构体,学生这个结构体包含出生日期这个结构体,这种就是包含结构体的结构体,如下图所示。

P3.1-学生信息结构体.webp

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// structOfStruct.cpp
#include <stdio.h>
typedef struct
{
unsigned short year;
unsigned char mon;
unsigned char day;
}Date;

typedef struct
{
char name[20];
unsigned char number;
char sex;
Date birth;
}student;

int main()
{
student stu1 = {"xiaowang", 1, 'm', {1996, 7, 5}};
printf("name:\t\t%s\r\nnumber:\t\t%d\r\nsex:\t\t%c\r\nbirthday:\t%d-%d-%d\r\n",
stu1.name, stu1.number, stu1.sex, stu1.birth.year,stu1.birth.mon,stu1.birth.day);
return 0;
}

链表

P4.1-链表原理.webp

在结构体内定义结构体指针,使这个指针指向链表的下一项,从而利用头指针读取所有结构体的内容。

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
// linkedList.cpp
#include<stdio.h>
typedef struct student
{
unsigned char name[20];
unsigned char sex;
unsigned short number;
student *pLast;
student *pNext;
}student;

int main()
{
unsigned char i;
student *pStu;
student stu[4] =
{
{"stu1", 'm', 1, 0, 0},
{"stu2", 'l', 2, 0, 0},
{"stu3", 'm', 3, 0, 0},
{"stu4", 'l', 4, 0, 0},
};
for (i = 0; i < 3; i++) stu[i].pNext = &stu[i+1];
for (i = 4; i > 1; i--) stu[i].pLast = &stu[i-1]; //双向链表
pStu = &stu[0];
for (i = 0; i < 4; i++)
{
printf("name:%s, sex:%c, number:%d\r\n",pStu->name,pStu->sex,pStu->number);
pStu = pStu->pNext;
}
return 0;
}

注意这里的结构体声明前后都有student。去掉上一个会直接报错,因为结构体内部也用到了student这个结构体;去掉下面那个可以运行但会提示 ‘typedef’ was ignored in this declaration,因为此时结构体没有使用别名,但MinGW的编译器是可以正常编译程序的。

如果要使用双向链表,则需要对每一个对象中的pLast赋值。

结构体和链表的实际应用

链表是基于结构体的一种线性的数据结构。
凡是有多种属性的对象,都可以用结构体来构造。比如说单片机的串口有时钟源、波特率、停止位、校验位等等属性,那么就可以用结构体构造单片机串口对象。
链表分为静态链表和动态链表。
静态链表适用于一些固定数量的数据结构。比如多级菜单,用屏幕显示多级菜单时都会用按键或者触摸进入子菜单或者返回主菜单,此时就可以把这些主菜单和子菜单做成一个链表,配合结构体指针就能轻松灵活地找到需要跳转的菜单。
动态链表适用于底层操作系统,在不知道串口到底会来多少数据的情况下,使用数组很有可能造成内存浪费,或者数组位数不足。动态链表在接收消息队列和任务创建方面有着得天独厚的优势。