前言
我自己写了第一版大作业,然后借给了一位搞开发的同学,完善了很多很多(赫赫,看起来好专业),决定放到博客做个纪念吧~以下是同学整理后的大作业
当我看到这个作业的第一版的时候,不得不说,有些地方很值得我学习。不管是链表的写法,还是文件读写的处理,等等,确实很是精妙。可能是我长期游历于高级(相对:))语言开发的缘故,我感觉,如果从零开始写的话,我在处理这样一些基本结构的写法的时候,大概率会笨拙许多。
于是乎(认真),本着取长补短的原则,我在原代码的基础上,做了业务逻辑的优化,功能的增加,以及对代码整体重新做了封装,以增强它的可维护性和可开发性。
奈何我c语言底子确实不咋的,寡是debug就花了两天,以及后续大部分时间都花在排序算法的研究上了(链表的排序真的难以实现qaq),所以在程序的处理上,有些地方也许不是最优解qwq,但我也尽力了捏。
总而言之言而总之,我想说的是,用c语言在黑框框里写交互系统,其实没啥实际效益,对我而言的话,此代码主要体现的是系统交互的开发思想。所有互联网应用,不管是web还是app,无非就三个部分,后端控制,数据库存储,和前端展示。如何处理好三者的关系,使用户在交互系统的时候更傻瓜更人性化,使开发者在更新和维护系统的时候,更好分工,效率更高,是我此次作业的核心要义,而这个问题也正是广大软件攻城狮们所热衷于讨论的。
好了,废话就这么多~
源码展示:
#include <stdio.h>
#include <string.h> //strcmp
#include <stdlib.h> //malloc
#include <stdbool.h> //bool类型
#include <assert.h> //异常检测
//创建单链表
typedef struct student //学生信息
{
char name[20];
char num[20];
int math;
int english;
int program;
int tot;
double average;
}STU;
typedef struct Node //学生节点
{
STU data;
struct Node *next;
}NODE;
NODE *list = NULL; //根节点
NODE *tail = NULL; //尾结点
//节点函数
NODE *createHead() //创建头节点
{
NODE *headNode = (NODE *)malloc(sizeof(NODE));
assert(headNode);
headNode->next = NULL;
return headNode;
}
NODE *createNode(STU data) //创建一般节点
{
NODE *newNode = (NODE *)malloc(sizeof(NODE));
assert(newNode); //抛出异常
newNode->data = data;
newNode->next = NULL;
return newNode;
}
NODE *getTail(NODE *tail) //寻找尾节点
{
while (tail->next)
{
tail = tail->next;
}
return tail;
}
//预处理
void makeMenu(); //输出菜单
void keyDown(); //用户输入处理
//Controller
void Logout();
void Add(STU temp);
void SearchAll();
void Sort();
void Search(STU temp);
void Update(STU temp);
void Delete(STU temp);
//Model
//增
void appendData(STU data);//增加数据函数
//删
bool deleteDataByNum(NODE *headNode, const char *num); //删除数据函数
//改
void updateData(NODE *node, int userkey);
//查
NODE *searchDataByNum(NODE *headNode, const char *num); //搜索数据函数
//排序函数
NODE *sortList(NODE *head, int subject, int mode);
NODE *mergeList(NODE *l1, NODE *l2, int subject, int mode);
bool compareNode(NODE *l1, NODE *l2, int subject, int mode);
//View
void printList(NODE *headNode); //打印所有数据函数
void printNode(NODE *node); //打印单个节点数据函数
//管理系统文件函数
void readFromFile(NODE *headNode, const char *fileName); //读文件
void saveToFile(NODE *headNode, const char *fileName); //写文件
int main()
{
list = createHead();
tail = list;
readFromFile(list, "student.txt");
while (1)
{
makeMenu();
keyDown();
saveToFile(list, "student.txt");
}
return 0;
}
//预处理
//读文件
void readFromFile(NODE *headNode, const char *fileName)
{
FILE *fp = fopen(fileName, "r");
//如果没有这个文件,则创建一份
if (!fp)
{
fp = fopen(fileName, "w+");
fclose(fp);
}
STU temp;
//加载已有数据函数
while (fscanf(fp, "%s %s %d %d %d %d %lf\n", temp.name, temp.num, &temp.math, &temp.english, &temp.program, &temp.tot, &temp.average) != EOF)
{
appendData(temp);
}
fclose(fp);
}
//写文件
void saveToFile(NODE *headNode, const char *fileName)
{
NODE *pmove = headNode->next;
FILE *fp = fopen(fileName, "w");
while (pmove != NULL)
{
fprintf(fp, "%s %s %d %d %d %d %.2lf\n",
pmove->data.name,
pmove->data.num,
pmove->data.math,
pmove->data.english,
pmove->data.program,
pmove->data.tot,
pmove->data.average);
pmove = pmove->next;
}
fclose(fp);
}
//菜单
void makeMenu()
{
printf("--------------[链式学生管理系统]--------------\n");
printf("||\t\t0.退出系统\t\t ||\n");
printf("||\t\t1.录入学生信息\t\t ||\n");
printf("||\t\t2.查找所有学生信息\t ||\n");
printf("||\t\t3.查找特定学生信息\t ||\n");
printf("||\t\t4.修改学生信息\t\t ||\n");
printf("||\t\t5.删除学生信息\t\t ||\n");
printf("||\t\t6.学生信息排序\t\t ||\n");
printf("||\t\t7.清空屏幕\t\t ||\n");
printf("----------------------------------------------\n");
printf("请输入(0~7):");
}
//用户输入处理
void keyDown()
{
int userkey = 0; //接收用户的输入
STU temp; //学生信息临时存储变量
scanf("%d", &userkey);
switch (userkey)
{
case 0: //登出系统
Logout();
break;
case 1: //增加学生信息
Add(temp);
break;
case 2: //查询所有学生信息
SearchAll();
break;
case 3: //查询特定学生信息
Search(temp);
break;
case 4: //更新学生信息
Update(temp);
break;
case 5: //删除学生信息
Delete(temp);
break;
case 6: //学生信息排序
Sort();
break;
case 7: //清屏
system("cls"); // clear
break;
default:
printf("输入错误,请重新输入!\n");
break;
}
}
//Controller
//退出模块
void Logout()
{
printf("\n\n--------------[已退出系统!]--------------\n\n");
exit(0);
}
//录入模块
void Add(STU temp)
{
printf("\n\n---------------[录入模块]---------------\n\n");
printf("||\t(输入0返回主页)\n");
printf("||\t请输入学生姓名:");
scanf("%s", temp.name);
if(!strcmp(temp.name, "0"))
{
printf("\n\n");
return;
}
printf("||\t请输入学生学号:");
scanf("%s", temp.num);
printf("||\t请输入学生高数成绩:");
scanf("%d", &temp.math);
printf("||\t请输入学生英语成绩:");
scanf("%d", &temp.english);
printf("||\t请输入学生c语言程序设计成绩:");
scanf("%d", &temp.program);
//手动处理 (可写函数)
temp.tot = temp.math + temp.english + temp.program;
temp.average = (double)temp.tot / 3;
appendData(temp);
printf("\n\n------------[添加数据成功!]------------\n\n");
}
//浏览模块
void SearchAll()
{
printf("\n\n---------------[浏览模块]---------------\n\n");
printList(list);
printf("请输入0返回主页面,或输入1对学生信息进行排序设置:");
int choice;
scanf("%d", &choice);
if(choice == 1)
Sort(list);
}
//排序模块
void Sort()
{
int subject, mode;
int flag = 0;
while(flag < 2)
{
flag = 0;
printf("|\t [排序科目] \t\t [排序模式]\n");
printf("|\t1.按照高数成绩\t\t1.升序\n");
printf("|\t2.按照英语成绩\t\t2.降序\n");
printf("|\t3.按照c语言程序设计成绩\n");
printf("|\t4.按照总成绩(平均成绩)\n");
printf("|\t5.按学号\n");
printf("请输入排序科目(输入0以返回主菜单):");
scanf("%d", &subject);
if(!subject) return;
printf("请输入排序模式(输入0以返回主菜单):");
scanf("%d", &mode);
if(!mode) return;
switch(subject)
{
case 1:
case 2:
case 3:
case 4:
case 5:
flag++;
break;
default:
printf("\n\n排序学科输入有误,请输入1、2、3或4,或请输入0返回主菜单\n\n");
}
switch(mode)
{
case 1:
case 2:
flag++;
break;
default:
printf("\n\n排序模式输入有误,请输入1或2,或请输入0返回主菜单\n\n");
}
}
list->next = sortList(list->next, subject, mode);
printf("\n\n--------------[排序成功!]---------------\n\n");
printList(list);
}
//查找模块
void Search(STU temp)
{
printf("\n\n---------------[查找模块]---------------\n\n");
printf("请输入要查找的学生学号:");
scanf("%s", temp.num);
NODE *node = searchDataByNum(list, temp.num);
if (node == NULL)
{
printf("\n\n--------------[未找到结果!]--------------\n\n");
}
else
{
printNode(node);
}
}
//修改模块
void Update(STU temp)
{
printf("\n\n---------------[修改模块]---------------\n\n");
printList(list);
printf("请输入要修改的学生的学号:");
scanf("%s", temp.num);
NODE *node = searchDataByNum(list, temp.num);
if (node == NULL)
{
printf("\n\n--------------[未找到结果!]--------------\n\n");
}
else
{
int userkey;
while(1)
{
printf("\n\n请选择修改项:\n\n");
printf("||0.退出并保存\n||1.姓名\n||2.学号\n||3.数学成绩\n||4.英语成绩\n||5.c语言程序设计成绩\n");
printf("请输入(0~5):");
scanf("%d", &userkey);
if(!userkey) break;
printf("请输入修改值:");
updateData(node, userkey);
}
printf("\n\n--------------[保存成功!]---------------\n\n");
printNode(node);
}
}
//删除模块
void Delete(STU temp)
{
printf("\n\n---------------[删除模块]---------------\n\n");
printList(list);
printf("请输入要删除的学生学号:");
scanf("%s", temp.num);
if(deleteDataByNum(list, temp.num))
{
tail=getTail(list); //防止删除的是尾结点
printf("\n\n--------------[删除成功!]---------------\n\n");
printList(list);
}
else {
printf("\n\n-------[删除失败!没有指定数据!]--------\n\n");
}
}
//View
//打印所有数据函数
void printList(NODE *headNode)
{
NODE *pmove = headNode->next;
int temp=1;
printf("\n\n---------------[所有数据]---------------\n\n");
printf("|序号|\t姓名\t|\t学号\t|\t高数\t|\t英语\t|\t程序设计\t|\t总分\t|\t平均分|\n");
while (pmove != NULL)
{
printf("|%d|\t%s\t|\t%s\t|\t%d\t|\t%d\t|\t%d\t|\t%d\t|\t%.2lf|\n",
temp++,
pmove->data.name,
pmove->data.num,
pmove->data.math,
pmove->data.english,
pmove->data.program,
pmove->data.tot,
pmove->data.average);
pmove = pmove->next;
}
printf("\n\n");
}
//打印单个节点
void printNode(NODE *node)
{
printf("\n\n---------------[个体数据]---------------\n\n");
printf("|姓名\t|\t学号\t|\t高数\t|\t英语\t|\t程序设计\t|\t总分\t|\t平均分\n");
printf("|%s\t|\t%s\t|\t%d\t|\t%d\t|\t%d\t|\t%d\t|\t%.2lf|\n",
node->data.name,
node->data.num,
node->data.math,
node->data.english,
node->data.program,
node->data.tot,
node->data.average);
printf("\n\n");
}
//Model
//增加数据
void appendData(STU data)
{
NODE *newNode = createNode(data);
tail->next = newNode;
tail = newNode;
tail->next = NULL;
}
//删除数据函数
bool deleteDataByNum(NODE *headNode, const char *num)
{
NODE *curNode = headNode, *preNode;
while (curNode != NULL && strcmp(curNode->data.num, num))
{
preNode = curNode;
curNode = curNode->next;
}
if (curNode == NULL) return false;
preNode->next = curNode->next;
free(curNode);
return true;
}
//修改数据
void updateData(NODE *node, int userkey)
{
switch(userkey)
{
case 1:
scanf("%s", node->data.name);
break;
case 2:
scanf("%s", node->data.num);
break;
case 3:
scanf("%d", &node->data.math);
break;
case 4:
scanf("%d", &node->data.english);
break;
case 5:
scanf("%d", &node->data.program);
break;
default:
printf("输入错误,请重新输入!\n");
return;
break;
}
//手动处理 (可写函数)
node->data.tot = node->data.math + node->data.english + node->data.program;
node->data.average = (double)node->data.tot / 3;
}
//搜索数据函数
NODE *searchDataByNum(NODE *headNode, const char *num)
{
NODE *pmove = headNode->next;
while (pmove != NULL && strcmp(pmove->data.num, num))
{
pmove = pmove->next;
}
return pmove;
}
//链表归并排序
NODE *sortList(NODE *head, int subject, int mode)
{
if(!head || head->next == NULL)
return head;
//找出链表中点
NODE *slow = head;
NODE *fast = head;
while(fast->next && fast->next->next)
{
slow = slow->next;
fast = fast->next->next;
}
NODE *midNode = slow->next;
slow->next = NULL;
NODE *l = sortList(head, subject, mode);
NODE *r = sortList(midNode, subject, mode);
return mergeList(l, r, subject, mode);
}
NODE *mergeList(NODE *l1, NODE *l2, int subject, int mode)
{
if(!l1)
return l2;
if(!l2)
return l1;
if(compareNode(l1, l2, subject, mode))
{
l1->next = mergeList(l1->next, l2, subject, mode);
return l1;
}
else{
l2->next = mergeList(l2->next, l1, subject, mode);
return l2;
}
return NULL;
}
bool compareNode(NODE *l1, NODE *l2, int subject, int mode)
{
switch(mode)
{
case 1:
switch(subject)
{
case 1:
return l1->data.math < l2->data.math;
break;
case 2:
return l1->data.english < l2->data.english;
break;
case 3:
return l1->data.program < l2->data.program;
break;
case 4:
return l1->data.tot < l2->data.tot;
break;
case 5:
return strcmp(l1->data.num, l2->data.num) < 0 ? 1 : 0;
break;
}
break;
case 2:
switch(subject)
{
case 1:
return l1->data.math > l2->data.math;
break;
case 2:
return l1->data.english > l2->data.english;
break;
case 3:
return l1->data.program > l2->data.program;
break;
case 4:
return l1->data.tot > l2->data.tot;
break;
case 5:
return strcmp(l1->data.num, l2->data.num) < 0 ? 0 : 1;
break;
}
break;
}
}
整体结构
流程:
也就是main函数的运行流程,起到index的作用,是整个系统的逻辑根本部分,只看主函数的话,能理解到系统运行的大致流程。
- 输出界面makeMenu:没啥好说的,相当于是index.html,用户交互的总视图。
- 文件处理:与桌面上的文件进行连接。
- 用户输入处理keyDown:既处理用户输入信息,同时起到分发(dispatch)的作用,将用户输入与后端的各个控制器(controller)连接起来。
变量说明:
- 结构体:将链表的Node与学生数据student分开成两个结构体,这是ff原代码写的(如果是我来写的话,可能就只有一个了qaq)
- 全局变量:整个代码都要用到的变量,头结点和尾结点,头结点一旦设置便不再改变,尾结点实时更新。听说写链表最好不要将头尾节点设成全局,否则不好设置多条链表?但好像我没这个需求,我这里就不求甚解了。
模块说明:
所有函数分为三个大类,分别是控制器(controller)、模型(model)和视图(View),这是基于经典MVC开发框架而写的。MVC框架虽然会增加程序的体量,从而增加运行负担,但其优秀的分类思想,使得项目具有极高的可开发性和可维护。在硬件设施强大的今天,这点负担已经完全可以忽略不计了。其中控制器大致对应后端程序处理,模型对应数据库控制,视图对应前端展示。
- controller:与keyDown()直接相连,在用户输入处理分发后,对该模块的功能进行总控制。
- model:与链表直接相连,处理controller的具体数据操作要求,同时更新文件数据。
- view:直白来说,就是将链表的数据处理后输出到小黑框中。
详细说明:
接下来就是具体讲讲每个模块的实现:
节点函数:
//节点函数
NODE *createHead() //创建头节点
{
NODE *headNode = (NODE *)malloc(sizeof(NODE));
assert(headNode);
headNode->next = NULL;
return headNode;
}
NODE *createNode(STU data) //创建一般节点
{
NODE *newNode = (NODE *)malloc(sizeof(NODE));
assert(newNode); //抛出异常
newNode->data = data;
newNode->next = NULL;
return newNode;
}
NODE *getTail(NODE *tail) //寻找尾节点
{
while (tail->next)
{
tail = tail->next;
}
return tail;
}
这些函数主要是用来构造链表的,与系统业务没有什么关系,其作用就像注释里面说的那样。
如果要创建链表,那么可以这么调用:
NODE *list = createHead();
NODE *node = createNode(data);
list->next = node;
没啥好说的吧。
值得一提的是,我们如何增加节点?显然有两个方法,一个是在链表尾增加节点,一个是在链表头插入节点。在我的代码中,我选择了在链表尾增加,也就是使用了append的形式,而非insert。
原因是,append是符合我们的业务逻辑的——我们期望在表格的后面增加数据,而非在表头插入一行。于是,我们需要实时获取到链表尾节点的位置,也就用到NODE *tail = getTail(list);
在后面的appendData()
函数中,我们有更优的处理方法,也就是不用每次都调用getTail()
函数来实现
预处理:
//预处理
//菜单
void makeMenu()
{
printf("--------------[链式学生管理系统]--------------\n");
printf("||\t\t0.退出系统\t\t ||\n");
printf("||\t\t1.录入学生信息\t\t ||\n");
printf("||\t\t2.查找所有学生信息\t ||\n");
printf("||\t\t3.查找特定学生信息\t ||\n");
printf("||\t\t4.修改学生信息\t\t ||\n");
printf("||\t\t5.删除学生信息\t\t ||\n");
printf("||\t\t6.学生信息排序\t\t ||\n");
printf("||\t\t7.清空屏幕\t\t ||\n");
printf("----------------------------------------------\n");
printf("请输入(0~7):");
}
//用户输入处理
void keyDown()
{
int userkey = 0; //接收用户的输入
STU temp; //学生信息临时存储变量
scanf("%d", &userkey);
switch (userkey)
{
case 0: //登出系统
Logout();
break;
case 1: //增加学生信息
Add(temp);
break;
case 2: //查询所有学生信息
SearchAll();
break;
case 3: //查询特定学生信息
Search(temp);
break;
case 4: //更新学生信息
Update(temp);
break;
case 5: //删除学生信息
Delete(temp);
break;
case 6: //学生信息排序
Sort();
break;
case 7: //清屏
system("cls"); // clear
break;
default:
printf("输入错误,请重新输入!\n");
break;
}
}
没啥讲的吧,挺清晰的。
运行展示:
--------------[链式学生管理系统]--------------
|| 0.退出系统 ||
|| 1.录入学生信息 ||
|| 2.查找所有学生信息 ||
|| 3.查找特定学生信息 ||
|| 4.修改学生信息 ||
|| 5.删除学生信息 ||
|| 6.学生信息排序 ||
|| 7.清空屏幕 ||
----------------------------------------------
请输入(0~7):
Controller:
接下来是核心部分
void Logout()
//退出模块
void Logout()
{
printf("\n\n--------------[已退出系统!]--------------\n\n");
exit(0);
}
exit(0),嗯,好用
void Add(STU temp)
//录入模块
void Add(STU temp)
{
printf("\n\n---------------[录入模块]---------------\n\n");
printf("||\t(输入0返回主页)\n");
printf("||\t请输入学生姓名:");
scanf("%s", temp.name);
if(!strcmp(temp.name, "0"))
{
printf("\n\n");
return;
}
printf("||\t请输入学生学号:");
scanf("%s", temp.num);
printf("||\t请输入学生高数成绩:");
scanf("%d", &temp.math);
printf("||\t请输入学生英语成绩:");
scanf("%d", &temp.english);
printf("||\t请输入学生c语言程序设计成绩:");
scanf("%d", &temp.program);
//手动处理 (可写函数)
temp.tot = temp.math + temp.english + temp.program;
temp.average = (double)temp.tot / 3;
appendData(temp);
printf("\n\n------------[添加数据成功!]------------\n\n");
}
目的:添加学生信息数据
业务逻辑:用户选择进入到该模块,输入0则返回主页面,正常输入则将数据录入到链表中
实现步骤:
- 将数据从输入流中读入到临时变量中存储
- 通过
appendData()
将数据存储到链表中
void SearchAll()
//浏览模块
void SearchAll()
{
printf("\n\n---------------[浏览模块]---------------\n\n");
printList(list);
printf("请输入0返回主页面,或输入1对学生信息进行排序设置:");
int choice;
scanf("%d", &choice);
if(choice == 1)
Sort(list);
}
目的:查找所有学生数据,并提供排序选择
业务逻辑:用户选择进入到该模块,看到所有学生信息,并提示输入0返回主页面,输入1进入排序程序。(实际上输入任何非1的键都会返回主页面)
实现步骤:
- 通过
printList()
打印链表 - 从输入流中读到用户的选择输入,进入
Sort()
void Sort()
//排序模块
void Sort()
{
int subject, mode;
int flag = 0;
while(flag < 2)
{
flag = 0;
printf("|\t [排序科目] \t\t [排序模式]\n");
printf("|\t1.按照高数成绩\t\t1.升序\n");
printf("|\t2.按照英语成绩\t\t2.降序\n");
printf("|\t3.按照c语言程序设计成绩\n");
printf("|\t4.按照总成绩(平均成绩)\n");
printf("|\t5.按学号\n");
printf("请输入排序科目(输入0以返回主菜单):");
scanf("%d", &subject);
if(!subject) return;
printf("请输入排序模式(输入0以返回主菜单):");
scanf("%d", &mode);
if(!mode) return;
switch(subject)
{
case 1:
case 2:
case 3:
case 4:
case 5:
flag++;
break;
default:
printf("\n\n排序学科输入有误,请输入1、2、3或4,或请输入0返回主菜单\n\n");
}
switch(mode)
{
case 1:
case 2:
flag++;
break;
default:
printf("\n\n排序模式输入有误,请输入1或2,或请输入0返回主菜单\n\n");
}
}
list->next = sortList(list->next, subject, mode);
printf("\n\n--------------[排序成功!]---------------\n\n");
printList(list);
}
目的:对链表进行模式化排序
业务逻辑:用户进入到该模块,让用户选择排序字段和排序模式(升序or降序)(有输入纠错),然后将排序结果打印
实现步骤:
- 从输入流中获取用户选择,如果两个选择都没有输入错误,则进入排序(flag>=2跳出循环)
- 通过
sortList()
对链表进行归并排序 - 通过
printList()
打印链表
void Search(STU temp)
//查找模块
void Search(STU temp)
{
printf("\n\n---------------[查找模块]---------------\n\n");
printf("请输入要查找的学生学号:");
scanf("%s", temp.num);
NODE *node = searchDataByNum(list, temp.num);
if (node == NULL)
{
printf("\n\n--------------[未找到结果!]--------------\n\n");
}
else
{
printNode(node);
}
}
其实查找跟排序一样,也有很多高效率,低时间空间复杂度的查找方式,但是此处我就写了顺序查找。
目的:指定学号查询特定学生数据
业务逻辑:用户进入到该模块,输入要查找的学生学号(有输入纠错),然后打印该学号对应的学生信息
实现步骤:
- 从输入流中获取学号
- 通过
searchDataByNum()
获取学生节点 - 通过
printNode()
打印单个学生节点
void Update(STU temp)
//修改模块
void Update(STU temp)
{
printf("\n\n---------------[修改模块]---------------\n\n");
printList(list);
printf("请输入要修改的学生的学号:");
scanf("%s", temp.num);
NODE *node = searchDataByNum(list, temp.num);
if (node == NULL)
{
printf("\n\n--------------[未找到结果!]--------------\n\n");
}
else
{
int userkey;
while(1)
{
printf("\n\n请选择修改项:\n\n");
printf("||0.退出并保存\n||1.姓名\n||2.学号\n||3.数学成绩\n||4.英语成绩\n||5.c语言程序设计成绩\n");
printf("请输入(0~5):");
scanf("%d", &userkey);
if(!userkey) break;
printf("请输入修改值:");
updateData(node, userkey);
}
printf("\n\n--------------[保存成功!]---------------\n\n");
printNode(node);
}
}
目的:指定学生更新指定信息
业务逻辑:用户进入到该模块,看到所有学生信息,输入要更新的学生学号(有输入纠错)。然后根据提示输入要更改的字段,再输入更改信息,最终看到该学生修改后的信息;或者根据提示,输入0返回主页面
实现步骤:
-
通过
printList()
打印链表 -
从输入流中获取该学生学号
-
通过
searchDataByNum()
获取学生节点 -
从输入流中读取更改字段和更改信息
-
通过
updateData()
修改学生信息 -
通过
printfNode()
打印单个学生节点
void Delete(STU temp)
//删除模块
void Delete(STU temp)
{
printf("\n\n---------------[删除模块]---------------\n\n");
printList(list);
printf("请输入要删除的学生学号:");
scanf("%s", temp.num);
if(deleteDataByNum(list, temp.num))
{
tail=getTail(list); //防止删除的是尾结点
printf("\n\n--------------[删除成功!]---------------\n\n");
printList(list);
}
else {
printf("\n\n-------[删除失败!没有指定数据!]--------\n\n");
}
}
目的:删除指定学生
业务逻辑:用户进入到该模块,看到所有学生信息,输入要删除的学生的学号(有输入纠错),然后看到删除后的所有学生信息。
实现步骤:
-
通过
printList()
打印链表 -
从输入流中获取该学生学号
-
通过
deleteDataByNum()
删除学生节点 -
通过
printList()
打印链表
View:
//View
//打印所有数据函数
void printList(NODE *headNode)
{
NODE *pmove = headNode->next;
int temp=1;
printf("\n\n---------------[所有数据]---------------\n\n");
printf("|序号|\t姓名\t|\t学号\t|\t高数\t|\t英语\t|\t程序设计\t|\t总分\t|\t平均分|\n");
while (pmove != NULL)
{
printf("|%d|\t%s\t|\t%s\t|\t%d\t|\t%d\t|\t%d\t|\t%d\t|\t%.2lf|\n",
temp++,
pmove->data.name,
pmove->data.num,
pmove->data.math,
pmove->data.english,
pmove->data.program,
pmove->data.tot,
pmove->data.average);
pmove = pmove->next;
}
printf("\n\n");
}
//打印单个节点
void printNode(NODE *node)
{
printf("\n\n---------------[个体数据]---------------\n\n");
printf("|姓名\t|\t学号\t|\t高数\t|\t英语\t|\t程序设计\t|\t总分\t|\t平均分\n");
printf("|%s\t|\t%s\t|\t%d\t|\t%d\t|\t%d\t|\t%d\t|\t%.2lf|\n",
node->data.name,
node->data.num,
node->data.math,
node->data.english,
node->data.program,
node->data.tot,
node->data.average);
printf("\n\n");
}
第一个是遍历链表操作,第二个单纯是打印结构体操作
Model:
void appendData(STU data)
//增加数据
void appendData(STU data)
{
NODE *newNode = createNode(data);
tail->next = newNode;
tail = newNode;
tail->next = NULL;
}
将新的节点接到链表后面
前文提到的append模式,即要求我们随时记录尾节点的位置,而我们获取尾结点不必要每次都使用getTail()
来获取,在appendData()
中,每添加一行数据就更新尾结点。
bool deleteDataByNum(NODE headNode, const char num)
//删除数据函数
bool deleteDataByNum(NODE *headNode, const char *num)
{
NODE *curNode = headNode, *preNode;
while (curNode != NULL && strcmp(curNode->data.num, num))
{
preNode = curNode;
curNode = curNode->next;
}
if (curNode == NULL) return false;
preNode->next = curNode->next;
free(curNode);
return true;
}
一前一后两个节点往后遍历链表,当后节点遇到目标时(以学号相等为目标),前节点跨过后节点,接入后节点的下一个节点,再free掉后节点即完成删除操作,最后返回true,代表删除成功。如果当后节点找到了NULL(也就是尾结点的next),说明没有找到目标,则返回false,代表没有找到该学生,删除失败。
void updateData(NODE *node, int userkey)
//修改数据
void updateData(NODE *node, int userkey)
{
switch(userkey)
{
case 1:
scanf("%s", node->data.name);
break;
case 2:
scanf("%s", node->data.num);
break;
case 3:
scanf("%d", &node->data.math);
break;
case 4:
scanf("%d", &node->data.english);
break;
case 5:
scanf("%d", &node->data.program);
break;
default:
printf("输入错误,请重新输入!\n");
return;
break;
}
//手动处理 (可写函数)
node->data.tot = node->data.math + node->data.english + node->data.program;
node->data.average = (double)node->data.tot / 3;
}
根据用户传入的参数,指定字段修改节点,就直接改值就好了。
NODE searchDataByNum(NODE headNode, const char *num)
//搜索数据函数
NODE *searchDataByNum(NODE *headNode, const char *num)
{
NODE *pmove = headNode->next;
while (pmove != NULL && strcmp(pmove->data.num, num))
{
pmove = pmove->next;
}
return pmove;
}
与删除数据的函数类似,也是找节点,唯一的不同是,该函数的目的是找到目标节点并返回节点,如果没照到目标节点则会返回循环结束时pmove指向的节点,也就是NULL。
链表归并排序
链表实现归并排序,跟数组最大的不同就是链表不能指定索引,只能通过顺序访问。于是,如何快速的定位节点,用一些精妙的办法避免遍历链表,就是链表归并的关键。
核心代码:
//链表归并排序
NODE *sortList(NODE *head, int subject, int mode)
{
if(!head || head->next == NULL)
return head;
//找出链表中点
NODE *slow = head;
NODE *fast = head;
while(fast->next && fast->next->next)
{
slow = slow->next;
fast = fast->next->next;
}
NODE *midNode = slow->next;
slow->next = NULL;
NODE *l = sortList(head, subject, mode);
NODE *r = sortList(midNode, subject, mode);
return mergeList(l, r, subject, mode);
}
NODE *mergeList(NODE *l1, NODE *l2, int subject, int mode)
{
if(!l1)
return l2;
if(!l2)
return l1;
if(compareNode(l1, l2, subject, mode))
{
l1->next = mergeList(l1->next, l2, subject, mode);
return l1;
}
else{
l2->next = mergeList(l2->next, l1, subject, mode);
return l2;
}
return NULL;
}
大致步骤如下:
- 找出链表中点
- 以中点分链
- 递归,以剩余两个节点为递归边界
- 回溯,归并链表
注:传入的两条链都是已排序好的链。从归纳法的角度来讲,传入的链是回溯回来的链,已经经过了排序。
找中点:
两个节点,一快一慢,当快节点跑到尾时,慢节点自然就在中点了
递归:
只递归左节点,也就是只递归头节点和慢节点的下一位,当只有两个节点时,递归结束,开始回溯
回溯+归并:
返回其中一条链的头节点,将该链的剩余部分与另一条链递归。值得一提的是,当某条链为空时(遍历到头了),则返回另一条链,直接将剩余部分接到上一层递归中链表的尾部,因为两条链都是已排序好的链,所以排序无误。
条件函数:
bool compareNode(NODE *l1, NODE *l2, int subject, int mode)
{
switch(mode)
{
case 1:
switch(subject)
{
case 1:
return l1->data.math < l2->data.math;
break;
case 2:
return l1->data.english < l2->data.english;
break;
case 3:
return l1->data.program < l2->data.program;
break;
case 4:
return l1->data.tot < l2->data.tot;
break;
case 5:
return strcmp(l1->data.num, l2->data.num) < 0 ? 1 : 0;
break;
}
break;
case 2:
switch(subject)
{
case 1:
return l1->data.math > l2->data.math;
break;
case 2:
return l1->data.english > l2->data.english;
break;
case 3:
return l1->data.program > l2->data.program;
break;
case 4:
return l1->data.tot > l2->data.tot;
break;
case 5:
return strcmp(l1->data.num, l2->data.num) < 0 ? 0 : 1;
break;
}
break;
}
}
给用户多种排序的模式选择,将选择结果封装在函数中,排序函数仅需调用该函数,传入选择参数和排序的链,坐收返回结果即可。
kirino
感觉不如涵涵
匿名
感觉不如涵涵