一、实习题目
通讯录管理系统
二、需求分析
问题描述:本程序属于非数值计算型算法设计,学生需要设计出图书馆模拟系统所需要的基本功能,并设计简单的界面(无需图形化)。
实现功能:
1、通过提示菜单选择可以进行的操作
2、将图书的信息存入文件中,并命名为BookInfo.txt
3、将图书借阅情况信息存入文件中,并命名为SendInfo.txt
4、在本系统中可以进行管理系统包含的基本操作,其中包括:
a)查看所有图书的信息;
b)输入一本图书的图书编号,从当前图书中进行查找,如果找到则显示该图书的相关信息,如果没有找到则给出提示信息;
c)添加一本图书的基本信息,通过输入图书编号,首先查找是否存在该图书编号的图书,如果存在则提示重新输入,否则将该图书按照顺序插入到相应位置;
d)删除一本图书的基本信息,通过输入图书编号,首先查找是否存在该图书编号的图书,如果存在则将该图书删除,否则给出提示信息,提示该图书不存在;
e)借阅一本图书,需要给出学号和图书编号,如果图书编号不存在则重新输入,直到输入正确为止,并将学号和相应的图书编号存入SendInfo.txt文件中。
5、图书基本信息包括图书编号、书名、作者、出版社和价钱这些简单信息。
6、图书信息文件中每一行存放一本图书的信息。
7、借阅信息文件中每一行存放一本书的借阅情况。
知识点:
本程序主要考察对自定义函数的熟悉程度,本程序中主要使用到的是数组的相关操作,包括数组的输入、输出、查找、插入、删除等操作,需要对数组有比较深入的掌握。
说明:
(1)当程序执行的时候所读取的图书信息文件必须存在,否则可能会出现错误。
(2)图书信息文件中存放图书信息的时候是按照行来存放的,即一行一本书。
(3)借阅信息文件中存放学生借阅情况是按照行来存放的,即一行存放一个学号和一个图书编号。
(4)程序执行的基本过程为:
a)在所有操作之前,也就是加载操作菜单之前,先从文件中读取所有图书的信息,并存入一个数组中,此时数组可以定义为包含100个元素;
b)然后根据菜单所进行的所有操作都是对当前数组进行操作,此时也就是对数组的查找、定位、添加、修改、删除操作;
c)当退出系统的时候再将当前数组中的所有元素按照一本图书一行的方式写回图书信息文件中,此时注意选择覆盖方式,这样就可以将原来的所有数据覆盖,只保留最新的数据;
当按行读取图书信息文件的时候,有可能最后一行只有一个回车,这时候实际上所读取的数据为空字符串,需要对此作判断,如果是空字符串,则说明已经没有人员,就必须将当前读入的空字符串写入数组中。
三、概要设计
系统功能模块图:
添加:可以添加通讯录记录,依次输入编号、姓名、年龄、电话号码、通讯地址、电子邮箱后,会提示是否继续添加。
显示:可以以表格形式输出所有通讯录里的记录。
删除:输入欲删除的那个人的名字后,会选择删除他(她)的记录内容。
查询:可以选择用姓名、电话、地址三种方式查询。
修改:输入欲修改的那个人的名字后,再依次输入编号、姓名、年龄、电话号码、通讯地址、电子邮箱即可完成修改。
保存:输入文件名(带后缀名)后,即可将通讯录信息保存到文件。
帮助:显示帮助信息。
四、详细设计
(1).通讯录程序主体:
#include #include #include typedef struct /*公共部分*/ { int score; /*编号*/ char name[10]; /*姓名*/ char num[15]; /*号码*/ char email[20]; /*邮箱*/ char age[8]; /*年龄*/ char adds[20]; /*住址*/ }Person; Person pe[80],temp; int menu_select(); /*界面函数 陆*/ int Input(Person per[],int n); /*添加记录 王*/ void Display(Person per[],int n); /*显示函数 公共*/ int Delete_a_record(Person per[],int n); /*删除函数 赵应振*/ void Query_a_record(Person per[],int n); /*查询函数 王嘉*/ void Change(Person per[],int n); /*修改函数 赵*/ void WritetoText(Person per[],int n); /*打印保存 公共*/ void rank(int n); /*排序函数 陆*/ void main() /*主函数 陆*/ { int n=0,i=0,k; FILE *fp1; /*运行之前从文件中读取之前输入的项目*/ if((fp1=fopen("PersonInfo.dat如果没有文件,自动创建一个*/ { fp1=fopen("PersonInfo.dat } do { k=fread(&pe[i],sizeof(Person),1,fp1); i++; n=i-1; }while(k!=0); /*文件读取完毕或错误时fread函数返回值为0。资料来自csdn论坛*/ fclose(fp1); for(;;) { switch(menu_select()) { case 1: printf("\\n\添加记录到通讯录\\n"); /*添加记录*/ n=Input(pe,n); break; case 2: printf("\\n\\\通讯录记录表\\n"); /*显示记录*/ Display(pe,n); break; case 3: printf("\\n\从通讯录中删除记录\\n"); n=Delete_a_record(pe,n); /*删除记录*/ printf("\"); system("pause"); break; case 4: printf("\\n\在通讯录中查找记录\\n"); Query_a_record(pe,n); /*查找记录*/ printf("\"); system("pause"); break; case 5: printf("\\n\修改通讯录中的记录\\n"); Change(pe,n); /*修改数据*/ printf("\"); system("pause"); break; case 6: printf("\\n\保存通讯录中的记录\\n"); WritetoText(pe,n); /*保存数据*/ printf("\"); system("pause"); break; case 7: /*帮助信息*/ printf("\\n\使用帮助\\n"); printf("\\n\#1.添加记录,用于输入你想添加的条目的信息,注意格式。\\n"); printf("\\n\#2.显示记录,用于显示当前所有条目。\\n"); printf("\\n\#3.删除记录,用于删除当前所有条目中的某一条。\\n"); printf("\\n\#4.查找记录,用于查找当前所有条目中的某一条,可以姓名模糊查找。\\n"); printf("\\n\#5.修改记录,用于修改当前所有条目中的某一条。\\n"); printf("\\n\#6.保存记录,用于保存当前所有条目和打印当前所有条目。\\n"); system("pause"); break; case 0: printf("\\n\\谢谢使用,再见!\\n"); /*结束程序*/ printf("\\n\\"); system("pause"); printf("\\n\\"); exit(0); } } } 主体部分主要做一些基本定义,如结构体定义,函数声明,主函数。 其中主函数中有读取函数,即开始使用时加载之前输入存储文件内容信息,另外的存储文件内容信息函数部分,我们放在了存储打印函数了。函数主体是一个死循环加通道函数,循环的终止时依靠system()和exit()函数控制。 (二)工作流程图: ①添加函数: 用于添加通讯录记录,入口参数为结构体,总人数n,出口参数n+i,无其他函数调用 ②显示函数: 用于显示通讯录记录,入口参数为结构体,总人数n,无出口参数,调用排序函数函数对编号进行排序 ③删除: 用于删除通讯录记录,入口参数为结构体,总人数n,出口参数为n,无其他函数调用 ④查询函数,先选择查询方式,以姓名查询精确查找方式为例 用于查询通讯录记录,入口参数为结构体,总人数n,无出口参数,无其他函数调用 查询函数的功能亮点在于模糊查找,例如查找“王家”,只需查“王”,就可显示王家的全部信息。我用的是strstr函数 ⑤修改: 用于修改通讯录记录,入口参数为结构体,总人数n,无出口参数,无其他函数调用 ⑥排序函数 用于显示函数的排序功能,入口函数:总人数n,无出口函数,无函数调用。 主要为冒泡排序内容,有两个循环控制结构体内容的交换。 五、运行结果及分析 1).显示的主菜单界面 2).添加界面: 3).显示界面: 4).删除界面: 删除后,通讯录里的显示记录: 5).查询界面: 精确查找单元演示 模糊查找单元演示 注:只有姓名查找有模糊查找。 6).修改界面: 修改函数后显示: 7).保存界面: PersonInfo.txt文档截图 六、调试与测试 测试1: 当主菜单选项要输入0到7的数字时,c=getchar();输入两个以上字符有gets(null);只对第一个数字进行选项操作。其余多余字符进入null数组 对于某些功能有确定选项的y/n输入时也有上面gets(null)讲多余字符进入null数组; 测试2: 当主菜单选项要输入0到7的数字时,输入字母,会在界面函数循环do while(c<'0'||c>'7'),循环中有清屏system("cls");直到有0到7输入时循环结束。 测试3 添加函数和修改函数中输入编号是一定为整型数字, Do { while(0==scanf("\%d",&temp)) { while('\\n'!= getchar()) {} printf("\输入无效!请重新输入\\n\编号:"); } for(j=0;j if(temp==per[j].score) k1++; } /*k1为两种情况0或1*/ if(k1==1) /*k1为1时,有重复并使k1归零进入循环*/ { printf("\编号重复,请重新输入\\n\编号:"); k1=0; } else break; /*k1为0时,无重复,结束循环*/ }while(k1==0); /*循环的终止条件是没有找出相同,k1=0*/ 输入字符会有 while(0==scanf("\%d",&temp)) { while('\\n'!= getchar()) {} printf("\输入无效!请重新输入\\n\编号:"); } 输入重复编号会有 if(temp==per[j].score) k1++; 控制输入的正确。 测试4 添加函数和修改函数中输入邮箱地址时 会有 if(per[n+i].email[j]=='@') 对邮箱@字符自动检测 测试5 删除函数时有姓名索引,进行删除操作的,对于重名的人我们采用姓名+编号删除。 先输入姓名,全部显示重名人,选择重名人的中确定删除人的编号进行删除。 测试6 查询函数中的模糊查找运用 strstr(per[i].name,s) 指向第一次出现,在控制i的循环指向 第二次出现的i,依次下去并每次的显示出找出的条目 测试7 文件的保存和打印, 保存为对结构体信息的保存,此信息用.dat格式保存,以便下次运行时在从.dat文件中读取放回到结构体数组中。 打印室对结构体信息的打印,此信息用.txt格式打印,我们可以在工程文件夹先看到此文件,并打开阅读。 七、课程设计心得体会 这次的我们进入大学以来学习程序设计语言结果的一次大检验。自己动手做,自己发现和解决问题。发现了自己许多的不足。平时没有掌握好的知识在这次实验中彻底暴露出来,经过不断思考,不断查阅资料和上机运行,并在CSDN论坛上学习到了很多东西。 例如: 在函数中的system函数的应用,system("cls")用于清屏,system("pause")用于暂停操作。fflush(stdin)用于清除缓存, 其中与两条我们在网上找了许多的资料 1,在c语言文件操作中的fread函数,fread函数返回值为0时为文件读取完毕或读取错误,我们用来把他作为控制录入功能中的循环终止条件,取得循环次数i,并用来得到输入之后的n值。 2,在c语言中的不同类型的变量读取的问题,输入中当用scanf(int*)输入了字母,则会发生死循环。 来自于互联网资料: 当输入字母时,scanf返回的是0,也就是输入没有成功,MSDN<微软开发文档>对此有解释: A return value of 0 indicates that no fields were assigned。 这时字母会遗留在"输入缓冲区"中。因为缓冲中有数据,所以scanf 不会等待用户输入,就再次去缓冲中读取,可是缓冲中的却是字母,这个字母再次被遗留在缓冲中,如此反复,从而导致死循环。可用getchar将输入缓冲区中的内容清掉,从而避免了scanf输入失败时的死循环 程序用了很多的循环,因此对于循环的控制要十分小心,循环三个函数for while和do-while的区别也是十分重要的,有的循环是要先作某步,就用do-while,有的循环不知道循环次数用while等等。 解决其中大部分问题,当然还存在一些问题没有解决。我相信在以后的学习能够解决好它们。 但是,收获还是不小的,我不仅对C的操作有了进一步的掌握,还了解到了程序设计的书写风格及其注释的格式。 还有一点体会就是,书上和老师教的内容是有限的,我们需要不断地靠自己去学习,向他人请教,了解和掌握更多的知识,这样我们才能编出更好的C程序。 总体来说,这次C语言程序设计实验还是比较成功的,虽然最终程序还存在一些不足,但能取得这样的成绩我还是比较高兴的。 希望以后的日子,能有更加努力。 八、附录 源程序清单 #include #include #include t公共部分*/ { 编号*/ 姓名*/ 号码*/ 邮箱*/ 年龄*/ 住址*/ }Person; Person pe[80],temp; i界面函数 陆*/ i添加记录 王*/ v显示函数 公共*/ i删除函数 赵应振*/ v查询函数 王嘉*/ v修改函数 赵*/ v打印保存 公共*/ v排序函数 陆*/ v主函数 陆*/ { 运行之前从文件中读取之前输入的项目*/ if((fp1=fopen("PersonInfo.dat如果没有文件,自动创建一个*/ fp1=fopen("PersonInfo.dat 文件读取完毕或错误时fread函数返回值为0。资料来自csdn论坛*/ 添加记录到通讯录\添加记录*/ 通讯录记录表\\n"); /*显示记录*/ 从通讯录中删除记录\\n"); 删除记录*/ 在通讯录中查找记录\\n"); 查找记录*/ 修改通讯录中的记录\\n"); 修改数据*/ 保存通讯录中的记录\\n"); 保存数据*/ case 7: /*帮助信息*/ 使用帮助\\n"); 添加记录,用于输入你想添加的条目的信息,注意格式。\\n"); 显示记录,用于显示当前所有条目。\\n"); 删除记录,用于删除当前所有条目中的某一条。\\n"); 查找记录,用于查找当前所有条目中的某一条,可以姓名模糊查找。\\n"); 修改记录,用于修改当前所有条目中的某一条。\\n"); 保存记录,用于保存当前所有条目和打印当前所有条目。\\n"); 谢谢使用,再见!结束程序*/ } i界面函数 陆*/ { 通讯录***** \\n"); ┌───────┐\\n"); │ 1. 添加记录 │\\n"); │ 2. 显示记录 │\\n"); │ 3. 删除记录 │\\n"); │ 4. 查询记录 │\\n"); │ 5. 修改记录 │\\n"); │ 6. 保存记录 │\\n"); │ 7. 功能帮助 │\\n"); │ 0. 退出程序 │\\n"); └───────┘\\n"); 提醒:退出程序前,请保存记录。\\n"); 请您选择(0-7):"); } i添加记录 王*/ { 为临时编号存储变量*/ 为编号检测阀开关,k2为@符检测阀开关*/ 编号:编号的唯一:by:王嘉*/ 输入无效!请重新输入\\n\编号:"); 此处循环为计数k1,k1初值为0*/ 为两种情况0或1*/ 为1时,有重复并使k1归零进入循环*/ 编号重复,请重新输入\\n\编号:"); 为0时,无重复,结束循环*/ 循环的终止条件是没有找出相同,k1=0*/ 临时到最终*/ 姓名:"); 年龄:"); 电话号码:"); 通讯住址:"); 电子邮箱:"); 的@符检测*/ 陆文虎*/ 格式错误,未检测到合法@符,请重新输入\\n\电子邮箱:"); 是否继续添加?(Y/N)"); 只能输入y和n(不区分大小写)*/ 选择错误,请重新选择\\n\是否继续添加?(Y/N)"); } v显示函数*/ /*公共*/ { 调用排序函数。*/ 格式*/ 编号 姓名 年龄 电话号码 通讯地址 电子邮箱\\n"); } i删除函数 赵应振*/ { 为标记的i值,deletescore为选择删除的编号*/ 请输入想删除记录中的名字:"); 检测输入的姓名存在*/ 通讯录中没有此人!\\n"); 同名人员删除问题*/ 统计并找出通讯录中存在相同姓名的条目*/ 此人编号: %d\\n",per[i].score); 此人姓名: %s\\n",per[i].name); 此人年龄: %s\\n",per[i].age); 通讯地址: %s\\n",per[i].adds); 电子邮箱: %s\\n",per[i].email); 当姓名唯一时*/ 是否删除? 已经成功删除!\\n"); 未删除\\n\谢谢使用!\\n\"); 当姓名有两个或以上时,按编号删除。*/ 请输入要删除人的编号!\\n\\"); 此人编号: %d\\n",per[i].score); 此人姓名: %s\\n",per[i].name); 此人年龄: %s\\n",per[i].age); 通讯地址: %s\\n",per[i].adds); 电子邮箱: %s\\n",per[i].email); 是否删除?(y/n)\\n\\"); 已经成功删除!\\n"); 谢谢使用!"); } v查询函数by:王嘉*/ { 请选择查询方式:查找总菜单*/ ┌───────┐\\n"); │1--------姓名 │\\n"); │2--------电话 │\\n"); │3--------地址 │\\n"); │4--------返回 │\\n"); └───────┘\\n"); 请选择:"); 输入错误,请重新选择:"); 请选择查询方式:查找二级菜单*/ ┌────────┐\\n"); │1------精确查找 │\\n"); │2------模糊查找 │\\n"); └────────┘\\n"); 请选择:"); 输入错误,请重新选择:"); 精确查找单元*/ 请输入想查询的姓名:"); 通讯录中没有此人!\\n"); 此人编号: %d\\n",per[i].score); 此人年龄: %s\\n",per[i].age); 电话号码: %s\\n",per[i].num); 通讯地址: %s\\n",per[i].adds); 电子邮箱: %s\\n",per[i].email); 模糊查找单元*/ 请输入想查询的姓名:"); 使用strstr函数指向第一次出现*/ 此人编号: %d\\n",per[i].score); 此人年龄: %s\\n",per[i].age); 电话号码: %s\\n",per[i].num); 通讯地址: %s\\n",per[i].adds); 电子邮箱: %s\\n",per[i].email); 通讯录中没有此人!\\n"); /*如果找了n次没结果,则无此人*/ 请输入想查询的电话:"); 通讯录中没有此人!\\n"); 此人编号: %d\\n",per[i].score); 此人姓名: %s\\n",per[i].name); 此人年龄: %s\\n",per[i].age); 通讯地址: %s\\n",per[i].adds); 电子邮箱: %s\\n",per[i].email); 请输入想查询的地址:"); 通讯录中没有此人!\\n"); 此人编号: %d\\n",per[i].score); 此人姓名: %s\\n",per[i].name); 此人年龄: %s\\n",per[i].age); 电话号码: %s\\n",per[i].num); 电子邮箱: %s\\n",per[i].email); } v修改函数 赵*/ { 请输入想修改的记录中的名字:"); 通讯录中没有此人!\\n"); 此人编号: %d\\n",per[i].score); 此人姓名: %s\\n",per[i].name); 此人年龄: %s\\n",per[i].age); 电话号码: %s\\n",per[i].num); 电子邮箱: %s\\n",per[i].email); 重新输入信息:\\n"); 编号:"); 修改函数同样重复输入函数时的问题*/ 输入无效!请重新输入\\n\编号:"); 此处循环为计数k1,k1初值为0*/ 为两种情况0或1*/ 为1时,有重复并使k1归零进入循环*/ 编号重复,请重新输入\\n\编号:"); 为0时,无重复,结束循环*/ 循环的终止条件是没有找出相同,k1=0*/ 姓名:"); 年龄:"); 电话号码:"); 通讯住址:"); 电子邮箱:"); 的@符检测*/ 陆文虎*/ 格式错误,未检测到合法@符,请重新输入\\n\电子邮箱:"); 修改成功!"); } v打印和保存数据 公共 */ { 打印信息*/ if((fp=fopen("PersonInfo.txt定义文件的具体位置 无法打开文件\\n"); 文件打印的格式*/ 通讯录****************************************\\n"); 编号 姓名 年龄 电话号码 通讯地址 电子邮箱\\n"); 输出文件各项精确对齐*/ 共有%d条记录*************************************\\n",n); 关闭文件*/ 打印成功!\\n"); 文件打印路径PersonInfo.txt"); 保存数据*/ if((fp1=fopen("PersonInfo.dat 不能打开!\\n"); } v排序函数 陆*/ { 按编号进行冒泡排序*/ } /*1.自动载入之前已存的条目,信息不丢失 2.编号唯一,精确定位你想要的条目 3.邮箱的@符号检测 4.模糊查找,你可以更快找到你要的信息 5.显示时,按编号排序 6.可打印出通讯录信息,导出更方便 7.优化输入关节,减少误操作影响*/