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

news2024/11/24 0:22:31

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

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

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/549807.html

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

相关文章

实际开发中一些实用的JS数据处理方法

写在开头 JavaScript 是一种脚本语言&#xff0c;最初是为了网页提供交互式前端功能而设计的&#xff0c;而现在&#xff0c;通过 Node.js&#xff0c;JavaScript 还可以用于编写服务器端代码。 JavaScript 具有动态性、基于原型的面向对象特性、弱类型、多范式、支持闭包执行…

Golang每日一练(leetDay0072) 课程表 I\II Course Schedule

目录 1. 课程表 Course Schedule I &#x1f31f;&#x1f31f; 2. 课程表 Course Schedule II &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一…

电子邮件协议(SMTP,MIME,POP3,IMAP)

SMTP 关键词&#xff1a; 电子邮件协议:SMTP简单邮件传输协议&#xff0c;负责将邮件上传到服务器&#xff0c;采用TCP的25端口&#xff0c;C/S工作。仅传送ASCII码文本 详细介绍&#xff1a; SMTP是一种提供可靠且有效的电子邮件传输的协议。SMTP是建立在FTP文件传输服务上…

学系统集成项目管理工程师(中项)系列23b_信息系统集成及服务管理(下)

1. 信息技术服务 1.1. 供方为需方提供如何开发、应用信息技术的服务&#xff0c;以及供方以信息技术为手段提供支持需方业务活动的服务 1.2. 信息技术咨询服务、设计与开发服务、信息系统集成服务、数据处理和运营服务及其他信息技术服务 2. 信息系统审计 2.1. 收集并评估证…

Golang中的协程(goroutine)

目录 进程 线程 并发 并行 协程(goroutine) 使用sync.WaitGroup等待协程执行完毕 多协程和多线程 进程 进程就是程序在操作系统中的一次执行过程&#xff0c;是系统进行资源分配和调度的基本单位&#xff0c;进程是一个动态概念&#xff0c;是程序在执行过程中分配和管理…

C语言_用VS2019写第一个C语言或C++程序

接上一篇&#xff1a;C语言简述、特点、常用编译器&#xff0c;VS2010写第一个C语言程序 本次来分享用VS2019来写C语言或C程序&#xff0c;也是补充上一篇的知识&#xff0c;话不多说&#xff0c;开始上菜&#xff1a; 此博主在CSDN发布的文章目录&#xff1a;我的CSDN目录&…

微信小程序nodejs+vue+uniapp超市网上购物商城系统

超市购物系统用户端要求在系统的安卓手机上可以运行&#xff0c;主要实现了管理端&#xff1b;首页、个人中心、用户管理、商品分类管理、商品信息管理、商品入库管理、订单信息管理、订单配送管理、订单评价管理、退货申请管理、换货申请管理、系统管理&#xff0c;用户端&…

总结857

学习目标&#xff1a; 月目标&#xff1a;5月&#xff08;张宇强化前10讲&#xff0c;背诵15篇短文&#xff0c;熟词僻义300词基础词&#xff09; 周目标&#xff1a;张宇强化前3讲并完成相应的习题并记录&#xff0c;英语背3篇文章并回诵 每日必复习&#xff08;5分钟&#…

4-《安卓进阶》

4-《安卓进阶》 1 Okhttp2 Retrofit3 Android常用图片库对比4 Glide原理手写图片加载框架思路5 Rxjava6 Android IPC机制&#xff08;面试八股文之一&#xff09;6.1.Android中进程和线程的区别6.2.IPC概念6.3.Android序列化与反序列化6.3.Android如何开启多进程&#xff1f;多…

MDIO总线

基于linux-3.14.16 首先要搞清楚总线的位置&#xff0c;即硬件上的位置 如上图&#xff0c;mdio总线是mac和phy之间的连接方式&#xff0c;主要用于配置配置phy的寄存器&#xff0c;所以phy应该是器的一类物理设备&#xff0c;mdio总线驱动和总线设备都是围绕phy工作的。 一…

一图看懂 async_timeout 模块:异步 I/O 的超时设置,资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 async_timeout 模块&#xff1a;异步 I/O 的超时设置&#xff0c;资料整理笔记&#xff08;大全&#xff09; &#x1f9ca;摘要&#x1f9ca;模块图&#x1f9ca;类关系图…

chatgpt赋能Python-pythonfrozenset

Python frozenset介绍 在Python中&#xff0c;可以通过frozenset创建不可变集合。与set不同&#xff0c;frozenset一旦被创建就无法修改。frozenset通常用于作为字典的键&#xff0c;因为字典键必须是不可变的。 如何创建frozenset frozenset可以通过将可迭代对象作为参数传…

Blazor实战——Known框架快速开始

Known是基于C#和Blazor开发的前后端分离快速开发框架&#xff0c;开箱即用&#xff0c;跨平台&#xff0c;一处代码&#xff0c;多处运行。 开源地址: https://gitee.com/known/Known 1. 安装项目模板并创建新项目 打开命令行输入如下命令安装和创建。 -- 安装模板 dotnet n…

chatgpt赋能Python-pythonformat的用法小数点位数

Python中的format函数和小数点位数 介绍 Python中的format函数是一种格式化输出字符串的方法&#xff0c;允许你使用占位符来指定输出的格式。你可以使用format函数来格式化字符串&#xff0c;比如确定字符串的长度、插入变量或按特定格式输出字符串。 在本文中&#xff0c;…

Xubuntu22.04之自动调节亮度护眼redshift(一百七十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

chatgpt赋能Python-pythonfib

Pythonfib&#xff1a;一个优秀的计算斐波那契数列的Python函数 斐波那契数列是一种非常有趣也非常常见的数列&#xff0c;它起源于数学但在计算机科学中也经常被用到。Pythonfib是一个优秀的Python函数&#xff0c;可以用来计算斐波那契数列。本文将对Pythonfib进行详细介绍&…

本地电脑搭建Plex私人影音云盘教程,内网穿透实现远程访问

文章目录 1.前言2. Plex网站搭建2.1 Plex下载和安装2.2 Plex网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 转发自CSDN远程穿透的文章&#xff1a;免费搭建Plex家庭影音中心 - 打造超级多媒体中心【公网远程访问…

【红队系列】外网信息收集(二)

红队系列 &#x1f525;系列专栏&#xff1a;红队系列 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2023年5月20日&#x1f334; &#x1f36d;作者水平很有限&#xff0c;如果发现错误&…

chatgpt赋能Python-pythondescribe

PythonDescribe&#xff1a;更快、更准确、更易用的Python文档生成工具 介绍 Python是一种在数据科学和Web开发等领域广泛应用的高级编程语言。但是&#xff0c;在编写Python代码时&#xff0c;文档的编写常常被忽视。文档的缺失会使代码难以理解、维护和重复使用。幸运的是&…

基于MAC地址的ACL配置

基于MAC地址的ACL配置 【实验目的】 掌握基于MAC地址的标准ACL的配置。验证配置。 【实验拓扑】 实验拓扑如图1所示。 图1 实验拓扑 设备参数如表所示。 表1 设备参数表 设备 接口 IP地址 子网掩码 默认网关 S1 e0/0 N/A N/A N/A e0/1 N/A N/A N/A PC1 N/…