目录
- 总体架构
- ⚪contact.h
- ⚪test.c
- 具体函数实现
- ⚪初始化通讯录
- ⚪销毁释放通讯录内存
- ⚪检查通讯录是否需要增容
- ⚪增加联系人
- ⚪打印联系人
- ⚪查找姓名
- ⚪查询联系人
- ⚪删除联系人
- ⚪修改联系人
- ⚪排序联系人
-
-
- 完整代码
- ⚪contact.c
总体架构
在写动态存储数据的通讯录前请最好自己熟悉静态通讯录的原理思路,这里放一篇静态通讯录。
我们使用contact.h,contact.c,test,c
三个文件
由于我们有时候只是用动态版本不进行存储数据,所以这里把所有跟存储功能有关的函数代码都用箭头标志上,定为版本三
contact.h
#pragma once
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TELE 30
#define MAX_ADDR 30
#define MAX 1000
//动态版本
#define DEFAULT_NUM 3//一次申请3peoinfo的空间
#define AD_NUM 2//一次的增量
//结构体类型的定义,通讯录一个联系人的信息
typedef struct PeoInfo
{
char name[MAX_NAME];
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
int age;
}PeoInfo;//将struct PeoInfo定义为PeoInfo
//动态版本
typedef struct Contact
{
PeoInfo *data;//指向动态申请的空间,存放人的信息
int num;//记录已经存在的有效信息的个数
int capcity;//记录当前通讯录有效信息的最大个数
}Contact;
//初始化通讯录
void InitContact(Contact* pc);
//增添联系人
void AddContact(Contact* pc);
//打印所有联系人
void PrintContact(Contact* pc);
//删除联系人
void DelContact(Contact* pc);
//查询联系人
void SearchContact(Contact* pc);
//修改联系人
void ModifyContact(Contact* pc);
//排列联系人
void SortContact(Contact* pc);
//释放通讯录
void DestoryContact(Contact* pc);
//存储联系人信息
void SaveContact(Contact* pc); <-(版本三)
//读取联系人信息
void LoadContact(Contact* pc); <-(版本三)
动态版本主要在于部分函数的改变以及增添
test.c
#define _CRT_SECURE_NO_DEPRECATE 1
#pragma warning(disable:4996)
#include "contact.h"
enum Option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SORT,
PRINT
};
void menu()
{
printf("*****************************\n");
printf("****** 1.Add 2.Del *******\n");
printf("****** 3.Search 4.Modify ****\n");
printf("****** 5.Sort 6.Print ****\n");
printf("*********** 0.Exit **********\n");
printf("*****************************\n");
}
//动态版本
//每次退出程序后保留之前的数据
int main()
{
int input = 0;
Contact con;//创建通讯录
InitContact(&con); //初始化通讯录
LoadContact(&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 SORT:
SortContact(&con);
break;
case PRINT:
PrintContact(&con);
break;
case EXIT:
SaveContact(&con); <-(版本三)//储存联系人信息
DestoryContact(&con);//释放通讯录
printf("退出成功\n");
break;
default:
printf("输入错误,重新输入");
break;
}
} while (input);
return 0;
}
具体函数实现
初始化通讯录
-
malloc
申请空间若申请失败则会返回NULL
void InitContact(Contact* pc)
{
pc->data = (PeoInfo*)malloc(DEFAULT_NUM * sizeof(PeoInfo));//用malloc申请3个PeoInfo大小的值
if (pc->data == NULL)//判断是否申请失败
{
perror("InitContact");//若失败perror报错
return;
}
pc->num = 0;//将数据个数置空
pc->capcity = DEFAULT_NUM;//容量设为3
}
销毁/释放通讯录
- 在退出时执行销毁功能
void DestoryContact(Contact* pc)
{
free(pc->data);//释放data内存
//整个过程只有pc->data开辟了动态内存
pc->data = NULL;//置空
pc->num = 0;//数据个数置零
pc->capcity = 0;//容量置零
}
检查通讯录是否需要扩容
- 增加联系人时会检查是否需要增容,我们更偏向于单独写出来这个功能
- realloc是重新调整ptr指向的内存块大小,所以我们
(pc->capcity + AD_NUM)
需要加上本身capacity
void CheckCapcity(Contact* pc)
{
if (pc->num == pc->capcity)//判断是否容量已经满了
{
PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capcity + AD_NUM) * sizeof(PeoInfo));//将realloc申请的空间放到一个临时的结构体指针中
if (ptr != NULL)//判断是否realloc成功
{
pc->data = ptr;//将ptr的内存容量赋给data
pc->capcity += AD_NUM;//扩容
printf("增容成功\n");
}
else//realloc失败
{
perror(AddContact);
printf("增加联系人失败\n");
return;
}
}
}
增加联系人
- 检查后正常输入
void AddContact(Contact* pc)
{
CheckCapcity(pc);//检查/扩容
printf("请输入姓名:>\n");
scanf("%s", pc->data[pc->num].name);
printf("请输入年龄:>\n");
scanf("%d", &(pc->data[pc->num].age));
printf("请输入性别\n");
scanf("%s", pc->data[pc->num].sex);
printf("请输入电话\n");
scanf("%s", pc->data[pc->num].tele);
printf("请输入地址\n");
scanf("%s", pc->data[pc->num].addr);
pc->num++;
}
打印联系人
-
"%-20s\t%-8s\t%-8s\t%-20s\t%-30s\n"
是为了对应各个信息的大小(姓名最大20个字节) -
'\t'
使标题对齐,for循环中i < pc->num
打印当前存储的所有联系人
void PrintContact(const Contact* pc)
{
//打印最上方标题
printf("%-20s\t%-8s\t%-8s\t%-20s\t%-30s\n", "姓名", "年龄", "性别", "电话", "地址");
//打印人物数据
for (int i = 0; i < pc->num; i++)
{
printf("%-20s\t%-8d\t%-8s\t%-20s\t%-30s\n",
pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
查找姓名
- 搜索联系人、修改联系人、删除联系人功能都有一个步骤——即查询姓名
- 所以我们把查找姓名也单独写一个函数
int FindName(Contact* pc, char name[])
{
for (int i = 0; i < pc->num; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
return i;
}
return -1;
}
当我们输入要查询的联系人姓名后,
FindName()
会遍历目前储存的所有联系人,用strcmp判断是否有相等,如果相等返回其下标
查询联系人
void SearchContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
if (pc->num == 0) //判断一下通讯录是否有联系人,如果为空直接退出,可去掉此if语句
{
printf("通讯录为空\n");
return;
}
printf("请输入要查找的人的名字");
scanf("%s", &name);
int ret = FindName(pc, name); //将查找出的联系人下标赋给ret
if (ret == -1) //判断是否找到该联系人
{
printf("您要查找的人物不存在\n");
return;
}
else //打印ret下标的联系人所有信息
{
printf("%-20s\t%-8s\t%-8s\t%-20s\t%-30s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-20s\t%-8d\t%-8s\t%-20s\t%-30s\n",
pc->data[ret].name,
pc->data[ret].age,
pc->data[ret].sex,
pc->data[ret].tele,
pc->data[ret].addr);
}
}
删除联系人
- 我们删除联系人的本质是把该联系人后面的所有联系人下标向前移一位,后面的联系人直接覆盖该联系人,最后
num--
(存储的联系人个数)
void DelContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
if (pc->num == 0) //判断一下通讯录是否有联系人,如果为空直接退出,可去掉此if语句
{
printf("通讯录为空\n");
return;
}
printf("输入要删除人物的名字\n");
scanf("%s", name);
//查找人物
int ret = FindName(pc,name);
if (ret == -1)
{
printf("您要删除的人物不存在\n");
return;
}
for (int i = ret; i < pc->num-1; i++)//注意num-1为了最后成员的名字无需将他后面的空白替换过来
{
pc->data[i] = pc->data[i + 1];//把从pc->data[ret]后的成员向前移
}
pc->num--;
printf("删除成功\n");
}
修改联系人
- 修改联系人与删除、查询联系人很类似,先判断在输入最后执行操作
void ModifyContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
if (pc->num == 0) //判断一下通讯录是否有联系人,可去掉此if语句
{
printf("通讯录为空\n");
return;
}
printf("请输入要修改的人的名字:>");
scanf("%s", name);
int ret = FindName(pc, name);
if (ret == -1)
{
printf("您要修改的人物不存在\n");
return;
}
else
{
/*printf("请输入姓名:>");
scanf("%s", pc->data[ret].name);
printf("请输入年龄:>");
scanf("%d", &(pc->data[ret].age));
printf("请输入性别");
scanf("%s", pc->data[ret].sex);
printf("请输入电话");
scanf("%s", pc->data[ret].tele);
printf("请输入地址");
scanf("%s", pc->data[ret].addr);
printf("修改成功\n");*/
int op = 0;
do {
printf("请输入需要修改的部分\n");
printf("1.姓名 2.年龄 3.性别 4.电话 5.地址 0.退出修改\n");
scanf("%d", &op);
switch (op)
{
case 1:
printf("请输入姓名:>");
scanf("%s", pc->data[ret].name);
break;
case 2:
printf("请输入年龄:>");
scanf("%d", &(pc->data[ret].age));
break;
case 3:
printf("请输入性别:>");
scanf("%s", pc->data[ret].sex);
break;
case 4:
printf("请输入电话:>");
scanf("%s", pc->data[ret].tele);
break;
case 5:
printf("请输入地址:>");
scanf("%s", pc->data[ret].addr);
break;
case 0:
printf("退出修改\n");
break;
default:
printf("输入错误,重新输入");
break;
}
} while (op);
}
}
这里提供了两种,因为修改联系人很多时候只需要修改某条信息,所以我们使用
do_while&&switch_case
来进行某些项的修改。
而注释掉的代码是逐步修改所有项
排序联系人
void SortContact(Contact* pc)
{
if (pc->num == 0) //先进行判断通讯录是否为空
{
printf("通讯录为空\n");
return;
}
for (int i = 0; i < pc->num - 1; i++)
{
for (int j = 0; j < pc->num - i - 1; j++)
{
if (strcmp(pc->data[j].name, pc->data[j + 1].name) > 0)
//比较data下标为j和下标为j+1的名字的大小
{
PeoInfo tmp = { 0 }; //创建一个临时变量用于交换
tmp = pc->data[j];
pc->data[j] = pc->data[j + 1];
pc->data[j + 1] = tmp;
}
}
}
printf("排序成功\n");
}
排序联系人使用冒泡排序的方法。
保存数据
- 为了使通讯录具有储存联系人的功能,每次退出前进行保存数据
- 注意
contact.dat
要在代码文件的同路径下
void SaveContact(Contact* pc)//3
{
FILE *pf = fopen("Contact.dat", "w");//打开只写文件,若文件不存在会自动创建
if (pf == NULL)//判断是否fopen打开成功
{
perror(SaveContact);
return;
}
for (int i = 0; i < pc->num; i++)
{
fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);//data+i即为下标为i的地址
}
fclose(pf);//关闭流
pf = NULL;//置空
}
读取数据
void LoadContact(Contact* pc)
{
FILE* pf = fopen("Contact.dat", "r");//以只读模式打开
if (pf == NULL)//判断是否打开成功
{
perror(LoadContact);
return;
}
//读文件
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf))//将pf读取到*tmp指向的内存中
{
//判断增容
CheckCapcity(pc);
pc->data[pc->num] = tmp;
pc->num++;
}
}
完整代码
这里贴一个笔者的gitee链接 卜及中-share-contact
contact.c
#define _CRT_SECURE_NO_DEPRECATE 1
#pragma warning(disable:4996)
#include "contact.h"
void InitContact(Contact* pc)
{
pc->data = (PeoInfo*)malloc(DEFAULT_NUM * sizeof(PeoInfo));
if (pc->data == NULL)
{
perror("InitContact");
return;
}
pc->num = 0;
pc->capcity = DEFAULT_NUM;
}
void CheckCapcity(Contact* pc)
{
if (pc->num == pc->capcity)
{
PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capcity + AD_NUM) * sizeof(PeoInfo));
if (ptr != NULL)
{
pc->data = ptr;
pc->capcity += AD_NUM;
printf("增容成功\n");
}
else
{
perror(AddContact);
printf("增加联系人失败\n");
return;
}
}
}
void AddContact(Contact* pc)
{
CheckCapcity(pc);
printf("请输入姓名:>\n");
scanf("%s", pc->data[pc->num].name);
printf("请输入年龄:>\n");
scanf("%d", &(pc->data[pc->num].age));
printf("请输入性别\n");
scanf("%s", pc->data[pc->num].sex);
printf("请输入电话\n");
scanf("%s", pc->data[pc->num].tele);
printf("请输入地址\n");
scanf("%s", pc->data[pc->num].addr);
pc->num++;
}
void PrintContact(const Contact* pc)
{
//打印最上方标题
printf("%-20s\t%-8s\t%-8s\t%-20s\t%-30s\n", "姓名", "年龄", "性别", "电话", "地址");
//打印人物数据
for (int i = 0; i < pc->num; i++)
{
printf("%-20s\t%-8d\t%-8s\t%-20s\t%-30s\n",
pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
//查找名字
int FindName(Contact* pc, char name[])
{
for (int i = 0; i < pc->num; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
return i;
}
return -1;
}
void DelContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
if (pc->num == 0)
{
printf("通讯录为空\n");
return;
}
printf("输入要删除人物的名字\n");
scanf("%s", name);
//查找人物
int ret = FindName(pc,name);
if (ret == -1)
{
printf("您要删除的人物不存在\n");
return;
}
for (int i = ret; i < pc->num-1; i++)//注意num-1为了最后成员的名字无需将他后面的空白替换过来
{
pc->data[i] = pc->data[i + 1];
}
pc->num--;
printf("删除成功\n");
}
void SearchContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
if (pc->num == 0) //判断一下通讯录是否有联系人,可去掉此if语句
{
printf("通讯录为空\n");
return;
}
printf("请输入要查找的人的名字");
scanf("%s", &name);
int ret = FindName(pc, name);
if (ret == -1)
{
printf("您要查找的人物不存在\n");
return;
}
else
{
printf("%-20s\t%-8s\t%-8s\t%-20s\t%-30s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-20s\t%-8d\t%-8s\t%-20s\t%-30s\n",
pc->data[ret].name,
pc->data[ret].age,
pc->data[ret].sex,
pc->data[ret].tele,
pc->data[ret].addr);
}
}
void ModifyContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
if (pc->num == 0) //判断一下通讯录是否有联系人,可去掉此if语句
{
printf("通讯录为空\n");
return;
}
printf("请输入要修改的人的名字:>");
scanf("%s", name);
int ret = FindName(pc, name);
if (ret == -1)
{
printf("您要修改的人物不存在\n");
return;
}
else
{
/*printf("请输入姓名:>");
scanf("%s", pc->data[ret].name);
printf("请输入年龄:>");
scanf("%d", &(pc->data[ret].age));
printf("请输入性别");
scanf("%s", pc->data[ret].sex);
printf("请输入电话");
scanf("%s", pc->data[ret].tele);
printf("请输入地址");
scanf("%s", pc->data[ret].addr);
printf("修改成功\n");*/
int op = 0;
do {
printf("请输入需要修改的部分\n");
printf("1.姓名 2.年龄 3.性别 4.电话 5.地址 0.退出修改\n");
scanf("%d", &op);
switch (op)
{
case 1:
printf("请输入姓名:>");
scanf("%s", pc->data[ret].name);
break;
case 2:
printf("请输入年龄:>");
scanf("%d", &(pc->data[ret].age));
break;
case 3:
printf("请输入性别:>");
scanf("%s", pc->data[ret].sex);
break;
case 4:
printf("请输入电话:>");
scanf("%s", pc->data[ret].tele);
break;
case 5:
printf("请输入地址:>");
scanf("%s", pc->data[ret].addr);
break;
case 0:
printf("退出修改\n");
break;
default:
printf("输入错误,重新输入");
break;
}
} while (op);
}
}
void SortContact(Contact* pc)
{
if (pc->num == 0)
{
printf("通讯录为空\n");
return;
}
for (int i = 0; i < pc->num - 1; i++)
{
for (int j = 0; j < pc->num - 1-i; j++)
{
if (strcmp(pc->data[j].name, pc->data[j + 1].name) > 0)
{
PeoInfo tmp = { 0 };
tmp = pc->data[j];
pc->data[j] = pc->data[j + 1];
pc->data[j + 1] = tmp;
}
}
}
printf("排序成功\n");
}
//动态版本
void DestoryContact(Contact* pc)//3
{
free(pc->data);
//整个过程只有pc->data开辟了动态内存
pc->data = NULL;
pc->num = 0;
pc->capcity = 0;
}
//版本三-存储数据
void SaveContact(Contact* pc)//3
{
FILE *pf = fopen("Contact.dat", "w");
if (pf == NULL)
{
perror(SaveContact);
return;
}
for (int i = 0; i < pc->num; i++)
{
fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);//data+i即为下标为i的地址
}
fclose(pf);
pf = NULL;
}
//版本三 - 加载数据
void LoadContact(Contact* pc)
{
FILE* pf = fopen("Contact.dat", "r");
if (pf == NULL)
{
perror(LoadContact);
return;
}
//读文件
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf))
{
//判断增容
CheckCapcity(pc);
pc->data[pc->num] = tmp;
pc->num++;
}
}