本文实现的是通讯录产品的需求分析和架构设计,重点在于结构层次的设计,方便代码阅读和维护。
一、通讯录实现的需求分析
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){ \
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);
}