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

news2024/12/23 13:16:53

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

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

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


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/550119.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[CTF/网络安全] 攻防世界 disabled_button 解题详析

[CTF/网络安全] 攻防世界 disabled_button 解题详析 input标签姿势disable属性总结 题目描述&#xff1a;X老师今天上课讲了前端知识&#xff0c;然后给了大家一个不能按的按钮&#xff0c;小宁惊奇地发现这个按钮按不下去&#xff0c;到底怎么才能按下去呢&#xff1f; input标…

Tiny+ 语言词法之C语言

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 语义分析本质上就是在语法分析的基础上进一步完善分析的功能。举个例子来说&#xff0c;在语法分析部分的 if_stmt 函数中&#xff0c;在语义上判断条件必须返回布尔类型的值&#xff0c;因此我们加入一个判断&#xff0c;判断…

Unity之OpenXR+XR Interaction Toolkit示例Demo详解

一.前言 自从升级Unity版本到2021,然后使用OpenXR开发VR之后,我们整个团队的开发效率都提升了不少,这证明了不管什么领域,统一接口,统一规范都是必须的。 关于XR Interaction Toolkit插件,我已经写了几篇文章了,今天才想起来,最基础的Demo讲解还没有写,其实官方的这个…

chatgpt赋能Python-pythonfor循环5次

Python中for循环的使用方法及技巧 Python作为一种高级编程语言&#xff0c;其独特的语法结构和方便的操作方法受到了越来越多人的欢迎和喜爱。其中&#xff0c;for循环是Python程序员必备的基本技巧之一。在这篇文章中&#xff0c;我们将介绍Python中for循环的使用方法及技巧。…

HTTP协议【面试高频考点】

目录 一、HTTP 响应 1.首行 2.状态码&#xff08;经典面试题&#xff0c;必考&#xff09; 2.1 200 OK 2.2 404 Not Found 2.3 403 Forbidden 2.4 500 Internal Server Error 2.5 504 Gateway Timeout 2.6 302 Move temporarily 2.7 301 Moved Permanently 2.8 状态…

clearmymac4.13.5专业的Mac系统清理优化工具

CleanMyMac X是一款功能强大的Mac清理工具&#xff0c;它可以扫描您的Mac电脑&#xff0c;清除垃圾文件&#xff0c;卸载无用的应用程序&#xff0c;并优化系统性能。此外&#xff0c;CleanMyMac X还可以找到和修复Mac电脑上的许多其他问题&#xff0c;即使您不是技术专家也可以…

chatgpt赋能Python-pythona__a

Python中的aa 介绍 Python是一种流行的编程语言&#xff0c;具有简单易学和可读性强的特点。在Python中&#xff0c;常常使用aa这样的表达式&#xff0c;它表示将变量a的原始值加上它自己的值&#xff0c;然后将结果赋值给变量a。这种语法看起来很简单&#xff0c;但实际上有…

C语言函数大全-- _w 开头的函数(5)

C语言函数大全 本篇介绍C语言函数大全-- _w 开头的函数 1. _wspawnl 1.1 函数说明 函数声明函数功能int _wspawnl(int mode, const wchar_t* cmdname, const wchar_t* arglist, ...);启动一个新的进程并运行指定的可执行文件 参数&#xff1a; mode &#xff1a; 启动命令的…

【008】C++数据类型之重要关键字详解

C数据类型之重要关键字详解 引言一、const修饰普通变量重点说明 二、register修饰寄存器变量三、volatile强制访问内存四、sizeof测试类型的大小五、typedef关键字总结 引言 &#x1f4a1; 作者简介&#xff1a;专注于C/C高性能程序设计和开发&#xff0c;理论与代码实践结合&a…

搭建go web 框架

