C语言实现通讯录-动态版本与文件版本
1.前言
在先前的探索中,我构建了一个C语言实现简单的通讯录,它能够存储一定数量的联系人信息。然而,这个版本存在局限性——不仅联系人数目被固定,而且一旦程序关闭,所有的联系人信息都会丢失。
为了克服这些局限性,我决定开发一个更加灵活且持久的通讯录系统。在这篇文章中,我将介绍如何利用C语言中的动态内存分配函数(如realloc
)来创建一个能够适应任意数量联系人的通讯录,即动态版本。
并且还将展示如何使用文件操作来确保联系人信息即使在程序关闭后也能得以保存,即文件版本。
2.动态版本
2.1联系人信息
之前的:
#define MAX 100
#define NAME_MAX 20
#define TELE_MAX 12
struct Peo
{
char name[NAME_MAX];
int age;
char tele[TELE_MAX];
};
struct Contact
{
struct Peo peo[MAX];
int num;
};
在原始版本中,我使用了一个固定大小的结构体数组来存储联系人信息。这种设计虽然简单,但在实际应用中存在明显的不足之处,尤其是当需要存储的联系人数量不确定时。
为此,我转向使用动态内存分配技术来解决这一问题,因此,不能再用结构体数组,而改为结构体指针。
且之前只准备了num
来存储当前数量,在动态版本,还需一个变量存储当前最大数量,以免溢出。
改版:
struct Contact
{
struct Peo* peo;
int num;
int now_max;
};
我引入了一个指向Peo结构体
的指针peo
,以及一个新的成员now_max
来记录当前分配的最大联系人数量。
这样的改动使得我们的通讯录能够动态地适应不断增加的联系人数量。
2.2初始化
之前的:
void init(struct contact* p)
{
assert(p);
p->num = 0;
memset(p->peo, 0, sizeof(p->peo));
}
在静态版本中,我使用memset
函数来初始化结构体数组中的每个元素。
然而,在动态版本中,我们需要采用不同的方法来进行初始化。
为此,我使用calloc
函数来分配内存,并将所有分配的内存初始化为零。
改版:
void Init(struct Contact* p)
{
assert(p);
p->num = 0;
struct Peo*tmp = (struct Peo*)calloc(INIT_NUM, sizeof(struct Peo));
if (!tmp)
{
perror("Init:calloc");
return;
}
p->peo = tmp;
p->now_max = INIT_NUM;
}
这里,我使用calloc
函数来分配足够的内存空间,并将now_max
初始化为INIT_NUM
,即初始最大联系人数量。
这样的改动使得我们的通讯录在初始化时就能正确地分配所需的内存,并将所有成员初始化为零。
其中,INIT_NUM
在头文件定义:
#define INIT_NUM 3
perror
perror
可打印错误信息,其头文件是<stdio.h>
"Init:calloc"
可改为任意字符串,我这样写的目的是在错误信息打印后,知道是Init
函数中的calloc
步骤出了问题
可简单验证:#define INIT_NUM 10000000000000000
运行结果:
2.3自动扩容
在动态版本中,我们面临的一个重要挑战是如何有效地管理内存,尤其是在联系人数量增加时。
为此,我实现了一个自动扩容机制,当现有的内存空间不足以容纳新增加的联系人时,该机制会自动扩大内存容量。
即,联系人当前数量num
与当前最大数量now_max
相等时,自动扩容:
void AddPeo(struct Contact* p)
{
if (p->num == p->now_max)
{
struct Peo* tmp = (struct Peo*)realloc(p->peo, (p->now_max + ADD_NUM) * sizeof(struct Peo));
if (!tmp)
{
perror("AddPeo:realloc");
return;
}
p->peo = tmp;
p->now_max += 2;
printf("扩容成功\n");
}
}
在这里,我使用realloc
函数来重新分配更大的内存空间,并更新now_max
以反映新的最大联系人数量。
通过这种方式,可以确保通讯录始终有足够的空间来存储新的联系人信息,而无需手动干预。
其中,ADD_NUM
为扩充的联系人数量,在头文件定义:
#define ADD_NUM 2
printf("扩容成功\n");
可便于验证当前程序的正确性:
注:自动扩容函数只需放入添加联系人函数,因为只有后者会增加联系人数量。
动态版本到此为止。
3.文件版本
3.1自动保存
除了动态管理内存之外,另一个重要的功能是确保联系人信息能够被持久化保存。为此,我实现了文件保存功能,能够在程序结束前自动将所有联系人信息保存到文件中:
case 0:
Ctrl_S(&con);//文件保存函数
printf("exit\n");
break;
使用Ctrl_S命名只为直观,而非我不懂’保存’的英文。
函数实现:
void Ctrl_S(struct Contact* p)
{
assert(p);
FILE* fp = fopen("contact.txt", "wb");
if (!fp)
{
perror("Ctrl_S:fopen");
return;
}
for (int i = 0; i < p->num; i++)
{
fwrite(p->peo + i, sizeof(struct Peo), 1, fp);
}
fclose(fp);
fp = NULL;
printf("自动保存成功\n");
}
通过使用fwrite
函数,我将每个联系人的信息写入到名为contact.txt
的文件中。
这样,即使在程序关闭后,所有的联系人信息也能被安全地保存下来,供下次使用。
注:fwrite函数原型:
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
效果:
需注意,由于是以二进制形式存储入文件,因此,以文本文档形式打开时会出现意义不明的乱码。
3.2打开时加载信息
函数实现:
void Load(struct Contact* p)
{
assert(p);
FILE* fp = fopen("contact.txt", "rb");
if (!fp)
{
perror("Load:fopen");
return;
}
struct Peo tmp = { 0 };
int i = 0;
while (fread(&tmp, sizeof(struct Peo), 1, fp))
{
AddPeo(p);
p->peo[i] = tmp;
p->num++;
i++;
}
fclose(fp);
fp = NULL;
printf("信息加载成功\n");
}
这里创建了一个临时的结构体,一次读一个联系人的信息,便于接收文件中的信息,并判断文件是否还有信息,如果没有,结束循环。
每次循环开始,需检查是否应该扩容。
将此函数置于初始化函数Init()
的最后,即可运行程序。
注:fread函数原型:
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
效果:
退出时:
打开时:
希望这篇博客能够帮助那些正在学习C语言或对内存管理和文件操作感兴趣的朋友!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!