用C语言和文本文件实现一个简单的,可保存的通讯录
我们先思考一个通讯录都有那些信息,很明显通讯录记录的是人
人有哪些信息呢
这里我就写5个吧,分别是姓名,年龄,电话,性别,地址
然后我们把他们写成一个结构体,最好定义在头文件里,这样在使用的时候更方便
我们还可以把要使用的一些常用的或者要修改的常量定义成枚举,后续有什么要添加的,直接就能在枚举中添加
//枚举的一些关键常量
enum NUM
{
NAME = 20, //名字
TELE = 12, //电话
SEX = 10, //性别
ADDR = 100, //地址
ADD = 1,DEL = 2,SEARCH = 3,MODIFY = 4,SHOW = 5,SORT = 6,EXIT = 0,CLEAR =7,//菜单
//ARRDATA = 100
SIZE = 3, //通讯录初始容量大小
SIZEADD = 2 //每次扩容的增加量
};
//人的信息
typedef struct peo
{
char name[NAME]; //姓名
int age; //年龄
char tele[TELE]; //电话
char sex[SEX]; //性别
char addr[ADDR]; //地址
}peo;
这里的枚举内容我直接把参数都写里了,后续在思考的过程就不再改动了
接下来思考,光有一个人的信息是不够的,无法完整表达一个通讯录,当然,由于我们只是简单实现,我们就只再记录通讯录的人员数量和通讯录的容量
把他们简单封装成一个结构体
typedef struct contact
{
peo* data; //人的相关数据
size_t count; //成员数量
size_t capacity; //通讯录容量
}contact;
这里我都是进行了重命名了的,方便后续的使用
这些都是头文件里面要用的,还有一些库函数,我就在这里提前定义了
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#include<errno.h>
然后我们写主函数
我们考虑通讯录运行一定要有菜单
然后我们用switch语句写分支结构
我们需要用户主动输入选择,所以我们还需要一个变量接收,这个变量还可以用于switch语句的判断
菜单我们可以单独封装一个函数
可以实现增加联系人,删除联系人,查找联系人,修改联系人,显示联系人,按名字排序联系人,清空联系人,和退出保存,将数据保存到文件中,把这些功能都封装成函数
还得创建一个结构体变量用于存放通讯录的数据
还需要一个函数对结构体进行初始化,并把保存在文件中的信息放入到结构体中
于是主函数如下
#include"contact.h"
static void menu() //菜单函数
{
printf("****************************************\n");
printf("****************************************\n");
printf("****************************************\n");
printf("*** 1.add 2.del ******\n");
printf("*** 3.search 4.modify ******\n");
printf("*** 5.show 6.sort ******\n");
printf("*** 0.exit 7.clear ******\n");
printf("****************************************\n");
printf("****************************************\n");
printf("****************************************\n");
}
int main()
{
int input = 0; //存放菜单选择输入的值
contact con; //创建存放通讯录数据的结构体
init_contact(&con);//初始化结构体
do
{
menu();
printf("请输入要进行的功能\n");
scanf("%d", &input); //菜单的选择输入
switch (input) //菜单选择的判断逻辑
{
case ADD:
add_contact(&con); //添加函数
break;
case DEL:
del_contact(&con); //删除函数
break;
case SEARCH:
search_contact(&con); //查找函数
break;
case MODIFY:
modify_contact(&con); //修改函数
break;
case SHOW:
show_contact(&con); //显示函数
break;
case SORT:
sort_contact(&con); //排序函数
break;
case CLEAR:
clear_contact(&con); //清空函数
break;
case EXIT:
save_contact(&con); //保存函数
destroycontact(&con); //推出后内存的销毁
printf("程序退出\n");
break;
default:
printf("选择错误,请重新选择\n");//其他逻辑判断
break;
}
} while (input)//do while的循环判断
return 0;
}
主函数别忘了引用头文件
接下来我们封装函数
把函数单独放在一个源文件
#include"contact.h"
//初始化函数
//对于初始化我们不仅要初始化结构体内容,还要把之前保存在文件中的信息放入到结构体中,当然还涉及到一个动态扩容的问题,我们的结构体初始设计的是有容量的,如果里面放的信息多了,还要涉及到扩容的问题,
这些都要设计函数
static void expandCapacity(contact* con) //扩容
{
assert(con); //断言防止空指针
if (con->capacity == con->count) //容量判断
{
void* p = 0;
p = realloc(con->data, (con->capacity + SIZEADD) * sizeof(peo));//追加信息容量
if (NULL == p) //报错判断
{
printf("expandCapacity::%s\n", strerror(errno));
return;
}
con->capacity += SIZEADD;//修改容量标记
con->data = (peo*)p; //修改指针位置(其实没变)
printf("增容成功\n");
}
}
static int read_conact(contact* con) //读取文件中保存的通讯录信息
{
static void expandCapacity(contact * con); //函数声明
FILE* p = fopen("contact.txt", "r"); //打开文件
if (p == NULL) //报错判断
{
return 1;
}
peo a = { 0 };
while (fread(&a, sizeof(peo), 1, p) == 1)//循环提取信息,这里取决于保存函数的写法
{
expandCapacity(con); //扩容
con->data[con->count] = a;//赋值
(con->count)++;//成员计数加一
}
fclose(p);
p = NULL;//关闭文件,指针空置
return 0;
}
void init_contact(contact* con) //初始化函数
{
static int read_conact(contact * con);//函数调用声明
assert(con);////断言防止空指针
con->count = 0;//初始化人数
void* p = 0;//内存指针
p = calloc(SIZE, sizeof(peo));//创建内存
if (NULL == con->data)//报错判断
{
printf("init_contact:calloc:%s\n", strerror(errno));
return;
}
con->data = (peo*)p;//初始化人信息指针
con->capacity = SIZE;//初始化容量
if (read_conact(con) == 1)//读取之前的数据
{
perror("读取旧数据失败");//问题判断
return;
}
}
//初始化我们已经完成了
//接下来我们写添加函数
void add_contact(contact* con)
{
static void expandCapacity(contact * con); //函数声明
assert(con);//断言防止空指针
expandCapacity(con);//扩容判断
printf("请输入姓名\n");
scanf("%s", (con->data+con->count)->name);
printf("请输入年龄\n");
scanf("%d", &((con->data + con->count)->age));
printf("请输入电话\n");
scanf("%s", con->data[con->count].tele);
printf("请输入性别\n");
scanf("%s", con->data[con->count].sex);
printf("请输入地址\n");
scanf("%s", con->data[con->count].addr);//这些都是信息是输入
con->count++;//计数加一
printf("增加成功\n");
}
//接下来我们写显示函数
void show_contact(contact* con)
{
assert(con);//断言防止空指针
int i = 0;
printf("%-20s\t%-5s\t%5s\t%12s\t%30s\n", "姓名", "年龄", "电话", "性别", "地址");
//显示时方便观察,打印一个标签
for (i = 0; i < con->count; i++)//循环打印人员信息
{
printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n",
con->data[i].name,
con->data[i].age,
con->data[i].tele,
con->data[i].sex,
con->data[i].addr);
}
}
//查找函数
//查找函数不仅要能查找,还要能显示,我们分成两个函数,因为删除也需要查找
//查找部分
static int find_by_name(const contact* con,const char* arr) //依据名字查找
{
assert(con && arr);//断言防止空指针
int i = 0;
for (i = 0; i < con->count; i++)//循环遍历查找
{
if (0 == strcmp(con->data[i].name, arr))
{
return i;//找到返回下标
}
}
return -1;//找不到返回-1
}
//查找
void search_contact(contact* con)
{
static int find_by_name(const contact * con, const char* arr);//函数声明
assert(con);//断言防止空指针
if (con->count == 0)//无联系人情况
{
printf("无联系人可查找\n");
return;
}
printf("请输入要查找的人的姓名\n");
char arr[NAME] = { 0 };
scanf("%s", arr);
int a = find_by_name(con, arr);//套用查找函数
if (a == -1)
{
printf("要查找的人不存在\n");
return;
}
printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "姓名", "年龄", "电话", "性别", "地址");
printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n",
con->data[a].name,
con->data[a].age,
con->data[a].tele,
con->data[a].sex,
con->data[a].addr);//显示部分
}
//删除
void del_contact(contact* con)
{
static int find_by_name(const contact * con, const char* arr);//函数声明
assert(con);//断言防止空指针
char arr[NAME] = { 0 };
if (con->count == 0)//无联系人情况
{
printf("无联系人可删除\n");
return;
}
printf("请输入要删除人的名字\n");
scanf("%s", arr);
int a = find_by_name(con,arr);//函数回调
if (a == -1)
{
printf("要删除的人不存在\n");
return;
}
for (; a < con->count - 1; a++)//删除本质就是把后面的信息提前,覆盖掉这个下标的信息
//当然也还有其他的写法
{
con->data[a] = con->data[a + 1];
}
con->count--;
printf("删除成功\n");
}
//修改函数
//修改也要先找
void modify_contact(contact* con)
{
static int find_by_name(const contact * con, const char* arr);//函数声明
assert(con);//断言防止空指针
if (con->count == 0)
{
printf("无联系人可修改\n");//没有的情况判断
return;
}
printf("请输入要修改的人的姓名\n");
char arr[NAME] = { 0 };
scanf("%s", arr);
int a = find_by_name(con, arr);//调用函数
if (a == -1)
{
printf("要修改的人不存在\n");
return;
}
printf("请输入修改姓名\n");
scanf("%s", con->data[a].name);
printf("请输入修改年龄\n");
scanf("%d", &(con->data[a].age));
printf("请输入修改电话\n");
scanf("%s", con->data[a].tele);
printf("请输入修改性别\n");
scanf("%s", con->data[a].sex);
printf("请输入修改地址\n");
scanf("%s", con->data[a].addr);
printf("修改成功\n");//信息的修改
}
//排序函数
//这里我使用了qsort
static int cmp_peo_by_name(const void* e1, const void* e2)//qsort调用的排序函数
{
return strcmp(((const peo*)e1)->name, ((const peo*)e2)->name);
}
void sort_contact(contact* con)//本体
{
static int cmp_peo_by_name(const void* e1, const void* e2);//函数调用
assert(con);//断言
if (con->count == 0)
{
printf("无联系人可排序\n");//没人的情况
return;
}
printf("正在按姓名排序\n");
qsort(con->data, con->count, sizeof(peo), cmp_peo_by_name);//qsort排序
printf("排序成功\n");
show_contact(con);//这里不声明是因为这些函数都会在头文件中声明,但内些静态函数不会
}
//销毁,保存,清空
void destroycontact(contact* con)//释放内存
{
assert(con);
free(con->data);
con->data = NULL;
}
void save_contact(const contact* con)//保存信息
{
assert(con);
FILE* p = fopen("contact.txt", "wb");
if (p == NULL)
{
perror("保存失败:save_contact:fopen");
return;
}
fwrite(con->data, sizeof(peo), con->count, p);
fclose(p);
p = NULL;
}
void clear_contact(contact* con)//清空文件信息和当前结构体信息
{
assert(con);
FILE* p = fopen("contact.txt", "w");
if (p == NULL)
return;
fclose(p);
p = NULL;
con->count = 0;
memset(con->data,0, sizeof(peo));
printf("已全部清除\n");
}
最后我们再在头文件中声明一下这些函数,为了调用
//初始化通讯录
void init_contact(contact* con);
//增加联系人
void add_contact(contact* con);
//显示
void show_contact(contact* con);
//删除
void del_contact(contact* con);
//查找
void search_contact(contact* con);
//修改
void modify_contact(contact* con);
//排序
void sort_contact(contact* con);
//退出后内存销毁
void destroycontact(contact* con);
//退出后的保存
void save_contact(const contact*con);
//清空通讯录
void clear_contact(contact*con);
静态函数不用声明,主要和一些商业习惯有关
好了,这样就结束了,比较简陋,旨在练手,大佬勿喷

浙公网安备 33010602011771号