qfedu-c-level/day14/homework/h6.c

524 lines
18 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 优化昨天的学生信息管理系统将STU的数据持久化到stu.data文件中
// 【提示】运行程序时,从文件中加载已有数据并生成链表,在退出程序时,将链表的数据更新到文件中。
#include <stdio.h>
#include <stdlib.h> // malloc
#include <string.h> // strcpy
typedef enum front_color_e
{
BLACK = 30, // 黑色
RED, // 红色
GREEN, // 绿色
YELLOW, // 黄色
BLUE, // 蓝色
PURPLE, // 紫色
CYAN, // 青色
WHITE, // 白色
} Front_Color; // 前景色
typedef enum back_color_e
{
BLACK_B = 40, // 黑色
RED_B, // 红色
GREEN_B, // 绿色
YELLOW_B, // 黄色
BLUE_B, // 蓝色
PURPLE_B, // 紫色
CYAN_B, // 青色
WHITE_B, // 白色
} Back_Color; // 背景色
typedef enum attr_e
{
BOLD = 1, // 加粗
UNDERLINE = 4, // 下划线
BLINK, // 闪烁
REVERSE = 7, // 反显
HIDE, // 隐藏
} Attr; // 文本属性
// 定义学生信息结构体,链表的节点
typedef struct stu_s
{
int sid;
char name[32];
int age;
struct stu_s *next;
} STU;
// 节点交换
void swap(STU *a, STU *b)
{
// 方法一: 直接通过临时指针交换数据
// STU *tp = (STU *)malloc(sizeof(STU));
// tp->sid = a->sid;
// strcpy(tp->name, a->name);
// tp->age = a->age;
// a->sid = b->sid;
// strcpy(a->name, b->name);
// a->age = b->age;
// b->sid = tp->sid;
// strcpy(b->name, tp->name);
// b->age = tp->age;
// free(tp); // 释放临时指针
// 方法二: 通过结构体指针交换数据
// 先换数据
STU *tp = malloc(sizeof(STU));
*tp = *a;
*a = *b;
*b = *tp;
// 再换指针的下一个节点指向
tp->next = a->next;
a->next = b->next;
b->next = tp->next;
free(tp); // 释放临时指针
}
// 添加学生
STU *insert_stu(STU *head, STU *item)
{
// 如果头指针为空,说明链表为空,直接将新建的节点作为头指针
if (head == NULL)
{
head = item;
}
else
{
// 如果头指针不为空,说明链表不为空,需要找到链表的尾部,将新建的节点插入到尾部
STU *p = head; // 创建一个临时指针,用来保存头指针,防止头指针丢失
while (p->next != NULL) // 当 p 的下一个节点不为空时,说明 p 不是尾部
{
p = p->next; // p 一直向后移动,直到找到尾部
}
p->next = item; // 将 item 插入到尾部
}
return head; // 返回头指针
}
// 删除学生
STU *delete_stu(STU *head, char sid)
{
if (NULL == head)
{
printf("\033[%dm无数据,无法删除\033[0m\n", RED);
return NULL;
}
if (head->sid == sid)
{
// *tp 为临时指针,用来保存头指针
STU *tp = head; // 保存头指针
head = head->next; // 头指针指向下一个节点
free(tp); // 释放头指针
}
else
{
STU *tp = head; // 保存头指针
while (NULL != tp->next && sid != tp->next->sid) // 当 tp 的下一个节点不为空时,且下一个节点的学号不等于要删除的学号时,继续向后移动指针 tp
{
tp = tp->next; // tp 一直向后移动,直到找到尾部
}
if (NULL == tp->next) // 到达了链表尾部还是没有找到要删除的内容
{
printf("\033[%dm未找到 sid = %d 的学生,删除失败\033[0m\n", RED, sid);
}
else
{
STU *tpx = tp->next; // 保存要删除的节点
tp->next = tpx->next; // 将链表位置指向要删除的节点的下一个节点
free(tpx); // 释放要删除的节点
printf("\033[%dm删除 sid = %d 的学生成功\033[0m\n", BLUE, sid);
}
}
return head; // 返回头指针
}
// 打印学生
void shows(STU *head)
{
// 如果头指针为空,说明链表为空,直接返回
if (head == NULL)
{
printf("\033[%dm无数据,无法打印\033[0m\n", RED);
return;
}
else
{
// 如果头指针不为空,说明链表不为空,需要遍历链表,打印每一个节点的信息
STU *p = head; // 创建一个临时指针,用来保存头指针,防止头指针丢失
// 清屏
// printf("\033[2J");
printf("--------------------\n");
printf("\033[%dm学号\t姓名\t年龄\033[0m\n", BLUE_B);
while (p != NULL) // 当 p 不为空时,说明 p 不是尾部,则打印 p 的信息
{
printf("\033[%dm%d\t%s\t%d\033[0m\n", BLUE, p->sid, p->name, p->age); // 打印 p 的信息
p = p->next; // p 一直向后移动,直到找到尾部
}
}
}
// 查询学生
STU *find_stu(STU *head, char sid)
{
if (NULL == head)
{
printf("\033[%dm无数据,无法查询\033[0m\n", RED);
return NULL; // 返回空指针,说明没有找到要查询的节点
}
STU *tp = head; // 保存头指针
while (NULL != tp->next && sid != tp->sid) // 当 tp 的下一个节点不为空时,且下一个节点的学号不等于要查询的学号时,继续向后移动指针 tp
{
tp = tp->next; // tp 一直向后移动,直到找到尾部
}
if (sid == tp->sid)
return tp; // 返回当前节点的指针,即为要查询的节点
return NULL; // 返回空指针,说明没有找到要查询的节点
}
// 修改学生
STU *update_stu(STU *head, char sid)
{
if (NULL == head)
{
printf("\033[%dm无数据,无法修改\033[0m\n", RED);
return NULL;
}
STU *tp = find_stu(head, sid); // 查找要修改的节点
if (tp == NULL)
{
printf("\033[%dm未找到 sid = %d 的学生\033[0m\n", RED, sid);
}
else
{
// 临时变量,用来保存要修改的学生信息
char msid;
char mname[32];
int mage;
// 输入要修改的内容
printf("输入要修改的学生的 学号 姓名 年龄: ");
scanf("%hhd %s %d", &msid, mname, &mage);
tp->sid = msid;
strcpy(tp->name, mname);
tp->age = mage;
// 修改成功
printf("\033[%dm修改 sid = %d 的学生成功\033[0m\n", BLUE, sid);
}
return head; // 返回头指针
}
// 排序学生(选择排序,也可以使用归并排序)
STU *sort_stu(STU *head)
{
if (NULL == head)
{
printf("\033[%dm无数据,无法排序\033[0m\n", RED);
return NULL;
}
// 选择排序方式: 1) 按照sid 2)按照age
int choose; // 选择排序方式
printf("选择排序方式: 1) 按照sid 2)按照age: ");
scanf("%d", &choose);
if (1 == choose) // 按照sid排序
{
// 选择排序方式: 1) 升序 2)降序
int choose2; // 选择排序方式
printf("选择排序方式: 1) 升序 2)降序: ");
scanf("%d", &choose2);
if (1 == choose2) // 升序
{
// 按照sid升序排序
STU *p = head; // 创建一个临时指针,用来保存头指针,防止头指针丢失
while (p != NULL) // 相当于 for (int i = 0; i < len; i++)
{
STU *q = p->next; // 创建一个临时指针,用来保存头指针,防止头指针丢失
while (q != NULL) // 相当于 for (int j = i + 1; j < len; j++)
{
if (p->sid > q->sid) // 相当于 if (arr[i] > arr[j])
{
swap(p, q); // 相当于 swap(arr[i], arr[j]
}
q = q->next;
}
p = p->next;
}
}
else if (2 == choose2) // 降序
{
STU *p = head; // 创建一个临时指针,用来保存头指针,防止头指针丢失
while (p != NULL)
{
STU *q = p->next; // 创建一个临时指针,用来保存头指针,防止头指针丢失
while (q != NULL)
{
if (p->sid < q->sid)
{
swap(p, q);
}
q = q->next;
}
p = p->next;
}
}
else
{
// 选择错误
printf("\033[%dm选择错误\033[0m\n", RED);
}
}
else if (2 == choose) // 按照age排序
{
// 选择排序方式: 1) 升序 2)降序
int choose2; // 选择排序方式
printf("选择排序方式: 1) 升序 2)降序: ");
scanf("%d", &choose2);
if (1 == choose2) // 升序
{
STU *p = head; // 创建一个临时指针,用来保存头指针,防止头指针丢失
while (NULL != p)
{
STU *q = p->next; // 创建一个临时指针,用来保存头指针,防止头指针丢失
while (NULL != q)
{
if (p->age > q->age)
{
swap(p, q);
}
q = q->next;
}
p = p->next;
}
}
else if (2 == choose2) // 降序
{
STU *p = head; // 创建一个临时指针,用来保存头指针,防止头指针丢失
while (NULL != p)
{
STU *q = p->next; // 创建一个临时指针,用来保存头指针,防止头指针丢失
while (NULL != q)
{
if (p->age < q->age)
{
swap(p, q);
}
q = q->next;
}
p = p->next;
}
}
else
{
// 选择错误
printf("\033[%dm选择错误\033[0m\n", RED);
}
}
else
{
// 选择错误
printf("\033[%dm选择错误\033[0m\n", RED);
}
return head; // 返回头指针
}
// 反序学生(头变尾,尾变头)
STU *reverse_stu(STU *head)
{
if (NULL == head)
{
printf("\033[%dm无数据,无法反序\033[0m\n", RED);
return NULL;
}
STU *pi = head->next; // 当前节点的下一个节点地址
head->next = NULL; // 把头节点的下一个节点置空,让头节点成为尾节点
STU *tp = NULL; // 当前节点的下一个节点地址 pi 的下一个节点地址
while (pi != NULL) // 当 pi 不为空时,说明 pi 不是尾部,则反序 pi 的信息
{
tp = pi->next; // 首先 tp 保存 pi 的下一个节点地址
pi->next = head; // 然后把 pi 的下一个节点地址指向 head
head = pi; // 然后让 hand 指向 pi
pi = tp; // 最后找到新的 pi即 tp
}
printf("\033[%dm反序成功\033[0m\n", BLUE);
return head; // 返回头指针
}
// 保存数据到文件
void saveToFile(STU *head, const char *filename)
{
FILE *file = fopen(filename, "w");
if (file == NULL)
{
printf("\033[%dm无法打开文件, 保存数据失败\033[0m", RED);
return;
}
STU *current = head; // 定义临时指针,用来保存头指针,防止头指针丢失
while (current != NULL) // 当 p 不为空时,说明 p 不是尾部,则打印 p 的信息
{
// 格式化存储数据,每个字段之间用逗号分隔
fprintf(file, "%d,%s,%d\n", current->sid, current->name, current->age);
current = current->next; // p 一直向后移动,直到找到尾部
}
printf("保存数据成功\n");
fclose(file);
}
// 从文件中读取数据
STU *readFromFile(const char *filename)
{
FILE *file = fopen(filename, "r");
if (file == NULL)
{
printf("\033[%dm无法打开文件, 加载数据失败\033[0m", RED);
return NULL;
}
STU *head = NULL; // 定义头指针
STU *prev = NULL; // 定义尾指针
char line[100]; // 100 个字符的数组,用来保存每一行的数据
while (fgets(line, sizeof(line), file) != NULL) // 逐行读取文件内容
{
STU *node = (STU *)malloc(sizeof(STU)); // 申请一个节点的内存, 用来保存学生信息
/*
%d :表示读取一个整数,并将其存储在 &(node->sid)的地址中,即学生的学号。
, :表示读取一个逗号,用于分隔学号和姓名之间的字段。
%[^,] :表示读取一个字符串,直到遇到逗号为止,并将其存储在 node -> name 中,即学生的姓名。[^,] 表示匹配除逗号以外的任意字符。
, :表示读取一个逗号,用于分隔姓名和年龄之间的字段。
%d :表示读取一个整数,并将其存储在 &(node->age)的地址中,即学生的年龄。
*/
sscanf(line, "%d,%[^,],%d", &(node->sid), node->name, &(node->age)); // 将每一行的数据保存到节点中
// fscanf() 每次执行后会丢失数据,所以需要使用 sscanf() 函数,原因是 fscanf() 函数会在每次执行后将文件指针向后移动,而 sscanf() 函数不会移动文件指针。
// fscanf(file, "%d,%[^,],%d\n", &(node->sid), node->name, &(node->age)); // 将每一行的数据保存到节点中
node->next = NULL; // 新建的节点的下一个节点指向空,因为是尾部插入
if (head == NULL) // 当头指针为空时,说明链表为空,直接将新建的节点作为头指针
{
head = node;
}
else // 否则,说明链表不为空,需要找到链表的尾部,将新建的节点插入到尾部
{
prev->next = node; // 将新建的节点插入到尾部
}
prev = node; // 将尾指针指向新建的节点
}
fclose(file);
printf("加载数据成功\n");
return head;
}
int main(int argc, char const *argv[])
{
STU *head = NULL; // 定义全局的头指针
// 声明临时变量,用来保存学生信息
char tsid;
char tname[32];
int tage;
// // 创建几个学生信息,用于测试
// STU *stu1 = (STU *)malloc(sizeof(STU));
// stu1->sid = 1;
// strcpy(stu1->name, "张三");
// stu1->age = 18;
// STU *stu2 = (STU *)malloc(sizeof(STU));
// stu2->sid = 2;
// strcpy(stu2->name, "李四");
// stu2->age = 21;
// STU *stu3 = (STU *)malloc(sizeof(STU));
// stu3->sid = 3;
// strcpy(stu3->name, "王五");
// stu3->age = 15;
// STU *stu4 = (STU *)malloc(sizeof(STU));
// stu4->sid = 4;
// strcpy(stu4->name, "赵六");
// stu4->age = 13;
// head = insert_stu(head, stu1);
// head = insert_stu(head, stu2);
// head = insert_stu(head, stu3);
// head = insert_stu(head, stu4);
while (1)
{
// printf("\033[2J");
// printf("--------------------\n");
printf("\t\t \033[%d;%dm学生管理系统\033[0m\n\n", BOLD, CYAN_B);
printf("\033[%d;%dm1) 添加学生 \t2) 删除学生 \t3) 打印 \n4) 查询 \t5) 修改 \t6) 排序 \n7) 反序 \t8) 加载数据 \t0) 退出(保存数据)\n请输入选项# \033[0m", BOLD, GREEN);
int cmd = 0;
scanf("%d", &cmd);
switch (cmd)
{
case 0:
// store_data(head); // 存储数据
saveToFile(head, "stu.date");
return 1;
case 1:
printf("输入学生的 学号 姓名 年龄: ");
// 键盘收集数据并动态插入链表
scanf("%hhd %s %d", &tsid, tname, &tage); // 键盘收集数据
STU *item = (STU *)malloc(sizeof(STU)); // 申请一个节点的内存, 用来保存学生信息
item->sid = tsid; // 保存学生学号到新建的节点
strcpy(item->name, tname); // 保存学生姓名到新建的节点
item->age = tage; // 保存学生年龄到新建的节点
item->next = NULL; // 新建的节点的下一个节点指向空,因为是尾部插入
head = insert_stu(head, item); // 将新建的节点插入到链表中
break;
case 2:
printf("输入要删除的学生的学号: ");
scanf("%hhd", &tsid);
head = delete_stu(head, tsid);
break;
case 3:
shows(head);
break;
case 4:
printf("输入要查询的学生的学号: ");
scanf("%hhd", &tsid);
head = find_stu(head, tsid);
if (head == NULL)
{
printf("\033[%dm未找到 sid = %d 的学生\033[0m\n", RED, tsid);
}
else
{
printf("查询到的学生信息如下: \n");
printf("\033[%dm学号\t姓名\t年龄\033[0m\n", BLUE_B);
printf("\033[%dm%d\t%s\t%d\033[0m\n", BLUE, head->sid, head->name, head->age);
}
break;
case 5:
printf("输入要修改的学生的学号:");
scanf("%hhd", &tsid);
head = update_stu(head, tsid);
break;
case 6:
printf("排序学生信息:\n");
head = sort_stu(head);
break;
case 7:
printf("反序学生信息:\n");
head = reverse_stu(head);
break;
case 8:
printf("加载数据......\n");
head = readFromFile("stu.date");
break;
default:
printf("\033[%dm选择错误\033[0m\n", RED);
break;
}
}
return 0;
}