前言
上篇文章C语言实现通讯录(初级版)中我们写了一个C语言的通讯录,但此通讯录存在较大缺陷
缺陷1.该通讯录大小是固定的,存在资源浪费或不够用的情况
缺陷2.通讯录的储存是临时的,一旦程序退出已储存的数据就会被系统释放,下次进入时数据就会丢失
一、缺陷1的改进
动态内存函数的介绍
在上篇文章中,我们#define MAX 100 用来定义通讯录的最大容量,存在资源浪费或不够用的情况
所以我们思考是否可以写一个动态的版本,我们一开始只给定三个大小的容量,当每次不够的时候就加两个
我们知道当我们在写一个.c文件时,系统会将数据存储在栈区,在程序结束时候会自动释放空间
届时我们需要自己申请空间,自己操作文件决定何时释放空间,我们可以使用动态内存函数来达实现
malloc calloc realloc都是动态内存函数 free函数可以将动态内存回收
这里我们简单介绍下这三个动态内存函数,下面会用到
1.malloc和free
C语言提供了一个动态内存开辟的函数:
void* malloc (size_t size);
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针
如果开辟成功,则返回一个指向开辟好空间的指针
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器
C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:
void free (void* ptr);
free函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的
如果参数 ptr 是NULL指针,则函数什么事都不做
所以动态内存函数和free内存释放函数是一起使用的,我们申请空间的同时也要考虑何时释放空间
我们给个使用样例
#include<stdio.h>
int main()
{
int* p = NULL;
//申请空间
p = (int*)malloc(sizeof(int) * 10);
if (p != NULL)
{
//这里写申请空间后想要做的事
}
free(p);//释放空间
p = NULL;//将p指针置为空(切记不能忘)
return 0;
}
2.calloc
语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。原型如下:
void* calloc (size_t num, size_t size);
函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0
3.realloc
realloc函数的出现让动态内存管理更加灵活
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时
候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整
函数原型如下:
void* realloc (void* ptr, size_t size);
ptr 是要调整的内存地址,size 调整之后新大小
返回值为调整之后的内存起始位置
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
情况1
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化
情况2
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用,这样函数返回的是一个新的内存地址
由于上述的两种情况,realloc函数的使用就要注意一些
改进通讯录
认识到动态内存函数后,我们就可以改进通讯录了,由上文我们存储通讯录可以自己实现申请空间
所以我们需要修改一下结构体 contact
//原结构体
//typedef struct contact
//{
// int sz;
// perinform inform[MAX];
//}contact;
//改进后
typedef struct contact
{
int sz;//记录当前存储了多少人
int max_sz;//记录通讯录最大容量
perinform* inform;//perinform类型的指针inform,用来接收动态内存函数开辟的空间
}contact;
由于申请空间方式改变,我们对通讯录的初始化初始化函数和联系人的增加函数都需要进行修改
初始化函数
//改进前
//void initialization(contact* pc)
//{
// pc->sz = 0;
// memset(pc->inform, 0, sizeof(pc->inform));
//}
//改进后
#define STAR_SIZE 3
#define ADD_SIZE 2
//这里两个宏定义应该放在头文件中,放在这里为了方便观察
void initialization(contact* pc)
{
pc->sz = 0;
pc->max_sz = STAR_SIZE;
pc->inform = malloc(sizeof(perinform) * STAR_SIZE);
if (pc->inform != NULL)
{
printf("初始化成功!\n");
}
else
printf("初始化失败!%s\n", strerror(errno));
}
增加函数
这里值得注意的一点是,我们的计划是先给三个人的储存空间,以后每次不够时就增加两个空间
我们在使用Add函数时需要判断内存空间是否满了,满了我们就需要增加空间
所以我们还需要写一个检查空间大小的函数
//void Add(contact* pc)
//{
// printf("请输入姓名>:");
// scanf("%s", pc->inform[pc->sz].name);
// printf("请输入年龄>:");
// scanf("%d", &(pc->inform[pc->sz].age));
// printf("请输入性别>:");
// scanf("%s", pc->inform[pc->sz].sex);
// printf("请输入电话号码>:");
// scanf("%s", pc->inform[pc->sz].telephone);
// printf("请输入住址>:");
// scanf("%s", pc->inform[pc->sz].address);
//
// pc->sz++;
//
// printf("添加成功\n");
//}
int check_size(contact* pc)
{
if (pc->sz == pc->max_sz)
{
perinform* ret = realloc(pc->inform, sizeof(perinform) * (pc->sz + ADD_SIZE));//扩容
if (ret == NULL)
{
printf("添加储存空间失败!%s\n", strerror(errno));
return 0;
}
else
{
pc->inform = ret;
printf("添加储存空间成功!\n");
pc->max_sz += 2;
return 1;
}
}
}
void Add(contact* pc)
{
int ret = check_size(pc);//进行判断
if (ret == 0)
{
printf("添加失败!\n");
return;
}
printf("请输入姓名>:");
scanf("%s", pc->inform[pc->sz].name);
printf("请输入年龄>:");
scanf("%d", &(pc->inform[pc->sz].age));
printf("请输入性别>:");
scanf("%s", pc->inform[pc->sz].sex);
printf("请输入电话号码>:");
scanf("%s", pc->inform[pc->sz].telephone);
printf("请输入住址>:");
scanf("%s", pc->inform[pc->sz].address);
pc->sz++;
printf("添加成功\n");
}
由于申请了在堆上申请了空间,我们需要在退出程序之前将堆上的空间释放
void Destorycontact(contact* pc)
{
free(pc->inform);
pc->inform = NULL;
pc->sz = 0;
pc->max_sz = 0;
printf("释放内存成功!\n");
}
到此通讯录的动态存储就完成了
二、缺陷2的改进
当通讯录运行起来的时候,可以给通讯录中增加、删除数据,此时数据是存放在内存中,当程序退出的时候,通讯录中的数据自然就不存在了,等下次运行通讯录程序的时候,数据又得重新录入,如果使用这样的通讯录就很难受。我们在想既然是通讯录就应该把信息记录下来,只有我们自己选择删除数据的时候,数据才不复存在
这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式
使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化
注:此部分需要文件操作相关的知识,相对复杂,会专门写一篇博客来介绍 这里只写大致思路和代码 会的大佬可以忽略
我们进行改进主要是通过相关操作,将退出通讯录之前保存我们存放在内存中的数据到磁盘中,而每次再进入通讯录时读取我们之前存储的数据
保存数据
void Savecontact(contact* pc)
{
FILE* pf = fopen("contact.dat", "wb");
if (pf == NULL)
{
perror("Savecontact::fopen");
return;
}
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->inform + i, sizeof(perinform), 1, pf);
}
fclose(pf);
pf = NULL;
printf("保存成功!\n");
}
加载数据
void Loadcontact(contact* pc)
{
FILE* pf = fopen("contact.dat", "rb");
if (pf == NULL)
{
perror("Loadcontact::fopen");
return;
}
perinform tmp = { 0 };
while (fread(&tmp, sizeof(perinform), 1, pf))
{
check_size(pc);
pc->inform[pc->sz] = tmp;
pc->sz++;
}
fclose(pf);
pf = NULL;
printf("加载通讯录成功!\n");
}
三、完整代码
text.c
#include "contact.h"
void menu()
{
printf("***************************\n");
printf("***** 1.add 2.del ***\n");
printf("***** 3.search 4.modify ***\n");
printf("***** 5.show 6.empty ***\n");
printf("***** 7.sort 0.exit ***\n");
printf("***************************\n");
}
enum op
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SHOW,
EMPTY,
SORT
};
int main()
{
int n = 0;
contact con;
initialization(&con);
Loadcontact(&con);
do
{
menu();
printf("请选择>:");
scanf("%d", &n);
switch (n)
{
case(ADD):
Add(&con);
break;
case(DEL):
Del(&con);
break;
case(SEARCH):
Search(&con);
break;
case(MODIFY):
Modify(&con);
break;
case(SHOW):
Show(&con);
break;
case(EMPTY):
initialization(&con);
printf("清空完成!\n");
break;
case(SORT):
Sort(&con);
break;
case(EXIT):
Savecontact(&con);
Destorycontact(&con);
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (n);
return 0;
}
contact.h
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define SIZE_NAME 20
#define SIZE_SEX 10
#define SIZE_TELEPHONE 20
#define SIZE_ADDRESS 30
#define MAX 100
#define STAR_SIZE 3
#define ADD_SIZE 2
typedef struct perinform
{
char name[SIZE_NAME];
int age;
char sex[SIZE_SEX];
char telephone[SIZE_TELEPHONE];
char address[SIZE_ADDRESS];
}perinform;
//typedef struct contact
//{
// int sz;
// perinform inform[MAX];
//}contact;
typedef struct contact
{
int sz;
int max_sz;
perinform* inform;
}contact;
void initialization(contact* pc);
void Add(contact* pc);
void Show(contact* pc);
void Del(contact* pc);
void Search(contact* pc);
void Modify(contact* pc);
void Sort(contact* pc);
void Savecontact(contact* pc);
void Destorycontact(contact* pc);
void Loadcontact(contact* pc);
contact.c
#include "contact.h"
int check_size(contact* pc);
//void initialization(contact* pc)
//{
// pc->sz = 0;
// memset(pc->inform, 0, sizeof(pc->inform));
//}
void initialization(contact* pc)
{
pc->sz = 0;
pc->max_sz = STAR_SIZE;
pc->inform = malloc(sizeof(perinform) * STAR_SIZE);
if (pc->inform != NULL)
{
printf("初始化成功!\n");
}
else
printf("初始化失败!%s\n", strerror(errno));
}
void Loadcontact(contact* pc)
{
FILE* pf = fopen("contact.dat", "rb");
if (pf == NULL)
{
perror("Loadcontact::fopen");
return;
}
perinform tmp = { 0 };
while (fread(&tmp, sizeof(perinform), 1, pf))
{
check_size(pc);
pc->inform[pc->sz] = tmp;
pc->sz++;
}
fclose(pf);
pf = NULL;
printf("加载通讯录成功!\n");
}
//void Add(contact* pc)
//{
// printf("请输入姓名>:");
// scanf("%s", pc->inform[pc->sz].name);
// printf("请输入年龄>:");
// scanf("%d", &(pc->inform[pc->sz].age));
// printf("请输入性别>:");
// scanf("%s", pc->inform[pc->sz].sex);
// printf("请输入电话号码>:");
// scanf("%s", pc->inform[pc->sz].telephone);
// printf("请输入住址>:");
// scanf("%s", pc->inform[pc->sz].address);
//
// pc->sz++;
//
// printf("添加成功\n");
//}
int check_size(contact* pc)
{
if (pc->sz == pc->max_sz)
{
perinform* ret = realloc(pc->inform, sizeof(perinform) * (pc->sz + ADD_SIZE));
if (ret == NULL)
{
printf("添加储存空间失败!%s\n", strerror(errno));
return 0;
}
else
{
pc->inform = ret;
printf("添加储存空间成功!\n");
pc->max_sz += 2;
return 1;
}
}
}
void Add(contact* pc)
{
int ret = check_size(pc);
if (ret == 0)
{
printf("添加失败!\n");
return;
}
printf("请输入姓名>:");
scanf("%s", pc->inform[pc->sz].name);
printf("请输入年龄>:");
scanf("%d", &(pc->inform[pc->sz].age));
printf("请输入性别>:");
scanf("%s", pc->inform[pc->sz].sex);
printf("请输入电话号码>:");
scanf("%s", pc->inform[pc->sz].telephone);
printf("请输入住址>:");
scanf("%s", pc->inform[pc->sz].address);
pc->sz++;
printf("添加成功\n");
}
void Show(contact* pc)
{
printf("%-10s %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
for (int i = 0; i < pc->sz; i++)
{
printf("%-10s %-5d %-5s %-12s %-30s\n", pc->inform[i].name, pc->inform[i].age, pc->inform[i].sex, pc->inform[i].telephone, pc->inform[i].address);
}
}
static int Find_name(char* arr, contact* pc)
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(arr, pc->inform[i]->name) == 0)
{
return i;
}
}
return -1;
}
void Del(contact* pc)
{
char arr[SIZE_NAME];
printf("请输入删除人姓名>;");
scanf("%s", arr);
int ret = Find_name(arr,pc);
if (ret == -1)
{
printf("未找到该联系人\n");
}
else
{
for (int i = ret; i < pc->sz - 1; i++)
{
pc->inform[i] = pc->inform[i + 1];
}
pc->sz--;
printf("删除成功!\n");
}
}
void Search(contact* pc)
{
char arr[SIZE_NAME];
printf("请输入查找人姓名>;");
scanf("%s", arr);
int ret = Find_name(arr, pc);
if (ret == -1)
{
printf("未找到该联系人\n");
}
else
{
printf("%-10s %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-10s %-5d %-5s %-12s %-30s\n", pc->inform[ret].name, pc->inform[ret].age, pc->inform[ret].sex, pc->inform[ret].telephone, pc->inform[ret].address);
}
}
void Modify(contact* pc)
{
char arr[SIZE_NAME];
printf("请输入查找人姓名>;");
scanf("%s", arr);
int ret = Find_name(arr, pc);
if (ret == -1)
{
printf("未找到该联系人\n");
}
else
{
printf("请输入姓名>:");
scanf("%s", pc->inform[ret].name);
printf("请输入年龄>:");
scanf("%d", &(pc->inform[ret].age));
printf("请输入性别>:");
scanf("%s", pc->inform[ret].sex);
printf("请输入电话号码>:");
scanf("%s", pc->inform[ret].telephone);
printf("请输入住址>:");
scanf("%s", pc->inform[ret].address);
printf("修改成功\n");
}
}
static int cmp_name(const void* p1, const void* p2)
{
return strcmp(((perinform*)p1)->name, ((perinform*)p2)->name);
}
void Sort(contact* pc)
{
qsort(pc->inform, pc->sz, sizeof(perinform), cmp_name);
printf("排序成功!\n");
}
void Destorycontact(contact* pc)
{
free(pc->inform);
pc->inform = NULL;
pc->sz = 0;
pc->max_sz = 0;
printf("释放内存成功!\n");
}
void Savecontact(contact* pc)
{
FILE* pf = fopen("contact.dat", "wb");
if (pf == NULL)
{
perror("Savecontact::fopen");
return;
}
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->inform + i, sizeof(perinform), 1, pf);
}
fclose(pf);
pf = NULL;
printf("保存成功!\n");
}