C语言通讯录大作业,支持永久数据储存,无内存浪费。
相关部分介绍
本工程用到库文件stdio.h 、stdlib.h 、string.h 、error.h
对于内存的管理使用了动态内存分配,每存入一个人的信息,就分配相应大小的空间。
相对于其他工程关闭程序后就会丢失信息,本工程支持文件操作,关闭系统时会将录入的信息写入一个contact.txt文件中,下次打开系统时又会录入信息。
代码详解
contact.h
项目头文件,声明了两个结构体
struct contact
{
char name[8]; //姓名
char sex[8]; //性别
int age; //年龄
char ID[12]; //电话
};
struct NumofCon
{
int Num; //作用是表示现在有几个人的信息被存到通讯录中,方便开辟内存
struct contact p[];//柔性数组,可以使用动态内存开辟空间
};
contact.c
这个源文件是代码的主要部分,相关功能的函数全都存储在这里
第一个函数:通讯录初始化函数
struct NumofCon* Contact_Init(void)
{
struct NumofCon* ps = (struct NumofCon*) malloc(4+32*1);
if(ps == NULL)
{
printf("%s\n",strerror(errno));
}
ps->Num = 0;
return ps;
}
该函数返回一个struct NumofCon型的指针,对应第二个声明的结构体第一行就是这个代码最重要的一部分,开辟一个4+321字节大小的内存,并把他的指针传给ps,如果开辟失败就返回空指针,printf就会打印失败原因。注意,strerror(errno);函数使用需要应用errno.h头文件。
开辟字节的大小要取决于struct NumofCon结构体的大小。
将ps指向的结构体里的Num成员赋值成0,因为还没有信息被储存。
第二个函数:向通讯录中增加信息
struct NumofCon* contact_add(struct NumofCon* ps)
{
ps->Num++;
struct NumofCon* ps2 = (struct NumofCon*) realloc(ps,4+32*(ps->Num));
if(ps2 == NULL)
{
printf("%s\n",strerror(errno));
}
int i = ps2->Num-1;
printf("请输入姓名:");
scanf("%s",ps2->p[i].name);
printf("请输入姓别:");
scanf("%s",ps2->p[i].sex);
printf("请输入年龄:");
scanf("%d",&(ps2->p[i].age));
printf("请输入电话:");
scanf("%s",ps2->p[i].ID);
ps = ps2;
ps2 = NULL;
return ps;
}
Num加一,告诉系统有新人要存入。Num的值取决于通讯录内有多少人。
使用realloc函数调整开辟内存的大小,并把新的地址传给ps2,用if语句判断是否开辟成功。
定义i作为柔性数组的下标,下标的值为Num-1。
接下来就是信息录入过程,使用了结构体嵌套调用,没有什么可讲的。
我重点要说一下最后几行,为什么要多定义一个ps2来储存地址呢?这和realloc函数的特性有关。realloc函数是调整动态内存空间大小的函数,他的参数有两个,一个是你要修改的地址,二是要修改的大小。假如要增加十个字节,如果ps指向的空间后面有足够大小的空间,那新修改的地址就不会改变,但是如果后面空间不够增加10个字节,系统就会在其他位置另起炉灶,并把原先的内容拷贝到新的地址里,并且释放原来的空间。当内存调整失败,返回空指针,直接用ps接受,那我们就没法找回原先空间的地址,程序崩溃。所以ps2是一个保险选项,防止由于内存开辟失败导致原有空间位置丢失。当函数执行到最后,ps2的使命结束,在置为空指针,防止越界访问。
第三个函数,查询,第四个函数,打印所有成员
void contact_find(struct NumofCon* ps)
{
int i=0;
char name[8];
printf("请输入要寻找的人物姓名:");
scanf("%s",name);
while(strcmp(name,ps->p[i].name))
{
i++;
}
printf("姓名:%s\n",ps->p[i].name);
printf("姓别:%s\n",ps->p[i].sex);
printf("年龄:%d\n",ps->p[i].age);
printf("电话:%s\n",ps->p[i].ID);
}
void contact_Printall(struct NumofCon* ps)
{
for(int i = 0;i<ps->Num; i++)
{
printf("Num:%d\n",i+1);
printf("姓名:%s\n",ps->p[i].name);
printf("姓别:%s\n",ps->p[i].sex);
printf("年龄:%d\n",ps->p[i].age);
printf("电话:%s\n",ps->p[i].ID);
}
}
没有什么好说的,只有查询函数用了一个库函数strcmp,声明在string.h中
第五个函数,删除
struct NumofCon* contact_delete(struct NumofCon* ps)
{
int i=0;
char name[8];
printf("请输入要删除的人物姓名:");
scanf("%s",name);
while(strcmp(name,ps->p[i].name))
{
i++;
if(i>ps->Num-1)
{
printf("没有此人信息\n");
return ps;
}
}
for(;i < ps->Num; i++)
{
strcpy(ps->p[i].name, ps->p[i+1].name);
strcpy(ps->p[i].sex, ps->p[i+1].sex);
ps->p[i].age = ps->p[i+1].age;
strcpy(ps->p[i].ID, ps->p[i+1].ID);
}
ps->Num = ps->Num - 1;
struct NumofCon* ps2 = (struct NumofCon*) realloc(ps,4+32*(ps->Num));
if(ps2 == NULL)
{
printf("%s\n",strerror(errno));
}
ps = ps2;
ps2 = NULL;
printf("%s的信息已经移除\n",name);
return ps;
}
这个函数的重点是删除一个人后要把ps->Num的值减一,并且调整内存大小,后面成员的位置依次向前36个字节
到这里通讯录的主要功能已经完成,现在要做数据的永久储存,也就是把信息导入文件中,防止关闭程序后信息丢失。
保存数据到contact.txt文件中
void contact_serve(struct NumofCon* ps)
{
FILE* pf = fopen( "contact.txt", "w");
int i = 0;
if(pf == NULL)
{
printf("%s\n",strerror(errno));
}
while(i < ps->Num)
{
fprintf(pf, "%d ",i+1);
fprintf(pf, "%s ",ps->p[i].name);
fprintf(pf, "%s ",ps->p[i].sex);
fprintf(pf, "%d ",ps->p[i].age);
fprintf(pf, "%s\n",ps->p[i].ID);
i++;
}
fclose(pf);
}
注意,要在代码当前路径建立一个文件contact.txt,否则程序报错。
读取文件信息
struct NumofCon* Contact_read(struct NumofCon* ps)
{
struct contact file;
int Num = 1;
int check = Num-1;
FILE* pf = fopen("contact.txt", "r");
if(pf == NULL)
{
printf("%s\n",strerror(errno));
}
char ch = fgetc(pf);
if(ch == EOF)
Num = 0;
rewind(pf);
while(Num)
{
fscanf(pf, "%d", &Num);
if(check == Num)
break;
check = Num;
fscanf(pf, "%s", file.name);
fscanf(pf, "%s", file.sex);
fscanf(pf, "%d", &(file.age));
fscanf(pf, "%s", file.ID);
ps = (struct NumofCon*) realloc(ps,4+32*Num);
ps->Num = Num;
strcpy(ps->p[Num-1].name, file.name);
strcpy(ps->p[Num-1].sex, file.sex);
ps->p[Num-1].age = file.age;
strcpy(ps->p[Num-1].ID, file.ID);
}
fclose(pf);
return ps;
}
上面两个代码涉及到文件操作系统,较为复杂,篇幅原因,我会新写一篇文章来介绍。
main.c
将上面代码都放在头文件声明后就可以写主函数了,该系统为模块化,主函数相对简单
#include <stdio.h>
#include "contact.h"
int main()
{
int i;
struct NumofCon* Con = Contact_Init();
Con = Contact_read(Con);
do{
system("pause");
system("cls");
printf("------------CONTACT-------------\n");
printf("-----1.添加---------2.查询------\n");
printf("-----3.显示---------4.删除------\n");
printf("------------0.退出--------------\n");
printf("--------------------------------\n");
printf("输入指令");
scanf("%d",&i);
if(i == 1)
Con = contact_add(Con);
if(i == 2)
contact_find(Con);
if(i == 3)
contact_Printall(Con);
if(i == 4)
Con = contact_delete(Con);
}while(i);
contact_serve(Con);
return 0;
}
功能演示
一定要通过指令0退出,不然无法保存信息
现在我们打开文件看看
再次打开程序,直接使用3命令打印
我们没有录入信息,可是系统直接读取了信息,是因为系统将文件里的信息读取了。
也可以直接在contact文件中添加人物,但要遵守格式。
这就是相关代码的讲解,我会把工程上传,需要自取。
https://download.csdn.net/download/Mr_Hanc_Tiskor/15533285
有关文件的那两个函数和柔性数组我也会在下一篇文章讲解。
如果本文对你有帮助,别忘了点赞哦。欢迎大家留言讨论与大佬们的建议。