(现已添加文件内容)
(版本:2.0,修改内容:排序,存储,装入)
要求:
首先对题目进行简单的分析:数据管理,只需要简单的链表即可完成。管理的方式有录入(将从键盘中打入的数据加入链表的最后),存储(将程序的学生数据存储到文件中),装入(将文件中的学生数据装入程序中),修改(修改学生数据中的某一项),插入(将学生数据插入到链表之中),删除(删除学生数据),查找(通过学号查找学生的数据),排序(将学生由学号从小到大排序),显示(显示全部学生数据)。
难点非常清楚:1.文件和程序的数据相互转移。2.链表的排序。
1.模块化编程。
为了使得代码的修改和理解更加简单,在本课设中我推荐用模块化编程。
这种大程序少说也有500行,如果只放在一个文件,将会导致深度过深,代码又长又臭。
为了实现模块化编程,在这里我会讲一下模块化编程的简单实现。
A.创建头文件
很多大学生都忽视了头文件的重要性,通过一个头文件,我们可以将一个结构体用于两个c文件中(在本课设中充分体现)。有了头文件,以后若需要使用到现在编写的代码,也可以通过头文件快速调用。头文件的编写如下:
其中最重要的就是用红色框圈定的部分。这是每个头文件不可缺少的内容,ifndef和define后面的内容就是头文件名,格式入上,注意前面有两个下划线。
将c文件中的函数放在头文件中声明,将结构体也放在头文件中使用,将会大大减少c文件的编写量。
B.在c文件中的引用
引用方式如下图
用该格式即可引用头文件。
2.整体代码展示
整体代码先放在这里,在后文将会一一解释。
main.cpp文件代码
#include <stdio.h>
#include "line_list.h" //线性表操作头文件
#include <stdlib.h>
extern struct Student;
//名称:学生数据管理。
//内容:录入,存储,装入,修改,插入,删除,查找,排序,显示。
//数据结构:线性表。
//数据对象:班或系的学生记录。
//学生记录:学号,姓名,年龄,性别,家庭住址,入学时间,家庭电话号码。
int main()
{
struct Student* head; //学生数据的头指针。
int state= -1; //初始状态,以进入和在选择操作中使用。
head = (struct Student* )malloc(150);
head->pNextNode=NULL;
printf("欢迎使用本学生数据管理系统");
while(state!=0)
{
printf("请输入您想要进行的操作的数字序号:0.退出 1.录入 2.修改 3.插入 4.删除 5.查找 6.排序 7.显示 8.装入 9.存储\n");
scanf("%d",&state);
getchar(); //吃回车
while(state==1)
{
List_LuRu(head); //将尾指针放进去,新建链表来改变尾指针,最后将尾指针赋值回去。
printf("如果需要继续录入,则输入1;退出录入,则输入-1。\n");
scanf("%d",&state);
getchar();
}
while(state==2)
{
int lin,col; //输入序号和列数以修改
List_XianShi(head); //显示帮助输入
printf("请问您要修改序号几的第几列内容?(一次修改一个内容,空格隔开输入)\n");
scanf("%d %d",&lin,&col);
List_XiuGai(head,lin,col); //修改函数
printf("如果需要继续修改,则输入2;退出修改,则输入-1。\n");
scanf("%d",&state);
}
while(state==3)
{
int lin; //输入序号
List_XianShi(head); //显示帮助输入
printf("请问您要插入到第几序号后:\n");
scanf("%d",&lin);
List_ChaRu(head,lin); //插入函数
printf("如果需要继续插入,则输入3;退出插入,则输入-1。\n");
scanf("%d",&state);
}
while(state==4)
{
int lin;
List_XianShi(head);
printf("请问您要删除第几序号的学生数据:");
scanf("%d",&lin);
List_ShanChu(head,lin); //删除函数
printf("如果需要继续删除,则输入4;退出删除,则输入-1。\n");
scanf("%d",&state);
}
while(state==5) //查找
{
int aim; //查找方式
printf("请问您是要什么方式查找数据?\n");
printf("1.学号 2.序号\n");
scanf("%d",&aim);
List_ChaZhao(head,aim); //查找函数,输入为查找方式
printf("如果需要继续查找,则输入5;退出查找,则输入-1。\n");
scanf("%d",&state);
}
while(state==6) //排序
{
printf("正在进行学号排序in....(若数据过多,则缓冲时间会比较长)\n");
List_PaiXv(head);
printf("如果需要继续排序,则输入6;退出排序,则输入-1。\n");
scanf("%d",&state);
}
while(state==7)
{
List_XianShi(head); //将头指针放进去,以获得整个链表的所有信息。
printf("如果需要继续显示,则输入7;退出显示,则输入-1。\n");
scanf("%d",&state);
}
while(state==8)
{
printf("正在装入data中。。。。。\n");
List_fLuRu(head);
printf("录入成功!\n");
state=-1;
}
while(state==9)
{
printf("正在储存数据到sum中。。。。\n");
List_fCunChu(head);
printf("存储成功!\n");
state=-1;
}
}
}
line_list.cpp代码
#include <stdio.h>
#include <stdlib.h>
#include "line_list.h"
#include <string.h>
void List_LuRu(struct Student *head)
{
struct Student* Temp;
struct Student* Temp2;
Temp2 = head;
while(Temp2->pNextNode!=NULL)
{
Temp2=Temp2->pNextNode;
}
printf("请输入学生的学号 ,姓名 ,年龄 ,性别(b/g) ,家庭住址 ,入学时间(xx/xx/xx) 家庭电话号码。(中间空格隔开)\n");
Temp = (struct Student*)malloc(150); //新建一个动态链表
scanf("%s %s %s %s %s %s %s",Temp->ID,Temp->name,Temp->age,Temp->sex,Temp->address,Temp->Time,Temp->number);//该表内容的输入
Temp2->pNextNode=Temp; //将目前尾指针的指向该链表
Temp2=Temp;
Temp2->pNextNode=NULL; //尾指针的下一项指向NULL
}
void List_XianShi(struct Student *head) //显示函数
{
int temp=0;
struct Student* List;
List = head->pNextNode; //初始指向头指针下一个链表。头指针是空的
while(List!=NULL) //只要List不是空的,就执行
{
temp=temp+1;
printf("序号%d: ",temp);
printf("%s %s %s %s %s %s %s\n",List->ID,List->name,List->age,List->sex,List->address,List->Time,List->number);
List=List->pNextNode; //指向下一个List
}
}
void List_XiuGai(struct Student *head,int lin,int col) //修改函数
{
struct Student* List;
List=head->pNextNode;
int Temp=lin;
while(lin>1) //指针不断指向下一个,直到找到目标指针
{
lin=lin-1;
List=List->pNextNode;
}
if(lin==1) //找到了
{
if(List==NULL) //如果是空指针,报错
{
printf("请输入合法的序号。\n");
}
else if(col==1)
{
printf("定位到%d号的学号\n",Temp);
printf("请输入您的修改后的内容:");
scanf("%s",List->ID);
}
else if(col==2)
{
printf("定位到%d号的姓名\n",Temp);
printf("请输入您的修改后的内容:");
scanf("%s",List->name);
}
else if(col==3)
{
printf("定位到%d号的年龄\n",Temp);
printf("请输入您的修改后的内容:");
scanf("%s",List->age);
}
else if(col==4)
{
printf("定位到%d号的性别\n",Temp);
printf("请输入您的修改后的内容:");
scanf("%s",List->sex);
}
else if(col==5)
{
printf("定位到%d号的地址\n",Temp);
printf("请输入您的修改后的内容:");
scanf("%s",List->address);
}
else if(col==6)
{
printf("定位到%d号的入学时间\n",Temp);
printf("请输入您的修改后的内容:");
scanf("%s",List->Time);
}
else if(col==7)
{
printf("定位到%d号的电话号码\n",Temp);
printf("请输入您的修改后的内容:");
scanf("%s",List->number);
}
else //若col不在范围内,报错
{
printf("请输入合法的列号\n");
}
}
else //如果是负或0的序号
{
printf("请输入合法的序号\n");
}
}
void List_ChaRu(struct Student *head,int lin) //插入函数
{
struct Student* List_begin; //前一个
struct Student* List_end; //后一个
struct Student* Temp; //中间插的
List_begin=head->pNextNode;
List_end=List_begin->pNextNode;//将后一个指针接于前一个指针后
while(lin>1) //寻找目标位置
{
lin=lin-1;
List_begin=List_begin->pNextNode;
List_end=List_begin->pNextNode;
}
if(lin==1) //找到了
{
Temp=(struct Student*)malloc(150); //创建新链表
printf("请输入学生的学号 ,姓名 ,年龄 ,性别(b/g) ,家庭住址 ,入学时间(xx/xx/xx) 家庭电话号码。(中间空格隔开)\n");
scanf("%s %s %s %s %s %s %s",Temp->ID,Temp->name,Temp->age,Temp->sex,Temp->address,Temp->Time,Temp->number);
List_begin->pNextNode=Temp;//插在目标位置
Temp->pNextNode=List_end;
}
else //序号为0或负,报错
{
printf("输入不合法!\n");
}
}
void List_ShanChu(struct Student* head,int lin)
{
struct Student* List_begin; //前一个
struct Student* Temp; //删除目标
Temp=head->pNextNode;
List_begin=head;
while(lin>1) //寻找
{
lin=lin-1;
List_begin=Temp;
Temp=Temp->pNextNode;
}
if(lin==1) //找到了,删除
{
List_begin->pNextNode=Temp->pNextNode; //让前一个指针指向后一个指针
printf("删除成功!\n");
}
else //序号错误
{
printf("输入非法序号\n");
}
}
void List_ChaZhao(struct Student* head,int aim) //目前只有两种查找方式
{
struct Student* List;
char ID[15];
int sum; //查找的序号或者学号
int Temp; //临时存储信息
List=head->pNextNode;
if(aim==0)
{
printf("请输入查找的序号:");
scanf("%d",&sum);
Temp=sum;
while(sum>1) //寻找查找序号目标
{
List=List->pNextNode;
sum=sum-1;
}
if(sum==1) //找到了
{
printf("序号%d: ",Temp);
printf("%s %s %s %s %s %s %s\n",List->ID,List->name,List->age,List->sex,List->address,List->Time,List->number);
}
else //没找到
{
printf("输入序号不合法\n");
}
}
else if(aim==1) //寻找查找学号目标
{
printf("请输入查找的学号:");
scanf("%s",ID);
while(List!=NULL)
{
if(strcmp(List->ID,ID)!=0)
{
List=List->pNextNode;
}
else
{
break;
}
}
if(strcmp(List->ID,ID)==0) //找到了
{
printf("学号%s: ",ID);
printf("%s %s %s %s %s %s %s\n",List->ID,List->name,List->age,List->sex,List->address,List->Time,List->number);
}
else //没找到
{
printf("没有该学号的数据!\n");
}
}
else //查找方式错误
{
printf("输入查找目标不合法\n");
}
}
void List_PaiXv(struct Student* head)
{
//本代码将采用简单的冒泡排序法.
struct Student* List_begin_1; //第一轮循环前一个
struct Student* List_begin_2; //第二轮循环前一个
struct Student* List_in_2; //第二轮循环中间一个
struct Student* List_end_2; //第二轮循环后一个
List_begin_1 = head;
while(List_begin_1->pNextNode->pNextNode!=NULL) //以下为简单的冒泡排序法
{
List_begin_2 = head;
List_in_2 = List_begin_2->pNextNode;
List_end_2 = List_in_2->pNextNode;
while(List_end_2!=NULL)
{
if(strcmp(List_in_2->ID,List_end_2->ID)>0)
{
List_begin_2->pNextNode=List_end_2;
List_in_2->pNextNode=List_end_2->pNextNode;
List_end_2->pNextNode=List_in_2;
List_in_2 = List_begin_2->pNextNode;
List_end_2 = List_in_2->pNextNode;
}
List_begin_2=List_begin_2->pNextNode;
List_in_2=List_in_2->pNextNode;
List_end_2=List_end_2->pNextNode;
}
List_begin_1=List_begin_1->pNextNode;
}
}
void List_fLuRu(struct Student* head) //装入函数
{
FILE* fp; //文件指针
int n=0; //数组的位置
int state=0;//链表的位置
char ch; //录入字符
struct Student* List;//链表指针
struct Student* Temp;
List=(struct Student*)malloc(150);
head->pNextNode=List;
if((fp=fopen("C:\\Users\\池上桜\\Desktop\\学习\\课设\\Project\\data.txt","r+"))==NULL) //打开文件
{
printf("No file!\n");
exit(0);
}
while(!feof(fp))
{
ch=fgetc(fp);
if(ch!='\n')//换行
{
if(ch!=' ') //空格切换一次链表位置
{
if(state==0) //链表在ID位置,以下同理
{
List->ID[n]=ch;
List->ID[n+1]='\0';
}
else if(state==1)
{
List->name[n]=ch;
List->name[n+1]='\0';
}
else if(state==2)
{
List->age[n]=ch;
List->age[n+1]='\0';
}
else if(state==3)
{
List->sex[n]=ch;
List->sex[n+1]='\0';
}
else if(state==4)
{
List->address[n]=ch;
List->address[n+1]='\0';
}
else if(state==5)
{
List->Time[n]=ch;
List->Time[n+1]='\0';
}
else if(state==6)
{
List->number[n]=ch;
List->number[n+1]='\0';
}
else
{
printf("格式有误!");
exit(0);
}
n++;
}
else
{
state=state+1;
n=0;
}
}
else
{
Temp = (struct Student*)malloc(150);
List->pNextNode=Temp;
List=Temp;
List->pNextNode=NULL;
n=0;
state=0;
}
}
fclose(fp);
}
void List_fCunChu(struct Student* head)
{
FILE* fp; //文件指针
struct Student* List;//链表指针
List=head->pNextNode;
if((fp=fopen("C:\\Users\\池上桜\\Desktop\\学习\\课设\\Project\\sum.txt","w+"))==NULL) //打开文件
{
printf("No file!\n");
exit(0);
}
while(List->pNextNode!=NULL)
{
fputs(List->ID,fp);
fputc('\0',fp);
fputs(List->name,fp);
fputc('\0',fp);
fputs(List->age,fp);
fputc('\0',fp);
fputs(List->sex,fp);
fputc('\0',fp);
fputs(List->address,fp);
fputc('\0',fp);
fputs(List->Time,fp);
fputc('\0',fp);
fputs(List->number,fp);
fputc('\n',fp);
List=List->pNextNode;
}
fputs(List->ID,fp);
fputc('\0',fp);
fputs(List->name,fp);
fputc('\0',fp);
fputs(List->age,fp);
fputc('\0',fp);
fputs(List->sex,fp);
fputc('\0',fp);
fputs(List->address,fp);
fputc('\0',fp);
fputs(List->Time,fp);
fputc('\0',fp);
fputs(List->number,fp);
fclose(fp);
}
line_list.h代码
#ifndef __LINE_LIST_H
#define __LINE_LIST_H
struct Student {
char ID[15]; //学生学号
char name[10]; //学生姓名
char age[3]; //学生年龄
char sex[5]; //性别
char address[30]; //住址
char Time[15]; //入学时间
char number[15]; //电话号码
struct Student * pNextNode;
};
void List_LuRu(struct Student *head);
void List_XianShi(struct Student *head);
void List_XiuGai(struct Student *head,int lin,int col);
void List_ChaRu(struct Student *head,int lin);
void List_ShanChu(struct Student* head,int lin);
void List_ChaZhao(struct Student* head,int aim);
void List_PaiXv(struct Student* head);
void List_fLuRu(struct Student *head); //装入
void List_fCunChu(struct Student* head); //存储
#endif
注意在头文件的最后一行,留一行空行,以防某些玄学错误.
3.代码全分析
A.主函数main.cpp中的代码:
由于main.cpp中代码逻辑简单,所以就只需要一栏就可以解释清楚.
首先,创建一个空链表作为头节点,这种创建方式是官方数据结构书上的标准线性表创建方式,能够方便后文许多操作(如排序).然后让该链表的下一项指向NULL,作为结尾的判断,这个链表就是所有数据存储的位置了.
然后用scanf接收用户想要进行的操作,在while循环中不断询问用户的需求,以达到管理系统的目的.每个二层while中需要添加第二个scanf,来接收用户在使用完一个功能后的意向.
这大致就是主函数的内容了.
B.头文件line_list.h的代码:
line_list.h就是简单的头文件,在这里需要说的就是里面的结构体,在这里,结构体不能够将重命名定为Student,不然就会报错,虽然我也不知道为什么,但是只要按照上面代码的做法就不会被玄学干扰.
C.函数库line_list.cpp的代码:
从上到下开始分析
a.录入函数
录入,是将数据接到目前链表的下一位,由此,需要输入为头结点,由while循环找到目前链表的最后一个结点,然后创建链表,将内容录入,将链表接到最后一个结点,然后将最后一个结点的下一位指向NULL即可.
NULL是用于判断链表的尾部的,在这里无疑体现了其优异的功能性.
代码:
b.显示函数
为什么把显示函数放在这么上面的位置?这当然是因为有了显示才能知道自己的编写有无问题.
显示函数就是从头结点之后开始,显示每一个表中的数据,直到NULL.
c.修改函数
修改需要到用户自己选择修改的内容,所以输入为序号和列号, 序号是指通过显示而展现出的目前链表的数据的序号,而列号就是修改的目标(如学号为1)
同样先通过while找到需要修改的数据,再用if语句确定列号,即可精准修改.
(部分)
d.插入函数
先将链表显示一遍,询问需要插入在序号几之后.在函数中定义前中后三个链表的指针,找到位置后将三个指针相连即可.
e. 删除函数
删除操作同样需要多个指针,找到位置后,将前指针的后一个变成删除目标的后一个,即可直接跳过删除目标,最后free即可
f.查找函数
查找函数是逻辑上最简单的,只需要判断和查找目标是否一致即可.因此在这里不再赘述.
g.排序函数
在排序函数中,使用的是冒泡排序法,具体实现如下,在本函数中体现了将头指针变成空的好处,多一个指针,避免了if来判断是不是头指针.
void List_PaiXv(struct Student* head)
{
//本代码将采用简单的冒泡排序法.
struct Student* List_begin_1; //第一轮循环前一个
struct Student* List_begin_2; //第二轮循环前一个
struct Student* List_in_2; //第二轮循环中间一个
struct Student* List_end_2; //第二轮循环后一个
List_begin_1 = head;
while(List_begin_1->pNextNode->pNextNode!=NULL) //以下为简单的冒泡排序法
{
List_begin_2 = head;
List_in_2 = List_begin_2->pNextNode;
List_end_2 = List_in_2->pNextNode;
while(List_end_2!=NULL)
{
if(strcmp(List_in_2->ID,List_end_2->ID)>0)
{
List_begin_2->pNextNode=List_end_2;
List_in_2->pNextNode=List_end_2->pNextNode;
List_end_2->pNextNode=List_in_2;
List_in_2 = List_begin_2->pNextNode;
List_end_2 = List_in_2->pNextNode;
}
List_begin_2=List_begin_2->pNextNode;
List_in_2=List_in_2->pNextNode;
List_end_2=List_end_2->pNextNode;
}
List_begin_1=List_begin_1->pNextNode;
}
}
h.装入函数
灵活运用文件函数,在本代码中使用的是fgetc来一个个从文件中获得数据,方便判断\n等内容,需要注意的是,最后一行不能打回车键。
void List_fLuRu(struct Student* head) //装入函数
{
FILE* fp; //文件指针
int n=0; //数组的位置
int state=0;//链表的位置
char ch; //录入字符
struct Student* List;//链表指针
struct Student* Temp;
List=(struct Student*)malloc(150);
head->pNextNode=List;
if((fp=fopen("C:\\Users\\池上桜\\Desktop\\学习\\课设\\Project\\data.txt","r+"))==NULL) //打开文件
{
printf("No file!\n");
exit(0);
}
while(!feof(fp))
{
ch=fgetc(fp);
if(ch!='\n')//换行
{
if(ch!=' ') //空格切换一次链表位置
{
if(state==0) //链表在ID位置,以下同理
{
List->ID[n]=ch;
List->ID[n+1]='\0';
}
else if(state==1)
{
List->name[n]=ch;
List->name[n+1]='\0';
}
else if(state==2)
{
List->age[n]=ch;
List->age[n+1]='\0';
}
else if(state==3)
{
List->sex[n]=ch;
List->sex[n+1]='\0';
}
else if(state==4)
{
List->address[n]=ch;
List->address[n+1]='\0';
}
else if(state==5)
{
List->Time[n]=ch;
List->Time[n+1]='\0';
}
else if(state==6)
{
List->number[n]=ch;
List->number[n+1]='\0';
}
else
{
printf("格式有误!");
exit(0);
}
n++;
}
else
{
state=state+1;
n=0;
}
}
else
{
Temp = (struct Student*)malloc(150);
List->pNextNode=Temp;
List=Temp;
List->pNextNode=NULL;
n=0;
state=0;
}
}
fclose(fp);
}
i.存储函数
用fputs将链表内容全部存储入文件,记得在吗,每一个后面加‘\0’,表示结束。
void List_fCunChu(struct Student* head)
{
FILE* fp; //文件指针
struct Student* List;//链表指针
List=head->pNextNode;
if((fp=fopen("C:\\Users\\池上桜\\Desktop\\学习\\课设\\Project\\sum.txt","w+"))==NULL) //打开文件
{
printf("No file!\n");
exit(0);
}
while(List->pNextNode!=NULL)
{
fputs(List->ID,fp);
fputc('\0',fp);
fputs(List->name,fp);
fputc('\0',fp);
fputs(List->age,fp);
fputc('\0',fp);
fputs(List->sex,fp);
fputc('\0',fp);
fputs(List->address,fp);
fputc('\0',fp);
fputs(List->Time,fp);
fputc('\0',fp);
fputs(List->number,fp);
fputc('\n',fp);
List=List->pNextNode;
}
fputs(List->ID,fp);
fputc('\0',fp);
fputs(List->name,fp);
fputc('\0',fp);
fputs(List->age,fp);
fputc('\0',fp);
fputs(List->sex,fp);
fputc('\0',fp);
fputs(List->address,fp);
fputc('\0',fp);
fputs(List->Time,fp);
fputc('\0',fp);
fputs(List->number,fp);
fclose(fp);
}
总结:
难度极低(不包含文件的话),作为课设确实是过于简单了.
本课设能够巩固如排序,查找,链表,结构体,文件等内容,涉及面广,内容丰富.对于初学者而言是不错的练习题.
弘扬开源精神,从大一做起~