4. 通讯录实现的需求分析和架构设计

本文实现的是通讯录产品的需求分析和架构设计,重点在于结构层次的设计,方便代码阅读和维护。

一、通讯录实现的需求分析

1、通讯录的功能清单

  1. 添加一个人员
  2. 打印显示所有人员
  3. 删除一个人员
  4. 查找一个人员
  5. 保存文件
  6. 加载文件

2,数据存储信息

  1. 人员存储方式 ——> 双向链表
  2. 文件存储格式 ——> 人员数据的格式
  3. 人员信息 ——> 姓名,电话
    name: xxx,phone: xxx
    name: xxx,phone: xxx

二、通讯录实现的架构设计

1、架构的设计应该从底层往上分析。

  • 支持层:数据链表的存储,以及文件的读写。
  • 接口层:将底层的链表数据进行读取后解析出name和phone(解包),以及读取name和phone后打包写入链表数据中(打包)。另外还有统一的功能接口层,这样即使文件存储方式改变,上层设计仍可以保持不变。
  • 业务层:业务逻辑

具体举个例子:
添加一个用户(功能) —> 输入用户名和电话号码(业务逻辑) —> 通过接口层add —> 插入到链表中
在这里插入图片描述
在这里插入图片描述

2、代码和难点

2.1代码实现过程中遇到以下几个难点

  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)
  1. 文件的操作函数
  2. 链表的插入删除

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);
}


猜你喜欢

转载自blog.csdn.net/Ricardo2/article/details/130787368