思想base部分day1:封装gee封装context上下文封装前缀tree路由树分组封装group与中间件封装文件解析封装封装错误处理测试 思想 web框架服务主要围绕着请求与响应来展开的 搭建一个web框架的核心思想 1 便捷添加响应路径与响应函数(base) 2 能够接收多种数据类型传入(上下文cont…

第二章 表操作

一、数据表的设计理念 数据表是包括数据库所有数据的数据库对象&#xff0c;数据在表中的组织方式与在电子表格中相似&#xff0c;都是按行和列的格式组织的&#xff0c;其中每一行代表一条唯一的记录&#xff0c;每一列代表记录中的字段&#xff0c;表中的数据库对象包含列、…

Godot4节点树右键菜单添加自定义选项

前言 查看godot的源码推荐使用在线版vscode直接从github上看。&#xff08;直接把网址的com改成dev即可&#xff09; 重点查看以下源码 scene_tree_dock.h scene_tree_dock.cpp 开始 tool extends EditorPluginvar window var scene_menustatic func find_child_by_class(no…

OneDrive同步角标消失 - 解决方案

问题 在电脑端使用OneDrive时&#xff0c;文件管理器OneDrive文件夹内的文件会在左下角显示同步状态&#xff0c;如下图。若没有显示同步角标&#xff0c;则此功能出现异常&#xff0c;下文介绍如何显示同步角标。 值得一提的是&#xff0c;同步角标只起到显示作用&#xff0…

secure CRT 颜色主题 系统间拷贝

文章目录 颜色主题如何切换 SecureCRT 颜色主题如何新建SecureCRT 颜色 主题如何拷贝我的颜色主题,主题名为pic VIM系统间拷贝vim中 ubuntu 拷贝 到 win1. 确保 ubuntu 上的 vim 支持 clipboard 特性2. 在 ~/.vim/vimrc 中添加设置3. vim中 win拷贝到ubuntu SecureCRT 关键字高…

STM32实现基于RS485的简单的Modbus协议

背景 我这里用STM32实现&#xff0c;其实可以搬移到其他MCU&#xff0c;之前有项目使用STM32实现Modbus协议 这个场景比较正常&#xff0c;很多时候都能碰到 这里主要是Modbus和变频器通信 最常见的是使用Modbus实现传感器数据的采集&#xff0c;我记得之前用过一些传感器都…

pg事务:隔离级别历史与SSI

事务隔离级别的历史 ANSI SQL-92定义的隔离级别和异常现象确实对数据库行业影响深远&#xff0c;甚至30年后的今天&#xff0c;绝大部分工程师对事务隔离级别的概念还停留在此&#xff0c;甚至很多真实的数据库隔离级别实现也停留在此。但后ANSI92时代对事物隔离有许多讨论甚至…

队列的实现(附含两道经典例题)

&#x1f349;文章主页&#xff1a;阿博历练记 &#x1f4d6;文章专栏&#xff1a;数据结构与算法 &#x1f68d;代码仓库&#xff1a;阿博编程日记 &#x1f365;欢迎关注&#xff1a;欢迎友友们点赞收藏关注哦&#x1f339; 文章目录 &#x1f33e;前言&#x1f3ac;队列&…

探索Windows CMD命令的经典应用:实用技巧大揭秘,值得收藏

文章目录 导语&#xff1a;第一部分&#xff1a;CMD命令基础1. ipconfig&#xff1a;获取网络配置信息2. ping&#xff1a;测试网络连接3. dir&#xff1a;查看目录内容4. telnet&#xff1a;远程登录和测试网络服务 第二部分&#xff1a;进程管理5. tasklist&#xff1a;查看正…

10-Docker发布微服务

文章目录 搭建SpringBoot项目发布微服务项目到Docker容器 搭建SpringBoot项目 搭建一个简单的SpringBoot项目&#xff1a; 创建maven工程&#xff0c;pom为&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://m…

【Java多线程案例】单例模式

本期讲解单例模式的饿汉模式与懒汉模式&#xff0c;以及如何解决懒汉模式造成线程的不安全问题。 目录 什么是单例模式&#xff1f; 1. 饿汉模式 2. 懒汉模式 2.1 懒汉模式单线程版 2.2 懒汉模式多线程版 3. 解决懒汉模式不安全问题 3.1 保证原子性 3.2 防止指令重排序…