✅作者简介:嵌入式入坑者,与大家一起加油,希望文章能够帮助各位!!!!
📃个人主页:@rivencode的个人主页
🔥系列专栏:玩转C语言
💬推荐一款模拟面试、刷题神器,从基础到大厂面试题👉点击跳转刷题网站进行注册学习
C语言实现动态通讯录
一.动态通讯录的要求
1.通讯录的内存可随联系人的增加而增加,不用一次性分配大量空间造成空间的浪费,并有文件保存联系人的信息
2.存储联系人的信息包含:
- 名字
- 性别
- 年龄
- 电话
- 地址
3.通讯录包含以下功能
- 增加好友信息
- 删除指定名字的好友信息
- 查找好友信息
- 修改指定名字的好友信息
- 显示通讯录所有联系人的信息
- 通讯录按照年龄的大小排序(升序)
- 保存联系人信息到文件
二.实现思路
将实现动态通讯录的代码分为三个代码块分别存放在test.c,Contact.c,Contach.h 三个文件,这样就不用把代码全放在一个文件显得乱且冗余
test.c
1.实现打印通讯录的界面
2.测试通讯录各个功能(即调用对应的功能函数) 通过一个switch语句来选择测试哪个功能
Contact.c
1.初始化通讯录
2.将各个通讯录的功能封装成一个个函数,即用函数实现通讯录的各个功能
Contact.h 只要其他文件包含Contact.h 这个头文件 就可以调用该文件的内容 包括其他头文件,宏定义,结构体的声明,函数的声明
1.项目的头文件的引用
2.其他文件用到的宏定义,枚举,将多次用到的数字都定义成宏方便后期修改,也可以提高代码的可读性一举两得
3.通讯录功能函数的声明
直接上代码讲解
test.c 首先创建一个通讯录(结构体的变量)该变量包含
1.维护动态内存的指针data
2.记录当前已有联系人的个数整形变量size
3.记录通讯录当前容量的整形变量capacity 然后初始化通讯录
1.初始化给通讯录分配默认动态内存交给data指针来维护
2.初始化当前通讯录元素个数为0 size为0
3.初始默认容量capacity(开始的容量 可以随意调整)
#include "contact.h"
//打印菜单
void menu(void)
{
printf("\n");
printf("*******************************************************\n");
printf("*******************************************************\n");
printf("****** 1.添加 2.删除 ************\n");
printf("****** 3.查询 4.修改 ************\n");
printf("****** 5.显示 6.排序 ************\n");
printf("****** 7.保存 0.退出 ************\n");
printf("*******************************************************\n");
printf("*******************************************************\n");
printf("\n");
}
int main()
{
int input;
//创建一个通讯录
struct Contact con;//con为一个通讯录结构体的变量,包含data指针和size, capacity
//初始化通讯录
InitContact(&con);
do
{
menu();
printf("请选择:>");
scanf("%d",&input);
switch(input)
{
case ADD:
AddContact(&con);
break;
case DEL:
DelContact(&con);
break;
case SEARCH:
SearchContact(&con);
break;
case MODIFY:
ModifyContact(&con);
break;
case SHOW:
ShowContact(&con);
break;
case SORT:
SortContact(&con);
break;
case EXIT:
SaveContact(&con);//退出前保存数据到文件
DestroyContact(&con);
printf("退出\n");
break;
case SAVE:
SaveContact(&con);
break;
default:
printf("选择错误\n");
break;
}
}while(input);
return 0;
}
Contact.h
只要其他文件包含Contact.h 这个头文件 就可以调用该文件的内容 包括其他头文件,宏定义,结构体的声明,函数的声明
1.项目的头文件的引用
2.其他文件用到的宏定义,枚举,将多次用到的数字都定义成宏或用枚举列举方便后期修改也可以提高代码的可读性一举两得
3.声明通讯录功能函数 方便 test.c 调用功能函数
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define DEFAULT_CAPACITY 3
#define MAX_NAME 10
#define MAX_GENDER 5
#define MAX_TEL 30
#define MAX_ADDR 30
//枚举,列举功能
enum Option
{
EXIT, //默认从0开始
ADD,//1
DEL,//2
SEARCH,//3
MODIFY,//4
SHOW,//5
SORT,//6
SAVE//7
};
//联系人信息的类型
struct PeoInfo
{
int age;
char name[MAX_NAME];
char gender[MAX_GENDER];
char tel[MAX_TEL];
char addr[MAX_ADDR];
};
//通讯录类型
struct Contact
{
//嵌套一个联系人信息的一个结构体
struct PeoInfo *data;//维护动态内存的指针
int size;//记录当前已有联系人的个数
int capacity;//记录通讯录当前容量
};
//初始化通讯录
void InitContact(struct Contact *ps);
//添加一个联系人到通讯录
void AddContact(struct Contact *ps);
//显示整个通讯录
void ShowContact(const struct Contact *ps);
//从通讯录删除一个指定联系人
void DelContact(struct Contact *ps);
//从通讯录寻找一个指定联系人
void SearchContact(const struct Contact *ps);
//修改指定联系人的信息
void ModifyContact(struct Contact *ps);
//按年龄排序
void SortContact( struct Contact *ps);
//检测容量是否够用,不够扩容默认一次扩两个
int CheckCapacity( struct Contact *ps);
//销毁通讯录(释放分配的动态内存)
void DestroyContact(struct Contact *ps);
//保存数据到文件
void SaveContact(struct Contact *ps);
//加载联系人
void LoadContact(struct Contact *ps);
Contact.c
用函数实现动态通讯录的各个功能
各个函数都有详细的注释这里就不在重复讲解
这里挑几个要注意的地方
1.static int FindByName(const struct Contact *ps,char name[MAX_NAME])
封装这个函数是因为有多个函数需要用到查找联系人并返回联系人的在内存中的位置,为了避免代码冗余将该功能封装成一个函数,其他函数要用到此功能直接调用该函数即可
2.每次添加新的联系人要先检测通讯录的容量是否足够,即用intCheckCapacity( struct Contact *ps) 函数检测当前通讯录的人数与通讯录的容量是否相等,若相等即通讯录已满需要进行扩容利用realloc进行扩容这里我默认每次扩充两个人的信息容量(两个结构体的大小,一个结构体相当于一条记录存储着联系人的名字,姓名,年龄,电话,地址)
3.从通讯录删除一个指定名字的联系人,先查找该联系人的下标(pos)然后看图
4.退出前将联系人的信息以二进制形式保存到文件
利用fpoen函数打开文件,将通讯录已有的联系人信息以二进制的形式保存到指定文件,然后关闭文件
值得注意的是以二进制写文件(“wb”)打开时每次打开文件时会生成一个新的空文件覆盖原有的文件,则每次保存的时候都会重新录一遍信息,这样就可以通讯录的实时性,即删除联系人时再保存会把删除之后的联系人信息重新录进文件(原有的信息全部擦除)
//保存数据到文件
void SaveContact(struct Contact *ps)
{
int i=0;
//打开文件
FILE *pfwrite = fopen("contact.dat","wb");
if ( pfwrite == NULL)//打开文件失败
{
printf("SaveContact:: %s",strerror(errno));
return ;//返回而不返回值 提前结束函数
}
for(i=0; i<ps->size; i++)
{
//将已有的联系人依次保存到文件
fwrite(&(ps->data[i]),sizeof(struct PeoInfo), 1, pfwrite);
}
//关闭文件
fclose(pfwrite);
pfwrite=NULL;
}
case EXIT:
SaveContact(&con);//退出前保存数据到文件
DestroyContact(&con);
printf("退出\n");
break;
当你保存时会在代码路径下会生成一个文件保存联系人的信息,初始化时加载联系人的时候也是从该文件读取信息保存到通讯录
5.初始化通讯录将文件里的上一次保存的联系人信息再以二进制形式读出则实现了联系人的存储
已有详细注释不再进行赘述
//初始化通讯录
void InitContact(struct Contact *ps)
{
ps->data=(struct PeoInfo*)malloc( DEFAULT_CAPACITY *sizeof(struct PeoInfo));//初始化给通讯录分配默认动态内存
ps->size=0;//初始化当前通讯录元素个数为0
ps->capacity=DEFAULT_CAPACITY;//默认容量
//加载联系人
LoadContact(ps);//调用加载函数
}
//声明函数方便加载函数调用
int CheckCapacity( struct Contact *ps);
//加载联系人
void LoadContact(struct Contact *ps)
{
struct PeoInfo tmp;//创建一个联系人的临时变量用来接收从文件读取的信息
//打开文件
FILE *pfread = fopen("contact.dat","rb");
if ( pfread == NULL)//打开文件失败
{
printf("LoadContact:: %s",strerror(errno));
return ;//返回而不返回值 提前结束函数
}
//fread函数返回值是 读取到的真正的元素个数,这里采用一个一个读取\
若文件有数据读取一个fread的则函数返回值为1进入循环,若文件数据\
已被读取完毕则返回0,退出循环
while( fread(&tmp, sizeof(struct PeoInfo), 1,pfread) )
{
CheckCapacity(ps);//存放前检测通讯录容量,不够就扩容
//初始化通讯录时size为0
ps->data[ps->size]=tmp;//将读出的数据存放进通讯录
ps->size++;
}
//关闭文件
fclose(pfread);
pfread=NULL;
}
Contact.c
#include "contact.h"
//初始化通讯录
void InitContact(struct Contact *ps)
{
ps->data=(struct PeoInfo*)malloc( DEFAULT_CAPACITY *sizeof(struct PeoInfo));//初始化给通讯录分配默认动态内存
ps->size=0;//初始化当前通讯录元素个数为0
ps->capacity=DEFAULT_CAPACITY;//默认容量
//加载联系人
LoadContact(ps);
}
//声明函数方便加载函数调用
int CheckCapacity( struct Contact *ps);
void LoadContact(struct Contact *ps)
{
struct PeoInfo tmp;//创建一个联系人的临时变量用来接收从文件读取的信息
//打开文件
FILE *pfread = fopen("contact.dat","rb");
if ( pfread == NULL)//打开文件失败
{
printf("LoadContact:: %s",strerror(errno));
return ;//返回而不返回值 提前结束函数
}
//fread函数返回值是 读取到的真正的元素个数,这里采用一个一个读取\
若文件有数据读取一个fread的则函数返回值为1进入循环,若文件数据\
已被读取完毕则返回0,退出循环
while( fread(&tmp, sizeof(struct PeoInfo), 1,pfread) )
{
CheckCapacity(ps);//存放前检测通讯录容量不够就扩容
//初始化通讯录时size为0
ps->data[ps->size]=tmp;//将读出的数据存放进通讯录
ps->size++;
}
//关闭文件
fclose(pfread);
pfread=NULL;
}
//因很多功能函数需要用到查找联系人,所以封装成一个函数方便调用,以减少代码的冗余
//加上static本文件可以调用
static int FindByName(const struct Contact *ps,char name[MAX_NAME])
{
int i=0;
for(i=0; i<ps->size; i++)
{
if(0==strcmp(ps->data[i].name,name))
{
return i;//找到联系人,返回该联系人的位置
break;
}
}
return -1;//没找到,返回-1
}
int CheckCapacity( struct Contact *ps)
{
if (ps->size == ps->capacity)
{
//当通讯录当前人数等于容量时进行扩容一次默认扩充两个空间
//参数1.要调整的内存地址 2.调整之后的大小(单位字节)
struct PeoInfo*str=(struct PeoInfo*)realloc(ps->data, (ps->capacity+2)*sizeof(struct PeoInfo) );
if ( str !=NULL )
{
ps->data=str;//开辟成功把地址赋给原来的指针继续维护
ps->capacity+=2;//通讯录的容量加2
printf("增容成功\n");
return 1;
}
else
{
printf("阵容失败\n");
return -1;
}
}
}
//添加一个联系人到通讯录
void AddContact(struct Contact *ps)
{
int ret=0;
if (ret= CheckCapacity(ps)==-1 )
{
printf("添加失败\n");
}
else
{
printf("输入姓名:");
scanf("%s",ps->data[ps->size].name);
printf("输入性别:");
scanf("%s",ps->data[ps->size].gender);
printf("输入年龄:");
//因年龄的数据类型为整形 须取地址操作
//其余为数组名本就为地址 则无需取地址
scanf("%d",&(ps->data[ps->size].age));
printf("输入电话:");
scanf("%s",ps->data[ps->size].tel);
printf("输入住址:");
scanf("%s",ps->data[ps->size].addr);
printf("添加成功\n");
ps->size++;//每加一个联系人当前通讯录元素个数加1
}
}
//显示整个通讯录
void ShowContact(const struct Contact *ps)
{
int i=0;
if ( ps->size ==0 )
{
printf("通讯录为空\n");
}
else
{
printf("%-10s %-5s %-5s %-10s %-30s \n","姓名","性别","年龄","电话","地址");
//将通讯录里每个联系人的信息一一打印出来
//打印size(当前通讯录几个联系人)个信息
for(i=0; i<ps->size; i++)
{
// "-5"号表示左对齐,打印长度为5
printf("%-10s %-5s %-5d %-10s %-30s \n",
ps->data[i].name,
ps->data[i].gender,
ps->data[i].age,
ps->data[i].tel,
ps->data[i].addr);
}
}
}
//从通讯录删除一个指定联系人
void DelContact(struct Contact *ps)
{
int j=0;
int pos=0;//创建一个变量来接收要删除联系人的位置
char name[MAX_NAME];
printf("请输入要删除人的名字");
scanf("%s",name);
pos=FindByName(ps,name);
if (pos ==-1 )
{
printf("没有找到此人\n");
}
else
{
for(j=pos; j<ps->size-1; j++)
{
ps->data[j]=ps->data[j+1];//将要删除联系人的后面的联系人的位置都向前移动一个位置覆盖要删除的联系人
}
printf("删除成功\n");
ps->size--;
}
}
//从通讯录寻找一个指定联系人
void SearchContact(const struct Contact *ps)
{
int pos=0;
char name[MAX_NAME];
printf("请输入要寻找人的名字");
scanf("%s",name);
pos=FindByName(ps,name);
if ( pos==-1 )
{
printf("没有找到此人\n");
}
else
{
//找到了打印信息
printf("找到了\n");
printf("%-10s %-5s %-5s %-10s %-30s \n","姓名","性别","年龄","电话","地址");
printf("%-10s %-5s %-5d %-10s %-30s \n",
ps->data[pos].name,
ps->data[pos].gender,
ps->data[pos].age,
ps->data[pos].tel,
ps->data[pos].addr);
}
}
//修改指定联系人的信息
void ModifyContact(struct Contact *ps)
{
int pos=0;
char name[MAX_NAME];
printf("请输入要修改的人的名字");
scanf("%s",name);
pos=FindByName(ps,name);
if ( pos==-1 )
{
printf("没有找到此人\n");
}
else
{
//找到该联系人重新录一遍信息,完成修改
printf("输入姓名:");
scanf("%s",ps->data[pos].name);
printf("输入性别:");
scanf("%s",ps->data[pos].gender);
printf("输入年龄:");
scanf("%d",&(ps->data[pos].age));
printf("输入电话:");
scanf("%s",ps->data[pos].tel);
printf("输入住址:");
scanf("%s",ps->data[pos].addr);
printf("修改成功\n");
}
}
//这里按年龄排序
int cmp_stu_by_name(const void *e1,const void *e2)
{
//若想按照其他排序排序方法修改此函数即可
return ((struct PeoInfo*)e1)->age - ((struct PeoInfo* )e2)->age ;
}
//按年龄排序
void SortContact( struct Contact *ps)
{
//利用qsort库函数排序
qsort(ps->data,ps->size,sizeof(struct PeoInfo),cmp_stu_by_name);
//自主实现按年龄排序
//int i=0;
//int j=0;
//int flag=1;
//struct PeoInfo tmp;
//for(i=0; i<ps->size; i++)
//{
// for(j=0; j<ps->size-i-1;j++)
// {
// if(ps->data[j].age - ps->data[j+1].age)//升序
// {
// tmp= ps->data[j] ;
// ps->data[j] = ps->data[j+1];
// ps->data[j+1] = tmp;
// flag=0;
// }
// }
// if ( flag==1 )
// {
// break;
// }
//}
ShowContact(ps);
}
//销毁通讯录(释放分配的动态内存)
void DestroyContact(struct Contact *ps)
{
free(ps->data);//释放分配的动态内存
ps->data=NULL;
}
//保存数据到文件
void SaveContact(struct Contact *ps)
{
int i=0;
//打开文件
FILE *pfwrite = fopen("contact.dat","wb");
if ( pfwrite == NULL)//打开文件失败
{
printf("SaveContact:: %s",strerror(errno));
return ;//返回而不返回值 提前结束函数
}
for(i=0; i<ps->size; i++)
{
//将已有的联系人依次保存到文件
fwrite(&(ps->data[i]),sizeof(struct PeoInfo), 1, pfwrite);
}
//关闭文件
fclose(pfwrite);
pfwrite=NULL;
}
最后退出通讯录要调用函数 void DestroyContact(struct Contact *ps)
释放动态分配的内存
case EXIT:
DestroyContact(&con);
printf("退出\n");
break;
效果演示
1.第一次打开通讯录
保存第二次打开,选择显示发现上次保存的联系人的信息还在
这里只展示了部分功能有兴趣的朋友可以自己实践一下,自己写的时候可以分功能一个一个函数写 ,写完一个功能就运行验证一下以免一下子写完错误太多,文章关于文件操作没有深入讲解,想要学习的朋友可以看 文件操作函数详解 教你玩转文件操作