通讯录实现(Linux+Cpp)

通讯录实现(Linux+Cpp)

产品底层思考:

  1. 人员如何存储 -> 链表 (增删改 但是排序不适合)

  2. 文件存储 -> 人员数据的格式 name:xxx,phone:xxx

  3. 人员信息 -> 姓名、电话 引出2

    name: xxx,phone: xxx,age: xxx,addr: xxx

    name: yyy,phone: yyy,age: yyy,addr: yyy

实现通讯录

person结构体

人的姓名 人的手机号

cpp 复制代码
//结构体人包含联系人信息
struct person{
    char name[NAME_LENGTH];
    char phone[PHONE_LENGTH];

    struct person* next;
    struct person* prev;
};

通讯录结构体

链表联系人

联系人的数量

cpp 复制代码
//通讯录结构
struct contacts{
    struct person* people;
    int count;
};

系统架构图

支持层(实现对数据底层操作)

使用宏定义函数 :

函数使用stack(内存)保存形参 宏使用寄存器保存形参 宏更快!!!

错误示范
cpp 复制代码
#define EXCH(x,y){
    int tem = x;
    x = y;
    y = tem;
}

续航符

复制代码
#define EXCH(x,y){\
    int tem = x;\
    x = y;\
    y = tem;\
}

续完行后宏定义可以使用

但是由于中间出现分号 有分号导致 if else无法链接

所以为了摆脱分号分开影响 使用do while(0);即可 do while(0);需要加分号!正好与函数;形式满足
do while(0)

核心代码
cpp 复制代码
#define EXCH(x,y) do{\  //续航符
    int tem = x;\
    x = y;\
    y = tem;\
}while(0)
使用后代码
cpp 复制代码
#include<stdio.h>
#define EXCH(x,y) do{\
    int tem = x;\
    x = y;\
    y = tem;\
}while(0)

int main()
{
    int x = 10,y = 20;

    if(x<y)
        EXCH(x,y);
    else
        printf("x大于等于y\n");


    return 0;
}

头插入

cpp 复制代码
#define LIST_INSERT(item,list) do{\
    item->next = list;\
    item->prev = NULL;\
    list->next->prev = item;\
    list = item;\
}while(0)

删除

cpp 复制代码
#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 = NULL;\
    item->next = NULL;\
}

接口层(实现逻辑)

cpp 复制代码
// define interface
//插入到链表的逻辑
//(*ppeople):先*再用  *ppeople:是二级指针操作  对于*的优先级最低!
int person_insert(struct person** ppeople,struct person* ps)
{
    if(ps == NULL) return -1;
    LIST_INSERT(ps,(*ppeople)); //*ppeople 变成了头指针值
    return 0; //成功
}

cpp 复制代码
int person_delete(struct person **ppeople,struct person* ps)
{
    if(ps == NULL) return -1;
    //双向链表直接删除
    LIST_REMOVE(ps,*ppeople);
    return 0; //成功
}

cpp 复制代码
//通过名字 查找
struct person*  person_search(struct person* people,const char* name)
{
    
    struct person* item;
    for(item = people; item != NULL;item = item->next)
    {
        if(!strcmp(name,item->name)) break; //查找到联系人 字符串比较使用strcmp 不能直接使用 == 
    }
    return item;
}

遍历

cpp 复制代码
int person_traversal(struct person* people)
{
    struct person* item ;
    for(item = people;item != NULL;item = item->next)
    {
        // 不建议直接使用printf 对于系统的接口使用定义宏方式 与接口一样但是命名不一样
        // printf("name: %s phone: %s",item->name,item->phone);
        INFO("name: %s phone: %s\n",item->name,item->phone);
    }
}

业务层(根据客户需要使用接口层)

cpp 复制代码
//插入 person_insert(struct person** ppeople,struct person* ps)
int insert_entry(struct contacts *cts)
{
    //判断传入参数通讯录
    if(cts == NULL) return -1;
    //初始化插入对象
    struct person* item = (struct person*)malloc(sizeof(struct person));
    //分配空间失败
    if(item ==  NULL) return -2;
    //获取姓名 手机号
    //写到 item->phone 所指向的内存 item->phone天生就是地址
    INFO("Please Input Name\n");
    scanf("%s",item->name);
    INFO("Please Input Phone\n");
    scanf("%s",item->phone);
    //插入到链表
    int ret = person_insert(&cts->people,item);
    if(0 != ret) // 0 != 1 1的时候失败
    {
        free(item); //释放内存
        return -3; //业务失败返回
    }    
    //插入成功 人数 +1
    cts->count++;
    INFO("插入成功\n");

}

