C项目实践--学生成绩管理系统

1.功能需求分析

学生成绩管理系统是对学生基本信息及成绩的管理。本程序主要实现了对学生的学号、姓名等基本信息以及各项学科成绩进行增加、删除、修改、查询和保存到磁盘文件等操作。主要功能描述如下:

(1)系统主控平台: 显示功能菜单界面,使用户根据提示输入相应的序号选择相应的操作。

(2)添加学生学生成绩信息:用户根据提示输入学生的学号、姓名、性别及四门课程的成绩。输入完一条记录之后,可根据提示继续输入下一条信息记录或继续其它操作。充许输入多条学生信息记录,输入完的学生信息记录暂时保存在单链表中,等待下一步操作。

(3)显示学生信息:将学生信息从单链表中调出显示,如果目前没有信息记录,则给出提示信息。

(4)查询学生信息:可分别根据学号,姓名,性别从单链表中对已录入的学生信息进行查询,如果没有查询到任何信息,则给出提示信息, 否则显示查询到的学生信息。

(5)修改学生信息:首先提示用户输入要进行修改操作的学号,如果单链表中有该学生的信息存在,则提示用户输入要修改的学生的基本信息,各科成绩等,并将修改结果重新存储在单链表中。如果没有找到要修改的学生信息,则给出提示信息。

(6)删除学生信息:首先提示用户要删除的学生的学号,如果在单链表中查到相应信息,则直接删除该信息,否则给出提示信息。

(7)计算学生成绩:该模块完成两个功能,即计算总分和平均分。选择计算菜单后,进入选择界面,提示用户输入要计算的内容,然后根据输入选项进行总分计算或平均分计算。

(8)保存数据到磁盘文本文件中:将单链表中的数据存储到磁盘文本文件中,系统会提示用户输入待保存的文本文件名(注:包括后缀,如应输入save.txt形式,方有效),确认后将单链表中的信息通过fprintf()写入文本文件中存储。

(9)退出系统。

2.总体设计

2.1功能模块设计

image

1.输入学生信息

在主菜单中调用input_person()函数,输入学生信息,首先建立单链表,将用户输入的学生信息存储到单链表中,输入完之后提示用户是否继续输入,如果用户输入"Y"或"y",则再次调用该函数,实现继续输入学生信息操作,否则返回主菜单界面。

2.显示学生信息

在主菜单中调用show_record()函数,显示学生信息,首先调用print_table_head()函数来显示学生成绩表格的表格标题栏部分,接着判断单链表不为空时,逐条显示单链表中的学生信息,显示时调用print_table_row(p) 函数按照指定的格式显示一位学生的信息。最后调用print_table_buttom()函数显示表格尾部。

3.查询学生信息

首先调用search_record()函数进入查询子菜单界面,可分别按学号、姓名和性别来查询学生信息。

(1)按学号查询,调用search_by_id()函数,按学号查询学生信息。提示用户输入要查询的学生学号,首先调用print_table_head()函数显示查询结果的表头,然后,根据用户输入的学生学号在单链表中利用strcmp()函数进行逐个比对查找。如果找到该学生,则调用print_table_row(p)函数按指定格式显示查询到的学生信息,如果没有找到,则给出提示信息,最后调用print_table_buttom函数显示查询结果的表尾。查询结束后提示用户是否继续进行查询操作,如果用户输入"Y"或"y",则再次调用该函数实现按学号查询学生信息的操作。如果用户输入"N"或"n",则调用search_record()函数重新显示查询子菜单界面。

(2)按姓名查询,调用search_by_name()进行按姓名查找学生信息。具体查询步骤与按学号查询类似。

(3)按性别查询,调用search_by_sex()进行按性别查询学生信息。具体查询步骤与按学号查询类似。

4.更新学生信息

调用update_record()以更新学生信息。首先按学号查询学生信息,如果没有找到该学号的学生信息,则给出提示信息,如果查找到了该学生信息,则调用create_stu_by_input()提示用户输入新的学生信息,完成更新的操作。操作结束后,提示用户是否继续更新的操作,如果用户输入"'y"或"Y" ,则再次调用该函数继续进行更新学生信息的操作。否则返回主菜单界面。

5.删除学生信息

调用delete_record()函数,以删除学生信息。首先按学号查询是否存在该学生信息,如果不存在,则给出提示信息,否则显示出该学生的学号和姓名,并提示用户是否真的要删除该学生的信息,如果用户输入"Y"或"y",则删除该学生信息,并给出删除成功的提示信息。如果用户输入"n"或"N",则不进行删除操作。操作结束后,提示用户是否继续进行删除操作,如果用户输入"Y“或"y",则再次调用该函数执行删除操作,否则返回主菜单界面。

6.计算学生成绩

调用calculate()函数直接进入计算子菜单界面,可以分别计算总分、计算平均分操作。

(1)计算总分,调用calc_total()函数计算总成绩。首先调用print_table_head_total()函数显示计算总分结果的表头。然后,调用print_table_row_total()函数逐个计算学生各项成绩的总分,并按一定格式显示。最后,调用print_table_bottom_total()函数显示计算总分结果的表尾。

(2)计算平均分, 调用calc_average()函数计算平均成绩。首先调用print_table_head_avg()函数显示计算平均成绩结果的表头。然后,调用print_table_bottom_avg()函数逐个计算学生各项成绩的平均分,并按一定格式显示。最后,调用print_table_bottom_avg()显示计算平均分结果的表尾。

7.保存到文件

首先提示用户输入要保存的文件名(注:在win系统下需要输入包括文件后缀的完整的文件名),文件将保存在程序目录下。系统将单链表中的全部学生信息保存到该文件中,并给出“保存成功”的提示信息。

8.退出系统

将单链表中的数据全部释放掉,防止内存泄露,退出系统。

2.2程序处理流程

系统的执行应从系统菜单的选择开始,充许用户输入1-8之间的数值来选择要进行的操作,输入其它字符,系统将给出出错的提示信息。

