简介:
写一个通讯录 能实现对联系人信息的增删查改,并用文件来存储联系人的信息,如图:
基础:
1.头文件
1.创建联系人结构体类型
创建一个结构体类型,它可以存储一个联系人各项的信息
typedef struct peoInfor {/重命名结构体方便编码
int age;/年龄
char name[NAME_MAX];/姓名
char sex[SEX_MAX];/性别
char addr[ADDR_MAX];/地址
char num[NUM_MAX];/电话号码
}peoInfor;
2.创建通讯录结构体类型
因为我们要使用动态内存来存储联系人信息,不妨使用柔性数组,给结构体内联系人数据分配一个连续的内存这样既可以方便内存释放,还能这样有利于访问速度。
typedef struct contact {/重命名结构体方便编码
int dataSize; /当前通讯录中的联系人个数
int MemerSize; /当前通讯录已开辟的动态内存空间
peoInfor data[]; /联系人柔性数组
}contact;
3.枚举常量,预处理标识符,函数声明
这里没什么难度,为了方便编码和提高可读性,先将可能反复用到的常量用标识符预定义到头文件中,把函数声明也到这里,再在源文件中定义,这是很常见的开发方式:
#pragma once /防止重复包含源文件
#define _CRT_SECURE_NO_WARNINGS 1
#define NAME_MAX 20
#define SEX_MAX 6
#define ADDR_MAX 50
#define NUM_MAX 20
#define DataFlie_NAME "infodata.txt"//默认文件名
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<errno.h>
typedef enum opintion //可能选项的枚举类型
{
ExitandSave,
Add,
Del,
Srch,
Moidfy,
Sort,
Show,
Init,
Save,
ExitwithoutSave,
}opintion;
typedef struct peoInfor {//重命名结构体方便编码
int age;
char name[NAME_MAX];
char sex[SEX_MAX];
char addr[ADDR_MAX];
char num[NUM_MAX];
}peoInfor;
typedef struct contact {//重命名结构体方便编码
int dataSize;
int MemerSize;
peoInfor data[];
}contact;
void InitContact(contact **p);//初始化通讯录
void GetMemory(contact **p);//给通讯录增容
void FreeContact(contact** p);
void meun();//通讯录菜单
void AddContact(contact **p);//新建联系人
void DelContact(contact **p);//删除联系人
int FindContact(contact **p);//返回联系人的data下标
void SearchContact(contact **p);//查找并打印联系人;
void ModifyContact(contact **p);//修改联系人
void SortContact(contact **p);//排序联系人
void ShowContact(contact **p);//显示联系人
void SaveContact(contact** p);//存储联系人
void LoadData(contact** p);//读取磁盘中存储的信息
实现:
1.main函数所在源文件
为了方便编码,将具体实现通讯录功能的函数的定义封装的其他的源文件 ,这个main函数所在的源文件直接调用它们,编写好大体的逻辑即可。
#include"contac.h" 引入头文件
contact* con; 声明一个待分配动态内存的通讯录结构体指针
int main() {
contact** pc = &con;/将用来开辟空间的指针作函数参数需要传这个指针的地址
FILE* pf;/用与接收打开文件函数fopen的返回值
InitContact(pc);/初始化
LoadData(pc,&pf);/加载文件数据
int input;/用户选择
do {
meun();/打印菜单
printf("请选择序号:>");
scanf("%d", &input);
switch (input) {
case Add:
AddContact(pc);/增
break;
case Del:
DelContact(pc);/删
break;
case Srch:
SearchContact(pc);/查
break;
case Moidfy:
ModifyContact(pc);/改
break;
case Sort:
SortContact(pc);/排序
break;
case Show:
ShowContact(pc);/展示已有联系人
break;
case Init:
InitContact(pc);/格式化通讯录
break;
case Save:
SaveContact(pc,&pf);/手动将联系人存储到文件中
break;
case ExitwithoutSave:/退出不保存
printf("退出程序...\n");
break;
case ExitandSave:/退出并保存
printf("保存并退出...");
SaveContact(pc,&pf);
break;
default:
printf("输入错误,重新输入!\n");
}
} while (input != ExitandSave && input != ExitwithoutSave);
FreeContact(pc,&pf);/释放动态内存
return 0;
}
2.初始化通讯录
接下来实现具体的功能。第一步需要初始化通讯录,为当前程序开辟动态内存。储存策略是先开辟两个联系人的空间,只有添加联系人时如果不够再扩容。
void InitContact(contact** p) {
*p=malloc(sizeof(**p)+2*sizeof(peoInfor));
if (*p == NULL) {
printf("初始化失败!\n");
printf("%s", strerror(errno));
return;
}
(*p)->MemerSize = 2;/当前共开辟了两份空间
(*p)->dataSize = 0;/已存入的联系人为0个
}
3.导入文件中的联系人信息
初始化完成后,导入上一次已经存储在文件中的联系人信息(二进制形式)
void LoadData(contact** p, FILE** pf) {
*pf = fopen(DataFlie_NAME, "rb");
if (*pf == NULL) {
printf("%s\n(若此次非本程序的首次运行,请检查%s是否遗失)\n", strerror(errno),DataFlie_NAME);
return ;
}
peoInfor tmp;
while (fread(&tmp, sizeof(peoInfor), 1, *pf)) {
if ((*p)->dataSize == (*p)->MemerSize)
GetMemory(p);
(*p)->data[(*p)->dataSize++] = tmp;
}
}
若在导入时,若发现当前开辟的空间已满,则调用GetMemory函数为柔性数组增容。
void GetMemory(contact** p) {
assert(*p);
contact* t = (contact*)realloc(*p, sizeof(contact) + ((*p)->MemerSize + 2) * sizeof(peoInfor));
if (t == NULL) {
printf("增容失败!\n");
printf("%s", strerror(errno));
return;
}
*p = t;
(*p)->MemerSize += 2;
}
4.联系人操作
1.显示联系人
首先检查dataSize的值,若为0则直接输出 (空)到屏幕上,否则依次将dataSize个联系人信息按个数打印到屏幕上。
void ShowContact(contact **p) {
assert((*p));
if (!(*p)->dataSize) {
printf(" (空) \n");
return;
}
printf("%-10s\t%-5s\t%-5s\t%s\t%s\n", "姓名", "性别", "年龄", "住址", "电话");
for (int i = 0; i < (*p)->dataSize; i++) {
printf("%-10s\t%-5s\t%-5d\t%s\t%s\n", (*p)->data[i].name, (*p)->data[i].sex, (*p)->data[i].age, (*p)->data[i].addr, (*p)->data[i].num);
}
printf("\n");
}
演示:
2.新建联系人
首先判断判断当前通讯录是否需要增容,再按照格式输入联系人信息,最后将dataSize自增一下即可
void AddContact(contact** p) {
assert(*p);
if ((*p)->dataSize==(*p)->MemerSize)
GetMemory(p);
printf("请输入姓名:>");
scanf("%s", (*p)->data[(*p)->dataSize].name);
printf("请输入性别:>");
scanf("%s", (*p)->data[(*p)->dataSize].sex);
printf("请输入年龄:>");
scanf("%d", &((*p)->data[(*p)->dataSize].age));
printf("请输入住址:>");
scanf("%s", (*p)->data[(*p)->dataSize].addr);
printf("请输入电话号码:>");
scanf("%s", (*p)->data[(*p)->dataSize].num);
printf("添加成功!\n");
(*p)->dataSize++;
}
3.查找联系人
编写FindContact函数来根据姓名遍历通讯录查找该联系人在柔性数组中的下标作为函数的返回值,若找不到则返回-1,SearchContact函数根据FindContact函数的返回值将联系人数组下标位置的信息按格式输出到屏幕上,返回值为-1则提示错误。
int FindContact(contact** p) {
assert(*p);
printf("请输入姓名:>");
char name[NAME_MAX];
scanf("%s",name);
for (int i = 0; i < (*p)->dataSize; i++) {
if (!strcmp((*p)->data[i].name,name ))
return i;
}
return -1;
}
void SearchContact(contact** p) {//查找并打印联系人;
assert(p);
if (!(*p)->dataSize) {
printf("当前通讯录为空!\n");
printf("\n");
return;
}
int i = FindContact(p);
if (i == -1) {
printf("查找不到该联系人!\n");
printf("\n");
return;
}
printf("%-10s\t%-5s\t%-5s\t%s\t%s\n", "姓名", "性别", "年龄", "住址", "电话");
printf("%-10s\t%-5s\t%-5d\t%s\t%s\n", (*p)->data[i].name, (*p)->data[i].sex, (*p)->data[i].age, (*p)->data[i].addr, (*p)->data[i].num);
printf("\n");
}
演示:
4. 修改联系人
如通讯录不为空,首先根据姓名查找联系人,得到下标后将联系人信息按格式输出到屏幕上,提示用户选择要修改那项信息,最后将修改后的信息输入到柔性数组中。
void ModifyContact(contact**p) {
assert((*p));
if (!(*p)->dataSize) {
printf("当前通讯录为空,无法修改!\n");
printf("\n");
return;
}
int ret = FindContact(p);
if (ret == -1) {
printf("查找不到该联系人!\n");
printf("\n");
return;
}
int input;
do {
printf("%-10s\t%-5s\t%-5s\t%s\t%s\n", "姓名", "性别", "年龄", "住址", "电话");
printf("%-10s\t%-5s\t%-5d\t%s\t%s\n", (*p)->data[ret].name, (*p)->data[ret].sex, (*p)->data[ret].age, (*p)->data[ret].addr, (*p)->data[ret].num);
printf("请选择需要修改的内容:>\n");
printf("1.%4s\t\t2.%4s\n", "姓名", "性别");
printf("3.%4s\t\t4.%4s\n", "年龄", "住址");
printf("5.%4s\t\t0.%4s\n", "电话号码", "返回");
scanf("%d", &input);
switch (input) {
case 0:
break;
case 1:
printf("请输入姓名:>");
scanf("%s", (*p)->data[ret].name);
break;
case 2:
printf("请输入性别:>");
scanf("%s", (*p)->data[ret].sex);
break;
case 3:
printf("请输入年龄:>");
scanf("%d", &((*p)->data[ret].age));
break;
case 4:
printf("请输入住址:>");
scanf("%s", (*p)->data[ret].addr);
break;
case 5:
printf("请输入电话号码:>");
scanf("%s", (*p)->data[ret].num);
break;
default:
printf("输入错误,请重新输入\n");
}
if (input)printf("修改成功!\n");
} while (input < 0 || input>5);
}
演示:
5.删除联系人
先根据名字找到该联系人在数组中的下标,得到下标后即利用memmove函数将返回下标位置后面的成员数据覆盖到返回下标的位置,最后dataSize再自减一下,程序便访问不到原本最后的一个成员位置的数据,即视为成功删除了目标元素。
void DelContact(contact** p) {
assert(*p);
if (!(*p)->dataSize) {
printf("当前通讯录为空,无法删除!\n");
return;
printf("\n");
}
int ret = FindContact(p);
if (ret == -1) {
printf("查找不到该联系人,删除失败!\n");
printf("\n");
return;
}
memmove((*p)->data + ret, (*p)->data + ret + 1, ((*p)->MemerSize - ret) * sizeof(peoInfor));
(*p)->dataSize--;
printf("删除成功!\n");
printf("\n");
}
演示:
6.联系人排序
要给通讯录里的联系人排序,可以使用库函数qsort,要求自己写好用于两个元素之间比大小的回调函数,一下函数帮助实现按照不同要素的排序。
int cmp_byage(void const* e1, void const* e2) {
return ((peoInfor*)e1)->age-((peoInfor*)e2)->age;
}
int cmp_bysex(void const * e1, void const * e2) {
return strcmp(((peoInfor*)e1)->sex, ((peoInfor*)e2)->sex);
}
int cmp_byaddr(void const* e1, void const* e2) {
return strcmp(((peoInfor*)e1)->addr, ((peoInfor*)e2)->addr);
}
int cmp_byname(void const* e1, void const* e2) {
return strcmp(((peoInfor*)e1)->name, ((peoInfor*)e2)->name);
}
int cmp_bynum(void const* e1, void const* e2) {
return strcmp(((peoInfor*)e1)->num, ((peoInfor*)e2)->num);
}
排序功能:
提示用户选择希望的排序方式,然后qsort调用对应的回调函数完成排序。
void SortContact(contact** p) {
int input;
if(!((*p)->dataSize))
printf("当前通讯录为空,无需排序!\n");
assert(*p);
do {
printf("1.年龄 2.性别 \n");
printf("3.地址 4.电话 \n");
printf("5.姓名 0.返回 \n");
printf("请选择排序方式:>");
scanf("%d", &input);
switch (input) {
case 1:
qsort((*p)->data, (*p)->dataSize, sizeof((*p)->data[0]), cmp_byage);
break;
case 2:
qsort((*p)->data, (*p)->dataSize, sizeof((*p)->data[0]), cmp_bysex);
break;
case 3:
qsort((*p)->data, (*p)->dataSize, sizeof((*p)->data[0]), cmp_byaddr);
break;
case 4:
qsort((*p)->data, (*p)->dataSize, sizeof((*p)->data[0]), cmp_bynum);
break;
case 5:
qsort((*p)->data, (*p)->dataSize, sizeof((*p)->data[0]), cmp_byname);
break;
case 0:
break;
default:
printf("输入错误,重新输入:>");
}
if (!input )break;
else
printf("排序成功!\n");
} while (input > 5 || input < 1);
}
演示:
7.格式化通讯录
格式化通讯录就是将它初始化,我们只需要调用InitContact函数,为通讯录重新分配内存,且不加载文件中的信息即可。
5.文件存储
void SaveContact(contact** p,FILE**pf) {
*pf = fopen(DataFlie_NAME, "wb");
if (*pf == NULL) {
printf("\nSaveContact:: %s\n", strerror(errno));
return;
}
fwrite((*p)->data, sizeof(peoInfor), (*p)->dataSize, *pf);
}
在程序中 用户既可以手动保存,也可以退出并保存,不论是那种情况,都只需要调用SaveContact函数,它会创建一个新的二进制文件,将当前的通讯录的内容以二进制形式读入到文件中。若该文件名不存在则会创建一个新文件,若已存在则会用新数据覆盖。
演示:
执行程序 导入文件中的数据后
6.释放内存,关闭文件
当程序结束后,需要释放程序在堆区申请的动态内存,和关闭文件指针维护的文件信息区。
void FreeContact(contact** p,FILE** pf) {
assert(*p&&*pf);
fclose(*pf);
*pf = NULL;
free(*p);
*p = NULL;
}
END
小白能力有限若有不完善的地方请大牛们多指教。
源码:通讯录 - Gitee.comhttps://gitee.com/leyi999/project/tree/master/%E9%80%9A%E8%AE%AF%E5%BD%95
程序安装包:LeYi_con_Setuphttps://gitee.com/leyi999/project/tree/master/LeYi_con_Setup/Debug