通讯录三种方式实现(基础版、动态内存版、文件管理版)
前言
本文将实现一个通讯录,对前面所学的指针、字符串库函数、结构体、动态内存、文件操作等内容进行巩固和复习,通讯录有三个版本的实现方式:
- 基本版
- 动态内存版
- 文件操作版
1、通讯录是什么?
百度百科:
通讯录主要包括以下6个主要功能:
- 添加联系人
- 删除联系人
- 搜索联系人
- 修改联系人
- 排序联系人
- 打印联系人
2、程序框架—基本版
程序整体的框架可以搬用之前学习【C语言基础6——数组(2)三子棋】、【C语言基础7——数组(3)扫雷】用的框架,这种框架也可以当作一种通用的形式,加以运用。
2.1 主函数
int main()//显得简洁
{
test();
return 0;
}
2.2 函数test
void test()
{
int input = 0;
//创建通讯录
contact con;
//初始化通讯录
InitContact(&con);//传结构体地址
do
{
menu();
printf("请选择操作: ==> ");
scanf("%d", &input);
switch (input)
{
case add://1、添加联系人
addPer(&con);
break;
case del://2、删除联系人
delPer(&con);
break;
case search://3、搜索联系人
searPer(&con);
break;
case modify://4、修改联系人
modiPer(&con);
break;
case sort://5、排序联系人
sortPer(&con);
break;
case print://6、打印联系人
prinPer(&con);
break;
case exit:
break;
default:
break;
}
} while (input);//选零就退出循环了
}
2.3 函数menu
void menu()
{
printf("\n");
printf("******************************\n");
printf("****1.addPer 2.delPer******\n");
printf("****3.searPer 4.modiPer*****\n");
printf("****5.sortPer 6.prinPer*****\n");
printf("****0.exit ******************\n");
printf("******************************\n");
printf("\n");
}
2.4 初始化通讯录—InitContact
//初始化通讯录
void InitContact(contact* pc)
{
assert(pc);
pc->sz = 0;//信息个数初始化为0
//通过字符串库函数,初始化结构体数组data,
//pc->data是数组首元素地址,后面是整个结构体数组大小,整个都初始化为0
memset(pc->data, 0, sizeof(pc->data));//字符串库函数
}
2.5 添加个人信息—addPer
//1、添加个人信息
void addPer(contact* pc)
{
assert(pc);
if (pc->sz==MAX)
{
printf("通讯录已经满了,无法添加新的联系人!\n");
return;
}
//个数没满的话,开始录入个人信息
printf("请输入名字 ==> ");
scanf("%s", pc->data[pc->sz].name);//结构体其他的数组名都是地址
printf("请输入性别 ==> ");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入年龄 ==> ");
scanf("%d", &(pc->data[pc->sz].age));//年龄不是地址,这里必须取地址
printf("请输入号码 ==> ");
scanf("%s", pc->data[pc->sz].phone);
printf("请输入住址 ==> ");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;//每当录入一个人的信息时,人数就增加1
printf("个人信息添加成功!\n");
}
2.6 删除指定联系人—delPer
//在通讯录中查找联系人,找到返回下标
int findPer(const contact* pc, char name[])
{
assert(pc);
//遍历整个data数组,查找名字
for (int i = 0; i < pc->sz; i++)
{//字符串库函数,字符串比较,相同,则函数返回0
if (0==strcmp(pc->data[i].name,name))
{//找到信息人的名字时,返回下标
return i;
}
}
return -1;//遍历都找不到,就返回-1
}
//2、删除指定联系人
void delPer(contact* pc)
{
assert(pc);
//检查信息个数
if (pc->sz == 0)
{
printf("通讯录已经空了,无法删除联系人!\n");
return;
}
//如果还有,则删除指定联系人
//1、指定联系人
char name[NAME_MAX] = {0};//名字的字符串
printf("请输入需要删除的联系人的姓名 ==> ");
scanf("%s", name);
//2、开始查找这个联系人
int res = findPer(pc, name);//传首字符的地址
if (res==-1)
{
printf("要删除的联系人不存在!\n");
return;//直接返回,后面的语句不需要执行了
}
//3、找到后删除
for (int j = res; j < pc->sz-1; j++)
{//删除就是后面的信息往前移动,覆盖前面一个信息的内容
pc->data[j] = pc->data[j + 1];
}
pc->sz--;//总的信息个数减少1个
printf("指定联系人删除成功!\n");
}
2.7 查找联系人—searPer
//3、查找个人信息
void searPer(contact* pc)
{
assert(pc);
//1、指定联系人
char name[NAME_MAX] = { 0 };
printf("请输入需要查找的联系人的姓名 ==> ");
scanf("%s", name);
//2、开始查找这个联系人
int res = findPer(pc, name);//找到这个人的信息,就返回下标
if (res == -1)//没找到就,打印出来
{
printf("要删除的联系人不存在!\n");
return;//直接返回,后面的语句不需要执行了
}
printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "性别", "年龄", "号码", "住址");
printf("%-20s %-5s %-5d %-12s %-30s\n",
pc->data[res].name, pc->data[res].sex, pc->data[res].age,
pc->data[res].phone, pc->data[res].addr);
}
2.8 修改联系人信息—modiPer
//4、修改联系人信息
void modiPer(contact* pc)
{
assert(pc);
//1、指定联系人
char name[NAME_MAX] = { 0 };
printf("请输入需要查找的联系人的姓名 ==> ");
scanf("%s", name);
//2、开始查找这个联系人
int res = findPer(pc, name);//找到这个人的信息,就返回下标
if (res == -1)//没找到就,打印出来
{
printf("要删除的联系人不存在!\n");
return;//直接返回,后面的语句不需要执行了
}
printf("已经查找到联系人的姓名,请重新输入个人信息 ==> \n");
//查找到后,开始修改,重新录入个人信息
printf("请输入名字 ==> ");
scanf("%s", pc->data[res].name);//结构体其他的数组名都是地址
printf("请输入性别 ==> ");
scanf("%s", pc->data[res].sex);
printf("请输入年龄 ==> ");
scanf("%d", &(pc->data[res].age));//年龄不是地址,这里必须取地址
printf("请输入号码 ==> ");
scanf("%s", pc->data[res].phone);
printf("请输入住址 ==> ");
scanf("%s", pc->data[res].addr);
printf("联系人个人信息修改成功!\n");
}
2.9 排序联系人信息—sortPer
//对结构体数组里的结构体成员的姓名进行排序
int cmp_data_name(const void* p1, const void* p2)
{
return strcmp(((struct perInfo*)p1)->name, ((struct perInfo*)p2)->name);
}
//5、对联系人个人信息排序
void sortPer(contact* pc)
{
assert(pc);
//检查信息个数 ,首先判断通讯录联系人有几人
if (pc->sz == 0)
{
printf("通讯录是空的,无法排序!\n");
return;
}
if (pc->sz == 1)//直接将信息打印出来
{
printf("%-20s %-5s %-5d %-12s %-30s\n",
pc->data[0].name, pc->data[0].sex, pc->data[0].age,
pc->data[0].phone, pc->data[0].addr);
return;
}
//信息的个数是2个及以上,开始排序
//按姓名进行升序排序
//结构体数组的地址 联系人信息的个数 每一个人信息的大小 函数地址
qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_data_name);
//打印出来排序后的结果
printf("对姓名排序后的结果:\n");
printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "性别", "年龄", "号码", "住址");
for (int i = 0; i < pc->sz; i++)
{//%-20s 是左对齐
printf("%-20s %-5s %-5d %-12s %-30s\n",
pc->data[i].name, pc->data[i].sex, pc->data[i].age,
pc->data[i].phone, pc->data[i].addr);
}
}
2.10 打印联系人信息—prinPer
//6、打印个人信息
void prinPer(const contact* pc)
{
assert(pc);
printf("通讯录现有的所有联系人的信息 ==> \n");
//在屏幕上打印一行提示信息: 姓名 性别 年龄 号码 住址
printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "性别", "年龄", "号码", "住址");
for (int i = 0; i < pc->sz; i++)
{//%-20s 是左对齐
printf("%-20s %-5s %-5d %-12s %-30s\n",
pc->data[i].name, pc->data[i].sex, pc->data[i].age,
pc->data[i].phone, pc->data[i].addr);
}
}
3、头文件 .h
#include<stdio.h>
#include<assert.h>
#include<string.h>
//类型的声明,宏定义、结构体,枚举
#define MAX 1000 //定义数组中元素的个数
//个人信息,所需的最大上限
#define NAME_MAX 20 //名字
#define SEX_MAX 5 //性别
#define PHONE_MAX 12 //手机号码
#define ADDR_MAX 30 //住址
enum option//操作选择
{
exit,//默认值从0开始
add,
del,
search,
modify,
sort,
print
};
//结构体类型,每个人信息:姓名、性别、年龄、号码、住址
typedef struct perInfo
{
char name[NAME_MAX];
char sex[SEX_MAX];
int age;
char phone[PHONE_MAX];
char addr[ADDR_MAX];
}perInfo;
//结构体类型,结构体数组都是人的信息、录入信息人的个数
typedef struct contact
{
perInfo data[MAX];//结构体类型的数组,1000个人的信息,每个元素是一个结构体类型
int sz;//记录此时通讯录的已有信息个数
}contact;
//函数的声明
//初始化通讯录
void InitContact(contact* pc);
//1、添加个人信息
void addPer(contact* pc);
//2、删除个人信息
void delPer(contact* pc);
//3、查找个人信息
void searPer(contact* pc);
//4、修改联系人信息
void modiPer(contact* pc);
//5、对联系人个人信息排序
void sortPer(contact* pc);
//6、打印个人信息
void prinPer(const contact* pc);
4、通讯录运行
运行结果见下图,基本实现了预期功能:
5、动态内存版本
在基本版的通讯录中,信息个数固定式1000的,这里有点问题:
- 有时存放的信息个数少,1000就有点浪费内存了
- 当存放的信息个数超过1000时,没法增加信息存储容量
在此基础上引入动态内存版本,让存储信息的容量随着真实存放的个数,实时改变。
5.1 添加个人信息—addPer 修改1
//动态内存添加
void checkCapacity(contact* pc)
{
assert(pc);
//当目前存放的信息个数 与 当前能够存放的最大容量相同时,进行扩容
if (pc->sz == pc->capacity)
{//每次增加2个 存放信息
perInfo* tmp = (perInfo*)realloc(pc->data, (pc->capacity + 2) * sizeof(perInfo));
if (tmp == NULL)
{
perror("checkCapacity:realloc");
return;
}
pc->data = tmp;
pc->capacity += 2;
printf("扩容成功\n");
}
}
//释放动态内存
void freePer(contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->capacity = 0;
pc->sz = 0;
printf("销毁成功\n");
}
void addPer(contact* pc)
{
assert(pc);
//基本版
/*if (pc->sz==MAX)
{
printf("通讯录已经满了,无法添加新的联系人!\n");
return;
}*/
//动态增容
checkCapacity(pc);
//个数没满的话,开始录入个人信息
printf("请输入名字 ==> ");
scanf("%s", pc->data[pc->sz].name);//结构体其他的数组名都是地址
printf("请输入性别 ==> ");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入年龄 ==> ");
scanf("%d", &(pc->data[pc->sz].age));//年龄不是地址,这里必须取地址
printf("请输入号码 ==> ");
scanf("%s", pc->data[pc->sz].phone);
printf("请输入住址 ==> ");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;//每当录入一个人的信息时,人数就增加1
printf("个人信息添加成功!\n");
}
5.2 初始化通讯录—InitContact 修改1
void InitContact(contact* pc)
{
assert(pc);
pc->sz = 0;//信息个数初始化为0
//一开始,能存储的信息个数定为 3
pc->capacity = INIT_SZ;
pc->data = (perInfo*)malloc(pc->capacity * sizeof(perInfo));
if (pc->data == NULL)
{
perror("InitContact:malloc");
return;
}
//通过字符串库函数,初始化结构体数组data,
//pc->data是数组首元素地址,后面是整个结构体数组大小,整个都初始化为0
memset(pc->data, 0, sizeof(pc->data));//字符串库函数
}
6、文件操作版本
基础版、动态内存版在程序结束时,输入的个人信息也销毁了。因此使用文件操作将个人信息存储起来,当下一次调用程序时,再将文件加载进来。
6.1 保存联系人的信息——savePer
void savePer(contact* pc)
{ //打开文件
FILE* pf = fopen("contact.dat", "wb");
if (pf == NULL)
{
perror("savePer:");
return;
}
//写文件
for (int i = 0; i < pc->sz; i++)
{//输出, 从程序向文件 输出
fwrite(pc->data + i, sizeof(perInfo), 1, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
}
6.2 初始化通讯录—InitContact 修改2
//初始化通讯录
void InitContact(contact* pc)
{
assert(pc);
pc->sz = 0;//信息个数初始化为0
//一开始,能存储的信息个数定为 3
pc->capacity = INIT_SZ;
pc->data = (perInfo*)malloc(pc->capacity * sizeof(perInfo));
if (pc->data == NULL)
{
perror("InitContact:malloc");
return;
}
//通过字符串库函数,初始化结构体数组data,
//pc->data是数组首元素地址,后面是整个结构体数组大小,整个都初始化为0
memset(pc->data, 0, sizeof(pc->data));//字符串库函数
//加载信息到通讯录中
loadPer(pc);
}
//将文件导入到 程序里
void loadPer(contact* pc)
{
//打开文件
FILE* pf = fopen("contact.dat", "rb");
if (pf == NULL)
{
perror("loadPer:");
return;
}
//读文件 从文件中读数据 到 程序中
perInfo tmp = { 0 };
//输入 从文件 读到 程序
while (fread(&tmp,sizeof(perInfo),1,pf))
{
checkCapacity(pc);//扩容
pc->data[pc->sz] = tmp;//将信息一个个 读取 到程序中
pc->sz++; //信息个数+1
}
//关闭文件
fclose(pf);
pf = NULL;
}
7、三种实现方式的完整代码
通讯录三种方式实现(基础版、动态内存版、文件管理版)的完整代码放在 gitee 中:
总结
通讯录三种版本的代码,使用了结构体传地址作为参数、指针、字符串库函数、结构体、动态内存、文件操作等已经学到的知识。整体框架和三子棋、扫雷是雷同的,在局部添加新的代码。随着学习的深入,学到新的知识将会更新代码。
下一篇开始学习程序编译相关的知识点。