打印

cpp 复制代码
int print_entry(struct contacts* cts)
{
    if(cts == NULL) return -1;
    //打印 person_traversal(struct person* people)
    person_traversal(cts->people);

}

删除

cpp 复制代码
int delete_entry(struct contacts* cts)
{
    if(cts == NULL) return -1;
    //找到 name联系人
    INFO("Please Input Delete Name\n");
    char name[NAME_LENGTH] = {0};
    scanf("%s",name);
    struct person* ps = person_search(cts->people,name);
    //找到了
    if(NULL == ps)
    {
        INFO("Person don't Exist\n");
        return -2;  
        
    }
    //如果有person 删除 
    person_delete(&cts->people,ps);
    free(ps);
    cts->count--;
    INFO("Delete Success\n");
    return 0;

    
    //删除该person
}

查找

cpp 复制代码
int search_entry(struct contacts* cts)
{
    if(cts == NULL) return -1;
    INFO("Please Input Search Name\n");
    char name[NAME_LENGTH] = {0};
    scanf("%s",name);
    struct person* ps = person_search(cts->people,name);
    if(NULL == ps)  //null == ps 是否查到
    {
        INFO("Person don't Exist\n");
        return -2;
    }
    //成功找到输出
    INFO("name: %s,phone: %s\n",ps->name,ps->phone);

}

主函数 选择模式

  1. 打印菜单

  2. 选择模式

  3. 选择模式

    1. 为了增强代码可解释性使用枚举 实际含义与数字一一对应

枚举类选择可解释性增强

cpp 复制代码
//枚举类将含义与数字对应 OPER:操作 第一个为1 接下来自动顺序补
enum{
    OPER_INSERT = 1,
    OPER_PRINT,
    OPER_DELETE,
    OPER_SEARCH,
    OPER_SAVE,
    OPER_LOAD // 最后一个没有都好
};

主函数

cpp 复制代码
int main()
{
    //初始化通讯录内存
    struct contacts *Contacts = (struct contacts*)malloc(sizeof(struct contacts));
    //目标内存全部补0
    memset(Contacts,0,sizeof(Contacts));
    while(1)
    {
        int select = 0;
        menu_info();
        scanf("%d",&select);
        switch(select){
            case OPER_INSERT:
                insert_entry(Contacts);
                break;
            case OPER_PRINT:
                print_entry(Contacts);
                break;
            case OPER_DELETE:
                delete_entry(Contacts);
                break; 
            case OPER_SEARCH:
                search_entry(Contacts);
                break; 
            case OPER_SAVE:
                break; 
            case OPER_LOAD:
                break;     
                   
        }
    }

    return 0;
}

系统架构图更新

支持层

保存数据 内存->磁盘

  1. 判断传入数据
  2. 遍历数据
  3. 保存到缓冲区
  4. 加载到磁盘
cpp 复制代码
int save_file(struct person* people,char* filename)
{
    if(people == NULL) return -1;
    FILE* fp = fopen(filename,"w");
    //1.遍历数据
    struct person* item = NULL;
    for(item = people;item != NULL;item = item->next)
    {
        //2.保存到缓冲区
        fprintf(fp,"name: %s,phone: %s\n",item->name,item->phone);
        //3.加载到磁盘
        fflush(fp);
    }
    //4.关闭文件中介
    fclose(fp);

}

加载数据 磁盘->内存

  1. 判断参数
  2. 打开文件中介
  3. 按行读取到缓冲区
  4. 解析一行数据获得name phone关键字
  5. 关闭文件中介
