本文实现的是通讯录产品的需求分析和架构设计,重点在于结构层次的设计,方便代码阅读和维护。
一、通讯录实现的需求分析
1、通讯录的功能清单
- 添加一个人员
- 打印显示所有人员
- 删除一个人员
- 查找一个人员
- 保存文件
- 加载文件
2,数据存储信息
- 人员存储方式 ——> 双向链表
- 文件存储格式 ——> 人员数据的格式
- 人员信息 ——> 姓名,电话
name: xxx,phone: xxx
name: xxx,phone: xxx
二、通讯录实现的架构设计
1、架构的设计应该从底层往上分析。
- 支持层:数据链表的存储,以及文件的读写。
- 接口层:将底层的链表数据进行读取后解析出name和phone(解包),以及读取name和phone后打包写入链表数据中(打包)。另外还有统一的功能接口层,这样即使文件存储方式改变,上层设计仍可以保持不变。
- 业务层:业务逻辑
具体举个例子:
添加一个用户(功能) —> 输入用户名和电话号码(业务逻辑) —> 通过接口层add —> 插入到链表中
2、代码和难点
2.1代码实现过程中遇到以下几个难点
- 二级指针
//插入 /*如果传入struct person *person,一开始person指向的是NULL,那么形参改变不会影响实参。 参考值传递交换两个数。因此要传入的是二级指针struct person **pperson*/ int person_insert(struct person **pperson,struct person *ps) //删除 /*如果传入struct person *person,最后person指向的是NULL,那么形参改变不会影响实参。 参考值传递交换两个数。因此要传入的是二级指针struct person **pperson*/ int person_delete(struct person **pperson,struct person *ps) //加载文件 //若使用struct person *person,刚开始空时候指向的是NULL,无法更改,因此要用二级指针 int load_file(struct person **pperson,int *count,const char *filename)
2.利用状态机读取文件信息存入到通讯录中
//解析文本内的通讯信息 int parser_token(char *buffer,int length,char *name,char *phone) int load_file(struct person **pperson,int *count,const char *filename)
- 文件的操作函数
- 链表的插入删除
2.2具体代码如下
#include<stdio.h> #include<string.h> #include<stdlib.h> //为了避免代码突然出现新定义的数字影响阅读,建议都放在宏定义中 #define NAME_LENGTH 16 #define PHONE_LENGTH 32 #define BUFFER_LENGTH 128 #define MIN_TOKEN_LENGTH 5 #define INFO printf //'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' //''''''''''''''''''''支持层'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' //do{...}while(0)一般用于宏定义中,只执行依次,并且条件不成立时退出 //宏不止一行,则在结尾加反斜线符号使得多行能连接上 //把item插入到list之前,(list)是因为传入的是*pperson,加括号是让指针优先,即(*pperson)->prev #define LIST_INSERT(item,list) do { \ item->prev = NULL; \ item->next = list; \ if ((list) != NULL) (list)->prev=item;\ (list) = item; \ }while(0); #define LIST_REMOVE(item,list) do { \ if (item->prev != NULL) item->prev->next=item->next; \ if (item->next != NULL) item->next->prev=item->prev; \ if (list == item) list = item->next; \ item->prev =item->next=NULL; \ }while(0) //person类,包含姓名、电话 struct person { char name[NAME_LENGTH]; char phone[PHONE_LENGTH]; struct person *next; struct person *prev; }; //通讯录,里面有person类,总人数 struct contact { struct person *person; int count; //人数 }; enum{ OPEN_INSERT=1, OPEN_PRINT, OPEN_DELETE, OPEN_SEARCH, OPEN_SAVE, OPEN_LOAD }; //'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' //''''''''''''''''''''接口层'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' //插入 int person_insert(struct person **pperson,struct person *ps){ /*如果传入struct person *person,一开始person指向的是NULL,那么形参改变不会影响实参。 参考值传递交换两个数。因此要传入的是二级指针struct person **pperson */ if ( ps == NULL ) return -1; LIST_INSERT(ps,*pperson); return 0; } //删除 int person_delete(struct person **pperson,struct person *ps){ /*如果传入struct person *person,最后person指向的是NULL,那么形参改变不会影响实参。 参考值传递交换两个数。因此要传入的是二级指针struct person **pperson */ if ( ps == NULL ) return -1; LIST_REMOVE(ps,*pperson); return 0; } //查找 struct person* person_search(struct person *person,const char *name){ struct person *item = NULL; for (item=person;item != NULL ;item=item->next){ if (!strcmp(item->name , name)){ break;; } } return item; } //遍历 int person_traversal(struct person *person){ struct person *item = NULL; for (item=person;item != NULL ;item=item->next){ INFO("name: %s,phone: %s\n",item->name,item->phone); } return 0; } //保存文件 int save_file(struct person *person,const char *filename){ FILE *fp=fopen(filename,"w"); if (fp == NULL) return -1; struct person *item=NULL; for (item=person;item!=NULL;item=item->next){ fprintf(fp,"name: %s,phone: %s\n",item->name,item->phone); fflush(fp);//fprintf是将数据存在缓冲区内,通过fflush刷新到磁盘中 } fclose(fp); } //解析文本内的通讯信息 int parser_token(char *buffer,int length,char *name,char *phone){ if (buffer == NULL ) return -1; if (length <MIN_TOKEN_LENGTH) return -2; //文件结尾默认有一个文件结束标识符,大小不会超过5个字节 //name: qiuxiang,telephone: 98765678123 int i=0,j=0,status=0; //读取 name: qiuxiang for (i=0;buffer[i]!=',';i++){ if (buffer[i]==' '){ status=1; } else if(status==1){ //将buffer[i]赋值给name[j],而后j++ name[j++]=buffer[i]; } } //读取 telephone: 98765678123 status=0; j=0; for (;i<length;i++){ if (buffer[i]==' '){ status=1; } else if(status==1){ //将buffer[i]赋值给name[j],而后j++ phone[j++]=buffer[i]; } } INFO("file token : %s --> %s\n", name, phone); return 0; } //加载文件 //若使用struct person *person,刚开始空时候指向的是NULL,无法更改,因此要用二级指针 int load_file(struct person **pperson,int *count,const char *filename){ FILE *fp=fopen(filename,"r"); if (fp == NULL ) return -1; //feof():侦测是否读取到了文件尾,如果已到文件尾则返回非零值,其他情况返回 0 while (!feof(fp)){ char buffer[BUFFER_LENGTH]={0}; //fgets(str,n,fp):从 fp 所指文件中读入 n-1 个字符放入 str 为起始地址的空间内;如果在未读满 n-1 个字符时,则遇到换行符或一个 EOF 结束本次读操作,并已 str 作为函数值返回. fgets(buffer,BUFFER_LENGTH,fp); int length=strlen(buffer); INFO("legth :%d\n",length); //name: qiuxiang,telephone: 98765678123 char name[NAME_LENGTH]={0}; char phone[PHONE_LENGTH]={0}; if (0 != parser_token(buffer,length,name,phone)){ continue; } struct person *p=(struct person*)malloc(sizeof(struct person)); if (p == NULL) return -2; //void *memcpy(void *str1, const void *str2, size_t n) 从存储区 str2 复制 n 个字节到存储区 str1 memcpy(p->name,name,NAME_LENGTH); memcpy(p->phone,phone,PHONE_LENGTH); person_insert(pperson,p); (*count)++; } fclose(fp); return 0; } //'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' //''''''''''''''''''''业务逻辑层'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' int insert_entry(struct contact *cts){ if (cts ==NULL) return -1; //输入空信息错误 struct person *p=(struct person*)malloc(sizeof(struct person)); if (p == NULL) return -2; //内存分配错误 //name INFO ("Please Input Name:\n"); scanf("%s",p->name); //phone INFO ("Please Input Phone:\n"); scanf("%s",p->phone); //add to person if (0 !=person_insert(&cts->person,p)){ free(p); return -3; } cts->count++; INFO("Insert Success\n"); return 0; } int print_entry(struct contact *cts){ //打印通讯录中的信息 if (cts == NULL ) return -1; person_traversal(cts->person); } int delete_entry(struct contact *cts){ //删除通讯录中某个人的信息 if (cts == NULL ) return -1; INFO("Please Input Name:\n"); char name[NAME_LENGTH] ={0}; scanf("%s",name); //判断输入的name是否存在通讯录中 struct person *ps=person_search(cts->person,name); if (ps == NULL) { INFO("Person don't Exit\n"); return -2; } //删除 person_delete(&cts->person,ps); free(ps); return 0; } int search_entry(struct contact *cts){ //查找通讯录中某个人的信息 if (cts == NULL ) return -1; INFO("Please Input Name:\n"); char name[NAME_LENGTH] ={0}; scanf("%s",name); //判断输入的name是否存在通讯录中 struct person *ps=person_search(cts->person,name); if (ps == NULL) { INFO("Person don't Exit\n"); return -2; } INFO("name: %s, phone: %s",ps->name,ps->phone); return 0; } int save_entry(struct contact *cts){ //将通讯录保存 if (cts==NULL) return -1; INFO("Please Input Save Filename:\n"); char filename[NAME_LENGTH]={0}; scanf("%s",filename); save_file(cts->person,filename); } int load_entry(struct contact *cts){ //加载文件中的通讯录 if(cts==NULL) return -1; INFO("Please Input Load Filename:\n"); char filename[NAME_LENGTH]={0}; scanf("%s",filename); load_file(&cts->person,&cts->count,filename); } void menu_info() { INFO("\n\n********************************************************\n"); INFO("***** 1. Add Person\t\t2. Print People ********\n"); INFO("***** 3. Del Person\t\t4. Search Person *******\n"); INFO("***** 5. Save People\t\t6. Load People *********\n"); INFO("***** Other Key for Exiting Program ********************\n"); INFO("********************************************************\n\n"); } int main(){ struct contact *cts =(struct contact *)malloc(sizeof(struct contact)); if(cts == NULL) return -1; memset(cts,0,sizeof(struct contact)); //初始化cts为0 while(1){ menu_info(); int select=0; scanf("%d",&select); switch (select) { case OPEN_INSERT: insert_entry(cts); break; case OPEN_PRINT: print_entry(cts); break; case OPEN_DELETE: delete_entry(cts); break; case OPEN_SEARCH: search_entry(cts); break; case OPEN_SAVE: save_entry(cts); break; case OPEN_LOAD: load_entry(cts); break; default: goto exit; } } exit: return 0; free(cts); }