通讯录实现(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 导致解析时buffer0数组访问越界 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;
相关推荐
律宏阔2 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔2 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴2 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千3 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei373 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei373 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei373 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
judezh3 小时前
api 容器一直 unhealthy?我把一个 Agent 运行时的健康检查逐条拆了,查出四个问题
运维·docker
GeW3 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux
百万蹄蹄向前冲3 小时前
双端同步!云服务器装最新Node.js v26.10全过程追踪
服务器·人工智能·node.js