问天下谁与争锋,唯我傲视苍穹
此句赠与在看文章的你
该通讯录使用的语言是C语言,涉及的知识有动态开辟内存,和文件内存管理。
动态开辟内存是用来不断给通讯录增加容量的
文件管理是用来将通讯录的信息存储到文件里。
我会先从简单的写起,然后再渐渐完善。
第一个版本:静态通讯录(容量大小固定也不能将通讯录信息存储到文件里)
第二个版本:动态通讯录(容量大小可以随人员增多而增大,但还没有存储文件的能力)
第三个版本:相对完善的通讯录(动态的,可以文件存储)
三个版本基本框架一样只是为了提高功能而增加或修改了部分代码,所以第一个版本还是有必要仔细学习的。
还有这里将各个函数分装,将函数的声明放在头文件里面,函数的定义放在contact.c文件,函数的使用
放在test.c文件,将一些宏定义和结构体变量定义都发在头文件里面,当需要使用时,引用头文件#include "contact.h"即可。
通讯录的实现
- 问天下谁与争锋,唯我傲视苍穹
- 版本一:静态通讯录
- 第一步:声明相关变量
- 第二步:构思一个框架
- 第三步:初始化结构体
- 第四步:完善各分装函数
- 初始化:
- 1.增加联系人:
- 2.删减联系人:
- 3.查询联系人:
- 4.修改联系人信息:
- 5.显示联系人信息:
- 6:排序-- 目前还没有写……
- 版本二:动态通讯录
- 第一步:对相关变量进行修改
- 第二步:对相关函数进行修改
- 1.修改初始化函数:
- 2.修改添加函数
- 3.释放空间
- 版本三:相对完善通讯录
- 第一步:掌握文件操作基本知识
- 第二步:保存通讯录数据到文件里
- 第三步:将文件的信息加载到通讯录里
版本一:静态通讯录
第一步:声明相关变量
定义一个结构体,用来表示人,里面存放着人的各种信息,比如姓名,年龄,性别,地址,电话等等信息。(用typedef 简化struct PoPinfo类型为PoPinfo)
typedef struct PoPinfo
{
char name[NAME_MAX];
int age;
char sex[SEX_MAX];
char addr[ADD_MAX];
char tele[TELE_MAX];
}PoPinfo;
再定义一个结构体,用来表示通讯录,里面存放着人的信息,还有通讯录里面的人数。
(用typedef将struct Contact类型简化为Contact类型)
typedef struct Contact
{
PoPinfo data[MAX];//通讯录里人员的信息
int sz;//当前已经放的信息的个数。
}Contact;
定义通讯录里面人员的信息容量比如姓名这个数组最多能容纳20个那用宏定义
#define NAME_MAX 20类似性别,地址,电话,通讯录人员个数等等。
#define MAX 100
#define NAME_MAX 20
#define SEX_MAX 5
#define ADD_MAX 30
#define TELE_MAX 12
#define FIFIST 3
#define IND 2
第二步:构思一个框架
思考一下,你这个通讯录应该有什么功能呢
1.添加联系人的信息
2.删减联系人的信息
3.查询联系人的信息
4.修改联系人的信息
5.显示联系人的信息
6.排序联系人
……等等,有什么想法可以自己加入。
我们按照这个想法来构建,先用一个do while()内置一个菜单,按照选项来选择,所以还需要一个switch语句
void mneun()
{
printf("**************************\n");
printf("****1.add 2.del ****\n");
printf("****3.search 4.modify ****\n");
printf("****5.show 6.sort ****\n");
printf("****0exit ****\n");
printf("**************************\n");
}
int main()
{
Contact con;//创建一个结构体变量con;
int input=0;
do
{
mneun();//菜单--来选择
printf("请输入要选择的:\n");
scanf("%d", &input);
switch (input)
{
case 1: AddContact(&con);//添加联系人信息
break;//将结构体的地址传过去
case 2:DelContact(&con);//删减联系人信息
break;
case 3:SearchContact(&con);//查询联系人的信息
break;
case 4:ModiContact(&con); //修改联系人信息
break;
case 5:DisplayContact(&con);//显示联系人的信息
break;
case 6://排序联系人
break;
case 0: printf("退出通讯录\n");
break;
default:break;
}
} while (input);
return 0;
}
第三步:初始化结构体
在未传参之前要将结构体初始化
所以在创建结构体变量con后,就对它进行初始化
这里再分装成一个函数InitContact(&con);
int main()
{
Contact con;//创建一个结构体变量con;
//首先进行初始化这个结构体变量
InitContact(&con);
int input=0;
第四步:完善各分装函数
Contact.h头文件里面进行函数的声明
//函数声明
void InitContact(Contact* pc);//初始化通讯录
void AddContact(Contact* pc);//添加联系人信息
void DelContact(Contact* pc);//删减联系人信息
void SearchContact(Contact* pc);//查询联系人信息
void DisplayContact(Contact* pc);//显示联系人信息
void ModiContact(Contact* pc);//修改联系人信息
2.函数的定义(函数功能的实现)
初始化:
//初始化结构体
//将结构体的地址传过去,用指针来接收(合理吧)
void InitContact(Contact* pc)
{
assert(pc);//首先断言确保pc不为NULL
pc->sz = 0;//初始化为0
memset(pc->data, 0, sizeof(pc->data));//用memset函数将data数据初始化为0.
}
1.增加联系人:
//添加ADD函数
void AddContact(Contact* pc)
{
assert(pc);//首先断言确保pc不为指针
if (pc->sz == MAX)//每次都要判断一下是否通讯录人员已经满了无法添加了
{
printf("通讯录已满,无法添加\n");
return;
}
//增加一个人的信息
printf("请输入姓名:\n");
scanf("%s", pc->data[pc->sz].name);
printf("请输入年龄:\n");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入性别:\n");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入地址:\n");
scanf("%s", pc->data[pc->sz].addr);
printf("请输入电话:\n");
scanf("%s", pc->data[pc->sz].tele);
pc->sz++;//增加一个人的信息,人数就加1
}
2.删减联系人:
//查人名字函数
int FindByName(Contact* pc, char name[])
{
int i;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;
}
}
return -1;
}
//删减联系人信息
void DelContact(Contact* pc)//
{
assert(pc);//断言判断
char name[NAME_MAX] = { 0 };
if (pc->sz == 0)//也要判断一下通讯录人员是否为0,为0 删个毛线呀
{
printf("通讯录为空,无法删除\n");
return;
}
//要删除指定的人;
//就先找到指定的人
printf("请输入要删除的联系人名字:\n");
scanf("%s", name);
int ret=FindByName(pc, name);//再分装一个查人姓名函数,所以将要删除的名字和pc传过去
if (ret == -1)
{
printf("删除的人不存在\n");
return;
}
else
{
int i;
for (i = ret; i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1];//将后面的信息往前覆盖,那这个信息就没有了,相当于被删掉了
}
pc->sz--;//删掉一个人数要减1
printf("删除成功\n");
}
return;
}
3.查询联系人:
void SearchContact(Contact* pc)
{
assert(pc);//断言判断
if (pc->sz == 0)//再判断一下是否还有人
{
printf("通讯录为空无法查询\n");
}
char name[NAME_MAX];
printf("请输入要查找的联系人名字:\n");
scanf("%s", name);
int pos=FindByName(pc, name);//查询信息要先找到这个人,根据查人名字函数来查找
if (pos == -1)
{
printf("抱歉,查找的人不存在\n");
return;
}
else
{//打印查找人的信息
printf("%s\n", pc->data[pos].name);
printf("%d\n", pc->data[pos].age);
printf("%s\n", pc->data[pos].sex);
printf("%s\n", pc->data[pos].addr);
printf("%s\n", pc->data[pos].tele);
}
}
4.修改联系人信息:
发没发现删减,查询,修改,关键都是要找到要查找的目标,所以我们的查人名字函数还是很好用的,嘿嘿。
void ModiContact(Contact* pc)//修改联系人操作
{
assert(pc);断言
char name[NAME_MAX];
printf("请输入要修改联系人名字:\n");
scanf("%s", name);
int pos = FindByName(pc, name);//查到这个名字,查到后就把下标返回过来
if (pos == -1)
{
printf("抱歉,该联系人不存在\n");
return;
}
else//如果有这个人,就把他当前的信息全部修改掉,通过返回的下标来修改这个人的信息。
{
printf("请输入姓名:\n");
scanf("%s", pc->data[pos].name);
printf("请输入年龄:\n");
scanf("%d", &(pc->data[pos].age));
printf("请输入性别:\n");
scanf("%s", pc->data[pos].sex);
printf("请输入地址:\n");
scanf("%s", pc->data[pos].addr);
printf("请输入电话:\n");
scanf("%s", pc->data[pos].tele);
printf("修改完毕\n");
}
}
5.显示联系人信息:
void DisplayContact(Contact* pc)
{
assert(pc);//断言
if (pc->sz == 0)//判断一下
{
printf("通讯录中没有人员可显示\n");
return;
}
int i = 0;
printf("%-10s\t%-4s\t%-4s\t%-10s\t%-12s\n", "姓名", "年龄", "性别", "地址", "电话");//这个是把列表打印出来然后下面人员的信息都要按照这里的格式书写,这样就可以美观一致了。都是左对齐。
for (i = 0; i < pc->sz; i++)
{
printf("%-10s\t%-4d\t%-4s\t%-10s\t%-12s\n",
pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].addr,
pc->data[i].tele);
}
}
6:排序-- 目前还没有写……
好了差不多版本一已经完成。我们来看看成果吧:
1.添加
2.删除
3.查询
4.修改
版本二:动态通讯录
第一步:对相关变量进行修改
第二版本的通讯录具有动态特性,也就是可以随着联系人的增多存储空间也增大。
那就不需要对data这个数组进行限制,直接使用指针,我们再定义一个变量capacity为/第一次存放人员的最大存储量,等每次超过这个最大存储量就自动给它增容,假设capacity 为3,每次增容为2个人员
利用宏定义#define FIFIST 3,#define IND 2
所以通讯录的结构体代码就改成这样:
typedef struct Contact
{
PoPinfo *data;//指向通讯录里人员信息的空间
int sz;//当前已经放的信息的个数。
int capacity;//第一次存放人员的最大存储量
}Contact;
第二步:对相关函数进行修改
1.修改初始化函数:
既然没有用数组来申请空间,那我们只能自己动态开辟空间给指针data,开辟的空间就是一开始的capacity存储量3个人员,然后再将sz置0,capacity置成3
void InitContact(Contact* pc)
{
assert(pc);
pc->sz = 0;
PoPinfo* ptr = (PoPinfo*)calloc(FIFIST, sizeof(PoPinfo));//动态开辟空间,并初始化为0
if (ptr == NULL)//每次动态开辟完空间都要检查一下,防止开辟失败
{
perror("InitContact:calloc");//开辟失败就将失败的信息打印出来
return;//结束
}
pc->data = ptr;//为data开辟了3个联系人大小的空间,所以一开始通讯录拥有3个存储空间
pc->capacity = FIFIST;//3个人员
}
2.修改添加函数
你想呀,添加函数肯定要修改的哇,当通讯录的人数添加到超过capacity(最初的容纳量)时就要进行增容呀,所以要对添加函数这里加一个增容函数,我们把这个增容操作,也分装成一个函数,后面还要用到,嘿嘿
//增容函数
void Check_capacity(Contact* pc)
{
if (pc->sz == pc->capacity)//如果人员数到达capacity就说明满了要增容
{
//需要增容
//动态增容,每次增容在原capacity容量下增加 IND个
PoPinfo* ptr = (PoPinfo*)realloc(pc->data, (pc->capacity + IND) * sizeof(PoPinfo));
if (ptr == NULL)//检查一下是否为NULL
{
perror("Check_capacity::realloc");//如果错误,把错误信息返回
}
pc->data = ptr;
pc->capacity += IND;//每增容一次,capacity要加上2
printf("增容成功\n");
}
}
void AddContact(Contact* pc)
{
assert(pc);
//进入函数首先要进行判断是否有容量存储。
Check_capacity(pc);//增容函数。
//增加一个人的信息
printf("请输入姓名:\n");
scanf("%s", pc->data[pc->sz].name);
printf("请输入年龄:\n");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入性别:\n");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入地址:\n");
scanf("%s", pc->data[pc->sz].addr);
printf("请输入电话:\n");
scanf("%s", pc->data[pc->sz].tele);
pc->sz++;
}
3.释放空间
其他函数基本不用改,照样能运行起来,只不过我们不要忘记,动态开辟空间后,用完一定要释放空间
不然会造成内存泄漏,会出bug的,所以我们在退出通讯录的时候记得要将开辟的空间释放掉,并将不用的指针置为NULL,防止变成野指针,所以我们在退出之前再分装一个函数,专门用来销毁空间的。
记得要到头文件里面去声明一下才能使用哟,
void Destroy_contact(Contact* pc)
{
free(pc->data);//释放空间
pc->data = NULL;
pc->capacity = 0;
pc->sz = 0;
pc = NULL;
int input=0;
do
{
mneun();
printf("请输入要选择的:\n");
scanf("%d", &input);
switch (input)
{//第二步增加ADD函数,增加联系人
case 1: AddContact(&con);
break;
case 2:DelContact(&con);//第三步添加删减函数,删减联系人
break;
case 3:SearchContact(&con);//寻找联系人
break;
case 4:ModiContact(&con); //修改联系人信息
break;
case 5:DisplayContact(&con);//显示联系人
break;
case 6: //sort()
break;
case 0: printf("退出通讯录\n");
Destroy_contact(&con);//销毁空间
break;
default:break;
}
} while (input);
好了,到这里,第二版本的动态通讯录也基本完成啦,让我们来看看成果吧:
首先我直接先给这个通讯录添加了3个联系人
再添加一位看看会怎么样:
发现可以自动增容,嘿嘿,每次增容增加两个数量让我再测测:
果然当添加到第6个的时候又开始增容了,所以这个版本弄好啦。你学会了嘛?
学会了就再学下面的完善版本吧
版本三:相对完善通讯录
第一步:掌握文件操作基本知识
写了通讯录的程序,当通讯录运行起来的时候,可以给通讯录中增加、删除数
据,此时数据是存放在内存中,当程序退出的时候,通讯录中的数据自然就不存在了,等下次运行通讯
录程序的时候,数据又得重新录入,如果使用这样的通讯录就很难受
这个版本我们需要做的是使用文件管理操作等知识让通讯录的信息存储到文件中来。还需要将文件里的信息再加载到通讯录里面去。使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。这里我们得了解文件的相关知识才能动手操作。
- 文件在读写之前应该先打开文件,在使用结束之后应该关闭文件
- 在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,就相当于建立了指针和文件的关系
- ANSIC规定使用fopen函数来打开文件,Fclose来关闭文件
//打开文件
FILE*fopen(const char*filename,const char *mode)
//关闭文件
int flcose(FILE*stream)
打开方式如下:
如果要将内存数据写入到文件里面请用 fwrite的喔
将屏幕上的信息输出到文件里面
如果要将文件的数据加载到电脑上请用fread的喔
将文件的信息输入到电脑里
好了基本知识理解后开整:
第二步:保存通讯录数据到文件里
我们想哈,我们写进去的数据原先是在内存里面保存着,然后退出通讯录后,数据就不存在了,我们学完文件操作后就能在退出通讯录之前将这些数据存到文件里面去。
所以在退出通讯录前,搞一个保存数据的函数,这个函数要在空间销毁之前哈,我原先就把这个保存数据的函数放在销毁空间函数的后面然后死活得不到通讯录里面的信息,,呜呜呜。所以你们可不能犯这样的错误。
int input=0;
do
{
mneun();
printf("请输入要选择的:\n");
scanf("%d", &input);
switch (input)
{//第二步增加ADD函数,增加联系人
case 1: AddContact(&con);
break;
case 2:DelContact(&con); //第三步添加删减函数,删减联系人
break;
case 3:SearchContact(&con);//寻找联系人信息
break;
case 4:ModiContact(&con); //修改联系人信息
break;
case 5:DisplayContact(&con);//显示联系人信息
break;
case 6:
break;
case 0: printf("退出通讯录\n");
Protect_contact(&con);//将通讯录的数据信息存到文件里面去,要放在销毁空间函数的前面哈,, //退出之前要将信息存储到文件里
Destroy_contact(&con);//销毁空间
break;
default:break;
}
} while (input);
void Protect_contact(Contact* pc)//保存数据到文件
{
//写数据之前要
//打开文件
FILE* pf = fopen("contact.txt", "wb");//打开方式为"wb"
//为了读和写,新建一个新的二进制文件
if (pf == NULL)//检查一下是否文件打开失败
{
perror("Protect_contact::fopen");//如果失败将失败信息返回
}
{
//写数据,将pc所指向的data的人员信息写入pf开辟的文件里面去,空间大小 为sizeof(PoPinfo),一次传一个数据。
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->data + i, sizeof(PoPinfo), 1, pf);
}
fclose(pf);
pf = NULL;
printf("保存数据成功\n");
}
}
fwrite如何使用请看下图喔:
fopen的使用:
第三步:将文件的信息加载到通讯录里
我们再想哈,如果我们第一次用通讯录添加了几个联系人信息,然后退出通讯录,这些信息存在一个文件里面,那下次再打开这个通讯录按道理来讲这些信息一个还在通讯录里面,所以每次打开通讯录都要将上次的文件信息加载到通讯录里面。
那我们每次进入通讯录是不是都要初始化下,我们应该将这个加载数据的函数放在初始化函数里面来,一上来就把上次的数据加载进来。
void InitContact(Contact* pc)
{
assert(pc);
pc->sz = 0;
PoPinfo* ptr = (PoPinfo*)calloc(FIFIST, sizeof(PoPinfo));//动态开辟空间,并初始化为0
if (ptr == NULL)//每次动态开辟完空间都要检查一下,防止开辟失败
{
perror("InitContact:calloc");//开辟失败就将失败的信息打印出来
return;//结束
}
pc->data = ptr;
pc->capacity = FIFIST;//3个人员
LoadContact(pc);//加载数据函数
//将文件信息加载到pc所指向的空间里面去也就是通讯录
}
怎么将文件信息加载到通讯录里面呢?
这时我们需要用 fread函数,将文件信息输入到电脑里
那是不是直接将文件里的数据全部加载到通讯录里面呢?弄个循环有多少人员信息就循环多少次呢?
当然不行啦
你们想一下,我们的通讯录一开始的容量是有限制的虽然能增容但一开始的容纳量就只有FIFIST (3个),一旦超过这个数量
那通讯录就没有容纳量存储了,就需要增容来接收文件里的数据。
所以在接收文件里的数据之前先判断一下是否要增容呀,上面我们已经将增容操作写成函数啦,嘿嘿,所以直接拿过来放在这里
void LoadContact(Contact* pc)
{
//将文件信息加载到通讯录里面
//读数据
//1..打开文件
FILE* pf = fopen("contact.txt", "rb");//以rb的方式打开文件
if (pf == NULL)//判断一下是否打开文件成功
{
perror("LoadContatc::fopen");
}
else
{
//2.读数据了
//因为一开始不知道文件里面有多少数据呀,我们得把文件里面的数据全部拿出来
//但因为一开始的通讯录容纳量有限制,所以一旦文件里面的数据超过一开始给定的容纳量就需要
//增容。
PoPinfo tmp = { 0 };
int i = 0;
while (fread(&tmp, sizeof(PoPinfo), 1, pf))
{
//首先考虑扩容问题;
Check_capacity(pc);
pc->data[i] = tmp;//将读取的数据放入通讯录里
pc->sz++;//放进去一个sz加1
i++;
}
fclose(pf);//关闭文件
pf = NULL;
}
}
fread函数如何使用:
还有前面函数都需要声明哈,有的我没说我直接给你们看下代码,不要漏掉了
函数的声明:
void InitContact(Contact* pc);//初始化通讯录
void Destroy_contact(Contact* pc);//销毁通讯录空间
void AddContact(Contact* pc);//添加联系人信息
void DelContact(Contact* pc);//删减联系人信息
void SearchContact(Contact* pc);//查询联系人信息
void DisplayContact(Contact* pc);//显示联系人信息
void ModiContact(Contact* pc);//修改联系人信息
void Protect_contact(Contact* pc);//将通讯录信息存储到文件里面去
void LoadContact(Contact* pc);//将文件信息加载到通讯录里面去
代码在Github上:https://github.com/ITwei6/-