一.通讯录的组成
1.什么是通讯录?
- 通讯录在我们日常生活中起到了非常重要的作用,里面保存了朋友,家人,同事等的联系方式,在需要时可以方便的查询,给我们的生活提供了非常大的便利。
- 通讯录里面保存了我们联系人的很多信息,包括姓名,年龄,性别,电话号码,家庭住址等等。
- 那我们学习C语言也有一段时间了,希望能用C语言做一个通讯录来检验自己的学习成果。
2.通讯录的功能
通讯录都有哪些功能呢?
- 增加联系人。我们可以在通讯录里面增加联系人和该联系人的详细信息。
- 删除联系人。我们可以在通讯录里面删除某个指定联系人。
- 查找联系人。我们可以在通讯录中找到指定的联系人。
- 修改联系人。我们可以在通讯录中修改某些联系人的详细信息,如名字,电话号码等。
- 显示联系人。我们找找到指定联系人,并且可以显示他的详细信息。
- 给联系人排序。我们可以给联系人排序,比如让联系人按名字排序。
二.通讯录的分析
1.基本分析
- 首先我们如果要实现一个通讯录的基本功能,那我们肯定需要各种各样的函数来进行相关的操作。
- 但是把这些函数和代码全部写在同一个文件里的话,就会看起来代码很复杂,阅读性很差。
- 所以我们的想法是创建三个文件分别是test.c contact.c contact.h从类别上看,前两个是源文件,而contact.h为头文件。分成三个文件各有各的功能,看起来比较清晰。
2.各个文件的功能
- test.c 该文件为测试文件,目的是为了测试通讯录的功能。我们会将主函数写在test.c里面。通过主函数的执行来调用其他文件中已经写好的函数,检查函数的功能是否正确。
- contact.c 该文件为具体实现文件,也就是各种函数的具体实现会在此文件里面进行书写。
- contact.h 该文件是头文件,里面会包含contact.c里面写好的函数和各种需要用到的头文件,定义宏等。而只要主函数所在的测试文件test.c包含这个头文件就可以调用各种函数。(注意包含自己写的头文件要用" ")
三.通讯录的代码实现
1.test.c
- 测试文件里我们首先要写出主函数,通过主函数调用其他函数才能实现通讯录的具体功能。比如让1到6分别为增,删,查,改,显示,排序。让0为退出通讯录。
- 首先写出主函数,先创建一个通讯录,并给通讯录初始化为0。这里是通过两个结构体嵌套实现的,稍后会说。
- 其次我们就需要一个菜单,在菜单上显示我们的所有功能。而且是在每一次做出选择之后,下一次选择功能之前还会继续显示菜单的内容。我们将菜单写在一个函数里通过循环调用就可以实现此功能。
void caidan()//菜单 { printf("######################################\n"); printf("######--1--<增加>####--2--<删除>######\n"); printf("######--3--<查找>####--4--<修改>######\n"); printf("######--5--<显示>####--6--<排序>######\n"); printf("######--0--<退出>####-----------######\n"); printf("######################################\n"); }
- 这里采用do—while循环,在每一次循环时,都会调用一次菜单函数,将菜单打印在屏幕上,然后需要一个变量来记录每一次选择的功能,如果选择的是0则退出通讯录意味着循环结束。如果是1到6的数字,就调用不同的函数用来实现通讯录的不同功能。
- 为了增加代码的阅读性,我们采用switch case语句和枚举常量。因为枚举常量默认从0开始,刚好符合我们功能的要求,我们将枚举常量写成通讯录功能的缩写,看起来一目了然,非常方便。
enum gongneng//利用枚举常量,增加代码可读性 { Extt, Add, Del, Search, Mod, Show, Sort }; do { caidan();//调用函数打印一个包含多个选项的菜单 printf("请选择要执行的功能:"); scanf("%d", &xuanxiang);//输入选项 switch (xuanxiang)//根据选择的选项不同,进行不同的操作 { case Add: Add_contact(&TXL);//增加 break; case Del: Del_contact(&TXL);//删除 break; case Search: Search_contact(&TXL);//查找 break; case Mod: Mod_contact(&TXL);//修改 break; case Show: Show_contact(&TXL);//显示 break; case Sort: Sort_contact(&TXL);//排序 break; case Extt: XHTXL(&TXL);//在退出通讯录时释放申请的内存 printf("已退出通讯录!\n"); break; default: printf("!!!选择错误!!!\n请重新选择!\n");//如果选择的不是0到6的数字,提示为错误选项 break; } } while (xuanxiang);//直到选择0的时候循环停止,退出通讯录
2.contact.h
- 该文件是头文件,我们首先创建一个结构体表示每一个人的基本信息,比如有姓名,年龄等等。
- 使用结构体的嵌套用来表示通讯录,第一个元素为通讯录的当前人数,第二个元素为结构体指针用来存储通讯录中每个人的信息,第三个元素表示通讯录的当前容量。
#include<stdio.h> #include<string.h> #include<assert.h> #include<stdlib.h> #define CSRL 100 typedef struct people_information//这个是人的信息 { char name[15];//名字 int age;//年龄 char sex[10];//性别 char number[15];//电话号码 char addr[50];//家庭住址 }pif; typedef struct contact//这个是通讯录 { int cent;//用来表示目前通讯录中的人数 pif* ifmn;//用来存放通讯录中每个人的信息 int RL;//用来表示当前通讯录的容量 }contact;
- 下面就是各种在主函数里面需要调用的函数,将其函数名写在头文件里面,在test.c里写上此头文件就可以调用了,关于这些函数的具体实现,我们放在contact.c里面去实现。
void CSH_contact(contact* pc);//初始化通讯录 void XHTXL(contact* pc);//释放申请的内存 void Add_contact(contact* pc);//给通讯录增加联系人 void Del_contact(contact* pc);//删除联系人 void Show_contact(const contact* pc);//显示联系人 void Search_contact(contact* pc);//查找联系人 void Mod_contact(contact* pc);//修改联系人信息 void Sort_contact(contact* pc);//给联系人排序
3.contact.c
- 首先就是初始化函数,当我们在主函数中声明通讯录并且开辟动态内存来存放通讯录的联系人信息要调用初始化函数来给通讯录初始化
void CSH_contact(contact* pc)//初始化 { assert(pc); pc->cent = 0; //memset(pc->ifmn, 0, sizeof(pc->ifmn)); pc->ifmn = (pif*)calloc(CSRL, sizeof(pif));//使用calloc在申请空间时一并初始化为0 pc->RL = CSRL;//这表示最开始的通讯录容量是100 }
- 首先实现增加函数。我们写成Add_contact。在增加联系人之前,首先要判断通讯录当前人数跟容量是否匹配,若当前人数等于当前容量,说明需要扩容,我们使用realloc函数进行修改之前申请好的内存,并且不要忘记通讯录的最大容量也会随之改变。但是请注意不要将初始容量定义的太小,因为增加容量每次增加的大小都是初始容量,如果初始容量太小,就会有很多次增容操作,这样的话程序会多很多次空间碎片化的机会。
void Add_contact(contact* pc) { assert(pc); if (pc->cent == pc->RL)//如果要增加联系人时动态内存不够了,需要修改申请的内存大小 { pif* p = (pif*)realloc(pc->ifmn, (pc->RL + CSRL) * sizeof(pif)); if (p == NULL)//判断如果p是空指针,说明增容失败,就直接返回 return; pc->ifmn = p; pc->RL += CSRL;//每次增容之后的通讯录当前容量也会随之改变 } printf("请输入联系人姓名:\n"); scanf("%s", pc->ifmn[pc->cent].name); printf("请输入联系人年龄:\n"); scanf("%d", &pc->ifmn[pc->cent].age); printf("请输入联系人性别:\n"); scanf("%s", pc->ifmn[pc->cent].sex); printf("请输入联系人电话号码:\n"); scanf("%s", pc->ifmn[pc->cent].number); printf("请输入联系人家庭住址:\n"); scanf("%s", pc->ifmn[pc->cent].addr); pc->cent++; printf("添加联系人成功!!!\n"); }
- 显示联系人的函数。我们写成Show_contact。当我们要显示联系人时,到底是要显示全部联系人还是显示指定的联系人?我们可以通过以下代码实现,这里以指定联系人的标号为例,也可以改成按名字显示。
void Show_contact(const contact* pc) { assert(pc); int x; if (pc->cent == 0) { printf("通讯录为空,没有联系人可以显示!\n"); return; } printf("请选择:<1>显示全部 <2>显示部分 \n"); scanf("%d", &x); if (x == 2)//指定显示某一个人的信息 { int i; printf("请输入要显示第几位的联系人信息:\n"); scanf("%d", &i); if (i>pc->cent)//如果该联系人还没有填入信息,提示为空,并直接返回 { printf("联系人为空!!!\n"); return; } else { printf("联系人<%d>\n姓名:%s\n年龄:%d\n性别:%s\n电话号码:%s\n家庭住址:%s\n", i, pc->ifmn[i - 1].name, pc->ifmn[i - 1].age, pc->ifmn[i - 1].sex, pc->ifmn[i - 1].number, pc->ifmn[i - 1].addr); printf("--------------------------------------\n"); } } else if (x == 1)//显示通讯录中的全部联系人信息 { for (int j = 0; j < pc->cent; j++) { printf("联系人<%d>\n姓名:%s\n年龄:%d\n性别:%s\n电话号码:%s\n家庭住址:%s\n", j + 1, pc->ifmn[j].name, pc->ifmn[j].age, pc->ifmn[j].sex, pc->ifmn[j].number, pc->ifmn[j].addr); printf("--------------------------------------\n"); } } }
- 查找指定联系人。我们写成Search_contact。查找联系人时我们可以按照联系人的名字查找,我们需要写一个函数用来比较当前通讯录中每一个人的名字和需要查找的名字,如果找到了返回这个人的在通讯录中的编号,然后打印他的详细信息就可以了,如果没有找到就提示没有找到该联系人。
static int chazhao(char a[], contact* pc)//用来查找 { assert(pc); for (int i = 0; i < pc->cent; i++) { if (0==strcmp(a, pc->ifmn[i].name)) return i; } return -1; } void Search_contact(contact* pc)//查找指定联系人并且显示他的所有信息 { assert(pc); char a[15] = { 0 }; printf("请输入要查找的联系人名字\n"); scanf("%s", &a); int d = chazhao(a, pc); if (d == -1) { printf("没有找到该联系人!\n");//如果没有找到,提示联系人不存在,并直接返回 return; } else { printf("该联系人为第 %d 个联系人\n", d+1); printf("是否显示该联系人详细信息? <1>显示 <2>不显示\n"); int chose; scanf("%d", &chose); if (chose == 1) { printf("联系人<%d>\n姓名:%s\n年龄:%d\n性别:%s\n电话号码:%s\n家庭住址:%s\n", d+1, pc->ifmn[d].name, pc->ifmn[d].age, pc->ifmn[d].sex, pc->ifmn[d].number, pc->ifmn[d].addr); printf("--------------------------------------\n"); } } }
- 修改指定联系人信息。我们写成Mod_contact。要先找到该联系人然后修改他的详细信息这里我们根据联系人编号查找比较方便,也可以改成按照名字查找修改。就像第四条里面的查找函数就是按照名字来查找联系人的。
void Mod_contact(contact* pc)//修改指定联系人 { assert(pc); printf("请输入你想要修改的联系人编号:"); int num; scanf("%d", &num); if (pc->cent<num) { printf("该联系人为空,请先添加!\n"); return; } printf("请输入想要修改的选项:\n"); printf("<1>姓名 <2>年龄 <3>性别 <4>电话号码 <5>家庭住址 <0>退出\n"); int z; do { scanf("%d", &z); switch (z) { case 1: memset(pc->ifmn[num - 1].name, 0, sizeof(pc->ifmn[num - 1].name)); printf("请输入修改后的姓名:\n"); scanf("%s", pc->ifmn[num - 1].name); break; case 2: printf("请输入修改后的年龄:\n"); scanf("%d", &pc->ifmn[num - 1].age); break; case 3: memset(pc->ifmn[num - 1].sex, 0, sizeof(pc->ifmn[num - 1].sex)); printf("请输入修改后的性别:\n"); scanf("%s", pc->ifmn[num - 1].sex); break; case 4: memset(pc->ifmn[num - 1].number, 0, sizeof(pc->ifmn[num - 1].number)); printf("请输入修改后的电话号码:\n"); scanf("%s", pc->ifmn[num - 1].number); break; case 5: memset(pc->ifmn[num - 1].addr, 0, sizeof(pc->ifmn[num - 1].addr)); printf("请输入修改后的家庭住址:\n"); scanf("%s", pc->ifmn[num - 1].addr); break; case 0: break; default: printf("!!!选择错误!!!\n请重新选择!\n");//如果选择的不是0到5的数字,提示为错误选项 break; } } while (z); printf("修改成功!\n"); printf("--------------------------------------\n"); }
- 删除指定联系人。写成Del_contact。如果要删除指定联系人我们首先通过名字找到该联系人,然后将其删除,也就是将后面的联系人依次向前移动。删除之后,通讯录中的实际人数也会减少一个。如果要清空通讯录的话,直接调用初始化函数,给所有赋值为0就可以了。
void Del_contact(contact* pc)//删除指定联系人或清空通讯录 { assert(pc); if (pc->cent == 0) { printf("通讯录为空,没有信息可以删除\n");//如果当前通讯录为空,提示没有信息可以删除, return; } printf("请选择:<1>删除指定联系人 <2>清空通讯录\n"); int m; scanf("%d", &m); if (m == 2) { printf("!!!确定要清空你的通讯录吗?!!!删除后不可恢复\n"); printf("<1>确定 <2>取消\n"); int w; scanf("%d", &w); if (w == 1) { pc->cent = 0; memset(pc->ifmn, 0, sizeof(pc->ifmn)); return; } else return; } else { printf("请输入你想删除的联系人名字:\n"); char a[15] = { 0 }; scanf("%s", &a); int d = chazhao(a, pc); if (d == -1) { printf("联系人不存在!!!\n"); return; } for (int i = d; i < pc->cent - 1; i++) pc->ifmn[i] = pc->ifmn[i + 1]; pc->cent--; printf("删除联系人成功!!!\n"); } }
- 给联系人排序。写成Sort_contact。这里以联系人的名字为例排序。
int PX(const void* e1, const void* e2) { return strcmp(((pif*)e1)->name, ((pif*)e2)->name); } void Sort_contact(contact* pc)//给联系人排序 { assert(pc); qsort(pc->ifmn, pc->cent, sizeof(pif), PX);//利用qsort函数给联系人排序 }
- 退出通讯录时需要销毁(释放)申请好的动态内存,以免造成内存泄漏。
void XHTXL(contact* pc)//释放申请的动态内存 { assert(pc); free(pc->ifmn); pc = NULL; }
四.通讯录的动态版本的完整代码
1.test.c
//这个文件是用来测试通讯录的功能
#include"contact.h"//注意:包含自己的头文件要用""
enum gongneng//利用枚举常量,增加代码可读性
{
Extt,
Add,
Del,
Search,
Mod,
Show,
Sort
};
void caidan()//菜单
{
printf("######################################\n");
printf("######--1--<增加>####--2--<删除>######\n");
printf("######--3--<查找>####--4--<修改>######\n");
printf("######--5--<显示>####--6--<排序>######\n");
printf("######--0--<退出>####-----------######\n");
printf("######################################\n");
}
int main()
{
int xuanxiang;//记录选择的数字(也就是记录选项)
contact TXL;//通讯录
CSH_contact(&TXL);//初始化通讯录
do
{
caidan();//调用函数打印一个包含多个选项的菜单
printf("请选择要执行的功能:");
scanf("%d", &xuanxiang);//输入选项
switch (xuanxiang)//根据选择的选项不同,进行不同的操作
{
case Add:
Add_contact(&TXL);//增加
break;
case Del:
Del_contact(&TXL);//删除
break;
case Search:
Search_contact(&TXL);//查找
break;
case Mod:
Mod_contact(&TXL);//修改
break;
case Show:
Show_contact(&TXL);//显示
break;
case Sort:
Sort_contact(&TXL);//排序
break;
case Extt:
XHTXL(&TXL);//在退出通讯录时释放申请的内存
printf("已退出通讯录!\n");
break;
default:
printf("!!!选择错误!!!\n请重新选择!\n");//如果选择的不是0到6的数字,提示为错误选项
break;
}
} while (xuanxiang);//直到选择0的时候循环停止,退出通讯录
return 0;
}
2.contact.h
//类型的声明,如果将类型写到头文件里,那么其他文件只要包含这个头文件就都可以使用。
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#define CSRL 100
typedef struct people_information//这个是人的信息
{
char name[15];//名字
int age;//年龄
char sex[10];//性别
char number[15];//电话号码
char addr[50];//家庭住址
}pif;
typedef struct contact//这个是通讯录
{
int cent;//用来表示目前通讯录中的人数
pif* ifmn;//用来存放通讯录中每个人的信息
int RL;//用来表示当前通讯录的容量
}contact;
void CSH_contact(contact* pc);//初始化通讯录
void XHTXL(contact* pc);//释放申请的内存
void Add_contact(contact* pc);//给通讯录增加联系人
void Del_contact(contact* pc);//删除联系人
void Show_contact(const contact* pc);//显示联系人
void Search_contact(contact* pc);//查找联系人
void Mod_contact(contact* pc);//修改联系人信息
void Sort_contact(contact* pc);//给联系人排序
3.contact.c
//用来实现通讯录
#include"contact.h"//自己写的头文件要用双引号来引用
static int chazhao(char a[], contact* pc)//用来查找
{
assert(pc);
for (int i = 0; i < pc->cent; i++)
{
if (0==strcmp(a, pc->ifmn[i].name))
return i;
}
return -1;
}
void CSH_contact(contact* pc)//初始化
{
assert(pc);
pc->cent = 0;
//memset(pc->ifmn, 0, sizeof(pc->ifmn));
pc->ifmn = (pif*)calloc(CSRL, sizeof(pif));//使用calloc在申请空间时一并初始化为0
pc->RL = CSRL;//这表示最开始的通讯录容量是100
}
void XHTXL(contact* pc)//释放申请的动态内存
{
assert(pc);
free(pc->ifmn);
pc = NULL;
}
void Add_contact(contact* pc)
{
assert(pc);
if (pc->cent == pc->RL)//如果要增加联系人时动态内存不够了,需要修改申请的内存大小
{
pif* p = (pif*)realloc(pc->ifmn, (pc->RL + CSRL) * sizeof(pif));
if (p == NULL)//判断如果p是空指针,说明增容失败,就直接返回
return;
pc->ifmn = p;
pc->RL += CSRL;//每次增容之后的通讯录当前容量也会随之改变
}
printf("请输入联系人姓名:\n");
scanf("%s", pc->ifmn[pc->cent].name);
printf("请输入联系人年龄:\n");
scanf("%d", &pc->ifmn[pc->cent].age);
printf("请输入联系人性别:\n");
scanf("%s", pc->ifmn[pc->cent].sex);
printf("请输入联系人电话号码:\n");
scanf("%s", pc->ifmn[pc->cent].number);
printf("请输入联系人家庭住址:\n");
scanf("%s", pc->ifmn[pc->cent].addr);
pc->cent++;
printf("添加联系人成功!!!\n");
}
void Del_contact(contact* pc)//删除指定联系人或清空通讯录
{
assert(pc);
if (pc->cent == 0)
{
printf("通讯录为空,没有信息可以删除\n");//如果当前通讯录为空,提示没有信息可以删除,
return;
}
printf("请选择:<1>删除指定联系人 <2>清空通讯录\n");
int m;
scanf("%d", &m);
if (m == 2)
{
printf("!!!确定要清空你的通讯录吗?!!!删除后不可恢复\n");
printf("<1>确定 <2>取消\n");
int w;
scanf("%d", &w);
if (w == 1)
{
pc->cent = 0;
memset(pc->ifmn, 0, sizeof(pc->ifmn));
return;
}
else
return;
}
else
{
printf("请输入你想删除的联系人名字:\n");
char a[15] = { 0 };
scanf("%s", &a);
int d = chazhao(a, pc);
if (d == -1)
{
printf("联系人不存在!!!\n");
return;
}
for (int i = d; i < pc->cent - 1; i++)
pc->ifmn[i] = pc->ifmn[i + 1];
pc->cent--;
printf("删除联系人成功!!!\n");
}
}
void Mod_contact(contact* pc)//修改指定联系人
{
assert(pc);
printf("请输入你想要修改的联系人编号:");
int num;
scanf("%d", &num);
if (pc->cent<num)
{
printf("该联系人为空,请先添加!\n");
return;
}
printf("请输入想要修改的选项:\n");
printf("<1>姓名 <2>年龄 <3>性别 <4>电话号码 <5>家庭住址 <0>退出\n");
int z;
do
{
scanf("%d", &z);
switch (z)
{
case 1:
memset(pc->ifmn[num - 1].name, 0, sizeof(pc->ifmn[num - 1].name));
printf("请输入修改后的姓名:\n");
scanf("%s", pc->ifmn[num - 1].name);
break;
case 2:
printf("请输入修改后的年龄:\n");
scanf("%d", &pc->ifmn[num - 1].age);
break;
case 3:
memset(pc->ifmn[num - 1].sex, 0, sizeof(pc->ifmn[num - 1].sex));
printf("请输入修改后的性别:\n");
scanf("%s", pc->ifmn[num - 1].sex);
break;
case 4:
memset(pc->ifmn[num - 1].number, 0, sizeof(pc->ifmn[num - 1].number));
printf("请输入修改后的电话号码:\n");
scanf("%s", pc->ifmn[num - 1].number);
break;
case 5:
memset(pc->ifmn[num - 1].addr, 0, sizeof(pc->ifmn[num - 1].addr));
printf("请输入修改后的家庭住址:\n");
scanf("%s", pc->ifmn[num - 1].addr);
break;
case 0:
break;
default:
printf("!!!选择错误!!!\n请重新选择!\n");//如果选择的不是0到5的数字,提示为错误选项
break;
}
} while (z);
printf("修改成功!\n");
printf("--------------------------------------\n");
}
int PX(const void* e1, const void* e2)
{
return strcmp(((pif*)e1)->name, ((pif*)e2)->name);
}
void Sort_contact(contact* pc)//给联系人排序
{
assert(pc);
qsort(pc->ifmn, pc->cent, sizeof(pif), PX);//利用qsort函数给联系人排序
}
void Search_contact(contact* pc)//查找指定联系人并且显示他的所有信息
{
assert(pc);
char a[15] = { 0 };
printf("请输入要查找的联系人名字\n");
scanf("%s", &a);
int d = chazhao(a, pc);
if (d == -1)
{
printf("没有找到该联系人!\n");//如果没有找到,提示联系人不存在,并直接返回
return;
}
else
{
printf("该联系人为第 %d 个联系人\n", d+1);
printf("是否显示该联系人详细信息? <1>显示 <2>不显示\n");
int chose;
scanf("%d", &chose);
if (chose == 1)
{
printf("联系人<%d>\n姓名:%s\n年龄:%d\n性别:%s\n电话号码:%s\n家庭住址:%s\n", d+1,
pc->ifmn[d].name,
pc->ifmn[d].age,
pc->ifmn[d].sex,
pc->ifmn[d].number,
pc->ifmn[d].addr);
printf("--------------------------------------\n");
}
}
}
void Show_contact(const contact* pc)
{
assert(pc);
int x;
if (pc->cent == 0)
{
printf("通讯录为空,没有联系人可以显示!\n");
return;
}
printf("请选择:<1>显示全部 <2>显示部分 \n");
scanf("%d", &x);
if (x == 2)//指定显示某一个人的信息
{
int i;
printf("请输入要显示第几位的联系人信息:\n");
scanf("%d", &i);
if (i>pc->cent)//如果该联系人还没有填入信息,提示为空,并直接返回
{
printf("联系人为空!!!\n");
return;
}
else
{
printf("联系人<%d>\n姓名:%s\n年龄:%d\n性别:%s\n电话号码:%s\n家庭住址:%s\n", i,
pc->ifmn[i - 1].name,
pc->ifmn[i - 1].age,
pc->ifmn[i - 1].sex,
pc->ifmn[i - 1].number,
pc->ifmn[i - 1].addr);
printf("--------------------------------------\n");
}
}
else if (x == 1)//显示通讯录中的全部联系人信息
{
for (int j = 0; j < pc->cent; j++)
{
printf("联系人<%d>\n姓名:%s\n年龄:%d\n性别:%s\n电话号码:%s\n家庭住址:%s\n", j + 1,
pc->ifmn[j].name,
pc->ifmn[j].age,
pc->ifmn[j].sex,
pc->ifmn[j].number,
pc->ifmn[j].addr);
printf("--------------------------------------\n");
}
}
}
五.动态文件版本
1.分析
- 上面写的为动态版本,我们只能在程序运行的时候进行改变一些通讯录的信息,但是当我们退出程序时,之前的信息都会清空,所以我们使用文件,当下次打开程序时,让信息自动读到通讯录中,在程序结束时,将通讯录的信息写到文件里,以便于下次打开程序时直接读入,这样的话联系人信息就可以长久存在,不会每次打开程序都得重新录入。
- 当我们最后选择0时,也就是退出通讯录之前,我们要将通讯录中的联系人信息保存在文件中。
- 当我们再次打开程序时,我们应该在初始化申请完初始空间后,先让通讯录读取上一次通讯录保存到文件中的信息。但是通讯录的初始容量为100,可能上次结束时,文件中保存的联系人数量超过了100,所以我们在初始化读入数据时,应该考虑增容问题。具体的实现代码如下。
2.具体代码(也是三文件分开)
1.test.c
//这个文件是用来测试通讯录的功能
#include"contact.h"//注意:包含自己的头文件要用""
enum gongneng//利用枚举常量,增加代码可读性
{
Extt,
Add,
Del,
Search,
Mod,
Show,
Sort
};
void caidan()//菜单
{
printf("######################################\n");
printf("######--1--<增加>####--2--<删除>######\n");
printf("######--3--<查找>####--4--<修改>######\n");
printf("######--5--<显示>####--6--<排序>######\n");
printf("######--0--<退出>####-----------######\n");
printf("######################################\n");
}
int main()
{
int xuanxiang;//记录选择的数字(也就是记录选项)
contact TXL;//通讯录
CSH_contact(&TXL);//初始化通讯录
do
{
caidan();//调用函数打印一个包含多个选项的菜单
printf("请选择要执行的功能:");
scanf("%d", &xuanxiang);//输入选项
switch (xuanxiang)//根据选择的选项不同,进行不同的操作
{
case Add:
Add_contact(&TXL);//增加
break;
case Del:
Del_contact(&TXL);//删除
break;
case Search:
Search_contact(&TXL);//查找
break;
case Mod:
Mod_contact(&TXL);//修改
break;
case Show:
Show_contact(&TXL);//显示
break;
case Sort:
Sort_contact(&TXL);//排序
break;
case Extt:
Save_contact(&TXL);//保存信息
XHTXL(&TXL);//在退出通讯录时释放申请的内存
printf("已退出通讯录!\n");
break;
default:
printf("!!!选择错误!!!\n请重新选择!\n");//如果选择的不是0到6的数字,提示为错误选项
break;
}
} while (xuanxiang);//直到选择0的时候循环停止,退出通讯录
return 0;
}
2.contact.h
//类型的声明,如果将类型写到头文件里,那么其他文件只要包含这个头文件就都可以使用。
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#define CSRL 100
typedef struct people_information//这个是人的信息
{
char name[15];//名字
int age;//年龄
char sex[10];//性别
char number[15];//电话号码
char addr[50];//家庭住址
}pif;
typedef struct contact//这个是通讯录
{
int cent;//用来表示目前通讯录中的人数
pif* ifmn;//用来存放通讯录中每个人的信息
int RL;//用来表示当前通讯录的容量
}contact;
void CSH_contact(contact* pc);//初始化通讯录
void XHTXL(contact* pc);//释放申请的内存
void Add_contact(contact* pc);//给通讯录增加联系人
void Del_contact(contact* pc);//删除联系人
void Show_contact(const contact* pc);//显示联系人
void Search_contact(contact* pc);//查找联系人
void Mod_contact(contact* pc);//修改联系人信息
void Sort_contact(contact* pc);//给联系人排序
void Save_contact(const contact* pc);//在退出通讯录时保存数据到指定文件
3.contact.c
//用来实现通讯录
#include"contact.h"//自己写的头文件要用双引号来引用
static int chazhao(char a[], contact* pc)//用来查找
{
assert(pc);
for (int i = 0; i < pc->cent; i++)
{
if (0==strcmp(a, pc->ifmn[i].name))
return i;
}
return -1;
}
void CSH_contact(contact* pc)//初始化
{
assert(pc);
pc->cent = 0;
//memset(pc->ifmn, 0, sizeof(pc->ifmn));
pc->ifmn = (pif*)calloc(CSRL, sizeof(pif));//使用calloc在申请空间时一并初始化为0
pc->RL = CSRL;//这表示最开始的通讯录容量是100
FILE* pfr = fopen("contact.txt", "rb");//二进制读,将文件保存的信息读入通讯录
if (pfr == NULL)
return;//如果是空指针直接返回
pif temp = { 0 };
while (1 == fread(&temp, sizeof(pif), 1, pfr))
{
if (pc->cent == pc->RL)//如果要读取联系人时动态内存不够了,需要修改申请的内存大小
{
pif* p = (pif*)realloc(pc->ifmn, (pc->RL + CSRL) * sizeof(pif));
if (p == NULL)//判断如果p是空指针,说明增容失败,就直接返回
return;
pc->ifmn = p;
pc->RL += CSRL;//每次增容之后的通讯录当前容量也会随之改变
}
pc->ifmn[pc->cent] = temp;
pc->cent++;
}
fclose(pfr);
pfr = NULL;
}
void XHTXL(contact* pc)//释放申请的动态内存
{
assert(pc);
free(pc->ifmn);
pc = NULL;
}
void Add_contact(contact* pc)
{
assert(pc);
if (pc->cent == pc->RL)//如果要增加联系人时动态内存不够了,需要修改申请的内存大小
{
pif* p = (pif*)realloc(pc->ifmn, (pc->RL + CSRL) * sizeof(pif));
if (p == NULL)//判断如果p是空指针,说明增容失败,就直接返回
return;
pc->ifmn = p;
pc->RL += CSRL;//每次增容之后的通讯录当前容量也会随之改变
}
printf("请输入联系人姓名:\n");
scanf("%s", pc->ifmn[pc->cent].name);
printf("请输入联系人年龄:\n");
scanf("%d", &pc->ifmn[pc->cent].age);
printf("请输入联系人性别:\n");
scanf("%s", pc->ifmn[pc->cent].sex);
printf("请输入联系人电话号码:\n");
scanf("%s", pc->ifmn[pc->cent].number);
printf("请输入联系人家庭住址:\n");
scanf("%s", pc->ifmn[pc->cent].addr);
pc->cent++;
printf("添加联系人成功!!!\n");
}
void Del_contact(contact* pc)//删除指定联系人或清空通讯录
{
assert(pc);
if (pc->cent == 0)
{
printf("通讯录为空,没有信息可以删除\n");//如果当前通讯录为空,提示没有信息可以删除,
return;
}
printf("请选择:<1>删除指定联系人 <2>清空通讯录\n");
int m;
scanf("%d", &m);
if (m == 2)
{
printf("!!!确定要清空你的通讯录吗?!!!删除后不可恢复\n");
printf("<1>确定 <2>取消\n");
int w;
scanf("%d", &w);
if (w == 1)
{
pc->cent = 0;
memset(pc->ifmn, 0, sizeof(pc->ifmn));
return;
}
else
return;
}
else
{
printf("请输入你想删除的联系人名字:\n");
char a[15] = { 0 };
scanf("%s", &a);
int d = chazhao(a, pc);
if (d == -1)
{
printf("联系人不存在!!!\n");
return;
}
for (int i = d; i < pc->cent - 1; i++)
pc->ifmn[i] = pc->ifmn[i + 1];
pc->cent--;
printf("删除联系人成功!!!\n");
}
}
void Mod_contact(contact* pc)//修改指定联系人
{
assert(pc);
printf("请输入你想要修改的联系人编号:");
int num;
scanf("%d", &num);
if (pc->cent<num)
{
printf("该联系人为空,请先添加!\n");
return;
}
printf("请输入想要修改的选项:\n");
printf("<1>姓名 <2>年龄 <3>性别 <4>电话号码 <5>家庭住址 <0>退出\n");
int z;
do
{
scanf("%d", &z);
switch (z)
{
case 1:
memset(pc->ifmn[num - 1].name, 0, sizeof(pc->ifmn[num - 1].name));
printf("请输入修改后的姓名:\n");
scanf("%s", pc->ifmn[num - 1].name);
break;
case 2:
printf("请输入修改后的年龄:\n");
scanf("%d", &pc->ifmn[num - 1].age);
break;
case 3:
memset(pc->ifmn[num - 1].sex, 0, sizeof(pc->ifmn[num - 1].sex));
printf("请输入修改后的性别:\n");
scanf("%s", pc->ifmn[num - 1].sex);
break;
case 4:
memset(pc->ifmn[num - 1].number, 0, sizeof(pc->ifmn[num - 1].number));
printf("请输入修改后的电话号码:\n");
scanf("%s", pc->ifmn[num - 1].number);
break;
case 5:
memset(pc->ifmn[num - 1].addr, 0, sizeof(pc->ifmn[num - 1].addr));
printf("请输入修改后的家庭住址:\n");
scanf("%s", pc->ifmn[num - 1].addr);
break;
case 0:
break;
default:
printf("!!!选择错误!!!\n请重新选择!\n");//如果选择的不是0到5的数字,提示为错误选项
break;
}
} while (z);
printf("修改成功!\n");
printf("--------------------------------------\n");
}
int PX(const void* e1, const void* e2)
{
return strcmp(((pif*)e1)->name, ((pif*)e2)->name);
}
void Sort_contact(contact* pc)//给联系人排序
{
assert(pc);
qsort(pc->ifmn, pc->cent, sizeof(pif), PX);//利用qsort函数给联系人排序
}
void Search_contact(contact* pc)//查找指定联系人并且显示他的所有信息
{
assert(pc);
char a[15] = { 0 };
printf("请输入要查找的联系人名字\n");
scanf("%s", &a);
int d = chazhao(a, pc);
if (d == -1)
{
printf("没有找到该联系人!\n");//如果没有找到,提示联系人不存在,并直接返回
return;
}
else
{
printf("该联系人为第 %d 个联系人\n", d+1);
printf("是否显示该联系人详细信息? <1>显示 <2>不显示\n");
int chose;
scanf("%d", &chose);
if (chose == 1)
{
printf("联系人<%d>\n姓名:%s\n年龄:%d\n性别:%s\n电话号码:%s\n家庭住址:%s\n", d+1,
pc->ifmn[d].name,
pc->ifmn[d].age,
pc->ifmn[d].sex,
pc->ifmn[d].number,
pc->ifmn[d].addr);
printf("--------------------------------------\n");
}
}
}
void Show_contact(const contact* pc)
{
assert(pc);
int x;
if (pc->cent == 0)
{
printf("通讯录为空,没有联系人可以显示!\n");
return;
}
printf("请选择:<1>显示全部 <2>显示部分 \n");
scanf("%d", &x);
if (x == 2)//指定显示某一个人的信息
{
int i;
printf("请输入要显示第几位的联系人信息:\n");
scanf("%d", &i);
if (i>pc->cent)//如果该联系人还没有填入信息,提示为空,并直接返回
{
printf("联系人为空!!!\n");
return;
}
else
{
printf("联系人<%d>\n姓名:%s\n年龄:%d\n性别:%s\n电话号码:%s\n家庭住址:%s\n", i,
pc->ifmn[i - 1].name,
pc->ifmn[i - 1].age,
pc->ifmn[i - 1].sex,
pc->ifmn[i - 1].number,
pc->ifmn[i - 1].addr);
printf("--------------------------------------\n");
}
}
else if (x == 1)//显示通讯录中的全部联系人信息
{
for (int j = 0; j < pc->cent; j++)
{
printf("联系人<%d>\n姓名:%s\n年龄:%d\n性别:%s\n电话号码:%s\n家庭住址:%s\n", j + 1,
pc->ifmn[j].name,
pc->ifmn[j].age,
pc->ifmn[j].sex,
pc->ifmn[j].number,
pc->ifmn[j].addr);
printf("--------------------------------------\n");
}
}
}
void Save_contact(const contact* pc)//保存数据
{
assert(pc);
FILE* pfw = fopen("contact.txt", "wb");//打开文件,二进制写
if (pfw == NULL)
return;//如果是空指针直接返回
for (int i = 0; i < pc->cent; i++)
fwrite(pc->ifmn + i, sizeof(pif), 1, pfw);
fclose(pfw);
pfw = NULL;
}
六.总结
通讯录的C语言实现完毕,但还是存在很多不合理的地方。这些代码笔者编译是没有问题的,通讯录的各项功能也检验过了,基本实现了。如果还有哪里有问题或者我的注释写错了,还请见谅!