通讯录实现(Linux+Cpp)
产品底层思考:
-
人员如何存储 -> 链表 (增删改 但是排序不适合)
-
文件存储 -> 人员数据的格式 name:xxx,phone:xxx
-
人员信息 -> 姓名、电话 引出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);
}
主函数 选择模式
-
打印菜单
-
选择模式
-
选择模式
- 为了增强代码可解释性使用枚举 实际含义与数字一一对应
枚举类选择可解释性增强
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;
}
系统架构图更新

支持层
保存数据 内存->磁盘
- 判断传入数据
- 遍历数据
- 保存到缓冲区
- 加载到磁盘
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);
}
加载数据 磁盘->内存
- 判断参数
- 打开文件中介
- 按行读取到缓冲区
- 解析一行数据获得name phone关键字
- 关闭文件中介
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
- 业务层传入通讯录(业务) 接口层传入其他的联系人链表(接口)
9.文件保存加载业务实现
保存磁盘 业务接受cts 以及实现用户要输入的参数 接口层接受people 操作数据
- 判断参数
- 输入文件名
- 调用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 操作数据
- 判断参数
- 输入文件名从改文件加载
- 调用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;