心血来潮把上学期课设给整理上传了。
先放演示视频:
C语言/程序设计基础课程设计:围棋棋手管理系统(自用)
GitHub项目地址:
https://github.com/CMD137/C-Go-Players-Management-System-
一、设计内容介绍:
1.项目名称 :围棋棋手管理系统(Go Players Management System)
2.项目背景 :
&围棋是一种古老而极富智慧的策略游戏,棋手需要在 19*19 的棋盘上使用黑白两色的棋子,通过布局、攻防、眼形等多方面的因素争夺领地,最终以总地盘面积的多少决定胜负。围棋棋手是专业从事围棋比赛的选手,他们精通围棋规则,具备深厚的棋艺和战术技能。
围棋棋手管理系统是一款专为围棋棋手设计的解决方案。该系统记录了棋手的信息, 并能跟踪其比赛表现。
3.项目内容及功能简介:
~~~~~~
围棋棋手管理系统(Go Players Management System)主要用于系统化、规范化地的记录并管理相关棋手的信息,并可以按一定要求、规则显示所存储的信息;
该系统仿照数据库的设计,在一个系统下管理多个不同的表,在源文件的案例中,我创建了CHN.dat与KOR.dat两个表分别储存中国棋手信息与韩国棋手信息。
~~~~~~
该系统中棋手的信息包括:棋手编号,棋手姓名,棋手身份,棋手段位和等级分。
~~~~~~
该系统可以完成的主要功能有:
(1)新建棋手信息表;
(2)选择并导入棋手信息表;
(3)显示棋手信息;
(4)查询棋手信息;
(5)增加棋手信息;
(6)删除棋手信息;
(7)修改棋手信息;
(8)统计棋手信息;
(9)对棋手信息进行排序;
(10)退出系统;
4.使用技术:
~~~~~~
C语言、Windows库函数。
二、设计思路:
(一)数据结构设计:
~~~~~~
围棋棋手数据结构:
~~~~~~
创建如下的围棋棋手数据结构体:
~~~~~~
id与name都属于字符串变量,直接输出。等级分grade也可以直接输出。而classification和level在输出时将会特殊处理,详见下文实现过程部分。而将classification和level设置为整形变量是为最后一个排序功能服务。
//枚举棋手类型
enum Class{ama=0,pro=1};
//围棋棋手数据结构:
struct GoPlayerType
{
char id[5];//P+XXX,四位id一位\0;
char name[20];//姓名
enum Class classification;//0为业余,1为 职业
int level;//1-27对应业余10级->业余8段- >职业初段->职业九段
int grade;//存储等级分
};
(二)文件架构设计:
~~~~~~
如图,在文件Go Players Management System内:
~~~~~~
在datafiles文件夹下存储多个围棋棋手信息表,此处以中国棋手信息表(CHN.dat)与韩国棋手围棋表(KOR.dat)为例;
~~~~~~
在inc文件夹下有两个自己编写的头文件:GPMS.h中主要用于写main函数中的10个功能函数;GPtable.h主要用于写有关棋手数据的结构与处理这些数据的函数;
~~~~~~
Main.c是主要源文件,程序的入口在其中;
~~~~~~
Go Players Management System.exe是源代码编译后的应用程序,打开即可使用围棋棋手管理系统;
~~~~~~
其他文件如Go Players Management System.dev为Dev C++ 5.11在搭建项目时产生的工程文件,若要完整的打开此项目,请如文本文件所提示的那样使用。
(三) 文件存储格式:
~~~~~~ 采用.dat的二进制文件进行对棋手信息的存储。
(四)程序功能架构设计:
~~~~~~
如图,打开应用后首先进入菜单,菜单中有:
1.新建棋手信息表;
2.选择并导入棋手信息表;
3.显示棋手信息;
4.查询棋手信息;
5.增加棋手信息;
6.删除棋手信息;
7.修改棋手信息;
8.统计棋手信息;
9.对棋手信息进行排序;
10.退出系统
~~~~~~
十个功能及菜单显示都被封装在GPMS.h这个库中的相应函数内;而在GPtable.h这个库中,又有多个函数服务于GPMS.h中的函数,且这些函数都可被重复使用。
(五)模块、算法设计:
~~~~~~ 详见 三、实现过程。
(六)界面设计:
~~~~~~ 在菜单及数据等的展示上仿照MySQL在控制台窗口中的风格,采用表格形式。
1.主菜单设计:
~~~~~~
如图,主菜单最上方显示“欢迎使用围棋棋手管理系统”,在下方框中,居中列出10条主要功能。在最下方提示输入1-10来使用相应功能,并提示使用信息前请先使用功能2导入数据,提高对用户的友好性。
2.数据表设计:
~~~~~~
如图,数据的展示上仿照MySQL在控制台窗口中的表格形式。最上方输出当前打开的表的名称,提示用户打开的是哪张表;第二行统计表内的总人数。第三行展示表的字段,依次为编号、姓名、类别、段位、等级分。再往下就是各个棋手的相应数据。
~~~~~~
使用表格的形式,让用户对所展示的棋手数据一目了然;
三、实现过程:
1.框架搭建:
~~~~~~
如图,打开软件后,一句软件功能架构设计,要在菜单中选择所提供的十个功能,因此,为选择在Main.c这个程序的主入口调用菜单展示函数ShowMenu(),并用变量接受用户输入的指令,调用相应函数。这些函数的定义被封装在GPMS.h头文件中。
~~~~~~
而在GPMS.h中调用十个功能函数时,有不少对棋手信息的操作是重复或类似的,为了不重复“造轮子”,我们选择把这些操作同样封装为函数,并全部在GPtable.h中定义,顺便在头文件中实现对棋手结构体的定义。
~~~~~~
函数之间的传参一开始我想使用传输所打开表的棋手信息的结构体数据,但在实际编写时发现由于在细节实现时大量指针的编写和使用会经常发生一些错误,于是抱着从简的理念,我选择直接通过一个字符串指针传输要打开表的文件路径,并在所用函数中在创建棋手信息结构体数组的方式,以此来提高开发效率与代码简洁性。
~~~~~~
简单来说,我们要实现如图所示的三层结构:
~~~~~~
以此,我们分别在三个源文件中通过简单的顺序、判断、循环即可搭好整个程序的总体框架,接下来就是各个函数的细节实现了。
2.分功能编写:
(1)主函数编写:
~~~~~~
int main():
~~~~~~
思路:引用所有要使用的头文件后,在主函数内创建一个filename的字符串变量,以指针形式存储接收到的文件路径,并传入需要使用的函数,filename在主函数中起穿针引线的功能。后定义一个整形变量select用于接受用户传入的指令。后用while(1)写一个死循环,在这个死循环中先调用ShowMenu()函数展示主菜单,提示用户输入指令,并在接下来的Switch判断中调用相应的功能函数,死循环的出口即为功能十“退出系统”。其中在死循环中使用windows库提供的system(“cls”)来实现界面的整洁,避免重复内容的出现影响用户体验。
(2)菜单展示函数编写:
~~~~~~
void ShowMenu();
~~~~~~
思路:直接用printf输出规划好的字符串,源代码见源文件,具体实现效果见
四、结果与分析。
(3)新建棋手信息表函数编写:
~~~~~~
void CreateTable();
~~~~~~
思路:在上文中已经提到采用一个系统下管理多个不同的表的思路,功能一即可实现直接在软件中建表而不是在资源管理器中打开相应文件夹再建表。再建表时我们需要注意不能创建与已存在表同名的表,那么问题就来了,如何知道已存在哪些表呢?一开始我打算使用一个文本文件专门存储该文件下有哪些表,实际编写后发现所涉及的操作太多,比如又要构建一个表目录的结构体数组,还要专门编写对于表目录信息的增删改查,于是我上网查询有没有直接可以让C语言程序获取某个文件下所有文件名的系统函数,于是我找到了dirent.h这个头文件,其中的函数与结构体可以满足我的要求:
char fname[50]="./datafiles/";
char in[30]={0};
scanf("%s",in);
strcat(fname,in);//组合为完整路径
strcpy(filename,fname);//将打开的文件名传输到filename以供其他文件使用
~~~~~~
在完成以上操作后,我们检验输入的文件是否存在,若不存在提示用户“文件不存在!返回后请重新输入!”,若存在提示用户“文件已成功打开”。
~~~~~~
完整源代码见源文件。
(4)选择并导入棋手信息表:
~~~~~~
void LoadTable(char * filename);
~~~~~~
思路:在上文中主函数的编写中已经提到使用filename作为函数参数。而在该函数中要在filename存储后续要操作的文件路径。于是创建一个字符串指针来作为形参,在主函数传输filename(即字符串地址)来改变它所存储的内容。
~~~~~~
要选择并导入棋手信息表,首先与新建棋手信息表函数中一样向用户展示文件目录,此处操作同上。然后提示用户输入要打开的文件名称,由于我使用了分文件编写,要打开的文件名称对于主函数来说并不是正确路径,于是我使用以下代码来将filename中的内容编写为正确的完整路径:
char fname[50]="./datafiles/";
char in[30]={0};
scanf("%s",in);
strcat(fname,in);//组合为完整路径
strcpy(filename,fname);//将打开的文件名传输到filename以供其他文件使用
~~~~~~
在完成以上操作后,我们检验输入的文件是否存在,若不存在提示用户“文件不存在!返回后请重新输入!”,若存在提示用户“文件已成功打开”。
~~~~~~
完整源代码见源文件。
(5)显示棋手信息:
~~~~~~
void ShowData(char* filename);
~~~~~~
思路:在接受到主函数传入的文件路径后,首先判断文件是否没有成功打开或根本就没有使用功能2导入文件,并依据判断结果提示用户“无数据,请使用功能2正确选择并导入棋手信息表!”,后返回主菜单。
若成功打开文件,则创建一个棋手信息结构体的数组players,并通过循环将文件中的信息读入结构体,具体实现如下:
//count得到该表棋手的数量。
int count=0;
while(!feof(fp))
{
if(fread(&players[count],sizeof(struct GoPlayerType),1,fp)==1)//必须对fread的返回值进行判断,否则可能会将最后一条数据读2遍
count++;
}
fclose(fp);
~~~~~~
通过一个循环来读取文件中的数据。循环的条件是文件指针fp未到达文件末尾,即feof(fp)返回值为假。
在循环中,使用fread函数从文件中读取一个sizeof(struct GoPlayerType)大小的数据块,并将读取的数据存储到players数组中的第count个元素。fread函数的返回值是成功读取的数据块数量。
~~~~~~
同时,在判断条件中使用了“==1”来验证fread函数是否成功读取一个数据块。这是为了防止可能发生的问题,比如当文件指针已经到达文件末尾时,fread函数仍然会返回0(表示未读取任何数据),但循环条件仍然为真,导致将最后一条数据读两遍。
~~~~~~
若成功读取一个数据块,将count自增1,以便统计棋手的数量并读入下一个棋手信息。循环会一直执行直到文件的数据读取完毕。
~~~~~~
另外有个问题需格外注意!在我一开始编写时,fread在读文件时不知什么原因没有读到GoPlayerType这个结构体中name字符串末尾的\0,导致后续输出信息时只有id正常读入并输出了,解决这个问题需要手动在name在字符串末尾加上‘\0’:
stud[i].name[strlen(stud[i].name)] = '\0';
~~~~~~ 之后就正常运行了,但当我将这句话删去后,程序再次正常运行且后续都没有出错,因此我在此次特别提醒,如果在其他电脑上重新生成应用程序时出现如上问题,请试试在
fread(&players[count],sizeof(struct GoPlayerType),1,fp) ==1
这句话后加上补‘/0’的代码。
~~~~~~
在结构图数组读入文件中的数据后,就该向控制台输出信息了。
~~~~~~
展示的样式参考参考上文界面设计部分。这里只讨论信息的输出细节:
~~~~~~
id与name都属于字符串变量,直接输出。等级分grade也可以直接输出。由于棋手信息中的类别与段位都没有直接存储为字符串,将进行以下处理,此部分内容在后文不再赘述:
~~~~~~
~~~~~~
A.棋手的类别:
~~~~~~
棋手的类别有职业、业余两种身份,此处用枚举Class存储,1表示职业,0表示业余。在输出时,通过
printf("|%-9s",players[i].classification==1?"职业":"业余");
即可将1、0转化输出为职业、业余。
~~~~~~
~~~~~~
B.棋手的段位:
~~~~~~
用整形变量1-27对应业余10级->业余8段- >职业初段->职业九段 。在输出时通过在GPtable.h中编写的Levelstr()函数即可实现转换。该函数的内部实现原理为传入1-27的整形变量,返回相应段位的字符串指针。
输出信息核心代码如下:
for(int i=0;i<count;i++){
char* level=Levelstr(players[i].level);
printf("|%-9s",players[i].id);
printf("|%-9s",players[i].name);
printf("|%-9s",players[i].classification==1?"职业":"业余");
printf("|%-9s",level);
printf("|%-10d|\n",players[i].grade);
printf("+--------------------------------------------------+\n");
free(level); // 释放函数中分配的内存
}
~~~~~~ 若表内无数据,则显示:“该表内暂无任何棋手信息”。
(6)查询棋手信息:
~~~~~~
void QuaryData(char* filename);
~~~~~~
在接受到主函数传入的文件路径后,首先判断文件是否没有成功打开或根本就没有使用功能2导入文件,并依据判断结果提示用户“无数据,请使用功能2正确选择并导入棋手信息表!”,后返回主菜单。
~~~~~~
若成功打开文件,则提供三种查询方式让用户选择:
~~~~~~
按棋手姓名查询-NameQuary(filename);按棋手身份查询-classificationQuary(filename);
~~~~~~
按棋手段位查询-LevelQuary(filename);这三个函数都定义在GPtable.h中,内部实现逻辑在显示棋手信息函数的基础上加了相应的条件判断,并在最后提示查询记录共有几条,除了姓名查询以外其他两个查询方式不再过多赘述,具体实现见源码。
~~~~~~
下面重点解释“按棋手姓名查询-NameQuary(filename)”,主要是模糊查询功能。该功能中输出信息的条件不简单是“输入=已存在信息”,而是“输入字符串中的子串=已存在信息中的子串”,以这个主要思想,就可以得到棋手姓名与查询的名字的匹配度,然后根据匹配度降序输出棋手信息,核心算法如下:
char qname[20];
printf("请输入要查询棋手的姓名(或其中的部分汉字):\n");
scanf("%s",qname);
//标注字符串匹配符,用flagcount数组记录对应棋手姓名匹配度
int flagcount[1000]={0};
int isize=strlen(qname);
for(int i=0;i<count;i++){
for(int j=0;j<isize;j+=2){
int tsize=strlen(players[i].name);
for(int k=0;k<tsize;k+=2){
if(qname[j]==players[i].name[k]&&qname[j+1]==players[i].name[k+1])
flagcount[i]++;
}
}
}
//根据匹配度对棋手进行降序排序,以防出现输入准确名字但是该棋手结果去排在后边的情况
for(int i=0;i<count-1;i++){
for(int j=0;j<count-1-i;j++){
if(flagcount[j]<flagcount[j+1]){
//交换对应棋手信息
struct GoPlayerType t=players[j];
players[j]=players[j+1];
players[j+1]=t;
//交换对应匹配度
int temp=flagcount[j];
flagcount[j]=flagcount[j+1];
flagcount[j+1]=temp;
}
}
}
//输出排序后的结果 ,输出条件为匹配度不为0
....
if(flagcount[i]>0){
...
输出信息
...
}
如果不按匹配度大小降序排序,而只标记姓名包含输入子串的棋手的话,就会出现输入准确名字但是该棋手结果去排在后边的情况。
另外,需要特别注意的是,由于汉字占两个字符的原因,需要
inname[i] == tname[j]&&inname[i+1] == tname[j+1]
这种形式来连续判断两个字符来判断一个汉字,否则就会出现输入“梅”却查出“明” 的错误结果。
实际上,尽管我多次改进该算法,它仍有不足和漏洞,如果要实现“十分精准的模糊搜索”,应该还需要结合KMP算法,以我目前的知识储备不足以实现。
(7)增加棋手信息:
~~~~~~
void AddData(char* filename);
~~~~~~
在接受到主函数传入的文件路径后,首先判断文件是否没有成功打开或根本就没有使用功能2导入文件,并依据判断结果提示用户“无数据,请使用功能2正确选择并导入棋手信息表!”,后返回主菜单。
~~~~~~
若成功打开文件,则先同ShowData(char* filename)一样用构建一个棋手信息结构体数组,并用它存储读入的文件信息。此时,在构建一个棋手信息结构体对象,并提示用户分别输入要增加的棋手信息的编号。由于一个表中棋手编号唯一,所以在输入新棋手编号后,需要先通过IdIsLegal(newPlayer.id)判断id输入是否合法,在判断是否与已有棋手编号重复,若重复,提示"该编号已存在,请核对后再添加棋手!",若不存在,则依次按照提示输入姓名、类别、段位、等级分,并在输入后通过IsLegal(newPlayer.name,newPlayer.classification,newPlayer.level,newPlayer.grade)函数进行信息的合法性检测。若不合法,则重新输入。若合法,则让新棋手信息加入到结构体数组中。
~~~~~~
将新消息保存在数组以后,将整个 players 数组写回到文件中:注意此时文件打开方式为“wb”而不是“ab”,是因为要重写整个文件,使用ab会导致数据重复!打开文件后使用fwrite()函数写回文件。最后提示用户"添加棋手成功!"。
~~~~~~
核心代码如下:
// 新增棋手信息
struct GoPlayerType newPlayer;
printf("请输入新增棋手的编号,格式为PXXX,如P042(不可与已有编号重复):\n");
do{
scanf("%s", newPlayer.id);
}while(IdIsLegal(newPlayer.id)); //检验棋手id输入是否合法
for (int i = 0; i < count; i++) {
if (strcmp(players[i].id, newPlayer.id) == 0) {
printf("该编号已存在,请核对后再添加棋手!\n");
system("pause");
return;
}
}
do{
printf("请输入新增棋手的姓名:\n");
scanf("%s", newPlayer.name);
printf("请输入新增棋手的类型(0表示业余,1表示职业):\n");
scanf("%d", &newPlayer.classification);
printf("请输入新增棋手的级别(1-27):\n");
printf("级别匹配如下:\n");
printf("业余10级->1\t");printf("业余9级->2\t");printf("业余8级->3\t");printf("业余7级->4\n");
printf("业余6级->5\t");printf("业余5级->6\t");printf("业余4级->7\t");printf("业余3级->8\n");
printf("业余2级->9\t");printf("业余1级->10\t");printf("业余初段->11\t");printf("业余二段->12\n");
printf("业余三段->13\t");printf("业余四段->14\t");printf("业余五段->15\t");printf("业余六段->16\n");
printf("业余七段->17\t");printf("业余八段->18\t");printf("职业初段->19\t");printf("职业二段->20\n");
printf("职业三段->21\t");printf("职业四段->22\t");printf("职业五段->23\t");printf("职业六段->24\n");
printf("职业七段->25\t");printf("职业八段->26\t");printf("职业九段->27\n");
scanf("%d", &newPlayer.level);
printf("请输入新增棋手的等级分:\n");
scanf("%d", &newPlayer.grade);
}while(IsLegal(newPlayer.name,newPlayer.classification,newPlayer.level,newPlayer.grade));
// 加入到 players 数组中
players[count] = newPlayer;
count++;
// 将整个 players 数组写回到文件中
fp=fopen(filename,"wb");//用wb打开而不是ab,是因为要重写整个文件,使用ab会导致数据重复!!
fseek(fp, 0, SEEK_SET);
fwrite(players, sizeof(struct GoPlayerType), count, fp);
(8)删除棋手信息:
~~~~~~ void DeleteData(char* filename);一开始处理同其他功能一样,在读入数据完毕后,提示用户输入要删除的棋手编号(只有棋手编号唯一,不会误删其他信息)。此时遍历数组寻找棋手,若没有找到,提示“没有找到您要删除的棋手信息,请核对后再试!”并返回主菜单。若找到该棋手,显示棋手的完整信息并再次询问是否确认删除。删除操作具体逻辑如下:找到该棋手后,记录他在数组中的下标flag,并在此处进行循环操作:让后一条信息覆盖当前信息。最后让count-1;将整个结构体数组写回文件,提示“删除成功”。核心代码如下:
int s;
scanf("%d",&s);//输入1确认删除;输入0返回
if(s!=1){
return ;
}else{
for(int i=flag;i<count;i++){
players[i]=players[i+1];
}
}
count--;//总人数减一
fp=fopen(filename,"wb");
fseek(fp,0,SEEK_SET);
fwrite(players,sizeof(struct GoPlayerType),count,fp);
fclose(fp);
printf("成功删除该条信息!\n");
(9)修改棋手信息:
~~~~~~
void ChangeData(char* filename)整体同AddData(char* filename)一样,主要区别在于将
加入操作:
// 加入到 players 数组中
players[count] = newPlayer;
count++;
变为替换操作:
players[flag]=newPlayer;//替换原来的信息
另外,由于修改后的棋手信息中编号可能不变,所以进行以下操作:
1.strcpy(players[flag].id,"00000");//修改原来棋手的id,检验棋手id输入是否已经存在时就可以输入原来的id;
修改成功后提示用户。
(10)统计棋手信息:
~~~~~~
void CountData(char* filename);一开始处理同其他功能一样,在确定文件导入成功后提示用户可按以下三种方式进行统计:
~~~~~~
1.统计职业棋手人数:CountPro(filename);
~~~~~~
2.统计业余棋手人数:CountAma(filename);
~~~~~~
3.统计x等级分以上的人数:CountGrade(filename);
~~~~~~
这三个函数都定义在GPtable.h中,内部实现逻辑在显示棋手信息函数的基础上加了相应的条件判断,并在最后提示统计后符合条件的棋手共有x人,这里不再过多赘述,具体实现见源码。
(11)对棋手信息进行排序:
~~~~~~
void SortData(char* filename)一开始处理同其他功能一样,在确定文件导入成功后提示用户可按以下两种方式进行排序:
~~~~~~
1.所有棋手按等级分进行降序排序:GradeSort(filename);
~~~~~~
2.所有棋手按段位进行降序排序:LevelSort(filename);(此处与任务书所给案例不同,不只对职业棋手进行按段位进行排序。若要同任务书一样只需要加一条判断,改成这样是因为我觉得这样设计功能更有实用性)
排序规则为:对所有棋手按等级分进行降序排序、对所以棋手按段位进行降序排序(等级分或段位相同的棋手按照棋手编号后三位数字大小升序排列)。
~~~~~~
由于需要在段位/等级分相等时对棋手编号后三位数字大小升序排列,所以判断时需要通过以下操作取出后三位数字,再通过strcmp()进行比较:
void GetIdNum(char* num, struct GoPlayerType* p){ //使用了结构体指针传参已获得ID字符串的后三位数字,以此进行大小比较
num[0] = p->id[1];
num[1] = p->id[2];
num[2] = p->id[3];
num[3] = p->id[4];
}
·GradeSort(filename)排序算法具体实现如下:
for(int i=0;i<count-1;i++){
for(int j =0;j<count-1-i;j++){
if(players[j].grade<players[j+1].grade){
struct GoPlayerType t=players[j];
players[j]=players[j+1];
players[j+1]=t;
}else if(players[j].level==players[j+1].level){
//取出棋手编号的后三位数字
char num1[4],num2[4];
GetIdNum(num1,&players[j]);
GetIdNum(num2,&players[j+1]);
if(strcmp(num1,num2)>0){
struct GoPlayerType t=players[j];
players[j]=players[j+1];
players[j+1]=t;
}
}
}
}
·LevelSort(filename)排序算法具体实现如下:
for(int i=0;i<count-1;i++){
for(int j =0;j<count-1-i;j++){
if(players[j].level<players[j+1].level){
struct GoPlayerType t=players[j];
players[j]=players[j+1];
players[j+1]=t;
}else if(players[j].level==players[j+1].level){
//取出棋手编号的后三位数字
char num1[4],num2[4];
GetIdNum(num1,&players[j]);
GetIdNum(num2,&players[j+1]);
if(strcmp(num1,num2)>0){
struct GoPlayerType t=players[j];
players[j]=players[j+1];
players[j+1]=t;
}
}
}
}
(12)退出:
void Exit();无需多言,具体实现如下:
void Exit(){
int Ecmd;
printf("是否确认退出系统?\n");
printf("退出请输入1 返回主菜单请输入0;\n");
scanf("%d",&Ecmd);
if(Ecmd==1)
exit(0);
else{
return;
}
}
四、结果与分析:
(一)使用案例及界面展示:
1.进入主菜单:
2.选择功能1.新建棋手信息表:
输入test.dat
提示新建表成功!按任意键返回主菜单。
3.不使用功能二选择并导入棋手信息表而使用其他功能的情况:
提示“无数据,请使用功能2正确选择并导入棋手信息表!”,按任意键返回主菜单。
4.选择功能2选择并导入棋手信息表:
可以看到之前创建的test.dat文件。
输入CHN.dat,提示文件已成功打开,按任意键返回主菜单。此时即可使用其他功能。
5.选择功能3显示棋手信息:
若打开一张没有信息的表(此处打开test.dat),则向用户提示如下:
如图,打开一张有信息的表,最上方输出当前打开的表的名称,提示用户打开的是哪张表;第二行统计表内的总人数。第三行展示表的字段,依次为编号、姓名、类别、段位、等级分。再往下就是各个棋手的相应数据。按任意键返回主菜单。
6.选择功能4查询棋手信息:
输入1。按棋手姓名查询,系统提示输入要查询的姓名,输入李梅,模糊搜索结果如下:
展示当前打开的表、棋手信息、共多少条查询记录。
同时精准输入姓名,也会根据输入的匹配度降序排序展示信息,输入韩梅梅:
输入2。按棋手身份查询,系统提示输入要查询的类别,输入1,结果如下:
展示当前打开的表、棋手信息、共多少条查询记录。
输入3。按棋手段位查询,系统提示输入要查询的段位,输入27,结果如下:
展示当前打开的表、棋手信息、共多少条查询记录。
输入3。按棋手段位查询,系统提示输入要查询的段位,输入19,没有对应信息的结果如下:展示当前打开的表、棋手信息、共多少条查询记录。
7.选择功能5增加棋手信息:
提示请输入新增棋手的编号,输入P001,结果如下:
再次进入,输入P1000,提示id输入不合法,需重新输入。重新输入P666、小明、0、30(不合法输入),666,提示级别输入不合法,需要在输入一遍。
重新输入正确格式信息,提示添加棋手成功,操作成功后,显示所有棋手信息。
8.选择功能6删除棋手信息:
提示输入要删除棋手的编号,输入P666,显示该棋手信息并在此询问是否删除。
输入1确定删除,提示成功删除。操作成功后,显示所有棋手信息。
9.选择功能7修改棋手信息:
提示输入要修改棋手的编号:输入P958(不存在),提示没有找到:
再次进入,输入P199,显示该棋手原来的信息,依次输入该棋手的新信息:
P199、蔡虚坤、0、12、521;提示修改成功。
按下回车,提示成功修改。操作成功后,显示所有棋手信息。:
10.选择功能8统计棋手信息:
输入2统计业余棋手人数:
输入3统计x等级分以上的人数,输入3000:
11.选择功能9对棋手信息进行排序:
输入1对所有棋手按等级分进行降序排序:
输入2职业棋手按段位进行降序排序:
满足排序规则:对所有棋手按等级分进行升序排序、对所以棋手按段位进行降序排序(等级分或段位相同的棋手按照棋手编号后三位数字大小升序排列)。
12.选择功能10退出:
提示是否退出。
输入1,程序结束,按下任意键软件退出。
(二)分析总结:
1.项目功能:
~~~~~~
项目介绍见“设计内容介绍”中的“项目内容及功能简介”。
~~~~~~
使用方案参考上文“使用案例及界面展示”。
2.项目创新点:
~~~~~~
搭建项目使用了分文件编写的方法,将多个独立或可重复使用的功能编写在头文件里,使得项目架构清晰,易于维护。
~~~~~~
在管理信息的方法上仿照数据库的设计,在一个系统下管理多个不同的表。并可以直接在软件中依照用户要求新建或打开目标数据文件。
~~~~~~
自己编写了“查询棋手信息”中模糊搜索的算法。
~~~~~~
学习了dirent.h内的相关函数与结构,来通过程序直接访问文件目录。
~~~~~~
使用了自学内容内的malloc函数相关内容。
3.心得体会及学习收获:
~~~~~~
本次课程设计让我第一次学会并成功开发一个相对完成的程序,第一次将各个零散的算法、程序等整合到一个工程中。同时让我明白,开发时编写文档和编写程序是相辅相成、齐头并进的,不是一股脑把文档写完再写程序,然后让未预料到的BUG或当前没有能力实现的功能阻碍进度。
~~~~~~
在写课程设计的时候学会了使用大驼峰命名法来规范函数名称、实践了用分文件编写的方式来完成一个项目、体会到了C语言中的精髓-指针的使用、拓展了课外许多有用的函数、学会了以DEV C++为例的IDE来管理项目。