前言:哈喽小伙伴们,我们在前边的文章中已经介绍过了如何用C语言实现一个简易的通讯录,但是我们这个通讯录存在两个问题:
一是通讯录的大小不能自由变化;二是通讯录的信息在程序退出之后就没有了,不能保存。
那么,在我们学习完动态内存管理和文件操作之后,这些问题就迎刃而解啦。
下面我们就来一起实现真正意义上的通讯录。
一.动态内存改进
1.创建联系人改进
在最初的通讯录中,我们是用一个结构体类型的数组来存储信息:
typedef struct Contact
{
Peomessage data[DATA_MAX];
int sz;
}Contact;
那么当我们用到动态内存管理时,就不需要数组了,转而需要一个能够指向我们所开辟的空间的Peomessage类型的指针:
typedef struct Contact
{
Peomessage* data;
int sz;
int Length;
}Contact;
能够看出,我们这里多了一个Length变量,它的作用是记录通讯录当前的大小,因为我们要根据存取的联系人信息数量是否要大于当前通讯录的大小来对通讯录进行扩充。
通讯录都要有一个初始大小,所以我们规定通讯录的初始大小为3,每次扩充时增加2个空间:
#define LENGTH 3
#define ADD_LENGTH 2
2.初始化改进
初始化通讯录,就是用calloc函数动态开辟一块大小为LENGTH的空间。
按照我们动态开辟空间的步骤即可:
void InitContact(Contact* pc)
{
assert(pc);
pc->sz = 0;
pc->Length = LENGTH;
pc->data = calloc(pc->Length, sizeof(Peomessage));
if (pc->data == NULL)
{
perror("InitContact->calloc");
return;
}
}
将sz初始化为0,Length初始化为LENGTH。
一定要注意对内存是否开辟成功的判断。
3.增加联系人改进
void AddContact(Contact* pc)
{
assert(pc);
if (pc->sz == pc->Length)
{
Add_Length(pc);
}
printf("请输入名字:");
scanf("%s", pc->data[pc->sz].name);
printf("请输入电话:");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入住址:");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("添加成功\n");
}
增加联系人时,我们要进行sz是否等于当前Length的判断,如果相等,说明当前通讯录已满,需要扩充,所以我们就调用扩容函数:
void Add_Length(Contact* pc)
{
Peomessage* ptr = (Peomessage*)realloc(pc->data, (pc->Length + 2) * sizeof(Peomessage));
if (ptr == NULL)
{
perror("Add_Length->realloc");
return;
}
else
{
pc->data = ptr;
pc->Length += 2;
printf("扩容成功\n");
}
}
既然是扩容,那么就是修改我们已经开辟的空间大小,要用到realloc函数来修改大小,新的大小为Length + 2。
这里要注意一点,realloc函数修改空间也可能会失败,如果就直接用data指针来接收的话,如果失败,就会返回空指针,那我们原本所存的数据就不复存在了。
所以要用一个新的指针ptr来测试,如果不为空,再将ptr赋给data,同时要注意Length要加2。
来看结果,我们当前已经存了三个人的信息,再次选择存信息时,就会显示扩容成功。
4. 销毁通讯录
在动态内存管理里边我们已经学习过,动态开辟的空间使用之后要及时销毁,所以我们在通讯录退出之时,要用一个销毁函数来释放空间并还原通讯录。
case EXIT:
Destroy_Contact(&con);
printf("退出通讯录\n");
break;
void Destroy_Contact(Contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->sz = 0;
pc->Length = LENGTH;
}
要将指针data释放并置空,同时sz和Length也要还原。
这样我们便能够实现通讯录的动态大小变换了。
接下来,我们再用文件操作的相关知识来实现信息的真正存储。
二.文件操作改进
1.写文件
想保存我们已经写过的联系人信息,就要把它们写入文件,那么请小伙伴们思考一下,什么时候写入文件最合适呢???
是不是当我们不需要在进行任何操作,要退出通讯录的时候呀。
所以这个时候我们就需要一个写文件的函数WriteContact来实现:
case EXIT:
WriteContact(&con);
Destroy_Contact(&con);
printf("退出通讯录\n");
break;
那么小伙伴们再来思考一下,我们该怎么写文件呢???
我们已经知道,要写的数据是一个结构体,含有多种数据类型,那对于我们已经学习过的文件读写函数,是不是用二进制读写函数是最好的,因为二进制不分数据类型:
void WriteContact(Contact* pc)
{
assert(pc);
FILE* pf = fopen("Contact.txt", "wb");
if (pf == NULL)
{
perror("WriteContact->fopen");
return;
}
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->data + i, sizeof(Peomessage), 1, pf);
}
fcolse(pf);
pf = NULL;
}
因为我们读入的数据可能不止一个,所以要使用for循环来实现全部读写。
这时候当我们的程序文件目录下就会生成一个Contact.txt文件,打开内容为:
这里虽然没有对齐,但是我们不用管,因为我们并不是要通过文件来看通讯录信息,而是通过程序来看的。 文件只是一个后台,而真正展现给用户的是程序运行窗口。
这样我们就实现了数据保存到文件的操作。
你以为这样就结束了吗,太天真了,当我们再次运行程序时,结果为:
通讯录依然为空,这是为什么呢???
因为虽然我们的文件确实还存在,内容也存在,但是我们的内存空间不存在了,我们的代码重启是开辟一个新的空间,新的空间中并没有我们已经写入的文件的数据。
所以在此之前,我们需要先将文件里的数据输入到我们开辟的新的空间中去。
2.读文件
读文件的操作,需要在我们对文件进行任何操作之前,也就是在初始化的时候就完成。
读二进制文件就要用到fread函数了,注意,因为我们的通讯录初始大小仅为3,但是文件中已经存放的数据可能要多余3个,所以要提前进行判断是否需要扩容,所以我们要先用一个临时指针ptr来接收信息,随后再赋值给data。
来看代码实现:
void ReadContact(Contact* pc)
{
assert(pc);
FILE* pf = fopen("Contact.txt", "rb");
if (pf == NULL)
{
perror("ReadContact -> fopen");
return;
}
Peomessage ptr = { 0 };
while (fread(&ptr, sizeof(Peomessage), 1, pf))
{
if (pc->sz == pc->Length)
{
Add_Length(pc);
}
pc->data[pc->sz] = ptr;
pc->sz++;
}
}
这个时候来看结果,当我们运行代码,并选择显示通讯录5时,我们已经有数据了,
然后我们再添加一位联系人信息并关闭,再次打开时,就多了一个:
至此,我们就完成了通讯录Promax的实现。
三.完整代码展示
1.Contact.h
#pragma once
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#define NAME_MAX 20
#define ADDR_MAX 30
#define DATA_MAX 100
#define LENGTH 3
#define ADD_LENGTH 2
//选项
enum Function
{
EXIT,//默认从0开始
ADD,
DEL,
SEARCH,
REVISE,
SHOW,
SORT,
EMPTY
};
enum Type
{
QUIT,
NAME,
TELE,
ADDR
};
//联系人信息
typedef struct Peomessage
{
char name[NAME_MAX];
char tele[12];
char addr[ADDR_MAX];
}Peomessage;
//原通讯录
//typedef struct Contact
//{
// Peomessage data[DATA_MAX];
// int sz;
//}Contact;
//改进通讯录
typedef struct Contact
{
Peomessage* data;
int sz;
int Length;
}Contact;
//初始化通讯录
void InitContact(Contact* pc);
//增加联系人信息
void AddContact(Contact* pc);
//显示联系人信息
void ShowContact(Contact* pc);
//查找联系人信息
void SearchContact(Contact* pc);
//删除联系人信息
void DelContact(Contact* pc);
//修改联系人信息
void ReviseContact(Contact* pc);
//排序联系人信息
void SortContact(Contact* pc);
//清空所有联系人
void EmptyContact(Contact* pc);
//摧毁通讯录
void Destroy_Contact(Contact* pc);
//写文件
void WriteContact(Contact* pc);
//读文件
void ReadContact(Contact* pc);
2.Contact.c
#include"Contact.h"
void Add_Length(Contact* pc);
//读文件
void ReadContact(Contact* pc)
{
assert(pc);
FILE* pf = fopen("Contact.txt", "rb");
if (pf == NULL)
{
perror("ReadContact -> fopen");
return;
}
Peomessage ptr = { 0 };
while (fread(&ptr, sizeof(Peomessage), 1, pf))
{
if (pc->sz == pc->Length)
{
Add_Length(pc);
}
pc->data[pc->sz] = ptr;
pc->sz++;
}
}
// 改进初始化
void InitContact(Contact* pc)
{
assert(pc);
pc->sz = 0;
pc->Length = LENGTH;
pc->data = calloc(pc->Length, sizeof(Peomessage));
if (pc->data == NULL)
{
perror("InitContact->calloc");
return;
}
ReadContact(pc);
}
//扩容通讯录
void Add_Length(Contact* pc)
{
Peomessage* ptr = (Peomessage*)realloc(pc->data, (pc->Length + 2) * sizeof(Peomessage));
if (ptr == NULL)
{
perror("Add_Length->realloc");
return;
}
else
{
pc->data = ptr;
pc->Length += 2;
}
}
//增加联系人信息
void AddContact(Contact* pc)
{
assert(pc);
//增加容量
if (pc->sz == pc->Length)
{
Add_Length(pc);
}
printf("请输入名字:");
scanf("%s", pc->data[pc->sz].name);
printf("请输入电话:");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入住址:");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("添加成功\n");
}
//显示联系人信息
void ShowContact(Contact* pc)
{
assert(pc);
if (pc->sz == 0)
{
printf("通讯录为空\n");
return;
}
printf("%-20s%-12s%-30s\n", "姓名", "电话", "住址");
int i = 0;
for (i = 0; i < pc->sz; i++)
{
printf("%-20s%-12s%-30s\n", pc->data[i].name, pc->data[i].tele, pc->data[i].addr);
}
}
//通过名字查找
int FindByName(Contact* pc, char* name)
{
assert(pc && name);
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(name, pc->data[i].name) == 0)
{
return i;
}
}
return -1;
}
//查找联系人信息
void SearchContact(Contact* pc)
{
assert(pc);
char name[NAME_MAX];
printf("请输入要查找人的名字:");
scanf("%s", name);
int ret = FindByName(pc, name);
if (ret == -1)
{
printf("找不到该联系人\n");
return;
}
printf("%-20s%-12s%-30s\n", "姓名", "电话", "住址");
printf("%-20s%-12s%-30s\n", pc->data[ret].name, pc->data[ret].tele, pc->data[ret].addr);
}
//删除联系人信息
void DelContact(Contact* pc)
{
assert(pc);
char name[NAME_MAX];
printf("请输入要删除联系人的名字:");
scanf("%s", name);
int ret = FindByName(pc, name);
if (ret == -1)
{
printf("该联系人信息不存在\n");
return;
}
//删除联系人
int i = 0;
for (i = ret; i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功\n");
}
void menu2()
{
printf("*******************************\n");
printf("***** 0.exit 1.name *****\n");
printf("***** 2.tele 3.addr *****\n");
printf("*******************************\n");
}
//修改联系人信息
void ReviseContact(Contact* pc)
{
assert(pc);
char name[NAME_MAX];
printf("请输入要修改联系人的名字:");
scanf("%s", name);
int ret = FindByName(pc, name);
if (ret == -1)
{
printf("该联系人信息不存在\n");
return;
}
int input;
do
{
menu2();
printf("请选择要修改的信息类型:");
scanf("%d", &input);
switch (input)
{
case QUIT:
printf("退出修改\n");
break;
case NAME:
printf("请输入名字:");
scanf("%s", pc->data[ret].name);
printf("修改成功\n");
break;
case TELE:
printf("请输入电话:");
scanf("%s", pc->data[ret].tele);
printf("修改成功\n");
break;
case ADDR:
printf("请输入地址:");
scanf("%s", pc->data[ret].addr);
printf("修改成功\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
}
//排序联系人信息
int compare(const void* a, const void* b)
{
return strcmp(((Peomessage*)a)->name, ((Peomessage*)b)->name);
}
void SortContact(Contact* pc)
{
assert(pc);
qsort(pc->data, pc->sz, sizeof(pc->data[0]), compare);
printf("排序成功\n");
}
//清空所有联系人
void EmptyContact(Contact* pc)
{
assert(pc);
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
printf("已清空通讯录\n");
}
//还原通讯录
void Destroy_Contact(Contact* pc)
{
assert(pc);
free(pc->data);
pc->data = NULL;
pc->sz = 0;
pc->Length = LENGTH;
}
//写文件
void WriteContact(Contact* pc)
{
assert(pc);
FILE* pf = fopen("Contact.txt", "wb");
if (pf == NULL)
{
perror("WriteContact->fopen");
return;
}
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->data + i, sizeof(Peomessage), 1, pf);
}
fclose(pf);
pf = NULL;
}
3. test.c
#include "Contact.h"
void menu1()//菜单
{
printf("*********************************\n");
printf("***** 1.add 2.del *****\n");
printf("***** 3.search 4.revise *****\n");
printf("***** 5.show 6.sort *****\n");
printf("***** 7.empty 0.exit *****\n");
printf("*********************************\n");
}
int main()
{
Contact con;
//初始化通讯录
InitContact(&con);
int input;
do {
menu1();
printf("请选择->:");
scanf("%d", &input);
switch (input)
{
case EXIT:
WriteContact(&con);
Destroy_Contact(&con);
printf("退出通讯录\n");
break;
case ADD:
AddContact(&con);
break;
case DEL:
DelContact(&con);
break;
case SEARCH:
SearchContact(&con);
break;
case REVISE:
ReviseContact(&con);
break;
case SHOW:
ShowContact(&con);
break;
case SORT:
SortContact(&con);
break;
case EMPTY:
EmptyContact(&con);
break;
default:
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
四.总结
每次写完一个大项目时,我都会为自己感到自豪,因为自己在不断地进步。
希望小伙伴们也能够不断地奋斗,争取早日拿到自己心意的offer!
最后还是不要忘记一键三连呀!
我们下期再见啦!!!