目录
3.1、初始化函数void initcontact(struct contact**p);
3.2、读取文件函数void Readcontact(struct contact**p);
3.3、扩容函数void check_addnum(struct contact*** p);
3.4、写入文件函数void savefile(struct contact* p);
一、改进方案
1、动态开辟内存
之前实现了一个简易版本的通讯录,当时存放联系人信息采用了一个结构体数组struct peoinf data[100],也就是说通讯录的容量为100人,超过一百人,信息就存不下。有人说那改成1000不就好了?这样做的缺点有两点。第一:麻烦,每次容量不够都要去更改;第二:浪费内存,假如我们要存放110个人的信息,用1000的数组太浪费内存了。动态开辟内存可以根据需要进行适当的扩容,不够了可以加。
2、文件存储信息
我们从键盘输入的信息都是存储到内存上,当程序结束,信息就被清除了。于是我们可以把内存上的信息写到文件中储存起来。
二、程序实现
1、头文件contact.h
柔性数组(Flexible Array)是引入的一个新特性(c99),它允许你在定义结构体时创建一个空数组,而这个数组的大小可以在程序运行的过程中根据你的需求进行更改特别注意的一点是:这个空数组必须声明为结构体的最后一个成员,并且还要求这样的结构体至少包含一个其他类型的成员
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define phone_num 11//电话号码11位
//#define max_peo_num 100
#define peo_name_num 10//能放下姓名的长度
//#define cap_num 2
enum star//增删查改
{
add=1,
del,
check,
change,
};
struct peoinf//联系人信息
{
char name[10];//姓名
char phone[phone_num+1];//电话号码
};
struct contact//通讯录
{
int size;//当前人数
int cap;//容量
struct peoinf data[];//联系人信息(柔性数组)
};
//声明
void menu();
void initcontact(struct contact**p);
void ADDinf(struct contact** p);
void DELinf(struct contact* p);
void Checkinf(struct contact* p);
void Changeinf(struct contact* p);
void print(struct contact* p);
void savefile(struct contact*p);
void Readcontact(struct contact** p);
void check_addnum(struct contact*** p);
2、测试文件test.c
要注意的是进行通讯录初始化 initcontact(&pcon);和“增”操作 ADDinf(&pcon);时应该传pcon的地址,因为之后要进行内存的调整(recalloc),重新分配内存大小,调整后的内存空间可能不是原来的地址,于是需要进行地址传递(&pcon),目的是要改变pcon的值(函数传址才能改变实参),使pcon指向调整后的内存空间。
#include"contact.h"
int main()
{
int input = 0;
struct contact *pcon=(struct contact *)malloc(sizeof(struct contact)+sizeof(struct peoinf));//开辟空间
menu();//菜单
initcontact(&pcon);//初始化通讯录
while (1)
{
printf("请选择菜单:");
scanf("%d", &input);//选择菜单
switch(input)
{
case add: ADDinf(&pcon);//增
break;
case del:DELinf(pcon);//删
break;
case check:Checkinf(pcon);//查
break;
case change:Changeinf(pcon);//改
break;
}
savefile(pcon);//保存文件
print(pcon);//打印整个通讯录
}
free(pcon);
pcon = NULL;
return 0;
}
3、功能的实现contact.c
主要介绍扩容操作和文件的读写操作,其他的函数跟之前的一样。
3.1初始化函数void initcontact(struct contact**p);
在进行初始化时,应该读取文件中现存的信息(可能是上次写入的信息)。
void initcontact(struct contact**p)//初始化通讯录
{
(*p)->size = 0;
memset((*p)->data,0,5*sizeof(struct peoinf));
(*p)->cap = 1;//初始容量为1
Readcontact(p);//读取文件中现存的信息到内存中
}
3.2 读取文件函数void Readcontact(struct contact**p);
以读的方式打开文件,用fscanf读取不同格式化的文本信息,每次读取信息,要检查是否要扩容,直至文件结束EOF。
fscanf():针对所有输入流(包含文件、标准输入流),进行格式化输入。
void Readcontact(struct contact**p)//读取文件中现存的信息到内存中
{
int i = 0;
FILE* pf = fopen("contact.txt","r");//以读的方式打开文件
if (pf == NULL)
{
perror(fopen);
return;
}
//读取文件中的信息,直至文件结束EOF
for (i = 0; (fscanf(pf, "%d %s %s", &((*p)->size), (*p)->data[i].name, (*p)->data[i].phone)) != EOF;i++)
{
check_addnum(&p);//检查是否要扩容
}
//关闭文件
fclose(pf);
pf = NULL;
}
3.3扩容函数void check_addnum(struct contact*** p);
这里用到了三级指针,对p进行解引用一次*p,找到pcon的地址,再解引用一次**p,就找到pcon。在进行recalloc之后,使pcon指向新开辟的内存空间(pcon=ptr)。
void check_addnum(struct contact*** p)//检查是否要扩容
{
if ((**p)->cap == (**p)->size)//人满了重新开辟空间
{
//重新开辟内存,每次增加一个
struct contact* ptr = realloc(**p, sizeof(struct contact) + ((**p)->cap+1) * sizeof(struct peoinf));
if (ptr != NULL)//开辟成功
{
**p = ptr;//pcon=ptr
(**p)->cap++;//更新一下容量
printf("增容成功\n");//提示方便看到扩容效果
}
}
}
3.4 写入文件函数void savefile(struct contact* p);
把信息从内存中写入文件,以文本的方式进行存储。
fprintf():针对所有输出流(包含文件和标准输出流),格式化输出。
void savefile(struct contact* p)//把信息从内存中写入文件
{
FILE* pf = fopen("contact.txt","w");//以写的方式打开文件
if (pf == NULL)//打开失败
{
perror(fopen);//打印错误信息
return;
}
for(int i=0;i<p->size;i++)//信息循环写入文件
fprintf(pf,"%d %s %s\n",i+1,p->data[i].name, p->data[i].phone);
//关闭文件
fclose(pf);
pf = NULL;
}
3.5 contact.c文件
#include"contact.h"
void check_addnum(struct contact*** p)//检查是否要扩容
{
if ((**p)->cap == (**p)->size)//人满了重新开辟空间
{
struct contact* ptr = realloc(**p, sizeof(struct contact) + ((**p)->cap+1) * sizeof(struct peoinf));//重新开辟内存,每次增加一个
if (ptr != NULL)//开辟成功
{
**p = ptr;//pcon=ptr
(**p)->cap++;//更新一下容量
printf("增容成功\n");//提示方便看到扩容效果
}
}
}
void menu()//菜单
{
printf("**********************************\n");
printf("********1.add 2.del********\n");
printf("********3.check 4.change*****\n");
printf("**********************************\n");
}
void initcontact(struct contact**p)//初始化通讯录
{
(*p)->size = 0;
memset((*p)->data,0,5*sizeof(struct peoinf));
(*p)->cap = 1;//初始容量为1
Readcontact(p);//读取文件中现存的信息到内存中
}
void ADDinf(struct contact** p)//增加联系人
{
check_addnum(&p);//检查是否要扩容
printf("请输入联系人姓名和电话:");
scanf("%s",&((*p)->data[(*p)->size].name));
scanf("%s",&((*p)->data[(*p)->size].phone));
(*p)->size++;//现有人数加1
}
void DELinf(struct contact* p)//删减联系人
{
printf("请输入被删联系人姓名:");
char del_name[peo_name_num] = {0};
scanf("%s",&del_name);
for (int i = 0; i < p->size; i++)
{
if (strcmp(del_name, p->data[i].name) == 0)//找到要删联系人
{
memset(&p->data[i], 0, sizeof(p->data[0]));
for (int j = i; j < p->size; j++)
p->data[j] = p->data[j + 1];
}
}
p->size--;
}
void Checkinf(struct contact* p)//查找联系人
{
char check_name[peo_name_num] = {0};
printf("请输入被查找人的姓名:");
scanf("%s",check_name);
for (int i=0; i < p->size; i++)
{
if (strcmp(check_name, p->data[i].name )== 0)//找到被找人的姓名
{
printf("%s %s\n", p->data[i].name, p->data[i].phone);
return 0;
}
}
printf("没有这个联系人");
}
void Changeinf(struct contact* p)//改联系人信息
{
char change_peo[peo_name_num] = { 0 };
printf("请输入被更改的对象:");
scanf("%s",&change_peo);
for (int i = 0; i < p->size; i++)
{
if (strcmp(change_peo, p->data[i].name) == 0)//找到被改联系人信息
{
printf("%s %s\n", p->data[i].name, p->data[i].phone);
memset(&p->data[i],0,sizeof(&p->data[0]));//删掉原有信息
printf("请重新输入:\n");
scanf("%s",&p->data[i].name);//填写更改后的信息
scanf("%s",&p->data[i].phone);
}
}
}
void print(struct contact* p)//打印通信录
{
printf("\n通讯录:\n");
for (int i = 0; i < p->size; i++)
{
printf("%d:", i + 1);
printf("%s ", p->data[i].name);
printf("%s\n", p->data[i].phone);
}
}
void savefile(struct contact* p)//把信息从内存中写入文件
{
FILE* pf = fopen("contact.txt","w");//以写的方式打开文件
if (pf == NULL)//打开失败
{
perror(fopen);//打印错误信息
return;
}
for(int i=0;i<p->size;i++)//信息循环写入文件
fprintf(pf,"%d %s %s\n",i+1,p->data[i].name, p->data[i].phone);
//关闭文件
fclose(pf);
pf = NULL;
}
void Readcontact(struct contact**p)//读取文件中现存的信息到内存中
{
int i = 0;
FILE* pf = fopen("contact.txt","r");//以读的方式打开文件
if (pf == NULL)
{
perror(fopen);
return;
}
//读取文件中的信息,直至文件结束EOF
for (i = 0; (fscanf(pf, "%d %s %s", &((*p)->size), (*p)->data[i].name, (*p)->data[i].phone)) != EOF;i++)
{
check_addnum(&p);//检查是否要扩容
}
//关闭文件
fclose(pf);
pf = NULL;
}
三、运行效果
一、初始化信息
首先,假设通讯录文件里已有三个人的信息。
二、读取通讯录里现存的信息
在进行增删查改操作之前,应该把之前存的信息读到内存中。为了能更清楚看到增容操作,把初始容量设置为1,且每次扩容操作时,扩容1位。
三、添加联系人信息并写入文件中
这里演示增加联系人的效果。在打印通讯录的同时,也把通讯录的信息重新写到文件里。
四、总结
1.函数传址才能改变实参。指针也是变量,传指针变量过去,对形参变量的改变,不会影响实参。形参是实参的临时拷贝。
2.realloc()返回的可能不是原来的地址,应该使pcon指向调整后的空间。
3.文件在读写完成后应该关闭文件。