cpp 复制代码
int load_file(struct person **people,int *count,char* filename)
{
    //1.判断参数
    if(people == NULL) return -1;
    //2.打开文件中介 加载中介数据到缓冲区
    FILE* fp = fopen(filename,"r");
    
    while(0 == feof(fp)) //0为未到文件尾
    {
        char buffer[BUFFER_LENGTH] = {0};
        fgets(buffer,BUFFER_LENGTH,fp); //name: tom,phone: 111111111111
        //3.解析数据得到name phone值
        char name[NAME_LENGTH] = {0};
        char phone[PHONE_LENGTH] = {0};
        if( 0 != parser_token(buffer,BUFFER_LENGTH,name,phone))
            continue; //解析失败
        //4.插入通讯录
        // insert_entry():客户插入 不是底层插入
        struct person* ps = (struct person*)malloc(sizeof(struct person));
        if(ps == NULL) return -2; //未识别到空间
        // strcpy(ps->name,name); 字符串赋值
        memcpy(ps->name,name,NAME_LENGTH);
        memcpy(ps->phone,phone,PHONE_LENGTH);
        person_insert(people,ps);

        (*count)++;//通讯录数量+1
    }
    

    
    //5.关闭文件中介
    fclose(fp);
}
bug1:16行 BUFFER_LENGTH

传入length 为 BUFFER_LENGTH时,length过长 在解析函数中i<length 为phone赋值 phone会越界赋值

cpp 复制代码
int load_file(struct person **people,int *count,char* filename)
{
    //1.判断参数
    
    //2.打开文件中介 加载中介数据到缓冲区
    FILE* fp = fopen(filename,"r");
    if(fp == NULL) return -1;
    while(0 == feof(fp)) //0为未到文件尾
    {
        char buffer[BUFFER_LENGTH] = {0};
        fgets(buffer,BUFFER_LENGTH,fp); //name: tom,phone: 111111111111
        //3.解析数据得到name phone值
        char name[NAME_LENGTH] = {0};
        char phone[PHONE_LENGTH] = {0};
        int length = strlen(buffer);
        if( 0 != parser_token(buffer,length,name,phone))
            continue; //解析失败
        //4.插入通讯录
        // insert_entry():客户插入 不是底层插入
        struct person* ps = (struct person*)malloc(sizeof(struct person));
        if(ps == NULL) return -2; //未识别到空间
        // strcpy(ps->name,name); 字符串赋值
        memcpy(ps->name,name,NAME_LENGTH);
        memcpy(ps->phone,phone,PHONE_LENGTH);
        person_insert(people,ps);

        (*count)++;//通讯录数量+1
    }
    

    
    //5.关闭文件中介
    fclose(fp);
}

bug2: length可能为0 导致解析时buffer[0]数组访问越界 Segmentation fault (core dumped)

设置buffer最小长度 防止越界访问

cpp 复制代码
//length
int parser_token(char* buffer,int length,char* name,char* phone)
{
    if(buffer == NULL) return -1;
    if(length < MIN_TOKEN_LENGTH) return -2;
    //对buffer中数据 分开 使用状态机得到value 
    //name: tom,phone: 111111111111
    int status = 0;
    int i = 0,j = 0;
    for(i = 0;buffer[i] != ',';i++) //对buffer中数据 分开遍历
    {
        if(status == 0)
        {
            if(buffer[i] == ' ')
            {
                status = 1;
            }
        }else if(status == 1) //保存value
        {
            name[j++] = buffer[i];
        }
    }
    j = 0;
    status=0;
    for(;i<length;i++)
    {
        if(status == 0)
        {
            if(buffer[i] == ' ')
            {
                status = 1;
            }
        }else if(status == 1)
        {
            phone[j++] = buffer[i];
        }
    }
    return 0;
}

bug3:在写入时将\n也写入了

组件式开发/产品化开发

状态机解析一行数据

首先

分解序列 ,前后分解序列

明确有几个状态 两个 key value

在某状态对应什么情况下 发生了各种动作 0 + 空格 =>1 0+非空格=》0 1+空格+》不存在 1+非空格=》保存字符串

状态如何改变 在某状态下我们为实现目的会怎么做 1+非空格=》保存字符 某状态实现目的

cpp 复制代码
int parser_token(char* buffer,int length,char* name,char* phone)
{
    if(buffer == NULL) return -1;
    //对buffer中数据 分开 使用状态机得到value 
    //name: tom,phone: 111111111111
    int status = 0;
    int i = 0,j = 0;
    for(i = 0;buffer[i] != ',';i++) //对buffer中数据 分开遍历
    {
        if(status == 0)
        {
            if(buffer[i] == ' ')
            {
                status = 1;
            }
        }else if(status == 1) //保存value
        {
            name[j++] = buffer[i];
        }
    }
    j = 0;
    status=0;
    for(;i<length;i++)
    {
        if(status == 0)
        {
            if(buffer[i] == ' ')
            {
                status = 1;
            }
        }else if(status == 1)
        {
            phone[j++] = buffer[i];
        }
    }
    return 0;
}