若用户输入1,则调用input_record()函数,进行输入学生信息操作。

若用户输入2,则调用show_record()函数,进行显示学生信息操作。

若用户输入3,则调用search_record()函数,进行查询学生信息操作。首先进入查询子菜单界面,查询子菜单充许用户输入1-4之间的数值来选择查询的方式,其中, 1是按学号查询,2是按姓名查询,3是按性别查询,4是返回主菜单。

若用户输入4,则调用update_record()函数,进行更新学生信息操作。

若用户输入5,则调用delete_record()函数,进行删除学生信息操作。

若用户输入6,则调用calculate()函数,计算学生成绩操作,此时进入计算子菜单,计算子菜单充许用户输入1-3之间的数值来选择查询的方式,其中,1是计算总成绩,2是计算平均分,3是返回主菜单。

若用户输入7,则调用save_to_file()函数,进行保存文件操作。

若用户输入8,则调用exit_system()函数,释放单链表中的数据,退出系统。

image

2.3详细设计与程序实现

打开vs ,新建win32 console Application 项目, 选择Empty Proj, 然后Finished  完成ScoreMng项目创建。为了便于维护,我们把函数和变量的声明放在头文件中,把函数功能的实现放到cpp文件中去,首先在Header Files 中新建ScoreMain.h 文件,然后在Source Files文件夹中新建ScoreMain.cpp。

下面先在ScoreMain.h文件进行函数和变量的声明,因为在处理过程中我们将要使用到输入输出函数,字符串处理函数strcmp()以及相关的标准库函数,所以先需要把包含这些函数的头文件include进来:

//Header Pre-conditions
#include <iostream> //标准输入输出函数库
#include <cstdlib>  //标准函数库
#include <cstring>  //需要使用到strcmp()字符串处理函数

学生成绩管理系统它会涉及到一个学生的很多信息,比如学号,姓名,性别等,那么为了便于操作,最好把这些信息构建成一个结构体类型,这样通过一个指针来访问它的这些成员函数就比较方便,但是有个问题就是像学生的学号,姓名这些信息它可能不是一个字符或一个数字的组成,而是多个。那这样就需要声明一个字符数组来存储它们,声明字符数组的时候需要指定它的大小,为了便于管理和修改,我们定义一些常量宏,通过宏来控制这些数组的大小。下面是这些常量宏的声明:

//Macro Definition
#define TITLE "学生成绩管理系统" //定义标题文字
#define MAX 10                   //定义接收学生信息的每个基本字段的最大长度
 
#define MENU_MAIN_COUNT 8        //主菜单的选项个数
#define MENU_SEARCH_COUNT 4      //查询子菜单的选项个数
#define MENU_CALC_COUNT 3        //计算子菜单的选项个数

用来控制数组大小的常量宏定义好了之后,现在就来定义这个包含学生信息的结构体类型student, 使用typedef 定义一个新类型stu, 并定义一个全局的头节点变量,具体定义如下:

//DataType Definition
typedef struct student{           
    char name[MAX];       //姓名
    char num[MAX];        //学号
    char sex[MAX];        //性别
    int chinese;          //语文成绩
    int mathematic;       //数学成绩
    int english;          //英语成绩
    int  computer;        //计算机成绩
    struct student *next; //指向下一个学生的指针
}stu;
 
stu* head = NULL;  //定义一个全局的头节点,并初始化为NULL 

要用到的数据结构声明完了之后,接下来就是要声明实现功能的函数,通过这些函数来实现学生成绩管理系统的功能,学生成绩管理系统主要有输入信息,显示信息,查询信息,更新信息、删除信息、计算成绩、保存文件和退出系统等8个功能。当然除此之外肯定还需要把这些函数进行细分,比如查询信息可以按照学号进行,也可以按照姓名进行,那么把它们都写在一个函数中显然容易造成混乱,所以最后各个子模块分别写成一个函数,除此之外把那些几个函数都会用到功能,比如输入学生信息,查找最后一个学生信息的节点之类的抽象出来,这样可以使的代码没有那么臃肿,也可以有效的避免代码的大量冗余,比如输入信息中需要用到输入学生信息的函数, 而在更新信息模块中也要用到这个函数,那么把这个函数抽象出来成为一个函数,这样在输入信息和更新信息的时候就只需要调用这个抽象出来的函数即可,而不需要在这两个函数中分别都写一篇。下面首先声明主要的处理函数,具体声明如下:

//Processing Function 
void input_record();  //输入学生成绩 
void show_record();   //显示学生成绩 
void search_record(); //查询学生成绩
void update_record(); //更新学生成绩
void delete_record(); //删除学生成绩
void calculate();     //计算学生成绩
void save_to_file();  //保存到文本文件
void exit_system();   //退出系统
因为查询学生信息可分别按照学号,姓名和性别进行查询,所以我们把它抽象成三个独立的子函数来声明:
//SubFunction of Query 
void search_by_id();   //通过学号检索学生信息
void search_by_name(); //通过姓名检索学生信息
void search_by_sex();  //通过性别检索学生信息
void search_exit();    //退出子菜单,返回上级菜单

同样计算学生成绩有计算总分和平均分,下面是计算子菜单所对应的函数声明:

//SubFunction of Computing
void calc_total();    //计算总成绩
void calc_average();  //计算平均成绩
void calc_exit();     //退出子菜单,返回上级菜单
为避免主函数过于冗余,把几个函数共用的公共部分抽象成独立的子函数,其中包含输入学生信息的函数,找最后一个学生记录的函数和清空所有记录的函数,它们声明如下:
//Auxiliary Function
void create_stu_by_input(stu* pNewStu); //接收学生的基本信息
stu* get_last_student(stu* p);          //找到最后一个学生的节点
void clear_record(stu *p);              //清空所有记录

同时为了让系统显示的更加规范,可操作性强,特编写了一些显示控制的函数,具体声明如下:

//Display Function
void print_menu_main();              //显示主菜单
void print_menu_title(char* title);  //显示菜单的标题
 
void print_table_head();             //显示学生信息记录的表格的头部
void print_table_row(stu* p);        //显示一条学生记录
void print_table_buttom();           //显示学生记录的表格的底部
 
void print_table_head_total();       //显示总分表格的头部
void print_table_row_total(stu* p);  //显示一个学生的总分
void print_table_buttom_total();     //显示总分表格的底部
 
void print_table_head_avg();         //显示平均分表格的头部
void print_table_row_avg(stu* p);    //显示一个学生的平均分
void print_table_buttom_avg();       //显示平均分表格的底部

除此之外,还需要声明一些全局变量,具体声明如下:

1.字符数组menu_main中存储的是主菜单要显示的字符。

//Global variable defintion
char menu_main[] = 
    "|
"
    "!  1 输入学生成绩
"
    "|  2 显示学生成绩
"
    "|  3 查询学生成绩
"
    "|  4 更新学生成绩
"
    "|  5 删除学生成绩
"
    "|  6 计算学生成绩
"
    "|  7 保存文件
"
    "|  8 退出系统
"
    "|
";

2.函数指针数组 menu_main_func[] 存储的是主菜单项中8个功能函数的地址,分别对应1-8菜单项。例如用户选择1时,调用数组的第0个元素,即调用input_record()函数,依次类推。

void (* menu_main_func[])() =
{
    input_record,
    show_record,
    search_record,
    update_record,
    delete_record,
    calculate,
    save_to_file,
    exit_system
};

3.字符数组menu_search中存储的是查询子菜单中要显示的字符

char menu_search[] =
    "|
"
    "|  1 按照学号查询
"
    "|  2 按照姓名查询
"
    "|  3 按照性别查询
"
    "|  4 返回上级菜单
"
    "|
";

4.函数指针数组 menu_search_func[] 存储的是查询子菜单项所对应的4个功能函数的地址,分别对应1-4菜单项。

void (* menu_search_func[])() =
{
    search_by_id,
    search_by_name,
    search_by_sex,
    search_exit
};
5.字符数组menu_main中存储的是计算子菜单要显示的字符
char menu_calc[] =
    "|
"
    "|  1 计算总成绩
"
    "|  2 计算平均分
"
    "|  3 返回上级菜单
"
    "|
";

6.函数指针数组menu_calc_func[]存储的是计算子菜单项所对应的3个功能函数的地址,分别对应1-3菜单项

void (*menu_calc_func[])() =
{
    calc_total,
    calc_average,
    calc_exit
};

7.为了便于格式输出,定义3个表格格式的字符常量数组,具体声明如下:

char table_head[] =
    "+----------+----------+----------+-------+-------+-------+---------+
"
    "|   学号   |   姓名   |   性别   |  语文 |  数学 |  英语 |  计算机 |
"
    "+----------+----------+----------+-------+-------+-------+---------+
";
 
char table_head_total[] = 
    "+----------+----------+----------+-------+-------+-------+---------+---------+
"
    "|   学号   |   姓名   |   性别   |  语文 |  数学 |  英语 |  计算机 |  总成绩 |
"
    "+----------+----------+----------+-------+-------+-------+---------+---------+
";
char table_bottom[] = 
    "+----------+----------+----------+-------+-------+-------+---------+
";

到目前为止,已将所有的函数及变量都声明完了,即便还有一些遗留的函数或变量需要声明的话,在后面发现确实需要就再来补充声明便是了。下面进入ScoreMain.cpp中,对功能模块依次进行实现:

0.入口函数

入口函数也就是main函数, 在main函数里头我们只放置调用显示主菜单界面的print_menu_main()子函数,其它的实现都分别在其相应的子函数中去实现,因此main函数的实现如下(main函数实现完成之后,接下来实现input_record函数):

//Entry Function 
void main()
{
    print_menu_main();
} 

1.输入学生信息函数

函数名称:input_record

函数功能:在主菜单界面选择操作1,调用此函数,用来输入学生的基本信息

处理过程:

(1)首先创建一个结构体指针变量stu , 并将其next指针域置空

(2)调用函数create_stu_by_input()为pNewStu赋值

(3)判断单链表是否有数据,如果没有,即head == NULL , 则置pNewStu为头节点;否则调用函数get_last_student()函数找单链表中的最后一个节点,将pNewStu连接到最后一个节点的后面,完成一个学生信息的输入。

(4)提示用户是否继续输入学生信息,如果用户输入"Y"或"y", 表示继续输入下一条学生信息, 则再次调用此函数进行操作,否则,调用显示主菜单函数print_menu_main(),返回主菜单界面。具体实现如下:

//输入学生信息函数
void input_record()
{
    //标识符,判断用户是否继续此函数的操作,初始化为N,表示不继续
    char continue_input = 'N';  
    stu* pLastStu = NULL;
    //创建一个stu的结构体指针pNewStu,并分配内存
    stu* pNewStu = (stu*)malloc(sizeof(stu));
    if(NULL == pNewStu) throw pNewStu;  //如果内存申请失败,则抛出异常提示
    pNewStu->next = NULL;               //其创建的pNewStu的next指针域置空
    
    create_stu_by_input(pNewStu);     //调用create_stu_by_input()接收输入的学生信息
    if(NULL == head)    //如果头节点为空,则把pNewStu赋值给head指针,使其成为头节点
    {
        head = pNewStu;
    }else
    {
        //否则调用get_last_student()函数寻找当前单链表中的最后一个节点
        pLastStu = get_last_student(head); 
        pLastStu->next = pNewStu;
    }
 
    printf("继续输入学生成绩?(Y 继续, N 返回菜单)");
    getchar();
    continue_input = getchar();
    if(continue_input == 'n' || continue_input =='N')
    {
        print_menu_main();
    }else{
        input_record();
    }
}

2.显示学生成绩函数

函数名称:show_record

