1. 需求分析
(1) 实现一个学生信息管理系统,其中学生属性包含了:学号,姓名,语文成绩,数学成绩,英语成绩。
(2) 设计合理的线性数据存储结构存储学生的信息。
(3) 可实现学生信息的插入。
(4) 可按学号、语文成绩、英语成绩、数学成绩实现学生信息的检索,且可以输出各科平均成绩。
(5) 实现学生信息的修改。
(6) 实现学生信息的删除。
(7) 可按照姓名进行模糊查询,比如:输入“王”,可以输出姓名中包含“王”的所有同学。
(8) 可将学生的信息以文件的形式保存。
(9) 可以以文件的形式将学生信息加载到系统中。
(10) 可以保存、查询被删除的信息。
2. 概要设计
(1)采用带头节点的单链表来存储学生信息
typedef struct LNode {
stuElem stu; // 数据域
struct LNode *next; // 指针域
} LNode, *LinkList;
主要函数
Status Init_list(LinkList &L);//带头结点的链表
LNode* MakeNode_L(stuElem e);// 构造数据域为e的单链表结点
Status InsertAfter_L(LNode *p, LNode *q);// 在p结点之后插入q结点
Status CreatList_L(LinkList &L, int n, stuElem *A);// 建立一个长度为n的单链 表,数组A[]提供n个元素值
void ListTraverse_L(LinkList L, Status (*Visit)(stuElem e));// 遍历单链表L
int ListLength_L(LinkList L);//链表长度
int getPosition(LinkList L, long long int str);//元素str在链表中的位置
Status findStu(LinkList L,long long int studentNum,stuElem &s);//查找链表是否 有某元素,有用s返回
Status Insert_L(LinkList L, int i, stuElem e);// 在第i个位置插入元素
Status Delete_L(LinkList L, int i, stuElem &e);// 删除第i个元素
Status Delete_L1(LinkList L, int i); // 删除第i个元素,用于修改信息
Status DestroyList_L(LinkList &L); // 销毁带头结点链表
(2)学生结构体的定义
typedef struct{
long long int stuNumber;
char name[N_max];
int math;
int chinese;
int english;
}stuElem;
(3)其他的一些定义
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OVERFLOW 0
#define MAX_RECORD 100
#define LL_max 9999999999//学号限制
#define score_max 100//分数限制
#define N_max 20
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef int Status;
(4)主体函数
void menu();//菜单函数
void keyDown();//选择函数,用于人机的信息交互
void out();//退出系统,实现退出系统的功能
void inputData();//插入信息函数,实现学生信息的录入(文件形式、手动形式)
void printAll();//浏览信息,输出所有学生信息:可以选择按学号排序,按语文成绩排序,按数学成绩排序,按英语成绩排序,排序算法采用了改进的冒泡排序
void deleteData();//删除信息实现,实现学生信息的删除,主要靠链表的删除特点节点操作
void modifyData();//修改信息实现 ,实现学生信息的修改
void lookUp();//查找信息实现,查找学生信息:可以按学号查找、按姓名模糊查找,也可以查找成绩为xx的 学生有哪些
3. 设计实现图解
4. 调试分析
在整个调式的过程中,较为艰难的是文件操作与链表的结合方面,要保证每次操作都可以正确地把数据读入链表,才能往下进行一系列操作,也由于文件操作部分需要自行学习,所有这一过程花了不少时间。
5. 用户使用说明
(1) 对于菜单的选项,用于要想进行某操作,则应该选择该操作对应的序号:0~6,输入其它数字的话,系统会提示你,让你再次输入你的选择
(2) 在操作过程中,用户会遇到较多的交互界面,但实际上都一样,选择对应的数字即可,若你选择了没有给出的数字,系统会提示你输入错误并且退到菜单界面。
(3) 对于学生属性等信息,用户在输入时应该注意:
学号的范围是0到9999999999,成绩范围是0到100,如果输入错误信息,系统会提示你让你再次输入
(4) 文件操作部分:
① 如果用户选择用文件形式来插入信息,最好把该文件一起放在项目文件夹里,这样的话插入信息时直接输入该文件的文件名即可;如果文件放在其他地方,主要要输入改文件的绝对路径;文本内容从左到右依次为:学号\t姓名\t语文成绩\t数学成绩\t英语成绩。
② 我在项目文件夹内用record.txt文件来记录保存被删除的信息,用户不能去修改它的名称或者内容,否则会导致无法读取被删除信息或是读取不准确(你修改文件内容的话);文本内容从左到右依次为:学号\t姓名\t语文成绩\t数学成绩\t英语成绩。
③ 系统的学生信息保存在项目文件夹中的student_data.txt文件中,像上一个一样,不要对该文件进行修改;文本内容从左到右依次为:学号\t姓名\t语文成绩\t数学成绩\t英语成绩。
④ 注意:为了防止文件读取时,读取汉字会出现乱码的现象,所有涉及到的txt文件的编码格式都应该是ANSI。修改方式:以笔记本方式打开文本文档——>点击“文件”——>点击“另存为”——>将下方的“UTF-8”改成“ANSI”——>点击“保存”。
6. 文件目录
7. 代码附上
myList.h
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OVERFLOW 0
#define MAX_RECORD 100
#define LL_max 9999999999//学号限制
#define score_max 100//分数限制
#define N_max 20
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef int Status;
typedef struct{
long long int stuNumber;
char name[N_max];
int math;
int chinese;
int english;
}stuElem;
typedef struct LNode {
stuElem stu; // 数据域
struct LNode *next; // 指针域
} LNode, *LinkList;
stuElem * make_array(int n);
Status Init_list(LinkList &L);//带头结点的链表
LNode* MakeNode_L(stuElem e);// 构造数据域为e的单链表结点
Status InsertAfter_L(LNode *p, LNode *q);// 在p结点之后插入q结点
Status CreatList_L(LinkList &L, int n, stuElem *A);// 建立一个长度为n的单链表,数组A[]提供n个元素值
Status printElement(stuElem e);//打印元素
Status printElement1(stuElem e);//打印元素 ,用于查找元素
void ListTraverse_L(LinkList L, Status (*Visit)(stuElem e));// 遍历单链表L
int ListLength_L(LinkList L);//链表长度
int getPosition(LinkList L, long long int str);//元素str在链表中的位置
Status findStu(LinkList L,long long int studentNum,stuElem &s);//查找链表是否有某元素,有用s返回
Status Insert_L(LinkList L, int i, stuElem e);// 在第i个位置插入元素
Status Delete_L(LinkList L, int i, stuElem &e);// 删除第i个元素
Status Delete_L1(LinkList L, int i); // 删除第i个元素,用于修改信息
Status DestroyList_L(LinkList &L); // 销毁带头结点链表
LinkList L;
stuElem *save = (stuElem *)malloc(MAX_RECORD*sizeof(stuElem));
int record = 0;//记录被删除的信息
Status checkInput(long long int iNum,int math,int chinese,int english){
if(iNum < 0 || iNum > LL_max)
{
if(math < 0 || math > score_max || english < 0 || english > score_max || chinese < 0 || chinese > score_max)
{
printf("输入的学号和成绩有误!请再次输入\n");
return ERROR;
}else{
printf("输入的学号有误!请再次输入\n");
return ERROR;
}
}
if(math < 0 || math > score_max || english < 0 || english > score_max || chinese < 0 || chinese > score_max)
{
printf("输入的成绩有误!请再次输入\n");
return ERROR;
}
return TRUE;
}
stuElem * make_array(int n){
stuElem *stu;
int i = 0;
stu = (stuElem *)malloc(n*sizeof(stuElem));
while(n)
{
do{
printf("学号:");
scanf("%lld",&stu[i].stuNumber);
printf("学生姓名:");
scanf("%s",&stu[i].name);
printf("数学:");
scanf("%d",&stu[i].math);
printf("语文:");
scanf("%d",&stu[i].chinese);
printf("英语:");
scanf("%d",&stu[i].english);
}while(checkInput(stu[i].stuNumber,stu[i].math,stu[i].chinese,stu[i].english) == ERROR);
i++;
n--;
}
return stu;
}
Status Init_list(LinkList &L){//带头结点的链表
L = (LNode *)malloc(sizeof(LNode));
if(NULL == L)return OVERFLOW;
L->next = NULL;
return OK;
}
LNode* MakeNode_L(stuElem e) { // 构造数据域为e的单链表结点
LNode *p;
p = (LNode*)malloc(sizeof(struct LNode)); // 分配结点空间
if (p!=NULL){
p->stu = e;
p->next = NULL;
}
return p;
}
Status InsertAfter_L(LNode *p, LNode *q) { // 在p结点之后插入q结点
if(NULL==p || NULL==q) return ERROR; // 参数不合理
q->next = p->next; // 修改q结点的指针域
p->next = q; // 修改p结点的指针域
return OK;
}
Status CreatList_L(LinkList &L, int n, stuElem *A) {
// 建立一个长度为n的单链表,数组A[]提供n个元素值
LNode *p, *q;
int i;
if(OVERFLOW==Init_list(L)) return OVERFLOW;
p = L;
for(i=0; i<n; i++) { // 依次在表尾插入n个结点
q = MakeNode_L(A[i]);
InsertAfter_L(p, q);
p = q; // 令p指向新插入结点
}
return OK;
}
Status printElement(stuElem e){//打印元素
printf("%lld\t%s\t\t%d\t%d\t%d\n",e.stuNumber,e.name,e.chinese,e.math,e.english);
return OK;
}
Status printElement1(stuElem e){//打印元素 ,用于查找元素
printf("学号:%lld\t姓名:%s\t\t语文:%d\t数学:%d\t英语:%d\n",e.stuNumber,e.name,e.chinese,e.math,e.english);
return OK;
}
void ListTraverse_L(LinkList L, Status (*Visit)(stuElem e)){
// 遍历单链表L
if(NULL == L->next)return;
LNode *p;
p = L->next;
printf("学号\t\t姓名\t\t语文\t数学\t英语\n");
while(p != NULL){
(* Visit)(p->stu);
p = p->next;
}
}
int ListLength_L(LinkList L)
{
int res=0;
LNode *p;
if(NULL==L)return -1;
p=L->next;
while(p!=NULL)
{
res++;
p=p->next;
}
return res;
}
int getPosition(LinkList L, long long int str){
if(NULL == L || NULL == L->next)return -1;
LNode *p;
int res = 1;
p = L->next;
while(p != NULL){
if(str == p->stu.stuNumber)return res;
res ++;
p = p->next;
}
if(NULL == p)return -1;
}
Status findStu(LinkList L,long long int studentNum,stuElem &s){
if(NULL == L || L->next == NULL)return ERROR;
LNode *p;
p = L->next;
while(p != NULL){
if(studentNum == p->stu.stuNumber){
s = p->stu;
return OK;
}
p = p->next;
}
return ERROR;
}
Status Insert_L(LinkList L, int i, stuElem e)
{ // 在第i个位置插入元素
LinkList p,q;
q = (LNode *)malloc(sizeof(LNode));
p = (LNode *)malloc(sizeof(LNode));
if(L == NULL|| i<1)return ERROR;
q->stu = e;
p=L;
int j=0;
while(p!=NULL&&j<i-1)
{
p=p->next;
j++;
}
if(NULL == p)return ERROR;
q->next = p->next;
p->next = q;
return OK;
}
Status Delete_L(LinkList L, int i, stuElem &e)
{ // 删除第i个元素
int j;
LinkList p,s;
if(NULL==L||i<1)return ERROR;
p=L;
j=0;
while(p!=NULL&&j<i-1)
{
p=p->next;
j++;
}
if(NULL==p||(NULL==p->next))return ERROR;
s = p->next;
e = s->stu;
p->next = s->next;
free(s);
return OK;
}
Status Delete_L1(LinkList L, int i)
{ // 删除第i个元素,用于修改信息
int j;
LinkList p,s;
if(NULL==L||i<1)return ERROR;
p=L;
j=0;
while(p!=NULL&&j<i-1)
{
p=p->next;
j++;
}
if(NULL==p||(NULL==p->next))return ERROR;
s = p->next;
p->next = s->next;
free(s);
return OK;
}
Status DestroyList_L(LinkList &L)
{ // 销毁带头结点链表
LNode *p,*q;
if(NULL==L)return FALSE;
q=L;
while(q!=NULL)
{
p=q;
q=q->next;
free(p);
}
L=NULL;
return TRUE;
}
menu.h
#include <stdio.h>
#include <stdlib.h>
#include "myList.h"
void bubblesort(stuElem stu[],int n,int key);//冒泡排序,key为排序类型:学号、语文、数学、英语
void sortBy_(LinkList &L,int u_key);//排序函数
void menu();//菜单函数
void saveFile(char *fileName,LinkList L);//保存文件
void savedel(char *fileName,stuElem stu);//保存删除的信息
void readFile(char *fileName,LinkList L);//该txt文件保存时要修改编码格式为ANSI
void read_del(char *fileName);//该文件保存时要修改编码格式为ANSI ,查看被删除记录
void findBy_wholeNum();//根据学号查找信息
void findByName(LinkList L,char str[20]);//根据姓名关键字查找信息
void findByScore(LinkList L);//根据分数查找
void print_average(LinkList L);//各科平均分
void keyDown();//选择函数
void out();//退出系统
void inputData();//插入信息函数
void printAll();//浏览信息
void deleteData();//删除信息实现
void modifyData();//修改信息实现
void lookUp();//查找信息实现
void menu(){
printf("-----------------------【学生管理系统】----------------------\n");
printf("\t\t\t0.退出系统\n");
printf("\t\t\t1.插入信息\n");
printf("\t\t\t2.浏览信息\n");
printf("\t\t\t3.删除信息\n");
printf("\t\t\t4.修改信息\n");
printf("\t\t\t5.查找信息\n");
printf("\t\t\t6.已删信息\n");
printf("-------------------------------------------------------------\n");
}
void keyDown(){
int uerKey;
printf("请选择你要进行的操作(整数0~6):");
scanf("%d",&uerKey);
switch(uerKey){
case 0:
out();
break;
case 1://要考虑到是否是第一次写入
inputData();
saveFile("student_data.txt",L);
break;
case 2:
if(L->next == NULL)readFile("student_data.txt",L);
printAll();
break;
case 3:
if(L->next == NULL)readFile("student_data.txt",L);//
deleteData();
saveFile("student_data.txt",L);
break;
case 4:
if(L->next == NULL)readFile("student_data.txt",L);
modifyData();
saveFile("student_data.txt",L);
break;
case 5:
if(L->next == NULL)readFile("student_data.txt",L);
lookUp();
break;
case 6:
read_del("record.txt");
break;
default:
printf("输入有误!请选择1~6的整数!\n");
break;
}
}
void print_average(LinkList L){
float ave_ch,ave_math,ave_eng;
int sum1=0,sum2=0,sum3=0;
int length = ListLength_L(L);
LNode *p;
p = L->next;
while(p != NULL){
sum1 += p->stu.chinese;
sum2 += p->stu.math;
sum3 += p->stu.english;
p = p->next;
}
ave_ch = (float)sum1/length;
ave_math = (float)sum2/length;
ave_eng =(float) sum3/length;
printf("语文平均分:%.2f\t数学平均分:%.2f\t英语平均分:%.2f\n",ave_ch,ave_math,ave_eng);
}
void bubblesort(stuElem stu[],int n,int key)
{
int i,j;
stuElem temp;
Status change = TRUE;
for(i=n-1;i>1&&change;--i){
change = FALSE;
switch(key){
case 1:
for(j=0;j<i;j++)
if(stu[j].stuNumber > stu[j+1].stuNumber){
temp = stu[j];
stu[j] = stu[j+1];
stu[j+1] = temp;
change = TRUE;
}
break;
case 2:
for(j=0;j<i;j++)
if(stu[j].chinese < stu[j+1].chinese){
temp = stu[j];
stu[j] = stu[j+1];
stu[j+1] = temp;
change = TRUE;
}
break;
case 3:
for(j=0;j<i;j++)
if(stu[j].math < stu[j+1].math){
temp = stu[j];
stu[j] = stu[j+1];
stu[j+1] = temp;
change = TRUE;
}
break;
case 4:
for(j=0;j<i;j++)
if(stu[j].english < stu[j+1].english){
temp = stu[j];
stu[j] = stu[j+1];
stu[j+1] = temp;
change = TRUE;
}
break;
default:
break;
}
}
}
void sortBy_(LinkList &L,int u_key){//排序函数
int length,i=0;
length = ListLength_L(L);
stuElem *stu =(stuElem *)malloc(length*sizeof(stuElem));
if(NULL == L->next)return;
LNode *p;
p = L->next;
while(p != NULL){
stu[i++] = p->stu;
p = p->next;
}
bubblesort(stu,length,u_key);
DestroyList_L(L);
CreatList_L(L,length,stu);
}
void saveFile(char *fileName,LinkList L){//保存文件
FILE *fp = fopen(fileName,"w");
LNode *p;
p = L->next;
while(p){
fprintf(fp,"%lld\t%s\t%d\t%d\t%d\n",
p->stu.stuNumber,p->stu.name,p->stu.chinese,p->stu.math,p->stu.english);
p = p->next;
}
fclose(fp);
}
void savedel(char *fileName,stuElem stu){//保存删除的信息
FILE *fp = fopen(fileName,"a");
fprintf(fp,"%lld\t%s\t%d\t%d\t%d\n",stu.stuNumber,stu.name,stu.chinese,stu.math,stu.english);
fclose(fp);
}
void readFile(char *fileName,LinkList L){//该文件保存时要修改编码格式为ANSI
FILE *fp = fopen(fileName,"r");
if(fp == NULL){
printf("找不到该文件!\n");
}
stuElem stu;
int i = 1;
while(fscanf(fp,"%lld\t%s\t%d\t%d\t%d\n",&stu.stuNumber,&stu.name,&stu.chinese,&stu.math,&stu.english) != EOF){
Insert_L(L,i,stu);
i++;
}
fclose(fp);
}
void read_del(char *fileName){//该文件保存时要修改编码格式为ANSI ,查看被删除记录
FILE *fp = fopen(fileName,"r");
if(fp == NULL){
printf("找不到该文件!\n");
}
long long int stuNumber;
char name[N_max];
int math,chinese,english;
while(fscanf(fp,"%lld\t%s\t%d\t%d\t%d\n",&stuNumber,&name,&chinese,&math,&english) != EOF){
printf("%lld\t%s\t%d\t%d\t%d\n",stuNumber,name,chinese,math,english);
}
fclose(fp);
}
void out(){//退出系统
printf("退出系统\n");
system("pause");
exit(0);
}
void inputData(){//插入信息函数
int key;
printf("插入信息\n请选择录入方式:\t1.手动输入\t2.文件录入\n");
scanf("%d",&key);
switch(key){
case 1:
int num,length,i;
printf("插入信息\n");
printf("录入个数为:");
scanf("%d",&num);
stuElem *stu1;
stu1 = make_array(num);
length = ListLength_L(L);
for(i=0;i<num;i++){
Insert_L(L,length+i+1,stu1[i]);
}
break;
case 2:
char str[20];
printf("请输入文件名:");
scanf("%s",&str);
readFile(str,L);
break;
default:
printf("输入错误!请选择1或2!");
}
}
void printAll(){//浏览信息
printf("浏览信息\n");
if(NULL == L || L->next == NULL){
printf("目前没有任何信息!\n");
return;
}
int u_key;
printf("选择排序的标准(学号小到大,成绩大到小);\n\t\t1.学号\t\t2.语文成绩\t\t3.数学成绩\t\t4.英语成绩\n");
scanf("%d",&u_key);
if(u_key != 1&& u_key != 2 && u_key != 3 && u_key != 4)
{
printf("输入有误!请选择整数1~4!\n");
return;
}
sortBy_(L,u_key);
ListTraverse_L(L,printElement);
print_average(L);
}
void deleteData(){//删除信息实现
printf("删除信息\n");
if(NULL == L || NULL == L->next){
printf("没有可以删除的数据!\n");
return;
}
long long int stu_number;
int pos;
printf("请输入你要删除的学生的学号:");
scanf("%lld",&stu_number);
pos = getPosition(L,stu_number);
if(Delete_L(L,pos,save[record]) == OK)
{
printf("删除成功!\n");
savedel("record.txt",save[record]);
record ++;
}else{
printf("删除失败!该学生的相关信息不存在!\n");
}
}
void modifyData(){//修改信息实现
printf("修改信息\n");
long long int student;
int pos;
do{
printf("输入要修改信息的学生学号:");
scanf("%lld",&student);
}while(checkInput(student,0,0,0) == ERROR);
pos = getPosition(L,student);
if(-1 == pos){
printf("没有该学生的信息!\n");
return;
}else{
stuElem stu;
do{
printf("请输入修改后的信息:\n");
printf("学号:");
scanf("%lld",&stu.stuNumber);
printf("学生姓名:");
scanf("%s",&stu.name);
printf("数学:");
scanf("%d",&stu.math);
printf("语文:");
scanf("%d",&stu.chinese);
printf("英语:");
scanf("%d",&stu.english);
}while(checkInput(stu.stuNumber,stu.math,stu.chinese,stu.english) == ERROR);
Delete_L1(L,pos);
if(Insert_L(L,pos,stu) == OK)
printf("修改成功!\n");
}
}
void findBy_wholeNum(){//根据学号查找信息
long long int student;
stuElem res;
do{
printf("输入所要查找的学生的学号:");
scanf("%lld",&student);
}while(checkInput(student,0,0,0) == ERROR);
if(ERROR == findStu(L,student,res)){
printf("没有该学生的信息!\n");
return;
}else{
printf("查找成功!\n");
printElement1(res);
}
}
void findByName(LinkList L,char str[20]){//根据姓名关键字查找信息
if(NULL == L || NULL == L->next)printf("系统还没有信息!\n");
else{
LNode *p;
p = L->next;
int flag = 0;
while(p != NULL){
if(strstr(p->stu.name,str) != NULL){
printElement1(p->stu);
flag = 1;
}
p = p->next;
}
if(!flag)printf("查找失败!\n");
}
}
void findByScore(LinkList L){
if(NULL == L || NULL == L->next)printf("系统还没有信息!\n");
else{
printf("选择分数类型:\t1.语文\t2.数学\t3.英语\n");
int key;
scanf("%d",&key);
while(key !=1&&key!=2&&key!=3){
printf("输入错误!请输入正确的数字!\n");
scanf("%d",&key);
}
int score;
printf("输入分数:");
scanf("%d",&score);
LNode *p;
p = L->next;
int flag = 0;
while(p != NULL){
switch(key){
case 1:
if(score == p->stu.chinese){
printElement1(p->stu);
flag = 1;
}
break;
case 2:
if(score == p->stu.math){
printElement1(p->stu);
flag = 1;
}
break;
case 3:
if(score == p->stu.english){
printElement1(p->stu);
flag = 1;
}
break;
}
p = p->next;
}
if(!flag)printf("查找失败!\n");
}
}
void lookUp(){//查找信息实现
printf("查找信息\n");
printf("选择查找模式\n\t\t1.学号查找\n\t\t2.姓名模糊查找\n\t\t3.按分数查找\n输入整数1或2或3:");
int f_key;
scanf("%d",&f_key);
switch(f_key){
case 1:
findBy_wholeNum();
break;
case 2:
printf("输入姓名关键字:");
char k_str[20];
scanf("%s",&k_str);
findByName(L,k_str);
break;
case 3:
findByScore(L);
break;
default:
printf("选择错误!请选择数字1或2或3!");
break;
}
}
myList_test.cpp
#include "menu.h"
int main(){
Init_list(L);
while(1){
menu();
keyDown();
system("pause");
system("cls");
}
DestroyList_L(L);
return 0;
}
最后的最后,整体的设计并没有采用什么比较难的算法或者高明的实现手段(本人实力不允许啊);笔者认为,写这类型的代码,应该注意:首先是要选好你的存储结构,把它实现了、玩明白了,在加入你想要的东西(这里就是学生类型的元素),再者就是先定下你的菜单功能,分布实现,逐步逐步来,写的代码才不会显得乱、没有逻辑。