一个简单的学生管理系统
1. 一些小技巧
在这之前需要一些小技巧~
1.1 刷新缓冲区
刷新缓冲区有三种方式
1. 利用getchar()
刷新
getchar
可以刷新缓冲区的一个字符。例如,之前经常会出现的莫名其妙的错误。
#include <stdio.h>
int main(void)
{
char ch;
int a;
scanf("%d", &a);
scanf("%c", &ch);
printf("%d %c\n", a, ch);
return 0;
}
当输入一个数字,再按回车时会出问题。
如下:
如果在第一个scanf后添加一个getchar()
就不会出现这样的错误。
2. getchar()
升级版
char *s_gets(char *st,int n)
{
char * ret_val = fgets(st, n, stdin);
int i = 0;
if(ret_val)
{
while(st[i] != '\n'&&st[i] != '\0')
i++;
if(st[i] == '\n')
st[i] = '\0';
else
while(getchar() != '\n')
continue;
}
return ret_val;
}
上述的函数时C Primer Plus中的一个函数,他的作用时限制输入的长度,并将缓冲区中多余的字符清除掉。这个函数的核心如下:
while(getchar() != '\n')
continue;
利用循环的getchar()
将多余的字符清除掉,以免影响后续操作。
3. 利用fflush()函数。
参数是:stdin、stdout。
stdin: 标准输入。
stdout: 标准输出。
stderr: 标准错误。
1.2 一些system的函数
system("cls")
清屏函数system("title 成绩管理系统")
将窗口的标题设置为“成绩管理系统”system("color xx")
颜色属性由两个十六进制数字指定 – 第一个为背景,第二个则为前景。每个数字可以为以下任何值之一:
0 = 黑色 8 = 灰色
1 = 蓝色 9 = 淡蓝色
2 = 绿色 A = 淡绿色
3 = 湖蓝色 B = 淡浅绿色
4 = 红色 C = 淡红色
5 = 紫色 D = 淡紫色
6 = 黄色 E = 淡黄色
7 = 白色 F = 亮白色system("mode con cols=90 lines=30");
设置窗口的高度和宽度,如上,设置窗口90列,30行。system("pasue");
1.3 分文件写
可以些一个或多个的头文件
这样做可以利用修改。
这个系统实现的一些功能,即菜单函数。
与学生成绩相关的无非是增、删、该、查,可以额外的使用到文件和排序等增加一些实用性。
void menu(void)
{
printf("\n\n\n\n\n\n");
printf("\t\t\t\t---------------欢迎来到成绩管理系统-----------------\n");
printf("\t\t\t\ta):查看所有学生的信息。\t\tb):添加学生的信息。\n");
printf("\t\t\t\tc):查看某个学生的信息。\t\td):删除学生的信息。\n");
printf("\t\t\t\tf):指定位置插入。\t\tg):更改学生信息。\n");
printf("\t\t\t\th):删除所有的数据。\t\ti):统计学生人数.\n");
printf("\t\t\t\ts):保存\t\t\t\tq):退出。\n");
}
程序的主体是用一个链表完成的,所以刚开始时,应该将之前存在文件中的数据读入链表。
建立链表需要的一些声明
typedef struct student {
char num[TSIZE];
char name[TSIZE];
float math;
float English;
float Chinese;
float sum;
float average;
}Item;
typedef struct node {
Item item;
struct node * next;
} Node;
将链表的数据域设置为一个结构体有利于后续的操作。
从上述代码中可以看出,我设置的学生信息有:学号、姓名、数学、英语和语文成绩。
程序开始的第一步,将数据读入链表
void CreatList(List * plist)
{
char str[100];
Item temp;
FILE *fp;
if((fp = fopen("学生成绩单.txt", "r")) == NULL){
fprintf(stderr, "不能打开“学生成绩单”文件。\n");
exit(EXIT_FAILURE);
}
InitializeList(plist);
while(fscanf(fp, "%s %s %f %f %f %f %f", temp.num, temp.name, &temp.math,
&temp.English, &temp.Chinese, &temp.sum, &temp.average) == 7)
{
AddItem(temp, plist);
}
fclose(fp);
}
尽量不要实用feof(),处理不恰当时可能会多判断一步。
AddItem()
函数的代码如下:
bool AddItem(Item item, List * plist){
Node * pnew;
Node * scan = *plist;
pnew = (Node *) malloc(sizeof(Node));
if(pnew == NULL)
return false;
CopyToNode(item,pnew);
pnew->next = NULL;
if(scan == NULL)
*plist = pnew;
else{
while(scan->next != NULL)
scan = scan->next;
scan->next = pnew;
}
return true;
}
这样可以方便添加学生。
第一个功能添加学生
添加学生这是一种默认的方式,将学生添加在链表的末尾。
void Addstudent(List * plist)
{
Item temp;
printf("请输入学生的姓名(按回车键结束):");
while(scanf("%s", temp.name) != EOF && temp.name[0] != 'q'){
printf("请输入学生的学号:");
scanf("%s", temp.num);
printf("请输入学生的数学成绩:");
scanf("%f", &temp.math);
printf("英语成绩:");
scanf("%f", &temp.English);
printf("语文成绩:");
scanf("%f", &temp.Chinese);
temp.sum = temp.math + temp.English + temp.Chinese;
temp.average = temp.sum / 3;
AddItem(temp, plist);
printf("请输入学生的姓名(按q键结束):");
}
}
最好不要每次只能添加一个,这样要每次调用添加命令,显得很麻烦。
第二个功能:显示所有学生的信息
void print(List * plist){
if(ListIsEmpty( plist )){
fprintf(stderr, "没有数据!!!\n");
return ;
}
printf("学号\t\t姓名\t数学\t英语\t语文\t总成绩\t平均\n");
Node * p = *plist;
while(p != NULL)
{
printf("%-12s\t%s\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\n",p->item.num,
p->item.name, p->item.math,p->item.English, p->item.Chinese, p->item.sum, p->item.average);
p = p->next;
}
}
整个程序都要注意的要点是一点要注意一些特殊情况。
这个程序的要点是一定要将信息对其。
第三个功能:查看某个学生的信息
bool cha(List * plist)
{
char ch;
char num[TSIZE];
printf("请输入你想查看的学生的学号:");
scanf("%s", &num);
if(ListIsEmpty(plist)){
printf("没有数据,无法查看!!\n");
return false;
}
Node * p = *plist;
while(p->next != NULL && strcmp(p->item.num, num)){
p = p->next;
}
if(strcmp(p->item.num, num) != 0 && p->next == NULL){
fprintf(stderr, "没有找到持学生的信息");
return false;
}
printf("m):数学\te):英语\tc):语文\tq):退出\n");
getchar();
while(1){
printf("请书输入你想查找的科目:");
fflush(stdin);
ch = getchar();
while(getchar() != '\n')
continue;
switch(ch){
case 'm':
printf("%s的数学成绩是:%.2lf\n", p->item.name, p->item.math);
break;
case 'e':
printf("%s的英语成绩是:%.2lf\n", p->item.name, p->item.English);
break;
case 'c':
printf("%s的语文成绩是:%.2lf\n", p->item.name, p->item.Chinese);
break;
case 'q':
return true;
default:
printf("请输入正确的指令!!\n");
}
}
return false;
}
第四个功能:删除学生的信息
bool ListDelete(List * plist, char num[])
{
Node * p, * q;
p = *plist;
if(ListIsEmpty(plist)){
fprintf(stderr, "没有数据,无法删除!!\n");
return false;
}
if(strcmp(p->item.num, num) == 0){
*plist = p->next;
return true;
}
else{
while(p){
if(strcmp(p->item.num, num) == 0){
q->next = p->next;
free(p);
return true;
}
q = p;
p = p->next;
}
}
return false;
}
这个函数的功能是删除学生的信息,它的方式是传递一个学生的学号,然后按照学号删除。当然你也可以在这个函数里输入学生的学号。
能删除学生的信息,那么就能改变学生的信息。
第五个功能:改变学生的信息
改变学生的信息有两种思路,全改或者部分改。
bool gai(List * plist){
char ch;
char num[TSIZE];
printf("请输入你想改的学生的学号:");
scanf("%s", num);
if(ListIsEmpty(plist))
return false;
Node * p = *plist;
while(p->next != NULL && strcmp(p->item.num, num) != 0){
p = p->next;
}
if(strcmp(p->item.num, num) != 0 && p->next == NULL){
return false;
}
printf("是否讲一个学生的信息完全改变(Y:是 N:否):");
fflush(stdin);
ch = getchar();
while(getchar() != '\n')
continue;
if(ch == 'Y'){
printf("请输入学生的姓名:");
scanf("%s", p->item.name);
printf("请输入学生的学号:");
scanf("%s", p->item.num);
printf("请输入学生的数学成绩:");
scanf("%f", &p->item.math);
printf("英语成绩:");
scanf("%f", &p->item.English);
printf("语文成绩:");
scanf("%f", &p->item.Chinese);
p->item.sum = p->item.math + p->item.English + p->item.Chinese;
p->item.average = p->item.sum / 3;
}
else{
char ch2;
printf("请输入你想更改的科目:\na):数学\tb):英语\tc):语文\t\n");
printf("d):学号\tf):姓名\tq):退出\n");
while(1){
printf("请选择:");
fflush(stdin);
ch2 = getchar();
while(getchar() != '\n')
continue;
switch(ch2){
case 'a':
printf("请输入学生的数学成绩:");
scanf("%f", &p->item.math);
getchar();
break;
case 'b':
printf("请输入学生的英语成绩:");
scanf("%f", &p->item.English);
getchar();
break;
case 'c':
printf("请输入学生的语文成绩:");
scanf("%f",&p->item.Chinese);
getchar();
break;
case 'd':
printf("请输入学生的学号:");
scanf("%s", p->item.num);
getchar();
break;
case'f':
printf("请输入学生的名字:");
scanf("%s", p->item.name);
getchar();
break;
case 'q':
p->item.sum = p->item.math + p->item.English + p->item.Chinese;
p->item.average = p->item.sum / 3;
return true;
default:
printf("请输入有效的命令!!\n");
}
}
}
return false;
}
第六个功能:插入
既然是插入,那么每个位置都要能插。当插在某个学生的后面时,第一个位置就插不到。那个就需要选择。当然当没有任何学生信息时,还要询问是否插在最开始。
bool ListInsert(List * plist)
{
Node *q = *plist;
char ch;
Node * p;
p = (struct node *) malloc(sizeof(struct node));
if(ListIsEmpty(plist)){
printf("没有数据,只能插在最开始。\n是否插在最开始?(Y:是 N:退出)\n");
char ch_1;
fflush(stdin);
ch_1 = getchar();
while(getchar() != '\n')
continue;
if(ch_1 == 'Y'){
ch == 'Y';
}
else
return false;
}
else{
printf("请问是否插在最开始?(Y:是 N:否)\n");
fflush(stdin);
ch = getchar();
while(getchar() != '\n')
continue;
}
if(ch == 'Y'){
*plist = p;
p->next = q;
}
else
{
char num[TSIZE];
List r;
printf("请输入插入后学生之前的那一个学生的学号:");
scanf("%s", num);
while(q != NULL && strcmp(q->item.num, num) != 0){
q = q->next;
}
if(q == NULL)
return false;
if(q->next == NULL){
q->next = p;
p->next = NULL;
}
else
{
r = q->next;
q->next = p;
p->next = r;
}
}
printf("请输入学生的姓名:");
scanf("%s", p->item.name);
printf("请输入学生的学号:");
scanf("%s", p->item.num);
printf("请输入学生的数学成绩:");
scanf("%f", &p->item.math);
printf("英语成绩:");
scanf("%f", &p->item.English);
printf("语文成绩:");
scanf("%f", &p->item.Chinese);
p->item.sum = p->item.math + p->item.English + p->item.Chinese;
p->item.average = p->item.sum / 3;
return true;
}
第七个功能:排序
List SelectSort(List head)
{
List pfirst; /* 排列后有序链的表头指针 */
List ptail; /* 排列后有序链的表尾指针 */
List pminBefore; /* 保留键值更小的节点的前驱节点的指针 */
List pmin; /* 存储最小节点 */
List p; /* 当前比较的节点 */
if(head == NULL){
fprintf(stderr, "没有数据。\n");
return head;
}
if(head->next == NULL){
fprintf(stderr, "只有一组数据无法排序!!\n");
return head;
}
pfirst = NULL;
while (head != NULL) /*在链表中找键值最小的节点。*/
{
/* 注意:这里for语句就是体现选择排序思想的地方 */
for (p = head, pmin = head; p->next != NULL; p = p->next) /*循环遍历链表中的节点,找出此时最小的节点。*/
{
if (strcmp(pmin->item.num, p->next->item.num) > 0) /*找到一个比当前min小的节点。*/
{
pminBefore = p; /*保存找到节点的前驱节点:显然p->next的前驱节点是p。*/
pmin = p->next; /*保存键值更小的节点。*/
}
}
/*上面for语句结束后,就要做两件事;一是把它放入有序链表中;二是根据相应的条件判断,安排它离开原来的链表。*/
/*第一件事*/
if (pfirst == NULL) /* 如果有序链表目前还是一个空链表 */
{
pfirst = pmin; /* 第一次找到键值最小的节点。 */
ptail = pmin; /* 注意:尾指针让它指向最后的一个节点。 */
}
else /* 有序链表中已经有节点 */
{
ptail->next = pmin; /* 把刚找到的最小节点放到最后,即让尾指针的next指向它。*/
ptail = pmin; /* 尾指针也要指向它。 */
}
/*第二件事*/
if (pmin == head) /* 如果找到的最小节点就是第一个节点 */
{
head = head->next; /* 显然让head指向原head->next,即第二个节点,就OK */
}
else /*如果不是第一个节点*/
{
pminBefore->next = pmin->next; /*前次最小节点的next指向当前pmin的next,这样就让pmin离开了原链表。*/
}
}
if (pfirst != NULL) /*循环结束得到有序链表first */
{
ptail->next = NULL; /*单向链表的最后一个节点的next应该指向NULL */
}
head = pfirst;
return head;
}
这里是一学号排序的。
还有一些其他的操作就不赘述了。
下面是一个简单的main函数
int main(void)
{
List head;
CreatList(&head);
int ch;
menu();
printf("请选择你想选择的操作:");
ch = getchar();
while(getchar() != '\n')
continue;
while(1){
switch(ch){
case 'a':
print(&head);
break;
case 'b' :
Addstudent(&head);
count++;
break;
case 'c':
cha(&head);
break;
case 'd':
char num_1[TSIZE];
printf("请输入你想删除的学生的学号:");
scanf("%s", num_1);
if(ListDelete(&head, num_1)){
printf("已删除。\n");
}
else{
printf("删除失败。\n");
}
count++;
break;
case 'f':
if(ListInsert(&head)){
printf("插入成功!!\n");
count++;
}
else{
printf("插入失败!!\n");
}
break;
case 'g':
if(!gai(&head)){
fprintf(stderr, "没有找到持学生的信息");
}
count++;
break;
case 'h':
int ch_2;
getchar();
printf("是否将所有数据都删除(Y:是 N:否):");
ch_2 = getchar();
if(ch_2 == 'Y'){
EmptyTheList(&head);
InitializeList(&head);
count++;
}
break;
case 'i':
printf("储存有%d个学生的成绩。\n", ListItemCount(&head));
break;
case 's':
save(&head);
break;
case 'm':
menu();
break;
case 'p':
head = SelectSort(head);
count++;
break;
case 'q':
if(count != 0 && count2 == 0)
{
char ch2;
printf("是否保存?(Y:是 N:否)\n");
fflush(stdin);
ch2 = getchar();
if(ch2 == 'Y')
save(&head);
}
return 0;
default:
printf("请书如正确的指令!!\n");
}
printf("\n\n");
fflush(stdin);
printf("请选择你想选择的操作:");
ch = getchar();
while(getchar() != '\n')
continue;
}
return 0;
}
总结
当然这个还有好多的优化,这里提供的仅仅只是一个思想,这个程序最大的缺点是缺少错误处理。
优化措施:
1. 增加错误处理,当输入学生的信息出错是提醒并重新输入。
2. 学号查重。
3. 美化界面等。
相信优秀的你能够写出更好的程序。