【嵌入式学习2】学生信息管理系统项目
目录
要求:
学习要点:
1、学生结构体和链表的定义
①为什么结构体里面可以定义指针?
②为什么结构体中的指针可以是结构体类型?
③如何理解 *next 和 *head?
2、显示所有学生信息
3、添加学生信息
①为什么要将新指针的next指向head?
4、查询学生信息
5、修改学生信息
①为什么输入年龄的时候要用&node->age?
6、删除学生信息
①新增current和prev指针的作用?
②prev == NULL 代表什么意思?
③如果prev !=NULL
④循环更新两个节点
7、排序学生信息
①为什么要重建链表?
最终代码:
要求:
-
使用动态链表管理学生数据:将学生数据存储在动态链表中,而不是静态数组中。
-
支持多字段排序功能:实现基于姓名、年龄或性别的排序。
-
实现数据的持久化存储:通过文件I/O操作将学生数据保存到文件中,并在程序启动时加载数据。
学习要点:
1、学生结构体和链表的定义
typedef struct stu {
char name[30]; // 姓名
int age; // 年龄
char sex[5]; // 性别
struct stu *next; // 指向下一个学生
} STU;
// 全局链表头指针
STU *head = NULL;
①为什么结构体里面可以定义指针?
结构体是一种用户自定义的数据类型,可以包含多个不同类型的成员。在结构体中定义指针,是为了实现更复杂的数据结构,例如链表、树等。链表是一种动态数据结构,通过在结构体中定义一个指向同一结构体类型的指针,可以将多个结构体连接起来,形成一个链表。
②为什么结构体中的指针可以是结构体类型?
在C语言中,结构体可以包含指向自身的指针。这是因为结构体的定义是在编译时完成的,而指针的大小是固定的(通常为4字节或8字节,取决于平台),与它指向的数据类型的实际大小无关。因此,即使结构体的定义尚未完全完成,编译器也可以确定指针的大小。
③如何理解 *next 和 *head?
每个链表节点(STU
结构体)都有一个next
指针。next
指向链表中的下一个节点,通过这种方式,多个节点可以连接起来形成一个链表。如果一个节点的next
指针为NULL
,表示该节点是链表的最后一个节点。
head
是一个指针,用于标识链表的起始位置。如果head
为NULL
,表示链表为空。通过head
可以访问链表中的第一个节点,进而通过next
指针遍历整个链表。
head -> node1 -> node2 -> NULL
-
head
是一个指针,指向node1
。 -
node1->next
是一个指针,指向node2
。 -
node2->next
是一个指针,值为NULL
,表示链表结束。
2、显示所有学生信息
void show_all_stu() {
printf("学生信息如下:\n");
STU *current = head;
while (current != NULL) {
printf("%s %d %s\n", current->name, current->age, current->sex);
current = current->next;
}
}
current
是一个临时指针,用于遍历链表。- 初始时,
current
指向head
。 - 每次循环中,通过
current->next
移动到下一个节点,直到current
为NULL
(链表结束)。
3、添加学生信息
void add_stu() {
STU *new_stu = (STU *)malloc(sizeof(STU));
if (new_stu == NULL) {
printf("内存分配失败\n");
return;
}
printf("请输入姓名:");
scanf("%s", new_stu->name);
printf("请输入年龄:");
scanf("%d", &new_stu->age);
printf("请输入性别(男或女):");
scanf("%s", new_stu->sex);
new_stu->next = head;
head = new_stu;
}
- 使用
malloc
动态分配一个学生节点。 - 将新节点插入到链表头部(即新节点的
next
指向当前的head
,然后将head
更新为新节点)。
①为什么要将新指针的next指向head?
在链表操作中,将新节点插入到链表头部是一种常见的操作方式,这种方式可以快速地将新节点添加到链表中,而不需要遍历整个链表。
假设我们有一个链表,其结构如下:head -> node1 -> node2 -> NULL
现在我们创建了一个新的节点new_stu
,并希望将它插入到链表的头部。插入后的链表结构应该是:head -> new_stu -> node1 -> node2 -> NULL
为了实现这一点,我们需要进行以下两步操作:
①将new_stu的next指向head,将新节点的next
指针指向当前的链表头部(head
),new_stu->next
的值:new_stu->next
被设置为 head
的值,即 node1
的地址:new_stu -> next = head
②将new_stu赋值给head,head
的值被更新为 new_stu
的地址,head
现在指向 new_stu
,而 new_stu->next
仍然指向 node1
:head = new_stu
此时链表结构:head -> new_stu -> node1 -> node2 -> NULL
4、查询学生信息
STU *find_stu_node(char *name) {
STU *current = head;
while (current != NULL) {
if (strcmp(current->name, name) == 0) {
return current;
}
current = current->next;
}
return NULL;
}
void show_one_stu() {
printf("请输入需要找的学生姓名:");
char name[30];
scanf("%s", name);
STU *node = find_stu_node(name);
if (node != NULL) {
printf("%s 信息如下\n", name);
printf("%s %d %s\n", node->name, node->age, node->sex);
} else {
printf("没有 %s 相关信息\n", name);
}
}
find_stu_node
函数通过遍历链表查找指定姓名的学生节点。show_one_stu
函数调用find_stu_node
,如果找到学生,则打印其信息;否则提示未找到。
5、修改学生信息
void modify_one_stu() {
printf("请输入需要修改的学生姓名:");
char name[30];
scanf("%s", name);
STU *node = find_stu_node(name);
if (node != NULL) {
printf("学生原来的信息:\n");
printf("%s %d %s\n", node->name, node->age, node->sex);
printf("\n");
printf("请输入新的学生信息:\n");
printf("请输入姓名:");
scanf("%s", node->name);
printf("请输入年龄:");
scanf("%d", &node->age);
printf("请输入性别(男或女):");
scanf("%s", node->sex);
printf("信息更新成功,新信息如下:\n");
printf("%s %d %s\n", node->name, node->age, node->sex);
} else {
printf("没有 %s 相关信息\n", name);
}
}
①为什么输入年龄的时候要用&node->age?
- 对于
scanf
函数,当需要读取的数据类型是非数组类型(如int
、float
、double
等)时,必须提供变量的地址,因为scanf
需要知道将读取到的数据存储到哪里。 - 对于
scanf
函数,当需要读取的数据类型是数组类型(如char
数组,用于存储字符串)时,可以直接传递数组名,因为数组名本身就是一个指向数组首元素的指针。
6、删除学生信息
void del_one_stu() {
printf("请输入需要删除的学生姓名:");
char name[30];
scanf("%s", name);
STU *current = head;// 从链表头部开始
STU *prev = NULL;
while (current != NULL) {
if (strcmp(current->name, name) == 0) {
if (prev == NULL) {
// 删除头节点
head = current->next;
} else {
// 删除中间或尾节点
prev->next = current->next;
}
free(current);
printf("%s 删除成功\n", name);
return;
}
prev = current;
current = current->next;
}
printf("没有 %s 相关信息\n", name);
}
①新增current和prev指针的作用?
在链表中删除一个节点时,需要更新前一个节点的next
指针,使其指向被删除节点的下一个节点。为了实现这一点,我们需要跟踪当前节点的前一个节点,这就是prev
指针的作用
current
指针:用于遍历链表,指向当前正在检查的节点。prev
指针:用于跟踪current
指针的前一个节点。
②prev == NULL 代表什么意思?
说明当前节点是链表的第一个节点(即head
),需要删除head节点直接将当前节点(head也可以是current的next赋值给head)即可。
③如果prev !=NULL
需要将前一个节点的next指向当前节点的next(prev->next = current->next;)删除当前节点
④循环更新两个节点
- prev = current;
- current = current->next;
7、排序学生信息
void sort_stu() {
printf("请选择排序字段:\n");
printf("1. 按姓名排序\n");
printf("2. 按年龄排序\n");
printf("3. 按性别排序\n");
int choice;
scanf("%d", &choice);
STU *arr[100];
int count = 0; //count 用于记录数组中存储的节点数量
STU *current = head;
while (current != NULL) {
arr[count++] = current;//遍历链表,将每个节点的指针存储到数组 arr 中
current = current->next;
}
for (int i = 0; i < count - 1; i++) {
for (int j = 0; j < count - i - 1; j++) {
int cmp;
if (choice == 1) {
cmp = strcmp(arr[j]->name, arr[j + 1]->name);
//如果第一个字符串大于第二个字符串,返回值大于0;相等,返回0;小于,返回值小于0
} else if (choice == 2) {
cmp = arr[j]->age - arr[j + 1]->age;
//比较两个整数(年龄),如果第一个年龄大于第二个年龄,返回值大于0;否则小于0
} else if (choice == 3) {
cmp = strcmp(arr[j]->sex, arr[j + 1]->sex);
}
if (cmp > 0) {
STU *temp = arr[j];//临时变量,用于存储交换过程中的节点指针
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
// 重建链表
for (int i = 0; i < count - 1; i++) {
arr[i]->next = arr[i + 1];
}
if (count > 0) {
arr[count - 1]->next = NULL;//最后一个节点的 next 指针设置为 NULL
}
head = arr[0];//更新链表的头指针 head
}
①为什么要重建链表?
在链表排序的过程中,重建链表是一个关键步骤,其目的是将排序后的节点重新连接起来,形成一个有序的链表。这是因为链表的节点在内存中是分散存储的,排序操作只是改变了节点的顺序,而没有改变它们在内存中的位置。
最终代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义学生结构体
typedef struct stu {
char name[30]; // 姓名
int age; // 年龄
char sex[5]; // 性别
struct stu *next; // 指向下一个学生
} STU;
// 全局链表头指针
STU *head = NULL;
// 帮助菜单显示函数
void help_menu() {
printf("\n");
printf(" 欢迎使用本学生信息管理系统\n");
printf("* ================================ *\n");
printf("* 1. 添加 *\n");
printf("* 2. 显示 *\n");
printf("* 3. 查询 *\n");
printf("* 4. 修改 *\n");
printf("* 5. 删除 *\n");
printf("* 6. 排序 *\n");
printf("* 7. 保存到文件 *\n");
printf("* 8. 从文件加载 *\n");
printf("* 9. 退出 *\n");
printf("* ================================ *\n");
}
// 显示所有学生信息
void show_all_stu() {
printf("学生信息如下:\n");
STU *current = head;
while (current != NULL) {
printf("%s %d %s\n", current->name, current->age, current->sex);
current = current->next;
}
}
// 添加学生信息
void add_stu() {
STU *new_stu = (STU *)malloc(sizeof(STU));
if (new_stu == NULL) {
printf("内存分配失败\n");
return;
}
printf("请输入姓名:");
scanf("%s", new_stu->name);
printf("请输入年龄:");
scanf("%d", &new_stu->age);
printf("请输入性别(男或女):");
scanf("%s", new_stu->sex);
new_stu->next = head;
head = new_stu;
}
// 查询学生所在的节点
STU *find_stu_node(char *name) {
STU *current = head;
while (current != NULL) {
if (strcmp(current->name, name) == 0) {
return current;
}
current = current->next;
}
return NULL;
}
// 打印找到学生的信息
void show_one_stu() {
printf("请输入需要找的学生姓名:");
char name[30];
scanf("%s", name);
STU *node = find_stu_node(name);
if (node != NULL) {
printf("%s 信息如下\n", name);
printf("%s %d %s\n", node->name, node->age, node->sex);
} else {
printf("没有 %s 相关信息\n", name);
}
}
// 修改某个学生的信息
void modify_one_stu() {
printf("请输入需要修改的学生姓名:");
char name[30];
scanf("%s", name);
STU *node = find_stu_node(name);
if (node != NULL) {
printf("学生原来的信息:\n");
printf("%s %d %s\n", node->name, node->age, node->sex);
printf("\n");
printf("请输入新的学生信息:\n");
printf("请输入姓名:");
scanf("%s", node->name);
printf("请输入年龄:");
scanf("%d", &node->age);
printf("请输入性别(男或女):");
scanf("%s", node->sex);
printf("信息更新成功,新信息如下:\n");
printf("%s %d %s\n", node->name, node->age, node->sex);
} else {
printf("没有 %s 相关信息\n", name);
}
}
// 删除某个学生
void del_one_stu() {
printf("请输入需要删除的学生姓名:");
char name[30];
scanf("%s", name);
STU *current = head;
STU *prev = NULL;
while (current != NULL) {
if (strcmp(current->name, name) == 0) {
if (prev == NULL) {
// 删除头节点
head = current->next;
} else {
// 删除中间或尾节点
prev->next = current->next;
}
free(current);
printf("%s 删除成功\n", name);
return;
}
prev = current;
current = current->next;
}
printf("没有 %s 相关信息\n", name);
}
// 排序学生信息
void sort_stu() {
printf("请选择排序字段:\n");
printf("1. 按姓名排序\n");
printf("2. 按年龄排序\n");
printf("3. 按性别排序\n");
int choice;
scanf("%d", &choice);
STU *arr[100];
int count = 0;
STU *current = head;
while (current != NULL) {
arr[count++] = current;
current = current->next;
}
for (int i = 0; i < count - 1; i++) {
for (int j = 0; j < count - i - 1; j++) {
int cmp;
if (choice == 1) {
cmp = strcmp(arr[j]->name, arr[j + 1]->name);
} else if (choice == 2) {
cmp = arr[j]->age - arr[j + 1]->age;
} else if (choice == 3) {
cmp = strcmp(arr[j]->sex, arr[j + 1]->sex);
}
if (cmp > 0) {
STU *temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
// 重建链表
for (int i = 0; i < count - 1; i++) {
arr[i]->next = arr[i + 1];
}
if (count > 0) {
arr[count - 1]->next = NULL;
}
head = arr[0];
}
// 保存学生信息到文件
void save_to_file() {
FILE *fp = fopen("students.dat", "wb");
if (fp == NULL) {
printf("文件打开失败\n");
return;
}
STU *current = head;
while (current != NULL) {
fwrite(current, sizeof(STU), 1, fp);
current = current->next;
}
fclose(fp);
printf("数据已保存到文件 students.dat\n");
}
// 从文件加载学生信息
void load_from_file() {
FILE *fp = fopen("students.dat", "rb");
if (fp == NULL) {
printf("文件打开失败\n");
return;
}
STU *new_stu;
while (fread(&new_stu, sizeof(STU), 1, fp) == 1) {
new_stu->next = head;
head = new_stu;
}
fclose(fp);
printf("数据已从文件 students.dat 加载\n");
}
int main() {
while (1) {
help_menu();
printf("请输入指令数字:");
int cmd;
scanf("%d", &cmd);
switch (cmd) {
case 1:
add_stu();
break;
case 2:
show_all_stu();
break;
case 3:
show_one_stu();
break;
case 4:
modify_one_stu();
break;
case 5:
del_one_stu();
break;
case 6:
sort_stu();
break;
case 7:
save_to_file();
break;
case 8:
load_from_file();
break;
case 9:
printf("退出系统\n");
return 0;
default:
printf("指令数字错误,请重新输入\n");
}
}
return 0;
}