当前位置:网站首页>简单的C语言版本通讯录
简单的C语言版本通讯录
2022-06-11 14:26:00 【qq_42120843】
C语言版通讯录
文章目录
主要功能
- 添加一个人员
- 打印显示所有人员
- 删除一个人员
- 查找一个人员
- 保存文件
- 加载文件
架构设计
我觉得这里对于我这个没有整体设计的菜鸟来说这个设计的方式真的让我眼前一亮。
主要的设计就是将程序分为三层支持层、接口层和业务逻辑层,每一层都只会使用其下面一层提供的功能,如业务层函数会调用接口层的函数,接口层的函数调用支持层的函数,不会让业务层直接调用支持层的函数。

源代码
/* * 2022年6月7日10:29:41 * 通讯录管理系统 * 三层:支持层、接口层、业务逻辑层 * 双向链表作为数据结构 * * */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//用宏定义让程序代码更直观
#define NAME_LENGTH 16
#define PHONE_LENGTH 32
#define BUFFER_LENGTH 128
//从文件中读出一行数据的最小值是5,即name:
#define MIN_TOKEN_LENGTH 5
//便于未来的改进,输出信息的函数更换只要在这修改就能整体修改
#define INFO printf
//用宏定义实现支持层的操作,要注意宏定义是直接替换
//将item插入list链表头部
//LIST_INSERT(ps, *ppeople)时这里list是*pperson,类型为(struct person *)直接替换
//要在将list加上括号等价于(*pperson)
#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) do{
\ if(item->prev != NULL) item->prev->next = item->next; \ if(item->next != NULL) item->next->prev = item->prev; \ if(item == list) list = item->next; \ item->prev = item->next = NULL; \ }while(0) // 只执行一次
struct person
{
char name[NAME_LENGTH];
char phone[PHONE_LENGTH];
struct person *next;
struct person *prev; //设计的是双向链表,便于删除节点
};
//通讯录类,相当于时一个管理类
struct contacts
{
struct person *people; // 指向通讯录链表
int count;
};
enum {
OPER_INSERT = 1,
OPER_PRINT,
OPER_DELETE,
OPER_SEARCH,
OPER_SAVE,
OPER_LOAD
};
//define interface ,接口层会使用支持层的功能
int person_insert(struct person **ppeople, struct person *ps)
{
if(ps == NULL) return -1;
LIST_INSERT(ps, *ppeople);
return 0;
}
int person_delete(struct person **ppeople, struct person *ps)
{
if(ps == NULL) return -1;
LIST_REMOVE(ps, *ppeople);
return 0;
}
struct person * person_search(struct person *people, const char *name)
{
struct person *item = NULL;
for(item = people; item != NULL; item = item->next)
{
//strcmp相等为0
if(!strcmp(name, item->name))
break;
}
return item;
}
int person_traverse(struct person *people)
{
struct person *item = NULL;
for(item = people; item != NULL;item = item->next)
{
INFO("name: %s,phone: %s\n", item->name, item->phone);
}
return 0; // 成功返回0
}
//将通讯录(链表)people信息保存到文件filename中
int save_file(struct person *people, const char *filename)
{
FILE *fp = fopen(filename, "w");
if(fp == NULL) return -1;
struct person *item = NULL;
for(item = people; item != NULL; item = item->next)
{
fprintf(fp, "name: %s, phone: %s\n", item->name, item->phone);
fflush(fp); // 将内容刷新到磁盘上,之前内容是写到缓冲(内存)中
}
fclose(fp);
}
//@length:为buffer的实际长度(一条联系人的记录)
int parser_token(char *buffer, int length, char *name, char *phone)
{
if(buffer == NULL) return -1;
if(length < MIN_TOKEN_LENGTH) return -2;
int i = 0, j = 0, status = 0;
//获取到的数据内容形如name: wangbojing,telephone: 15889650380
//解析出姓名
//采用状态机的方法
for(i = 0; buffer[i] != ',';++i)
{
//遇到空格之后的状态就要改变,之后就为我们要的名字了
if(buffer[i] == ' ')
{
status = 1;
}
else if(status == 1)
{
name[j++] = buffer[i];
}
}
status = 0;
j = 0;
for(;i < length; ++i)
{
if(buffer[i] == ' ')
{
status = 1;
}
else if(status == 1)
{
phone[j++] = buffer[i];
}
}
INFO("file token : %s ---- %s\n", name, phone);
return 0;
}
//将filename文件中读取到的数据写入(加载到)到通讯录people中
//count:contacts对象的成员,改变通讯录记录条数
int load_file(struct person **ppeople, int *count,const char *filename)
{
FILE *fp = fopen(filename, "r");
if(fp == NULL)
return -1;
//fp内部指针不在文件尾就循环
while(!feof(fp))
{
char buffer[BUFFER_LENGTH] = {
0};
//从fp所指的文件中读取长度BUFFER_LENGTH的数据到buffer中,遇到换行或结尾便停止
fgets(buffer, BUFFER_LENGTH, fp);
int length = strlen(buffer);
INFO("length : %d\n", length);
//name: wangbojing,telephone: 15889650380
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;
//memcpy() 会复制 name 所指的内存内容的前 NAME_LENGTH 个字节到 p->name 所指的内存地址上。
memcpy(p->name, name, NAME_LENGTH);
memcpy(p->phone, phone, PHONE_LENGTH);
person_insert(ppeople, p);
(*count)++;
}
fclose(fp);
return 0;
}
//define interface end
//业务逻辑的实现
int insert_entry(struct contacts *cts)
{
if(cts == NULL) return -1;
struct person *p = (struct person*)malloc(sizeof(struct person));
memset(p, 0x00, 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 people
//如果cts->people为NULL,我们要改变的是cts->people的值,所以必须传地址
//故person_insert这里第一个参数必须为二级指针
if(0 != person_insert(&cts->people, p))
{
free(p);
return -3;
}
cts->count++; //联系人数量更新
INFO("insert success!\n");
return 0;
}
int print_entry(struct contacts *cts)
{
if(cts == NULL) return -1;
//cts->people
person_traverse(cts->people);
return 0;
}
int delete_entry(struct contacts *cts)
{
if(cts == NULL) return -1;
//name
INFO("Please input name: \n");
char name[NAME_LENGTH] = {
0};
scanf("%s",name);
//person
//先查找再删除
struct person *ps = person_search(cts->people, name);
if(ps == NULL)
{
INFO("Person don't exist!\n");
return -2;
}
//delete
//若删的是最后一个元素或者是第一个元素(第一个元素)
//则要改变people的值,所以这个参数要传地址
//故参数类型是二级指针struct person **
person_delete(&cts->people, ps);
free(ps);
INFO("Delete success!\n");
return 0;
}
int search_entry(struct contacts *cts)
{
if(cts == NULL) return -1;
//name
INFO("Please input name: \n");
char name[NAME_LENGTH] = {
0};
scanf("%s",name);
//person
struct person *ps = person_search(cts->people, name);
if(ps == NULL)
{
INFO("Person don't exist!\n");
return -2;
}
INFO("name:%s, phone: %s\n",ps->name, ps->phone);
return 0;
}
int save_entry(struct contacts * cts)
{
if(cts == NULL) return -1;
INFO("Please input save filename:\n");
char filename[NAME_LENGTH] = {
0};
scanf("%s", filename);
save_file(cts->people, filename);
}
int load_entry(struct contacts * cts)
{
if(cts == NULL) return -1;
INFO("Please input load filename:\n");
char filename[NAME_LENGTH] = {
0};
scanf("%s", filename);
load_file(&cts->people, &cts->count, filename);
}
void menu_info()
{
INFO("\n******************************************************\n");
INFO("*****1.Add Person\t\t2.Print Person *******\n");
INFO("*****3.Del Person\t\t4.Search Person ******\n");
INFO("*****5.Save Person\t\t6.Load Person ********\n");
INFO("*****Other Key for Exiting Program *******************\n");
INFO("\n******************************************************\n");
}
int main()
{
struct contacts *cts = (struct contacts*)malloc(sizeof(struct contacts));
if(cts == NULL) return -1;
//初始化结构体
memset(cts, 0x00, sizeof(struct contacts));
while(1)
{
menu_info();
int select = 0;
scanf("%d", &select);
switch(select)
{
//调用业务逻辑层的函数
case OPER_INSERT:
insert_entry(cts);
break;
case OPER_PRINT:
print_entry(cts);
break;
case OPER_DELETE:
delete_entry(cts);
break;
case OPER_SEARCH:
search_entry(cts);
break;
case OPER_SAVE:
save_entry(cts);
break;
case OPER_LOAD:
load_entry(cts);
break;
default:
goto exit;
}
}
exit:
free(cts);
return 0;
}
代码拆解分析
链表实现与数据结构定义
本程序使用双向链表来进行通讯录的存储 :
struct person
{
char name[NAME_LENGTH];
char phone[PHONE_LENGTH];
struct person *next;
struct person *prev; //设计的是双向链表,便于删除节点
};
通讯录的管理类,便于操作
struct contacts
{
struct person *people; // 指向通讯录链表
int count;
};
实现接口层的链表的头部插入和删除节点操作,注意宏定义是直接文本替换,注意加括号,防止优先级造成的问题。
#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) do{
\ if(item->prev != NULL) item->prev->next = item->next; \ if(item->next != NULL) item->next->prev = item->prev; \ if(item == list) list = item->next; \ item->prev = item->next = NULL; \ }while(0) // 只执行一次
数据结构操作接口层的函数的实现
//define interface ,接口层会使用支持层的功能
//这里之所以ppeople要用二级指针是因为,当传入的若为NULL,即第一次插入节点的时候
//我们要修改其一级指针的值。
int person_insert(struct person **ppeople, struct person *ps)
{
if(ps == NULL) return -1;
LIST_INSERT(ps, *ppeople);
return 0;
}
//若删除的是最后一个元素,那么指向链表的指针(一级)要修改为NULL,所以要传二级指针
int person_delete(struct person **ppeople, struct person *ps)
{
if(ps == NULL) return -1;
LIST_REMOVE(ps, *ppeople);
return 0;
}
struct person * person_search(struct person *people, const char *name)
{
struct person *item = NULL;
for(item = people; item != NULL; item = item->next)
{
//strcmp相等为0
if(!strcmp(name, item->name))
break;
}
return item;
}
int person_traverse(struct person *people)
{
struct person *item = NULL;
for(item = people; item != NULL;item = item->next)
{
//这里INFO是宏定义
INFO("name: %s,phone: %s\n", item->name, item->phone);
}
return 0; // 成功返回0
}
业务逻辑函数的实现
将不同的操作用匿名枚举定义而不直接使用数字表示
enum {
OPER_INSERT = 1,
OPER_PRINT,
OPER_DELETE,
OPER_SEARCH,
OPER_SAVE,
OPER_LOAD
};
main函数给用户提供功能的显示和选择:
void menu_info()
{
INFO("\n******************************************************\n");
INFO("*****1.Add Person\t\t2.Print Person *******\n");
INFO("*****3.Del Person\t\t4.Search Person ******\n");
INFO("*****5.Save Person\t\t6.Load Person ********\n");
INFO("*****Other Key for Exiting Program *******************\n");
INFO("\n******************************************************\n");
}
int main()
{
struct contacts *cts = (struct contacts*)malloc(sizeof(struct contacts));
if(cts == NULL) return -1;
//初始化结构体
memset(cts, 0x00, sizeof(struct contacts));
while(1)
{
menu_info();
int select = 0;
scanf("%d", &select);
switch(select)
{
//调用业务逻辑层的函数
case OPER_INSERT:
insert_entry(cts);
break;
case OPER_PRINT:
print_entry(cts);
break;
case OPER_DELETE:
delete_entry(cts);
break;
case OPER_SEARCH:
search_entry(cts);
break;
case OPER_SAVE:
save_entry(cts);
break;
case OPER_LOAD:
load_entry(cts);
break;
default:
goto exit;
}
}
exit:
free(cts);
return 0;
}
业务逻辑层的函数实现
//业务逻辑的实现
int insert_entry(struct contacts *cts)
{
if(cts == NULL) return -1;
struct person *p = (struct person*)malloc(sizeof(struct person));
memset(p, 0x00, 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 people
//如果cts->people为NULL,我们要改变的是cts->people的值,所以必须传地址
//故person_insert这里第一个参数必须为二级指针
if(0 != person_insert(&cts->people, p))
{
free(p);
return -3;
}
cts->count++; //联系人数量更新
INFO("insert success!\n");
return 0;
}
int print_entry(struct contacts *cts)
{
if(cts == NULL) return -1;
//cts->people
person_traverse(cts->people);
return 0;
}
int delete_entry(struct contacts *cts)
{
if(cts == NULL) return -1;
//name
INFO("Please input name: \n");
char name[NAME_LENGTH] = {
0};
scanf("%s",name);
//person
//先查找再删除
struct person *ps = person_search(cts->people, name);
if(ps == NULL)
{
INFO("Person don't exist!\n");
return -2;
}
//delete
//若删的是最后一个元素或者是第一个元素(第一个元素)
//则要改变people的值,所以这个参数要传地址
//故参数类型是二级指针struct person **
person_delete(&cts->people, ps);
free(ps);
INFO("Delete success!\n");
return 0;
}
int search_entry(struct contacts *cts)
{
if(cts == NULL) return -1;
//name
INFO("Please input name: \n");
char name[NAME_LENGTH] = {
0};
scanf("%s",name);
//person
struct person *ps = person_search(cts->people, name);
if(ps == NULL)
{
INFO("Person don't exist!\n");
return -2;
}
INFO("name:%s, phone: %s\n",ps->name, ps->phone);
return 0;
}
文件保存与加载的接口层实现
保存就是将链表上数据存储到磁盘文件上,加载就是从磁盘文件中读出联系人数据插入到链表中
文件保存接口层实现
//将通讯录(链表)people信息保存到文件filename中
int save_file(struct person *people, const char *filename)
{
FILE *fp = fopen(filename, "w");
if(fp == NULL) return -1;
struct person *item = NULL;
for(item = people; item != NULL; item = item->next)
{
fprintf(fp, "name: %s, phone: %s\n", item->name, item->phone);
fflush(fp); // 将内容刷新到磁盘上,之前内容只是写到缓冲(内存)中
}
fclose(fp);
}
文件加载接口层实现
此处,我们要从文件解析出名字以及电话号码,我们存在磁盘中的内容是形如:
name: wangbojing,telephone: 15889650380
根据这个格式,
我们可以通过逗号将名字和电话解析成两个部分即
name: wangbojing和telephone: 15889650380然后根据有限状态机,初始状态为0,遇到空格则状态变为1,之后遇到的非空格字符都是名字
电话号码解析也是同理。
//@length:为buffer的实际长度(一条联系人的记录)
int parser_token(char *buffer, int length, char *name, char *phone)
{
if(buffer == NULL) return -1;
if(length < MIN_TOKEN_LENGTH) return -2;
int i = 0, j = 0, status = 0;
//解析出姓名
//采用状态机的方法
for(i = 0; buffer[i] != ',';++i)
{
//遇到空格之后的状态就要改变,之后就为我们要的名字了
if(buffer[i] == ' ')
{
status = 1;
}
else if(status == 1)
{
name[j++] = buffer[i];
}
}
status = 0;
j = 0;
for(;i < length; ++i)
{
if(buffer[i] == ' ')
{
status = 1;
}
else if(status == 1)
{
phone[j++] = buffer[i];
}
}
INFO("file token : %s ---- %s\n", name, phone);
return 0;
}
加载文件的接口层实现:
//将filename文件中读取到的数据写入(加载到)到通讯录people中
//count:contacts对象的成员,改变通讯录记录条数
int load_file(struct person **ppeople, int *count,const char *filename)
{
FILE *fp = fopen(filename, "r");
if(fp == NULL)
return -1;
//fp内部指针不在文件尾就循环
while(!feof(fp))
{
char buffer[BUFFER_LENGTH] = {
0};
//从fp所指的文件中读取长度BUFFER_LENGTH的数据到buffer中,遇到换行或结尾便停止
fgets(buffer, BUFFER_LENGTH, fp);
int length = strlen(buffer);
INFO("length : %d\n", length);
//name: wangbojing,telephone: 15889650380
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;
//memcpy() 会复制 name 所指的内存内容的前 NAME_LENGTH 个字节到 p->name 所指的内存地址上。
memcpy(p->name, name, NAME_LENGTH);
memcpy(p->phone, phone, PHONE_LENGTH);
person_insert(ppeople, p);
(*count)++;
}
fclose(fp);
return 0;
}
文件保存与加载的业务逻辑层实现
int save_entry(struct contacts * cts)
{
if(cts == NULL) return -1;
INFO("Please input save filename:\n");
char filename[NAME_LENGTH] = {
0};
scanf("%s", filename);
save_file(cts->people, filename);
}
int load_entry(struct contacts * cts)
{
if(cts == NULL) return -1;
INFO("Please input load filename:\n");
char filename[NAME_LENGTH] = {
0};
scanf("%s", filename);
load_file(&cts->people, &cts->count, filename);
}
心得
这个程序函数通过三层设计方法让设计变得很清晰,同时利用宏定义增强可读性,对我来说加深了程序开发的一些经验,开阔了自己的视野。
边栏推荐
- Current situation and future development trend of precision air conditioning market in the world and China
- Architectural concept exploration: Taking the development of card games as an example
- HMS core shows the latest open capabilities in mwc2022, helping developers build high-quality applications
- . Net C Foundation (6): namespace - scope with name
- gensim. Models word2vec parameter
- 【公开课预告】:MXPlayer OTT音视频转码实践和优化
- Analyse approfondie de la conception du système relationnel du Groupe de cercles
- SQL数据查询之单表查询
- one hundred and twenty-three thousand four hundred and sixty-five
- Summary of some classic embedded C interview questions
猜你喜欢

【公开课预告】:MXPlayer OTT音视频转码实践和优化

NoSQL之Redis配置与优化

【Try to Hack】URL

Individual income tax rate table

MySQL advanced statement

Leetcode 1962. 移除石子使总数最小(应该是向上取整)

What is excess product power? Find the secret key of the second generation cs75plus in the year of the tiger

Analyse approfondie de la conception du système relationnel du Groupe de cercles

Top 10 bone conduction earphones in the list, and five easy-to-use bone conduction earphones are recommended

uniapp设置页面跳转效果 - navigateTo切换效果 - 全局animationType动画
随机推荐
After many years of digital transformation projects, the main architects are desperate: outsourcing should not have been used at the beginning!
数据库“百亿蓝海”中,每位玩家都能找到一叶扁舟 | C位面对面
2021-2027 China scaffold and accessories market status analysis and development prospect forecast report
What is excess product power? Find the secret key of the second generation cs75plus in the year of the tiger
非常值得学习的调度开源库推荐
Recommended open source scheduling libraries worth learning
浙江大学搞出了一款无人机,自动规避障碍,像鸟一样穿过树林,真正的蜂群来了...
North China pushed Yale hard, MIT won the first place in a row, and the latest 2023qs world university ranking was released
Webgl programming guide learning (0)
为什么需要public static void main(String[ ] args)这个方法?
Hamad application layout scheme of hashicopy 01
Seven parameters of thread pool and reject policy
Airtest automated test
Architectural concept exploration: Taking the development of card games as an example
In depth research and analysis report on ready to eat meat market for vacuum low temperature cooking in the world and China
Distributed file system and enterprise application -- elk enterprise log analysis system
System.out.println()方法使用需要注意哪些问题
gensim. Models word2vec parameter
mysql高级语句
leetcode每日一题——搜索插入位置