Share this post on:

前言

我自己写了第一版大作业,然后借给了一位搞开发的同学,完善了很多很多(赫赫,看起来好专业),决定放到博客做个纪念吧~以下是同学整理后的大作业

当我看到这个作业的第一版的时候,不得不说,有些地方很值得我学习。不管是链表的写法,还是文件读写的处理,等等,确实很是精妙。可能是我长期游历于高级(相对:))语言开发的缘故,我感觉,如果从零开始写的话,我在处理这样一些基本结构的写法的时候,大概率会笨拙许多。

于是乎(认真),本着取长补短的原则,我在原代码的基础上,做了业务逻辑的优化,功能的增加,以及对代码整体重新做了封装,以增强它的可维护性和可开发性。

奈何我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;
    }
}

给用户多种排序的模式选择,将选择结果封装在函数中,排序函数仅需调用该函数,传入选择参数和排序的链,坐收返回结果即可。

Share this post on:

2 Comments

Leave a Comment

您的电子邮箱地址不会被公开。 必填项已用 * 标注