流程目录
一、题目
二、分析题目
三、构思搭建框架
四、封装各个功能模块
五、运行调试
文章目录
- 文章目录
- 前言及要求
- 一、分析设计框架
- 二、搭建框架、封装功能模块
- 1、总体思路以及引入库
- 2、Makefile文件搭建
- 3、登录密码设计
- 4、主函数编写
- 5、教师模块封装
- 6、管理员模块封装
- 7、学生模块封装
- 8、调试
- 三、总结与收获
前言及要求
前言:
1、语言:C语言
2、环境:Linux虚拟机ubuntu,编译器是vim,调试gcc,多文件操作使用Makfile。
项目要求:
本项目管理三种身份人员的信息:
1. 管理员(仅一个):姓名:admin 密码:
2. 教师(多个):姓名、密码、工号、性别、出生日期、
3. 学生(多个):姓名、密码、学号、性别、出生日期、数学、语文、英语三门功课成绩管理员登录后可以进行如下操作:
1. 修改自身登录密码
2. 添加新教师
3. 查看所有教师
4. 删除教师
5. 修改教师信息教师登录后可以进行如下操作:
1. 修改自身登录密码
2. 查阅自身信息
3. 添加新学生
4. 删除学生
5. 查阅指定学生信息
6. 修改学生信息(姓名、性别、出生日期、三门功课成绩)
7. 按学号从低到高查看所有学生信息
8. 按数学成绩从高到低查看所有学生信息
9. 按语文成绩从高到低查看所有学生信息
10. 按英语成绩从高到低查看所有学生信息
11. 按总分从高到低查看所有学生信息学生登录后可以进行如下操作:
1. 修改自身登录密码
2. 查阅自身信息
一、分析设计框架
这个项目其实不难,仔细想想都是一些简单的操作。要想做好首先思路得清晰。按题目的要求我们需要做多个界面菜单,当然可以在一个文件里面完成,但是那样太过于繁琐复杂,而且不便于调试,所以我们要用多文件来进行操作。
首先,根据题目,我们会先进入第一级菜单即主菜单用来选择管理员、教师、学生的菜单。选择不同的菜单就需要进行不同的密码输入。
进入二级菜单后,里面应该展示每个功能就比如学生的二级菜单,里面应该有三个选项,分别是1、修改自身密码。2、查阅自身信息。3、返回上一级。其他的类似这样。
还有一些小细节需要注意,每次录入信息或者修改过密码之后需要进行保存,不然退出系统后再进入系统后信息就没了。大体思路就这样,下面来展示一下我的搭建样式。
大体框架就是这样,不过细心的可能发现了,图片中每次进入都会出现加载成功的字样,那是程序在进入时会自动加载之前保存的信息,这个需要使用到fprintf和fscanf函数,稍后会详细介绍。接着往下看。
二、使用步骤
1.引入库
代码如下:其中还有一些自定义的没有加入,后序会给出
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>
2.Makefile文件搭建
代码如下:
Project:main.o code.o teacher.o tealist.o student.o stulist.o admin.o adlist.o
gcc *.o -o $@
%.o:%.c
gcc -c $< -o $@
clean :
rm -f *.o Project
这其实就是一个脚本文件,用来自动生成.o文件的,上面是个万能模板,只需要将要添加的文件写到第一行的Project里面就行了,Project后面 是我所添加的所有文件
3、主函数编写 main.c
#include <stdio.h>
#include <stdbool.h>
#include "teacher.h"
#include "student.h"
#include "tealist.h"
#include "adlist.h"
#include "admin.h"
#include "code.h"
#define N 32
int main()
{
int sel;//选择
int flag=1;//控制循环
char admName[N]="admin"; //初始账户密码
char admCode[N]="12345";
while(flag)
{
printf("=======================\n");
printf("| 请选择你要进入的界面|\n");
printf("-----------------------\n");
printf("| 1、管理员界面 |\n");
printf("-----------------------\n");
printf("| 2、教师界面 |\n");
printf("-----------------------\n");
printf("| 3、学生界面 |\n");
printf("-----------------------\n");
printf("| 0、退出系统 |\n");
printf("=======================\n");
scanf("%d",&sel);
while(getchar()!='\n');
switch(sel)
{
case 0:
printf("系统已退出!\n");
flag=0;
break;
case 1:
printf("请输入管理员账号与密码:\n");
if(AdmCode(admName,admCode))
{
printf("尊敬的admin,欢迎你!\n");
admin(admCode);
}
else
{
printf("密码错误!已返回\n");
}
break;
case 2:
printf("请输入教师账号与密码:\n");
if(TeaCode())
{
printf("尊敬的teacher用户,欢迎你!\n");
teacher();
}
else
{
printf("密码错误!已返回\n");
}
break;
case 3:
printf("请输入学生账号与密码:\n");
if(StuCode())
{
printf("尊敬的student用户,欢迎你!\n");
student();
}
else
{
printf("密码错误!已返回\n");
}
break;
default:
printf("选择不在菜单内,请重新选择!\n");
continue;
}
}
return 0;
}
在正式编写函数前,我创建了一个message.h文件来保存所有结构体。
#pragma once //防止重复调用
#define N 32
struct admNode
{
char name[N];
char code[N];
};
struct Time
{
int year;
int month;
int day;
};
struct grade
{
int Math;
int Chinese;
int English;
};
struct teaNode
{
char name[N]; //名称
char code[N]; //密码
int num;
char sex[N];
struct Time teaBir;//出生日期
struct teaNode *next;
};
struct stuNode
{
char name[N];
char code[N];
int num;
char sex[N];
struct Time stuBir;
struct grade stu;
struct stuNode *next;
};
4.登录密码设计--code.c
在进入一级菜单后我们需要进入二级菜单,我在这创建了一个code.c文件来写登录密码。我的设计是对三类使用者分别设计三个函数,然后在进入时进行一个判断。首先是管理员的,管理员的最好设计,因为题目要求管理员只有一个,那么我们只需要写两个个字符串数组分别保存账户名称和密码,在里面初始化一下就行了,然后跟输入的账户密码进行比较,下面是代码。
bool AdmCode(char admName[],char admCode[])
{
char inName[N];
char inCode[N];
char *pname=admName;
char *pcode=admCode;
readAdm(admCode);
printf("请输入账户名称:");
scanf("%s",inName);
while(getchar()!='\n');
printf("请输入账户密码:");
scanf("%s",inCode);
while(getchar()!='\n');//清空缓存区
if(strcmp(pname,inName)==0 && strcmp(pcode,inCode)==0)
{
return true;
}
else
{
return false;
}
}
这里定义了bool类型,不会的可以使用int类型,使用bool类型需要在头文件中包含#include<stdbool.h>文件,当中用到了strcmp这个函数,strcmp(字符串1,字符串2),当字符串1和字符串2相等时,返回0,当然使用这个函数得包含头文件#include<string.h>。 其中有个N是用宏定义定义成32,如果后期要更改数组长度的话,直接在宏定义里面更改就行了,不用一个个麻烦的去程序里找。
其中的账户数组和密码数组是需要从主函数中传过来的,因为需要更改所以使用地址传参。当中有一个readAdm(admCode)的代码就是读取函数。在后面会介绍的,用在这里是要判断之前的密码是否被更改,若更改则用更改后的来判断。
接下来就是学生登录密码的设计。
#include <stdio.h>
#include <stdbool.h>
#include "message.h"
#include <string.h>
#include "tealist.h"
#include "adlist.h"
#define N 32
bool StuCode()
{
char stuName[N]="student";
char stuCode[N]="12345";
char inName[N];
char inCode[N];
struct stuNode *p=NULL;
p=readStu();
printf("请输入账户名称:");
scanf("%s",inName);
while(getchar()!='\n');
printf("请输入账户密码:");
scanf("%s",inCode);
while(getchar()!='\n');
while(p!=NULL)
{
if(strcmp(p->name,inName)==0 && strcmp(p->code,inCode)==0)
{
return true;
}
p=p->next;
}
if(strcmp(stuName,inName)==0 && strcmp(stuCode,inCode)==0)
{
return true;
}
else
{
return false;
}
}
这里我把所有用到的头文件都写进来了,方便大家观看,这里在上面的基础上加了一个while循环,他的功能是用来读取我们添加的学生账号和密码,因为学生和老师是有多个的,上面做的初始化,可以当做是初始账户和密码,在第一次进入系统时没有任何信息的时候或者忘记添加的账户和密码时就可以使用初始账户密码进入,当然初始账户密码是不可以被更改的。下面的教师登录密码也是一样的。
bool TeaCode()
{
char teaName[N]="teacher";
char teaCode[N]="12345";
char inName[N];
char inCode[N];
struct teaNode *p=NULL;
p=readTea();
printf("请输入账户名称:");
scanf("%s",inName);
while(getchar()!='\n');
printf("请输入账户密码:");
scanf("%s",inCode);
while(getchar()!='\n');
while(p!=NULL)
{
if(strcmp(p->name,inName)==0 && strcmp(p->code,inCode)==0)
{
return true;
}
p=p->next;
}
if(strcmp(teaName,inName)==0 && strcmp(teaCode,inCode)==0)
{
return true;
}
else
{
return false;
}
}
5、教师模块封装
上面的函数写完后我选择写教师模块封装,因为教师模块写完的话,其他两个就会简单很多。其实这里说白了就是对链表进行增、删、改、查的操作,对于已经熟悉链表的同学而言就没什么难度。首先在写之前我们还要做做准备工作。创建链表,首先得创建节点。对于一二两题我选择放后面写,因为管理员模块没封装的话这两题是写不了的,如果嫌这样麻烦的同学也可以先写管理员,但是我就喜欢先从难的入手。所以先冲了教师这个模块。
在写教师模块的时候,我们也需要先写一个教师的主函数模块。里面用来调用各个封装的功能。
教师菜单主函数--teacher.c
#include <stdio.h>
#include <stdbool.h>
#include "message.h"
#include "tealist.h"
#include "admin.h"
#include "adlist.h"
struct stuNode *teacher()
{
int sel;//选择
int flag=1;
int index;//需要查找的数
struct stuNode *head = NULL;
struct stuNode *p=NULL;
struct teaNode *teaHead = NULL;
struct teaNode *tp = NULL;
head = createNode(); //创建头节点
head = readStu();//加载老师文件
teaHead=readTea();//加载管理员
while(flag)
{
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("* 10、按英语成绩从高到低查看所有学生信息 *\n");
printf("* 11、按总分成绩从高到低查看所有学生信息 *\n");
printf("* 0、返回上一层 *\n");
printf("**********************************************\n");
scanf("%d",&sel);
while(getchar()!='\n');
switch(sel)
{
case 0:
printf("已返回上一层\n");
writeStu(head);//写入文件
writeTea(teaHead);//写入文件
flag=0;
break;
case 1:
//modCode();//修改教师密码
modTeaCode(teaHead);//修改密码
break;
case 2:
tp=findTea_self(teaHead);//查找自身信息
printf("姓名:%s\t 密码:%s\t性别:%s\n",tp->name,tp->code,tp->sex);
printf("工号%d \n",tp->num);
printf("出生日期%d/%d/%d \n",tp->teaBir.year,tp->teaBir.month,tp->teaBir.day);
break;
case 3:
addStu(head);//添加学生信息
break;
case 4:
printf("请输入你要查找的学号:");
scanf("%d",&index);
while(getchar()!='\n');
p=findStu(head,index);//查找学生信息
if(p==NULL)
{
printf("学号不存在,查询失败\n");
break;
}
printf("姓名:%s\t 密码:%s\t性别:%s\n",p->name,p->code,p->sex);
printf("学号%d \n数学:%d\t语文:%d\t 英语:%d \n",p->num,\
p->stu.Math,p->stu.Chinese,p->stu.English);
printf("出生日期%d/%d/%d \n",p->stuBir.year,p->stuBir.month,p->stuBir.day);
break;
case 5:
delStu(head);//根据学号删除学生信息
break;
case 6:
modStu(head);//修改学生信息
break;
case 7:
sortNum(head);//升序排序学号
showStu(head);//显示学生信息
break;
case 8:
sortMath(head);//降序排序数学成绩
showStu(head);//显示学生信息
break;
case 9:
sortChin(head);//降序排序语文成绩
showStu(head);//显示学生信息
break;
case 10:
sortEnglish(head);//降序排序英语成绩
showStu(head);//显示学生信息
break;
case 11:
sortTotal(head);//降序排序总分成绩
showStu(head);//显示学生信息
break;
default :
printf("选择不在菜单内,请重新选择\n");
continue;
}
}
return head;
}
然后就是功能模块的封装--tealist.c
先给大家展示一下这个模块包含的文件
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>
#include "tealist.h"
#include "adlist.h"
#include "message.h"
#define N 32
#define M(x,y,z) ((x)+(y)+(z)) //总成绩
static int num=1000; //防止学号重复
首先是创建节点
struct stuNode *createNode() //创建节点
{
struct stuNode *pnew=NULL;
pnew=(struct stuNode *)malloc(sizeof(struct stuNode));
assert(pnew!=NULL);
strcpy(pnew->name,"non");//初始化姓名、密码、学号、性别、生日、成绩
strcpy(pnew->code,"non");
strcpy(pnew->sex,"non");
pnew->num=num;
pnew->stuBir.year=0;
pnew->stuBir.month=0;
pnew->stuBir.day=0;
pnew->stu.Math=0;
pnew->stu.Chinese=0;
pnew->stu.English=0;
pnew->next = NULL;
return pnew;
}
节点的创建很简单,但是要记得初始化,如果是字符串的话,初始化和赋值需要使用strcpy函数。其中用到的assert函数就是用来判断malloc函数申请空间是否成功的。
创建完节点后,创建链表,并且添加学生信息。下面就可以写一个添加函数,插入链表使用的是带头节点的头插法,在teacher.c文件里面已经创建了一个head节点。
void addStu(struct stuNode *head)//添加学生
{
char flag;
struct stuNode *pnew=NULL;
do
{
pnew=createNode();
printf("请输入姓名:");
scanf("%s",pnew->name);
while(getchar()!='\n');
printf("请输入密码:");
scanf("%s",pnew->code);
while(getchar()!='\n');
printf("请输入性别:");
scanf("%s",pnew->sex);
while(getchar()!='\n');
printf("请输入成绩:\n");
printf("数学:");
scanf("%d",&pnew->stu.Math);
while(getchar()!='\n');
printf("语文:");
scanf("%d",&pnew->stu.Chinese);
while(getchar()!='\n');
printf("英语:");
scanf("%d",&pnew->stu.English);
while(getchar()!='\n');
printf("请输入出生日期(1999/07/11):");
scanf("%d/%d/%d",&pnew->stuBir.year,&pnew->stuBir.month,&pnew->stuBir.day);
while(getchar()!='\n');
num++;
pnew->num=num;//每次添加学号都自增一
pnew->next=head->next;//用头插法加入链表
head->next=pnew;
printf("加入成功\n");
printf("按任意键继续添加(q返回上一层):");
scanf("%c",&flag);
while(getchar()!='\n');
}while(flag!='q');
return ;
}
添加也没什么好说的,使用一个循环添加比较方便,不然每次只能添加一个那不得麻烦死用户。添加完为了验证是否成功,我写了一个正常的显示函数,只显示不带其他功能的。
当然也方便后面调用。
void showStu(struct stuNode *head) //显示学生信息
{
if(head->next==NULL)
{
printf("暂无学生信息!\n");
return ;
}
struct stuNode *p=head->next;
while(p!=NULL)
{
printf("姓名:%s\t 密码:%s\t性别:%s\n",p->name,p->code,p->sex);
printf("学号%d \n数学:%d\t语文:%d\t 英语:%d \n",p->num,\
p->stu.Math,p->stu.Chinese,p->stu.English);
printf("出生日期%d/%d/%d \n",p->stuBir.year,p->stuBir.month,p->stuBir.day);
p=p->next;
}
}
接着就是查找函数了,我选择使用按学号查找,当然大家可以选择按其他方式查找,比如数学成绩,姓名等等,都是可以的。
struct stuNode *findStu(struct stuNode *head,int index)//根据学号查找
{
if(head->next==NULL)
{
return NULL;
}
struct stuNode *p=head->next;
while(p!=NULL)
{
if(p->num == index)
{
return p;
}
p=p->next;
}
return NULL;
}
删除首先还是查找,先定位到数据在链表的位置然后进行删除。这时候要分为头删和中间删除,如果需要删除的节点是第一个节点的话直接把需要删除的节点的next赋值给头节点的next就可以了,中间或者尾节点的话,需要找到前一个节点,遍历一下链表就可以了。当然还有很重要的一点,每次删除节点都要用free进行释放,不然申请那块就会被一直占用,时间就了内存自然就不够用了。
void delStu(struct stuNode *head)//根据学号删除学生信息
{
if(head->next==NULL)
{
printf("数据为空,删除失败!\n");
return ;
}
struct stuNode *pdel=NULL;
struct stuNode *p=head->next;
int index;
printf("请输入你要删除的学生学号:\n");
scanf("%d",&index);
while(getchar()!='\n');
pdel=findStu(head,index);
if(pdel==NULL)
{
printf("学号不存在,删除失败\n");
return ;
}
if(pdel==head->next)
{
head->next=pdel->next;
}
else
{
while(p->next!=pdel)
{
p=p->next;
}
p->next=pdel->next;
}
free(pdel);
printf("删除成功!\n");
num--;
return ;
}
删除结束后我们来看看修改,修改的核心也是查找,不过这里我们还需要写一个三级菜单,因为题目要求修改各项,而不是全部修改。当然这里不需要另写函数进行调用,直接在对应的case里面进行重新输入就行。
void modStu(struct stuNode *head)//修改学生信息
{
int flag=1;
int sel;
int index;
struct stuNode *p=NULL;
printf("请输入你要修改的学生学号:\n");
scanf("%d",&index);
while(getchar()!='\n');
p=findStu(head,index);
if(p==NULL)
{
printf("学号不存在,修改失败!\n");
return ;
}
printf("姓名:%s\t 密码:%s\t性别:%s\n",p->name,p->code,p->sex);
printf("学号%d \n数学:%d\t语文:%d\t 英语:%d \n",p->num,\
p->stu.Math,p->stu.Chinese,p->stu.English);
printf("出生日期%d/%d/%d \n",p->stuBir.year,p->stuBir.month,p->stuBir.day);
while(flag)
{
printf("****************************\n");
printf("* 1、修改姓名 *\n");
printf("* 2、修改性别 *\n");
printf("* 3、修改出生日期 *\n");
printf("* 4、修改三门科目成绩 *\n");
printf("* 0、返回上一层 *\n");
printf("****************************\n");
printf("* 请选择你要修改的对象 *\n");
scanf("%d",&sel);
while(getchar()!='\n');
switch(sel)
{
case 1 :
printf("请输入修改后的名字:");
scanf("%s",p->name);
while(getchar()!='\n');
printf("修改成功\n");
break;
case 2 :
printf("请输入修改后的性别:");
scanf("%s",p->sex);
while(getchar()!='\n');
printf("修改成功\n");
break;
case 3 :
printf("请输入修改后的出生日期(1999/01/01):");
scanf("%d/%d/%d",&p->stuBir.year,&p->stuBir.month,\
&p->stuBir.day);
while(getchar()!='\n');
printf("修改成功\n");
break;
case 4 :
printf("请输入修改后的成绩\n");
printf("数学:");
scanf("%d",&p->stu.Math);
while(getchar()!='\n');
printf("语文:");
scanf("%d",&p->stu.Chinese);
while(getchar()!='\n');
printf("英语:");
scanf("%d",&p->stu.English);
while(getchar()!='\n');
printf("修改成功\n");
break;
case 0 :
flag=0;
break;
default:
printf("选择不在菜单内,请重新选择\n");
continue;
}
}
}
下面的几个选项其实本质都是一样的我就不作过多赘述了,只要清楚其中一个的原理,其他几个稍作修改就行。那我们就来看看第一个程序。其实也很好理解,这一段程序可分为三个小部分。
第一部分就是找出最大值:只要定义两个结构体指针p和pmax,pmax指向第一个节点,p用来遍历,并把每次遍历的节点都与pmax进行比较,如果小于pmax则继续往后遍历,如果大于pmax则把p的值赋给pmax并停止。
第二部分就是将最大值从链表中分离出来,说白了就是将最大值从链表中删除,跟上述的删除操作没什么两样。
第三部分就是将分离下来的最大值再以头插法插入新的不带头节点的链表。。
然后循环执行上面三部分就好了。我们在函数的开头将头结点分离出来了,到时候只需要将新链表的头插入的旧头节点的后面就行了。
其他几个也只有细微的改动,无非就是升序变降序。直接上代码了。
void sortNum(struct stuNode *head)//升序排序学号
{
struct stuNode *head1=head->next;
head->next=NULL; //将头节点取下来
struct stuNode *p=NULL;
struct stuNode *pmax=NULL;
struct stuNode *newhead=NULL;
while(head1!=NULL)
{
p=head1;
pmax=head1;
while(p!=NULL)
{
if(p->num>pmax->num)//找最大值
{
pmax=p;
}
p=p->next;
}
if(pmax==head1) //分离最大值
{
head1=head1->next;
}
else
{
struct stuNode *pfront=head1;
while(pfront->next!=pmax)
{
pfront=pfront->next;
}
pfront->next=pmax->next;
}
pmax->next=NULL;
pmax->next=newhead;
newhead=pmax;
}
head->next=newhead;
return ;
}
void sortMath(struct stuNode *head)//降序排序数学成绩
{
struct stuNode *head1=head->next;
head->next=NULL; //将头节点取下来
struct stuNode *p=NULL;
struct stuNode *pmin=NULL;
struct stuNode *newhead=NULL;
while(head1!=NULL)
{
p=head1;
pmin=head1;
while(p!=NULL)
{
if(p->stu.Math<pmin->stu.Math)//找最小值
{
pmin=p;
}
p=p->next;
}
if(pmin==head1) //分离最小值
{
head1=head1->next;
}
else
{
p=head1;
while(p->next!=pmin)
{
p=p->next;
}
p->next=pmin->next;
}
pmin->next=NULL;
pmin->next=newhead;
newhead=pmin;
}
head->next=newhead;
return ;
}
void sortChin(struct stuNode *head)//降序排序语文成绩
{
struct stuNode *head1=head->next;
head->next=NULL; //将头节点取下来
struct stuNode *p=NULL;
struct stuNode *pmin=NULL;
struct stuNode *newhead=NULL;
while(head1!=NULL)
{
p=head1;
pmin=head1;
while(p!=NULL)
{
if(p->stu.Chinese < pmin->stu.Chinese)//找最小值
{
pmin=p;
}
p=p->next;
}
if(pmin==head1) //分离最小值
{
head1=head1->next;
}
else
{
struct stuNode *pfront=head1;
while(pfront->next!=pmin)
{
pfront=pfront->next;
}
pfront->next=pmin->next;
}
pmin->next=NULL;
pmin->next=newhead;
newhead=pmin;
}
head->next=newhead;
return ;
}
void sortEnglish(struct stuNode *head)//降序排序英语成绩
{
struct stuNode *head1=head->next;
head->next=NULL; //将头节点取下来
struct stuNode *p=NULL;
struct stuNode *pmin=NULL;
struct stuNode *newhead=NULL;
while(head1!=NULL)
{
p=head1;
pmin=head1;
while(p!=NULL)
{
if(p->stu.English < pmin->stu.English)//找最小值
{
pmin=p;
}
p=p->next;
}
if(pmin==head1) //分离最小值
{
head1=head1->next;
}
else
{
struct stuNode *pfront=head1;
while(pfront->next!=pmin)
{
pfront=pfront->next;
}
pfront->next=pmin->next;
}
pmin->next=NULL;
pmin->next=newhead;
newhead=pmin;
}
head->next=newhead;
return ;
}
void sortTotal(struct stuNode *head)//降序排序总分成绩
{
struct stuNode *head1=head->next;
head->next=NULL; //将头节点取下来
struct stuNode *p=NULL;
struct stuNode *pmin=NULL;
struct stuNode *newhead=NULL;
while(head1!=NULL)
{
p=head1;
pmin=head1;
while(p!=NULL)
{
if(M(p->stu.Math,p->stu.Chinese,p->stu.English)<(M(pmin->stu.Math,\
pmin->stu.Chinese,pmin->stu.English)))
{
pmin=p;
}
p=p->next;
}
if(pmin==head1) //分离最小值
{
head1=head1->next;
}
else
{
struct stuNode *pfront=head1;
while(pfront->next!=pmin)
{
pfront=pfront->next;
}
pfront->next=pmin->next;
}
pmin->next=NULL;
pmin->next=newhead;
newhead=pmin;
}
head->next=newhead;
return ;
}
基本功能都完成了,接下来就是文件的写入和保存了。关于fsanf和fprintf的操作大家应该都很熟悉了。这两个函数都需要遵循打开文件,再进行读写操作,最后再关闭文件的规则。这里需要说明一下读函数,读取的话我们需要新建链表,即新建节点把每次读取的值保存到节点里面,最后再通过头插法将他们连接起来,最后返回头结点。fscanf这个函数每次读取只会一个节点的内容,因为我们写的时候就是每个节点的写入。当fscanf返回EOF的时候就说明读到文件结尾了,这个时候关闭文件就行了。
bool writeStu(struct stuNode *head)//写入文件
{
FILE *fp;
fp=fopen("./data.txt","w");//存放学生信息
if(fp==NULL)
{
perror("fopen");
return false;
}
struct stuNode *p=head->next;
int ret;
while(p!=NULL)
{
ret=fprintf(fp,"%s\t%s\t%s\n%d\n%d\t%d\t%d\n%d/%d/%d\n",p->name,p->code,\
p->sex,p->num,p->stu.Math,p->stu.Chinese,p->stu.English,p->stuBir.year,\
p->stuBir.month,p->stuBir.day);
if(ret<0)
{
perror("fprintf");
fclose(fp);
return false;
}
p=p->next;
}
printf("写入成功!\n");
fclose(fp);
return true;
}
struct stuNode *readStu()//加载文件
{
struct stuNode *p=NULL;
struct stuNode *head=NULL;
struct stuNode *newhead=NULL;
head=createNode();
FILE *fp=NULL;
fp=fopen("./data.txt","r");
if(fp==NULL)
{
perror("fopen");
return head;
}
int ret;
while(1)
{
p=createNode();
ret=fscanf(fp,"%s%s%s%d%d%d%d%d/%d/%d",p->name,p->code,p->sex,&p->num,&p->stu.Math,&p->stu.Chinese,&p->stu.English,&p->stuBir.year,&p->stuBir.month,&p->stuBir.day);
if(ret==EOF)
{
break;
}
num++;
p->next=newhead;
newhead=p;
}
free(p);
head->next=newhead;
fclose(fp);
printf("加载成功\n");
return head;
}
到这里tealist.c文件就写的差不多了,除了一二两小题。写完后我们还需要写一个tealist.h文件,到时候用来调用函数的时候使用,如果全部在一个.c文件中写那就不需要。当然不止是tealist.c要写头文件,teacher.c也需要写,因为他最后都要被main.c调用。
teacher.h
#pragma once
struct stuNode *teacher();
tealist.h
#pragma once
#include <stdbool.h>
struct teaNode *modTeaCode(struct teaNode *head);//修改密码
struct stuNode *createNode();//创建节点并初始化
void addStu(struct stuNode *head);//添加学生信息
void showStu(struct stuNode *head); //显示学生信息
struct stuNode *findStu(struct stuNode *head,int index);//根据学号查找
void delStu(struct stuNode *head); //根据学号删除学生信息
void modStu(struct stuNode *head); //修改学生信息
void sortNum(struct stuNode *head); //升序排序学号
void sortMath(struct stuNode *head); //降序排序数学成绩
void sortChin(struct stuNode *head); //降序排序语文成绩
void sortEnglish(struct stuNode *head); //降序排序英语成绩
void sortTotal(struct stuNode *head); //降序排序总分成绩
struct stuNode *readStu(); //加载文件
bool writeStu(struct stuNode *head);//写入文件
struct teaNode *findTea_self(struct teaNode *head);//查找自身信息
6、学生模块封装
写完老师的我们可以直接写学生的,学生的模块那就太好写了。老规矩,首先先写一个学生的主函数student.c,然后再写学生的函数文件stulist.c,首先先写student.c文件.
student.c
#include <stdio.h>
#include "stulist.h"
#include "teacher.h"
#include "tealist.h"
#include "student.h"
#include "message.h"
void student()
{
int flag=1;
int sel;
struct stuNode *p=NULL;
p=readStu();
struct stuNode *sp=NULL;
while(flag)
{
printf("***********************\n");
printf("* 1、修改自身登陆密码 *\n");
printf("* 2、查阅自身信息 *\n");
printf("* 0、返回上一层 *\n");
printf("***********************\n");
scanf("%d",&sel);
while(getchar()!='\n');
switch(sel)
{
case 0:
writeStu(p);
flag=0;
break;
case 1:
modStuCode(p);
break;
case 2:
sp=lookStu(p); //查询自身信息
printf("姓名:%s\t 密码:%s\t性别:%s\n",sp->name,sp->code,sp->sex);
printf("学号%d \n数学:%d\t语文:%d\t 英语:%d \n",sp->num,\
sp->stu.Math,sp->stu.Chinese,sp->stu.English);
printf("出生日期%d/%d/%d \n",sp->stuBir.year,sp->stuBir.month,sp->stuBir.day);
// showStu(p);
break;
default:
printf("选择不在菜单内,请重新选择\n");
continue;
}
}
return ;
}
然后就是函数封装文件
stulist.c
#include <stdio.h>
#include <string.h>
#include "stulist.h"
#include "tealist.h"
#include "message.h"
#define N 32
struct stuNode *modStuCode(struct stuNode *head)//修改密码
{
struct stuNode *pnew=NULL;
pnew=lookStu(head);
printf("请输入新的密码:\n");
scanf("%s",pnew->code);
while(getchar()!='\n');
printf("修改成功!\n");
return head;
}
struct stuNode *lookStu(struct stuNode *head) //查询自身信息
{
struct stuNode *p=head->next;
char StuName[N];
printf("输入自身账户名称:");
scanf("%s",StuName);
while(getchar()!='\n');
while(p!=NULL)
{
if(strcmp(p->name,StuName)==0)
{
return p;
}
p=p->next;
}
}
这里调用tealist.c文件,所以头文件需要包含tealist.h文件,这里的查询信息使用的就是通过姓名查询,还是需要使用strcmp函数进行比较。学生部分就是这么简单,下面是他的头文件。
student.h
#pragma once
void student();
stulist.h
#pragma once
struct stuNode *modStuCode(struct stuNode *head);//修改学生密码
struct stuNode *lookStu(struct stuNode *head); //查询自身信息
7、管理员模块封装
最后一个模块,管理员模块,教师模块写完,完全可以复制粘贴但是要稍作修改,因为管理员模块使用的教师的结构体内容和学生的内容是不一样的。
首先是管理员模块的主函数admin.c文件
admin.c
#include <stdio.h>
#include "adlist.h"
#include "message.h"
void admin(char admCode[])
{
int flag=1;
int sel;
int n;//接收改过后的密码长度
struct teaNode *head=NULL;
head=createTeaNode();
head=readTea();
while(flag)
{
printf("—————————————————————————— \n");
printf("| 欢迎进入管理员菜单 | \n");
printf("—————————————————————————— \n");
printf("| 1、修改自身登陆密码 | \n");
printf("| 2、添加新教师 | \n");
printf("| 3、查看所有教师 | \n");
printf("| 4、删除教师 | \n");
printf("| 5、修改教师信息 | \n");
printf("| 0、返回上一层 | \n");
printf("—————————————————————————— \n");
scanf("%d",&sel);
while(getchar()!='\n');
switch(sel)
{
case 1:
modAdmCode(admCode);
writeAdm(admCode);
break;
case 2:
addTea(head);
break;
case 3:
showTea(head);
break;
case 4:
delTea(head);
break;
case 5:
modTea(head);
break;
case 0:
writeTea(head);
flag=0;
break;
default:
printf("选择不在菜单内,请重新选择\n");
continue;
}
}
}
这里需要接收主函数的admCode,即管理员密码的数组首地址。因为待会要在模块内对密码进行修改,所以这里使用地址传参。
接下来就是功能模块了,其实个教师模块大差不差,甚至比教师模块还简单。
adlist.c
#include <stdio.h>
#include "message.h"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
static int teaNum=0; //静态全局,防止工号重复
char *modAdmCode(char admCode[])//修改密码
{
int n;
n=strlen(admCode);
for(int i=0;i<n;i++)
{
admCode[i]=0;
}
printf("请输入新的密码:\n");
scanf("%s",admCode);
while(getchar()!='\n');
printf("修改成功!\n");
return admCode;
}
struct teaNode *createTeaNode() //创建节点
{
struct teaNode *pnew=NULL;
pnew=(struct teaNode *)malloc(sizeof(struct teaNode));
assert(pnew!=NULL); //判断节点是否创建成功
strcpy(pnew->name,"non");//初始化姓名、密码、工号、性别、出生
strcpy(pnew->code,"non");
strcpy(pnew->sex,"non");
pnew->num=teaNum;
pnew->teaBir.year=0;
pnew->teaBir.month=0;
pnew->teaBir.day=0;
pnew->next = NULL;
return pnew;
}
void addTea(struct teaNode *head)//添加老师
{
char flag;
struct teaNode *pnew=NULL;
do
{
pnew=createTeaNode();
printf("请输入姓名:");
scanf("%s",pnew->name);
while(getchar()!='\n');
printf("请输入密码:");
scanf("%s",pnew->code);
while(getchar()!='\n');
printf("请输入性别:");
scanf("%s",pnew->sex);
while(getchar()!='\n');
printf("请输入出生日期(1999/07/11):");
scanf("%d/%d/%d",&pnew->teaBir.year,&pnew->teaBir.month,&pnew->teaBir.day);
while(getchar()!='\n');
teaNum++;
pnew->num=teaNum;//每次添加工号都自增一
pnew->next=head->next;//用头插法加入链表
head->next=pnew;
printf("按任意键继续添加(q返回上一层):");
scanf("%c",&flag);
while(getchar()!='\n');
}while(flag!='q');
return ;
}
void showTea(struct teaNode *head) //显示教师信息
{
if(head->next==NULL)
{
printf("暂无教师信息!\n");
return ;
}
struct teaNode *p=head->next;
while(p!=NULL)
{
printf("姓名:%s\t 密码:%s\t性别:%s\n",p->name,p->code,p->sex);
printf("工号%d \n",p->num);
printf("出生日期%d/%d/%d \n",p->teaBir.year,p->teaBir.month,p->teaBir.day);
p=p->next;
}
}
struct teaNode *findTea(struct teaNode *head,int index)//根据工号查找
{
if(head->next==NULL)
{
return NULL;
}
struct teaNode *p=head->next;
while(p!=NULL)
{
if(p->num == index)
{
return p;
}
p=p->next;
}
return NULL;
}
void delTea(struct teaNode *head)//根据工号删除教师信息
{
if(head->next==NULL)
{
printf("数据为空,删除失败!\n");
return ;
}
struct teaNode *pdel=NULL;
struct teaNode *p=head->next;
int index;
printf("请输入你要删除的教师工号:\n");
scanf("%d",&index);
while(getchar()!='\n');
pdel=findTea(head,index);
if(pdel==NULL)
{
printf("工号不存在,删除失败\n");
return ;
}
if(pdel==head->next)
{
head->next=pdel->next;
}
else
{
while(p->next!=pdel)
{
p=p->next;
}
p->next=pdel->next;
}
free(pdel);
printf("删除成功!\n");
teaNum--;
return ;
}
void modTea(struct teaNode *head)//修改教师信息
{
int flag=1;
int sel;
int index;
struct teaNode *p=NULL;
printf("请输入你要修改的教师工号:\n");
scanf("%d",&index);
while(getchar()!='\n');
p=findTea(head,index);
if(p==NULL)
{
printf("该教师不存在,修改失败\n");
return ;
}
printf("姓名:%s\t 密码:%s\t性别:%s\n",p->name,p->code,p->sex);
printf("工号%d \n",p->num);
printf("出生日期%d/%d/%d \n",p->teaBir.year,p->teaBir.month,p->teaBir.day);
while(flag)
{
printf("****************************\n");
printf("* 1、修改姓名 *\n");
printf("* 2、修改性别 *\n");
printf("* 3、修改出生日期 *\n");
printf("* 0、返回上一层 *\n");
printf("****************************\n");
printf("* 请选择你要修改的对象 *\n");
scanf("%d",&sel);
while(getchar()!='\n');
switch(sel)
{
case 1 :
printf("请输入修改后的名字:");
scanf("%s",p->name);
while(getchar()!='\n');
printf("修改成功\n");
break;
case 2 :
printf("请输入修改后的性别:");
scanf("%s",p->sex);
while(getchar()!='\n');
printf("修改成功\n");
break;
case 3 :
printf("请输入修改后的出生日期(1999/01/01):");
scanf("%d/%d/%d",&p->teaBir.year,&p->teaBir.month,\
&p->teaBir.day);
while(getchar()!='\n');
printf("修改成功\n");
break;
case 0 :
flag=0;
break;
default:
printf("选择不在菜单内,请重新选择\n");
continue;
}
}
}
接着是对内容的保存,这里我使用了两个文件进行保存,一个用来存储教师的信息,另一个用来存储管理员的密码,虽然可以在一个文件里面进行读写操作,但是那样容易出错,所以我选择使用两个。
bool writeTea(struct teaNode *head,char admCode[])//写入文件
{
FILE *fp;
fp=fopen("./teaData.txt","w");
if(fp==NULL)
{
perror("fopen");
return false;
}
struct teaNode *p=head->next;
int ret;
while(p!=NULL)
{
ret=fprintf(fp,"%s\t%s\t%s\n%d\n%d/%d/%d\n",p->name,p->code,\
p->sex,p->num,p->teaBir.year,p->teaBir.month,p->teaBir.day);
if(ret<0)
{
perror("fprintf");
fclose(fp);
return false;
}
p=p->next;
}
printf("写入成功!\n");
fclose(fp);
return true;
}
struct teaNode *readTea()//加载文件
{
struct teaNode *p=NULL;
struct teaNode *head=NULL;
struct teaNode *newhead=NULL;
head=createTeaNode();
FILE *fp=NULL;
fp=fopen("./teaData.txt","r");
if(fp==NULL)
{
perror("fopen");
return head;
}
int ret;
while(1)
{
p=createTeaNode();
ret=fscanf(fp,"%s%s%s%d%d/%d/%d",p->name,p->code,p->sex,&p->num,&p->teaBir.year,&p->teaBir.month,&p->teaBir.day);
if(ret==EOF)
{
break;
}
teaNum++;
p->next=newhead;
newhead=p;
}
free(p);
head->next=newhead;
fclose(fp);
printf("加载成功\n");
return head;
}
bool writeAdm(char admCode[])//写入文件
{
FILE *fp;
fp=fopen("./Admcode.txt","w");//专门存放修改的密码
if(fp==NULL)
{
perror("fopen");
return false;
}
int ret;
ret=fprintf(fp,"%s",admCode);
if(ret<0)
{
perror("fprintf");
fclose(fp);
return false;
}
printf("写入成功!\n");
fclose(fp);
return true;
}
bool readAdm(char admCode[])//加载文件
{
FILE *fp=NULL;
fp=fopen("./Admcode.txt","r");
if(fp==NULL)
{
perror("fopen");
return false;
}
int ret;
ret=fscanf(fp,"%s",admCode);
if(ret<0)
{
fclose(fp);
return false;
}
fclose(fp);
printf("加载成功\n");
return true;
}
每次在关闭管理员界面时进行写入操作,然后在打开时进行读取操作。这是对教师信息文件的读写,对于密码的文件,需要在修改密码后进行写操作,然后在进入管理员选项时进行读操作,这样登录时用的就是之前修改过的密码了。下面是两个文件的头文件。
admin.h
#pragma once
void admin();
adlist.h
#pragma once
#include <stdbool.h>
char *modAdmCode(char admCode[]);//修改密码
struct teaNode *createTeaNode(); //创建节点
void addTea(struct teaNode *head);//添加老师
void showTea(struct teaNode *head); //显示学生信息
void modTea(struct teaNode *head);//修改教师信息
struct teaNode *findTea(struct teaNode *head,int index);//根据工号查找
void delTea(struct teaNode *head);//根据工号删除教师信息
bool writeTea(struct teaNode *head);//写入文件
struct teaNode *readTea();//加载文件
bool writeAdm(char admCode[]);//将修改的密码写入文件
bool readAdm(char admCode[]);//读出存放密码的文件
当然还没结束,管理员模块写完后我们要去写教师模块遗留的两个题目。操作其实也很简单,但是需要在tealist.c文件中包含adlist.h文件。就跟在stulist.c里面包含tealist.h是一样的道理。
tealist.c
struct teaNode *modTeaCode(struct teaNode *head)//修改密码
{
struct teaNode *pnew=NULL;
pnew=findTea_self(head);
printf("请输入新的密码:\n");
scanf("%s",pnew->code);
while(getchar()!='\n');
printf("修改成功!\n");
return head;
}
struct teaNode *findTea_self(struct teaNode *head)//查找自身信息
{
if(head->next==NULL)
{
return NULL;
}
char teaName[N]={0};
printf("请输入自身名称\n");
scanf("%s",teaName);
while(getchar()!='\n');
struct teaNode *p=head->next;
while(p!=NULL)
{
if(strcmp(p->name,teaName)==0)
{
return p;
}
p=p->next;
}
return NULL;
}
写完后记得将这两个函数的函数头添加到tealist.h文件中。这样所有模块都写完了。
8、调试
最后到了调试环节,我建议大家没次写完一个函数就进行调试,这样有问题可以及时发现并且修改,我这是提前写好的,所有功能我都以及调试过许多次了,所以基本上以及没有什么bug了。程序中我加了许多条件判断的语句,为了防止程序出现段错误而异常退出。下面就是我随便录入的几个信息,大家随便看看就行。
三、总结与收获
做完这个项目最大的收获是知道了什么叫学以致用,其中也遇到了许多问题,什么段错误,还有重复定义,函数未声明或者隐式声明什么的,总之问题很多,但是经过一步一步的找错调试,最后都完美的解决了,满满的成就感啊。其中我遇到的最大的问题就是函数调用以及传参的问题,当时在写管理员密码这一块,我一直在思考如何将主函数里定义的密码传到管理员菜单里面然后修改后又能保存到文件中以及下次进入如何读取修改后的管理员密码。当然最后都解决了,所以在写这种项目的时候,思路和逻辑一定要清晰,这样在写的过程中才不会乱。有时候遇到错误比代码还长,这时候更不能慌,细心查找总能解决。好了,大概就这么多,感谢观看!