函数功能:在主菜单选择操作2,进行此操作,用来显示学生的基本信息

处理过程:

(1)调用函数print_table_head(),按照既定格式显示表头

(2)在单链表中循环调用print_table_row(),按照既定格式显示学生的信息,直到单链表被循环完毕。

(3)调用函数print_table_bottom()函数,显示表尾

(4)调用显示主菜单函数print_menu_main(),返回主菜单界面。具体实现如下:

void show_record()
{
    stu* p = head;
    print_table_head(); //显示学生成绩表头
    while(NULL != p) //逐条显示学生记录
    {
        print_table_row(p);
        p = p->next;
    }
 
    print_table_buttom();
    printf("按任意键返回菜单..
");
    fflush(stdin);
    getchar();
    print_menu_main();
}

3.查询学生成绩函数

函数名称:search_record

函数功能:在主菜单界面选择操作3,调用此函数,用来对学生的基本信息进行查询,函数中显示一个子菜单,等待用户选择子菜单项,根据用户的选择调用相应的函数。

处理过程:

(1)调用自定义显示控制函数,显示查询成绩子菜单界面

(2)等待用户输入1-4中的任一数据,如果用户输入1-4之外的数据,则打印出出错提示信息。提示用户再次输入,直到输入正确为止。

(3)如果输入正确,根据用户输入,调用相应的函数指针数组中的函数,例如,输入1,则用menu_search_func[0]()这种方式调用函数search_by_id(),按学号查询学生信息。具体实现如下:

//查询学生成绩
void search_record()
{
    int selected = 0; //菜单选择标识符,初始化为0,表示未选择任何正确选项
    system("cls");
    print_menu_title("查询学生成绩");
    printf(menu_search);
    printf("=================================================
");
    while(!(selected >= 1&& selected <= MENU_SEARCH_COUNT))
    {
        printf(">请选择:");
        fflush(stdin);
        scanf("%d",&selected);
        if(selected >= 1 && selected <= MENU_SEARCH_COUNT)
        {
            break;
        }
        printf("
>输入错误!(请选择1 - %d)
",MENU_SEARCH_COUNT);
    }
    //调用相应的函数指针数组中的函数,执行操作
    menu_search_func[selected - 1]();
}

3.1按学号查询一个学生的信息函数

函数名称:search_by_id

函数功能:查询子菜单所对应的函数,根据学号查找一个学生的成绩

处理过程:i.先输入学号,根据输入的学号在单链表中查找学生信息,while循环语句的功能是对单链表的每个节点逐一查找;ii.如果找到,即strcmp(p->num ,id) == 0, 则调用函数print_table_row(),显示该学生的信息,并提示是否继续查找,如果用户输入"y"或"Y",则继续调用此函数,否则返回查询子菜单界面;iii.如果没有找到要查询的学生信息,给出提示信息,并调用print_table_bottom函数打印表格底部。具体实现如下:

//按学号查询学生成绩
void search_by_id()
{
    char id[MAX];  //接收学号的数组
    char continue_input = 'N'; //是否继续再次调用此函数的标识符
    stu* p = head; //声明一个stu结构体指针p 指向头节点
     //定义一个表示查找输入学号的学生信息是否存在,初始化为0,表示不存在
    int isfound = 0; 
    printf(">请输入学生学号:");
    fflush(stdin); //清空当前缓冲区中的字符,避免影响scanf()接收值
    scanf("%s",id);
    printf("
>查找[%s]....
",id);
    print_table_head();
    while(NULL != p)
    {
        //比对单链表中的学号与要查询的学号,如果存在则立即跳出循环
        if(strcmp(p->num,id) == 0) 
        {
            print_table_row(p);
            isfound = 1;
            break;
        }
        p = p->next;
    }
 
    if(!isfound)
        printf("
没有找到结果!
");
    print_table_buttom();
    printf("继续查找?(Y 继续, N  返回)");
    getchar();
    continue_input = getchar();
    if(continue_input == 'Y' || continue_input == 'y')
    {
        search_by_id();
    }else{
        search_record();
    }
}
3.2

按姓名查询一个学生的成绩

函数名称:search_by_name

函数功能:根据姓名查找一个学生的成绩。

处理过程:同按学号查找。具体实现如下:

void search_by_name()
{
    char name[MAX];
    char continue_input = 'N';
    stu* p = head;
    int isfound = 0;
    printf(">请输入学生姓名:");
    fflush(stdin);
    scanf("%s",name);
    printf("
>查找[%s]...
",name);
    print_table_head();
    while(NULL != p)
    {
        if(strcmp(p->name, name) == 0)
        {
            print_table_row(p);
            isfound = 1;
        }
        p = p->next;
    }
    if(!isfound)
        printf("
没有找到结果!
");
    print_table_buttom();
    printf("继续查找?(Y 继续, N 返回)");
    getchar();
    continue_input = getchar();
    if(continue_input == 'Y' || continue_input == 'y')
    {
        search_by_name();
    }else{
        search_record();
    }
}

3.3按性别查询一个学生的成绩

函数名称:search_by_sex

函数功能:根据性别查询一个学生的成绩

处理过程:同按学号查找、具体实现如下:

void search_by_sex()
{
    char sex[MAX];
    char continue_input = 'N';
    stu* p = head;
    int isfound = 0;
    printf(">请输入学生性别:");
    fflush(stdin);
    scanf("%s",sex);
    printf("
>查找[%s]...
",sex);
    print_table_head();
    while(NULL != p)
    {
        if(strcmp(p->sex,sex) == 0)
        {
            print_table_row(p);
            isfound = 1;
        }
        p = p->next;
    }
    if(!isfound)
        printf("
 没有找到结果!
");
    print_table_buttom();
    printf("继续查找?(Y 继续, N 返回)");
    getchar();
    continue_input = getchar();
    if(continue_input == 'Y' || continue_input == 'y')
    {
        search_by_sex();
    }else{
        search_record();
    }
}

3.4退出子菜单函数

函数名称:search_exit

函数功能:退出子菜单,返回到主菜单界面

void search_exit()
{
    print_menu_main();
}

4.更新学生信息

函数名称:update_record

函数功能:修改学生的基本信息。

处理过程:i首先输入学号,按学号查找要修改的学生的信息;ii.找到该学号的学生信息,则调用create_stu_by_input()函数,输入新信息,新信息将覆盖原来学生的信息。如果没有找到,则系统会给出信息提示。iii.提示用户是否继续更新,如果用户输入"N"或"n", 则不再继续更新操作,调用print_menu_main(),返回主菜单,否则再次调用本函数继续执行更新操作。具体实现如下:

//更新学生信息函数
void update_record()
{
    stu* p = head;
    char id[MAX];
    char continue_input = 'N';
    printf("请输入要更新的学生学号:");
    fflush(stdin);
    scanf("%s",id);
    while(NULL != p)
    {
        if(strcmp(p->num, id) == 0)
        {
            break;
        }
        p = p->next;
    }
    if(NULL == p)
    {
        printf("没有学号是[%s]的学生.",id);
    }else{
        //传入当前节点指针p,调用create_stu_by_input()覆盖原来的信息
        create_stu_by_input(p);
    }
 
    printf("继续更新吗?(Y 继续, N返回菜单)");
    getchar();
    continue_input = getchar();
    if(continue_input == 'n'||continue_input =='N')
    {
        print_menu_main();    
    }else{
        update_record();
    }
}

5.删除学生信息函数

函数名称:delete_record

函数功能:根据给定的学号,删除学生基本信息

处理过程:

(1)首先输入要删除的学生学号,按学号查找学生的信息;

(2)如果该学生不存在,系统给出提示信息,如果存在,则再次提示用户是否确认删除,如果用户输入"y"或"Y",则表示确认删除。删除过程如下:首先定义结构体指针pPrec,用来存储p节点的前驱节点,如果要删除的学生信息(即p)是单链表的头节点,即 pPre = head; pPre == p,则直接删除p , 即head = p->next。 如果p不是头节点,则找到p的前驱节点pPre后,然后将pPre的next指针指向p的next,即执行语句pPre->next = p->next;后, 再free(p) ; 然后提示用户是否继续删除操作,如果用户输入"n"或"N",则表示不再继续删除操作,则调用print_menu_main()返回主菜单,否则继续调用本函数。具体实现过程如下:

void delete_record()
{
    stu* p = head;
    stu* pPre = NULL;
    char id[MAX];
    char continue_input = 'N';
    printf("请输入要删除的学生学号:");
    fflush(stdin);
    scanf("%s",id);
    while(NULL != p)
    {
        if(strcmp(p->num , id) == 0)
        {
            break;
        }
        p = p->next;
    }
    if(NULL == p)
    {
        printf("没有学号[%s]的学生.",id);
    }else{
        printf("确定要删除学号为[%s],姓名为[%s]的学生信息吗?(Y 确定,N 取消)",
            p->num,p->name);
        getchar();
        continue_input = getchar();
        if(continue_input == 'y'|| continue_input == 'Y')
        {
            pPre = head;
            if(pPre == p) //表示要删除的节点就是头节点
            {
                head = p->next;
                free(p);
            }else{
                //如果不是头节点,则先查找要删除节点的前驱节点
                while(pPre != NULL) 
                {
                    if(pPre->next == p)//找到前驱节点
                    {
                        pPre->next = p->next;
                        free(p);
                        break;
                    }
                    pPre = pPre->next;
                }
            }
            printf(">删除成功!
");
        }
    }
    printf("继续删除吗?(Y 继续, N 返回菜单)");
    getchar();
    continue_input = getchar();
    if(continue_input == 'n' || continue_input == 'N')
    {
        print_menu_main();
    }else{
        delete_record();
    }
}

5计算学生成绩的函数

函数名称:calculate

函数功能:本函数用来对学生成绩进行计算,函数中显示一个子菜单,等待用户选择子菜单项,根据用户输入的子菜单项调用相应的函数。

处理过程:(1)调用显示控制函数,显示计算成绩子菜单界面,等待用户输入1-3中的数值,如果用户输入的1-3之外的数值,则提示错误信息,提示用户再次输入,直到输入正确为止

(2)如果输入正确,根据用户的输入调用相应的函数指针数组中的函数,例如输入1,则用menu_calc_func[0]()这种方式调用计算总成绩函数calc_total()。具体实现如下:

void calculate()
{
    int selected = 0;
    system("cls");
    print_menu_title("计算学生成绩");
    printf(menu_calc);
    printf("=================================================
");
    while(!(selected >= 1 && selected <= MENU_CALC_COUNT))
    {
        printf(">请选择:");
        fflush(stdin);
        scanf("%d",&selected);
        if(selected >= 1 && selected <= MENU_CALC_COUNT)
        {
            break;
        }
        printf("
>输入错误!(请选择(1-%d)
",MENU_CALC_COUNT);
    }
    menu_calc_func[selected-1]();
}

6.1计算总成绩函数

函数名称:calc_total

函数功能:计算每个学生的总成绩

处理过程:(1)首先调用print_table_head_total()函数,显示学生信息的表格头部。(2)调用函数print_table_row_total()函数打印学生总分。(3)调用函数print_table_buttom_total()函数打印总分表格底部;(4)调用函数calculate()返回上级菜单。具体实现如下:

void calc_total()
{
    stu* p = head;
    print_table_head_total();
    while(NULL != p)
    {
        print_table_row_total(p);
        p = p->next;
    }
    print_table_buttom_total();
    printf("按任意键返回菜单..
");
    fflush(stdin);
    getchar();
    calculate();
}

6.2计算平均成绩函数

函数名称:calc_average

函数功能:计算学生平均成绩

处理过程:(1)首先调用辅助函数print_table_head_total()函数显示学生信息的表格头部。(2)调用print_table_row_avg()函数计算并显示每个学生的平均总分。(3)调用函数print_table_bottom_avg(),打印平均分表格底部。(4)调用函数calculate()返回上级菜单。具体实现如下:

void calc_average() 
{
    stu* p = head;
    print_table_head_avg();
    while(NULL != p)
    {
        print_table_row_avg(p);
        p = p->next;
    }
    print_table_buttom_avg();
    printf("按任意键返回菜单..
");
    fflush(stdin);
    getchar();
    calculate();
}

6.3退出计算子菜单界面,返回主菜单界面

void calc_exit()
{
    print_menu_main();
}

7保存文件到磁盘文本文件

函数名称:save_to_file

函数功能:用来将单链表中所有成绩信息保存到磁盘文本文件中。

处理过程:(1)提示用户输入要保存的文本文件名称(注:要输入包括后缀的文件名,如:save.txt)。 (2)以只写("W")方式打开文本文件,使用fprintf()函数将链表中的信息写入到文本文件中。(3)给出保存成功提示,并返回主菜单界面。具体实现如下:

void save_to_file()
{
    FILE *fp;
    char file[100];
    stu* p = head;
    printf("请输入文件名:");
    fflush(stdin);
    scanf("%s",file);
    fp = fopen(file,"w");//以只写方式打开文本文件进行写入操作
    fprintf(fp,table_head); // 打印信息表头
    while(p != NULL)//将单链表中的信息逐条写入到文本文件中
    {
        /*fprintf(fp,"%s,%s,%s,%d,%d,%d,%d,
",
            p->num,p->name,p->sex,p->chinese,p->mathematic,p->english,p->computer);*/
        fprintf(fp,"|%10s|%10s|%10s|%7d|%7d|%7d|%9d|
",
        p->num,p->name,p->sex,p->chinese,p->mathematic,p->english,p->computer);
        p = p->next;
    }
    fprintf(fp,table_bottom); // 打印信息表尾
    fclose(fp);
    printf("保存成功!
按任意键返回菜单..
");
    fflush(stdin);
    getchar();
    print_menu_main();//返回主菜单界面
}

8.退出系统

函数名称:exit_system

函数功能:当用户在主菜单中选择8时, 退出系统。具体实现如下:

void exit_system()
{
    clear_record(head);//释放掉整个链表
    exit(0);
}

以上完成了对系统主要功能函数的实现,前面为了使得各个函数模块相对独立和抽象出共用部分从而避免代码冗余,由此编写了一些辅助函数,此外为了与用户更好地进行人机交互,还需要一些显示控制函数,它们的实现如下:

1.显示主菜单界面

函数名称:print_menu_main
函数功能:显示主菜单界面

处理过程:

调用显示控制函数,显示主菜单界面,等待用户输入1-8中的任一数据,如果用户输入1-8之外的数据,提示输入有误,并提示重新输入,直到输入正确为止。如果输入正确,则根据用户的输入调用相应的函数指针数组中的函数进行相应的操作。例如输入1,则用menu_main_func[0]()这种方式调用输入学生信息的函数input_record()函数进行相应的操作。具体实现如下:

void print_menu_main()
{
    int selected = 0;
    system("cls");
    print_menu_title(TITLE);
    printf(menu_main);
    printf("=================================================
");
    while(!(selected >= 1&& selected <= MENU_MAIN_COUNT))
    {
        printf("请选择:");
        fflush(stdin);
        scanf("%d",&selected);
        if(selected >= 1 && selected <= MENU_MAIN_COUNT)
        {
            break;
        } 
        printf("
>输入错误!(注:请选择1 - %d)
",MENU_MAIN_COUNT);
    }
 
    menu_main_func[selected-1]();
}

2.提示用户输入学生信息的函数

函数名称:create_stu_by_input

函数功能:使用户按照提示输入相应的学生信息,具体实现如下:

void create_stu_by_input(stu* pNewStu)
{
    printf(">请输入学生信息(注:最大长度是10个字符).
");
    printf("学号:"); scanf("%s",pNewStu->num);
    printf("姓名:"); scanf("%s",pNewStu->name);
    printf("性别:"); scanf("%s",pNewStu->sex);
    printf("
>请输入%s的成绩(注:成绩范围0-100).
",pNewStu->name);
    int flag = 0, // 赋值标志位
        value = -1; // 接收键盘值的变量 初始化为-1
    while(flag !=4)
    {
        if(flag == 0)
        {
            fflush(stdin);
            printf("语文:"); scanf("%d",&value);
            //如果value值是在0-100之间,则赋值给pNewStu->chinese
            //否则提示输入有误,提示继续输入,直到输入正确为止
            if(value>=0&&value<=100)
            {
                pNewStu->chinese = value;
                flag =1;
                value = -1;
            }
        }
        if(flag == 1)
        {
            fflush(stdin);
            printf("数学:"); scanf("%d",&value);
            if(value>=0&&value<=100)
            {
                pNewStu->mathematic = value;
                flag = 2;
                value = -1;
            }
        }
        if(flag == 2)
        {
            fflush(stdin);
            printf("英语:"); scanf("%d",&value);
            if(value>=0&&value<=100)
            {
                pNewStu->english = value;
                flag = 3;
                value = -1;
            }
        }
        if(flag == 3)
        {
            fflush(stdin);
            printf("计算机:"); scanf("%d",&value);
            if(value>=0&&value<=100)
            {
                pNewStu->computer = value;
                flag = 4;
                value = -1;
            }
        }
        if(flag != 4)
            printf("
成绩输入有误,请继续输入,(注:成绩范围0-100)
");
    }
}

3.取得链表最后一个元素的节点

函数名称:get_last_student

函数功能:取得链表最后一个元素的节点指针并返回该指针。具体实现如下:

stu* get_last_student(stu* p)
{
    if(NULL == p->next)
    {
        return p;
    }else{
        return get_last_student(p->next);
    }
}

4.递归删除student结构链表的函数

函数名称:clear_record

函数功能:递归删除student结构链表中的节点,从最后一个开始删除。

void clear_record(stu* p)
{
    if(NULL == p)
    {
        return ;
    }
    //如果student结构体的next指针是空则表示没有下一条数据,删除该节点
    if(NULL == p->next)
    {
        free(p);
    }else{ //如果student结构体的next指针域不为空
        //再次调用本函数,并以student结构体next指向的节点作为参数传入
        clear_record(p->next);
        free(p); //删除当前节点
        p = NULL;//将指针置空
    }
}

5.显示菜单的标题处理函数

函数名称:print_menu_title

函数功能:显示菜单的标题。具体实现如下:

void print_menu_title(char* title)
{
    printf("=================================================
");
    printf("| %s
",title);
    printf("-------------------------------------------------
");
}

6.显示学生记录表格头部的函数

函数名称:print_table_head

函数功能:显示学生记录表格头部。具体实现如下:

void print_table_head()
{
    printf(table_head);
}

7.显示总分表格头部函数

函数名称:print_table_head_total

函数功能,显示总分表格头部。具体实现如下:

void print_table_head_total()
{
    printf(table_head_total);
}

8.显示平均分表格头部的函数

函数名称:print_table_head_avg

函数功能:显示平均分表格头部。具体实现如下:

void print_table_head_avg()
{
    printf("+----------+----------+----------+-------+-------+-------+---------+----------+
");
    printf("|   学号   |   姓名   |   性别   |  语文 |  数学 |  英语 |  计算机 | 平均成绩 |
");
    printf("+----------+----------+----------+-------+-------+-------+---------+----------+
");
}

9.显示一个学生记录的函数

函数名称:print_table_row

函数功能:显示一个学生信息记录。具体实现如下:

void print_table_row(stu* p)
{
    printf("|%10s|%10s|%10s|%7d|%7d|%7d|%9d|
",
        p->num,p->name,p->sex,p->chinese,p->mathematic,p->english,p->computer);
}

10.显示一个学生的总分函数

函数名称:print_table_head_total

函数功能:显示一个学生的总分。具体实现如下:

void print_table_row_total(stu* p)
{
    int t = p->chinese + p->mathematic + p->english + p->computer;
    printf("|%10s|%10s|%10s|%7d|%7d|%7d|%9d|%6d
",
        p->num,p->name,p->sex,p->chinese,p->mathematic,p->english,p->computer,t);
}

11.显示一个学生的平均分的函数

函数名称:print_table_row_avg

函数功能:显示一个学生的平均分。具体实现如下:

void print_table_row_avg(stu* p)
{
    float a = ((float)(p->chinese + p->mathematic + p->english + p->computer))/(float)4.0;
    printf("|%10s|%10s|%10s|%7d|%7d|%7d|%9d|%8.1f
",
        p->num,p->name,p->sex,p->chinese,p->mathematic,p->english,p->computer,a);
 
}

12.显示表格底部的函数

函数名称:print_table_bottom

函数功能:显示表格底部。具体现实如下:

void print_table_buttom()
{
    printf(table_bottom);
}

13.显示总分表格底部的函数

函数名称:print_table_bottom_total

函数功能:显示总分表格底部。具体实现如下:

void print_table_buttom_total()
{
    printf("+----------+----------+----------+-------+-------+-------+---------+---------+
");
}

14.显示平均分表格的底部

函数名称:print_table_bottom_avg

函数功能:显示平均分表格的底部。具体实现如下:

void print_table_buttom_avg()
{
    printf("+----------+----------+----------+-------+-------+-------+---------+----------+
");
}
 
3.系统操作过程
1.主界面
系统运行之后,首先进入主菜单界面,充许用户输入1-8之间的不同数字,来实现相应的操作。

=================================================

| 学生成绩管理系统

-------------------------------------------------

|

1 输入学生成绩

|  2 显示学生成绩

|  3 查询学生成绩

|  4 更新学生成绩

|  5 删除学生成绩

|  6 计算学生成绩

|  7 保存文件

|  8 退出系统

|

=================================================

请选择:

 

2.输入学生信息

在主菜单中选择操作1,进入输入学生信息界面,用户可以根据提示信息输入学生的基本信息和各项成绩值,输入完一条信息后系统将提示用户是否继续进行下一条信息的输入,用户"y"或"Y",则继续进行输入操作,否则返回主菜单。

=================================================

| 学生成绩管理系统

-------------------------------------------------

|

1 输入学生成绩

|  2 显示学生成绩

|  3 查询学生成绩

|  4 更新学生成绩

|  5 删除学生成绩

|  6 计算学生成绩

|  7 保存文件

|  8 退出系统

|

=================================================

请选择:1

>请输入学生信息(注:最大长度是10个字符).

学号:201310026

姓名:Mike

性别:Male

 

>请输入Mike的成绩(注:成绩范围0-100).

语文:89

数学:100

英语:90

计算机:99

继续输入学生成绩?(Y 继续, N 返回菜单)N

3.显示学生信息

在主菜单中选择操作2,则进入显示学生信息界面,系统将会把单链表中的学生信息按预定格式显示出来,如果没有没有学生,系统将会给出提示。

=================================================

| 学生成绩管理系统

-------------------------------------------------

|

1 输入学生成绩

|  2 显示学生成绩

|  3 查询学生成绩

|  4 更新学生成绩

|  5 删除学生成绩

|  6 计算学生成绩

|  7 保存文件

|  8 退出系统

|

=================================================

请选择:2

+----------+----------+----------+-------+-------+-------+---------+

|   学号   |   姓名   |   性别   |  语文 |  数学 |  英语 |  计算机 |

+----------+----------+----------+-------+-------+-------+---------+

| 201310026|      Mike|      Male|     89|    100|     90|       99|

|  20131007|      Jhon|      Male|     88|     95|     68|       89|

|  20131008|     Alice|    Female|    100|     90|    100|       78|

|  20131011|      Alex|    Female|     99|     78|     89|       87|

+----------+----------+----------+-------+-------+-------+---------+

按任意键返回菜单..

 

4.查询学生信息

在主菜单中,选择操作3,进入查询学生信息子菜单界面,如图。

=================================================

| 查询学生成绩

-------------------------------------------------

|

|  1 按照学号查询

|  2 按照姓名查询

|  3 按照性别查询

|  4 返回上级菜单

|

=================================================

>请选择:

上图的查询子菜单中提供了1-4个选项,分别按照三种不同的方式对学生信息进行查询和返回上级菜单操作。例如输入1按学号查询,系统提示输入学号,如果输入学号不存在,则给出提示信息,如果存在,则显示该学生的信息。

=================================================

| 查询学生成绩

-------------------------------------------------

|

|  1 按照学号查询

|  2 按照姓名查询

|  3 按照性别查询

|  4 返回上级菜单

|

=================================================

>请选择:1

>请输入学生学号:201310026

 

>查找[201310026]....

+----------+----------+----------+-------+-------+-------+---------+

|   学号   |   姓名   |   性别   |  语文 |  数学 |  英语 |  计算机 |

+----------+----------+----------+-------+-------+-------+---------+

| 201310026|      Mike|      Male|     89|    100|     90|       99|

+----------+----------+----------+-------+-------+-------+---------+

继续查找?(Y 继续, N  返回)Y

>请输入学生学号:201310

 

>查找[201310]....

+----------+----------+----------+-------+-------+-------+---------+

|   学号   |   姓名   |   性别   |  语文 |  数学 |  英语 |  计算机 |

+----------+----------+----------+-------+-------+-------+---------+

 

没有找到结果!

+----------+----------+----------+-------+-------+-------+---------+

继续查找?(Y 继续, N  返回)

 

5.更新学生信息

在主菜单中选择操作4,进入更新学生信息界面,根据系统提示输入要更新的学生的学号,如果该学号不存在,则给出提示信息,如果存在,则提示用户输入新的信息,并覆盖原有数据。

=================================================

| 学生成绩管理系统

-------------------------------------------------

|

|  1 输入学生成绩

|  2 显示学生成绩

|  3 查询学生成绩

|  4 更新学生成绩

|  5 删除学生成绩

|  6 计算学生成绩

|  7 保存文件

|  8 退出系统

|

=================================================

请选择:4

请输入要更新的学生学号:201310

没有学号是[201310]的学生.继续更新吗?(Y 继续, N返回菜单)Y

请输入要更新的学生学号:201310026

>请输入学生信息(注:最大长度是10个字符).

学号:2013106

姓名:Stanson

性别:Male

 

>请输入Stanson的成绩(注:成绩范围0-100).

语文:78

数学:99

英语:100

计算机:99

继续更新吗?(Y 继续, N返回菜单)N

 

6.删除学生信息

在主菜单中选择5,则进入删除学生信息界面,根据系统提示输入要删除的学生学号,如果不存在,则给出提示信息,如果存在,系统提示确认操作,得到确认之后,则直接删除信息。

=================================================

| 学生成绩管理系统

-------------------------------------------------

|

|  1 输入学生成绩

|  2 显示学生成绩

|  3 查询学生成绩

|  4 更新学生成绩

|  5 删除学生成绩

|  6 计算学生成绩

|  7 保存文件

|  8 退出系统

|

=================================================

请选择:5

请输入要删除的学生学号:1201310

没有学号[1201310]的学生.继续删除吗?(Y 继续, N 返回菜单)Y

请输入要删除的学生学号:2013106

确定要删除学号为[2013106],姓名为[Stanson]的学生信息吗?(Y 确定,N 取消)Y

>删除成功!

继续删除吗?(Y 继续, N 返回菜单)

 

7.计算学生成绩

在主菜单中输入操作6,进入计算学生成绩子菜单,如图所示。

=================================================

| 计算学生成绩

-------------------------------------------------

|

|  1 计算总成绩

|  2 计算平均分

|  3 返回上级菜单

|

=================================================

>请选择:

 

计算学生成绩子菜单可充许选择1-3选项,分别计算学生的总分,平均分和返回上级菜单操作。例如输入1,提示按照预定格式将学生信息,各项成绩,总分打印出来,如图:

=================================================

| 计算学生成绩

-------------------------------------------------

|

|  1 计算总成绩

|  2 计算平均分

|  3 返回上级菜单

|

=================================================

>请选择:1

+----------+----------+----------+-------+-------+-------+---------+---------+

|   学号   |   姓名   |   性别   |  语文 |  数学 |  英语 |  计算机 |  总成绩 |

+----------+----------+----------+-------+-------+-------+---------+---------+

|  20131007|      Jhon|      Male|     88|     95|     68|       89|   340

|  20131008|     Alice|    Female|    100|     90|    100|       78|   368

|  20131011|      Alex|    Female|     99|     78|     89|       87|   353

+----------+----------+----------+-------+-------+-------+---------+---------+

按任意键返回菜单..

 

8保存文件

进入保存界面,系统提示输入文件名,保存完成之后,给出提示信息。

=================================================

| 学生成绩管理系统

-------------------------------------------------

|

!  1 输入学生成绩

|  2 显示学生成绩

|  3 查询学生成绩

|  4 更新学生成绩

|  5 删除学生成绩

|  6 计算学生成绩

|  7 保存文件

|  8 退出系统

|

=================================================

请选择:7

请输入文件名:save.txt

保存成功!

按任意键返回菜单..

9.退出系统。

4.总结与Bug调式

在开始调试的过程中出现过链接错误,后来发现.h 中声明的函数名和.cpp中实现时写的函数不同导致找不到这个函数而提示链接错误, 不过它竟然不再编译时给出错误报告,而是到了链接阶段才提示这样的错误,就不知道了。

原文地址:https://www.cnblogs.com/AI-Algorithms/p/3389377.html