学习:

cpp 复制代码
fprintf
feof
fgets
fopen
fflush
  1. 业务层传入通讯录(业务) 接口层传入其他的联系人链表(接口)

9.文件保存加载业务实现

保存磁盘 业务接受cts 以及实现用户要输入的参数 接口层接受people 操作数据

  1. 判断参数
  2. 输入文件名
  3. 调用save_file(people,filename) 保存到本地
cpp 复制代码
//业务逻辑用户输入filename而不是传入filename    char* filename:
int save_entry(struct contacts* cts)
{
    if(cts == NULL) return -1;
    //0.提示输入保存的文件名
    char filename[FILENAME_LENGTH] = {0};
    INFO("Please Input Saving Filename\n");
    scanf("%s",filename);
    //1.调用保存文件保存
    //cts->people 保存数据哪里来   filename:保存文件的文件名  
    save_file(cts->people,filename);
}

加载到内存 业务接受cts 以及实现用户要输入的参数 接口层接受people 操作数据

  1. 判断参数
  2. 输入文件名从改文件加载
  3. 调用load_file(&people,filename)
cpp 复制代码
//加载文件到内存
int load_entry(struct contacts* cts)
{
    //1.参数判断
    if(cts == NULL) return -1;
    //2.提示输入要加载文件
    char filename[FILENAME_LENGTH] = {0};
    INFO("Please Input Loading Filename\n");
    scanf("%s",filename);
    //3.调用加载接口函数
    load_file(&(cts->people),&(cts->count),filename);

}

主函数使用

cpp 复制代码
int main()
{
    //初始化通讯录内存
    struct contacts *Contacts = (struct contacts*)malloc(sizeof(struct contacts));
    //目标内存全部补0
    memset(Contacts,0,sizeof(Contacts));
    while(1)
    {
        int select = 0;
        menu_info();
        scanf("%d",&select);
        switch(select){
            case OPER_INSERT:
                insert_entry(Contacts);
                break;
            case OPER_PRINT:
                print_entry(Contacts);
                break;
            case OPER_DELETE:
                delete_entry(Contacts);
                break; 
            case OPER_SEARCH:
                search_entry(Contacts);
                break; 
            case OPER_SAVE:
                save_entry(Contacts);
                break; 
            case OPER_LOAD:
                load_entry(Contacts);
                break;     
            default:
                goto exit;
        }
    }
exit:
    free(Contacts);
    return 0;
}

退出进程

cpp 复制代码
default:
                goto exit;
exit:
    free(Contacts);
    return 0;
相关推荐
小鱼小鱼.oO3 小时前
阿里云服务器安装nginx并配置前端资源路径(前后端部署到一台服务器并成功访问)
服务器·nginx·阿里云
资讯第一线3 小时前
Windows系统工具:WinToolsPlus 之 SQL Server Suspect/质疑/置疑/可疑/单用户等 修复
运维
惊起白鸽4504 小时前
LVS负载均衡
运维·负载均衡·lvs
Sapphire~5 小时前
Linux-07 ubuntu 的 chrome 启动不了
linux·chrome·ubuntu
伤不起bb5 小时前
NoSQL 之 Redis 配置与优化
linux·运维·数据库·redis·nosql
广东数字化转型5 小时前
nginx怎么使用nginx-rtmp-module模块实现直播间功能
linux·运维·nginx
love530love6 小时前
【笔记】在 MSYS2(MINGW64)中正确安装 Rust
运维·开发语言·人工智能·windows·笔记·python·rust
啵啵学习6 小时前
Linux 里 su 和 sudo 命令这两个有什么不一样?
linux·运维·服务器·单片机·ubuntu·centos·嵌入式
半桔6 小时前
【Linux手册】冯诺依曼体系结构
linux·缓存·职场和发展·系统架构
网硕互联的小客服7 小时前
如何利用Elastic Stack(ELK)进行安全日志分析
linux·服务器·网络·安全