嗨嗨大家~我们又见面啦!今天为大家带来的内容是:保姆级通讯录的实现,包括了三个版本(静态、动态,文件版)。
在写代码之前,我们必须要思考:实现通讯录的步骤及其功能是怎样的。想象一下,平时在手机上关于通讯录的功能分别有:添加联系人,删除联系人,查询联系人信息,修改联系人信息,显示通讯录,通讯录名字的排序等等。那么现在我们就正式开始吧!
目录
一、静态通讯录
1 代码分装
为避免在一个源文件中过于冗杂,我们选择使用两个源文件(一个用于功能函数的运用:test.c;一个用于写各功能函数具体实现:contact.c),一个头文件(声明函数:contact.h)。
源文件:
- test.c: 测试通讯录的基本功能
- contact.c: 实现通讯录的相关函数
头文件:
- contact.h: 相关函数和类型的声明,整个代码要引用的头文件以及宏定义
2 代码实现
(1)main主函数的实现
- 在test.c中定义一个main函数打印菜单,提示我们进行选择。
- scanf函数用来接收我们所选择的数据项,并用switch判断,针对判断进行详细操作。为了让用户完成一个功能操作后在进行下一个操作,这里需要使用do while语句将各个选择包装起来。
- 使用枚举来一一列举菜单选择的变量名称,以便我们使用时知晓其意,增加代码的可读性。
test.c--测试功能:
void menu()
{
printf("***********************************\n");
printf("****** 1.Add 2.Del ******\n");
printf("****** 3.Search 4.Modify ******\n");
printf("****** 5.Show 6.Sort ******\n");
printf("****** 0.Exit ******\n");
printf("***********************************\n");
}
enum option //枚举常量
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SHOW,
SORT
};
int main()
{
int input = 0;
do
{
menu();
printf("请输入你的选择:>");
scanf("%d", &input);
switch (input)
{
case ADD:
break;
case DEL:
break;
case SEARCH:
break;
case MODIFY:
break;
case SHOW:
break;
case SORT:
break;
case EXIT:
printf("退出通讯录\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
(2) 创建通讯录
首先我们要理清思路,通讯录里所储存的是多个联系人的信息,包括姓名、年龄、性别、电话、地址,因此我们可以创建两个结构体,一个储存联系人的信息,一个储存通讯录。与此同时,还需要一个变量来定义通讯录的大小(即通讯录人数多少)。
创建结构体类型 PeoInfo,用于描述人的信息,还需要创建 Contact 结构体包含 PeoInfo 和个数,使用宏#define定义的变量,以便后续代码的更改(只需更改宏定义的变量)。到这步一个通讯录结构体类型便创建成功了。
#define MAX 100
#define NAME_MAX 20
#define SEX_MAX 5
#define TELE_MAX 12
#define ADDR_MAX 30
typedef struct PeoInfo
{
char name[NAME_MAX];
int age;
char sex[SEX_MAX];
char tele[TELE_MAX];
char addr[ADDR_MAX];
}PeoInfo;
typedef struct Contact
{
PeoInfo data[MAX];
int count;
}Contact;
(3) 初始化通讯录
使用memset函数初始化通讯录:
//初始化通讯录函数
void InitContact(Contact* pc)
{
pc->count = 0;
memset(pc->data, 0, sizeof(pc->data));
}
(4) 添加联系人信息
使用assert 进行断言操作,作用是对传入的指针进行判断,防止对空指针进行操作
//添加联系人到通讯录
void addContact(Contact* pc)
{
assert(pc);
//判断通讯录是否已经满员
if (pc->count == MAX)
{
printf("\n通讯录已满,无法添加\n");
return;
}
//添加信息
printf("\n请输入名字:>");
//每次放进去的信息都是放到data下标为count的数组中
scanf("%s", pc->data[pc->count].name);
printf("\n请输入年龄:>");
//因为name是存放在数组中,数组名本身就是地址,不需要再取地址
//这里的年龄是int型的变量,需要取地址
scanf("%d", &(pc->data[pc->count].age));
printf("\n请输入性别:>");
scanf("%s", pc->data[pc->count].sex);
printf("\n请输入电话:>");
scanf("%s", pc->data[pc->count].tele);
printf("\n请输入地址:>");
scanf("%s", pc->data[pc->count].addr);
pc->count++;
printf("\n增加成功\n");
}
为了检查所添加的功能是否正确,需要显示通讯录信息,因此我们来实现下一个函数ShowContact:
(5) 显示联系人信息
为了让通讯录的信息更加清晰地显示,这里进行了格式化处理:在%后加上负数,为信息预留了空间,如下代码:
void ShowContact(Contact* pc)
{
int i = 0;
printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
for (i = 0; i < pc->count; i++)
{
printf("%-10s %-4d %-5s %-12s %-30s\n",
pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
(6) 删除联系人信息
我们需要先找到想要删除的联系人,再进行相应操作,代码如下:
//查找联系人的函数,这个函数不需要进行声明,它是代码内部逻辑的需要
static int findByName(Contact* pc, char name[])
{
assert(pc);
int i = 0;
for (i = 0; i < pc->count; i++)
{
if (0 == strcmp(pc->data[i].name, name))
return i;
}
return -1;
}
//删除指定联系人
void delContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
assert(pc);
int i = 0;
if (pc->count == 0)
{
printf("\n通讯录为空\n");
return;
}
printf("\n请输入要删除的人名字:>");
scanf("%s", name);
//进行删除
//1.查找,因为各种操作都需要先进行查找,可以独立分装一个函数
int flag = findByName(pc, name);
if (flag == -1)
{
printf("要删除的人不存在\n");
return;
}
//2.删除
for (i = flag; i < pc->count-1; i++)
{
//用i+1位置的数据覆盖掉pos位置的数据,完成删除
pc->data[i] = pc->data[i + 1];//i+1最大99
}
pc->count--;
//这里是为了解决当要删除最后一个人的信息时,
//后面没有数据来覆盖最后一个联系人的数据。
//count--;最后一个元素为99,访问不到最后一个元素了,从而起到删除的作用
printf("\n删除成功\n");
}
(7) 查找指定联系人信息
在该代码的实现过程中,同样使用上面的查找函数来查找此人的下标并返回,格式化输出此人信息。
void SearchContact(Contact* pc)
{
if (pc->count == 0)
{
printf("通讯录为空\n");
}
char name[NAME_MAX];
printf("请输入要查找联系人的姓名:");
scanf("%s", &SearchName);
int flag = findByName(pc, name);
if (flag == -1)
{
printf("要查找的人不存在\n");
return;
}
printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-10s %-4d %-5s %-12s %-30s\n",
pc->data[flag].name,
pc->data[flag].age,
pc->data[flag].sex,
pc->data[flag].tele,
pc->data[flag].addr);
}
(8) 修改联系人信息
我们还是要调用上面的查找函数找到该人的信息,然后进行更改其内容(若不存在则结束函数运行)。
//修改指定联系人
void ModifyContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
assert(pc);
printf("\n请输入要修改的人名字:>");
scanf("%s", name);
//1.查找
int flag = findByName(pc, name);
if (flag == -1)
{
printf("要修改的人不存在\n");
return;
}
//2.修改
printf("\n请输入名字:>");
//每次放进去的信息都是放进data 下标为count的数组
scanf("%s", pc->data[flag].name);
printf("\n请输入年龄:>");
//因为name是存放在数组中,数组名本身就是地址,不需要再取地址
//这里的年龄是int 型变量,需要取地址
scanf("%d", &(pc->data[flag].age));
printf("\n请输入性别:>");
scanf("%s", pc->data[flag].sex);
printf("\n请输入电话:>");
scanf("%s", pc->data[flag].tele);
printf("\n请输入地址:>");
scanf("%s", pc->data[flag].addr);
printf("\n修改成功\n");
}
(9) 整理(排序)通讯录
这里是根据年龄进行了排序:
int cmp_age(const void* e1, const void* e2)
{
return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}
void SortContact(Contact* pc)
{
assert(pc);
if (pc->count == 0)
{
printf("通讯录为空,无需排序\n");
return;
}
qsort(pc, pc->count, sizeof(PeoInfo), cmp_age);
printf("排序成功\n");
}
学到这里通讯录的实现就基本完成了,但是我们可以注意到,此通讯录还存在两个问题:
- 通讯录的大小是固定的100个元素,最多只能存放100个人。当信息太少时,就会导致空间剩余过大浪费空间,而当信息太多时空间又太小了无法进行存入,而解决这个问题需要使用到动态内存管理的知识。如果大家对动态内存管理没有深入研究,可以到博主的 http://t.csdnimg.cn/w7Du3 这篇文章中学习。
- 录入的信息,等程序结束后就不存在了,这是因为数据只存放到了内存中。为了解决这个问题,需要使用到C语言文件存储的相关知识。 如果你们对C语言文件存储不太了解,可以看博主的 http://t.csdnimg.cn/akhKd 这篇博客。
下面就来优化一下通讯录:
二、动态通讯录
我们知道,静态通讯录的空间是有限的,如果我们需要储存的联系人过多,那便无法存下联系人。对于这个问题,我们就可以使用 malloc 和 realloc 两个函数来实现动态通讯录。
代码实现
(1)优化思路
首先,使用malloc函数对通讯录的数据开辟空间,那么数组data就需要是指针类型;
其次,使用realloc函数进行扩容,需要一个变量来记录函数当前最大储存容量,通过与联系人多少比较来决定是否要扩容;
最后,释放所开辟的空间。
综上,我们需要更改的就只有三个地方,更改通讯录结构体,更改初识化通讯录函数,更改添加联系人函数,在退出时释放空间。
(2)代码实现
//宏定义——加在contact.h文件中
#define INIT_MAX 3
#define CAPACITY_ADD 2
//通讯录结构体的更改
typedef struct Contact
{
PeoInfo *data;//存放人的信息
int count; //记录当前通讯录有多少人的信息
int capacity; //记录当前通讯录容量
}Contact;
//通讯录初始化的更改
void InitContact(Contact* pc)
{
pc->data = malloc(sizeof(PeoInfo) * INIT_MAX);
if (pc->data == NULL)
{
printf("通讯录初始化失败:%s\n", strerror(errno));
return;
}
pc->count = 0;
pc->capacity = INIT_MAX;
}
//通讯录添加联系人的更改
void CheckCapacity(Contact* pc)
{
if (pc->capacity == pc->count)
{
PeoInfo *ptr = realloc(pc->data, (pc->capacity + 2) * sizeof(PeoInfo));
if (ptr == NULL)
{
printf("CheckCapacity:%s\n", strerror(errno));
}
pc->capacity += CAPACITY_ADD;
printf("增容成功,当前容量%d\n", pc->capacity);
}
}
void AddContact(Contact* pc)
{
CheckCapacity(pc);
printf("请输入姓名:");
scanf("%s", pc->data[pc->count].name);
printf("请输入年龄:");
scanf("%d", &(pc->data[pc->count].age));
printf("请输入性别:");
scanf("%s", pc->data[pc->count].sex);
printf("请输入电话号码:");
scanf("%s", pc->data[pc->count].tele);
printf("请输入地址:");
scanf("%s",pc->data[pc->count].addr);
pc->count++;
printf("添加成功\n");
}
//释放空间--销毁通讯录
void DestroyContact(Contact* pc)
{
assert(pc);
free(pc->data);
pc->data=NULL;
}
三、文件通讯录
文件通讯录的实现是为了方便我们在程序退出后,也能查找到相应信息。也就是说,文件操作处理通讯录,可以存档联系人的信息。这样就不需要每一次都重新输入数据。
代码实现
(1)优化思路
首先,我们需要创建文件,将联系人的数据写到文件中;
其次,在通讯录初始化时,将文件的内容提前写入到通讯录中。
下面我们来优化处理通讯录:
(2)代码实现
//通讯录写入文件中
void SaveContact(Contact* pc)
{
FILE* pf = fopen("contact.dat", "wb");
if (pf == NULL)
{
perror("SaveContact::fopen");
return;
}
//写数据
int i = 0;
for (i = 0; i < pc->count; i++)
{
fwrite(pc->data + i, sizeof(struct PeoInfo), 1, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
printf("保存成功\n");
}
//加载文件信息到通讯录中
//注意:函数内使用了CheckCapacity(pc)来增容,如果写在此函数前面,需要声明此函数
void LoadContact(Contact* pc)
{
//打开文件
FILE* pf = fopen("Contact.dat", "rb");
if (pf == NULL)
{
perror("LoadContact::fopen");
return;
}
//读文件
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf))
{
CheckCapacity(pc);
pc->data[pc->count] = tmp;
pc->count++;
}
//关闭文件
fclose(pf);
pf = NULL;
}
void InitContact(Contact* pc)
{
pc->data = malloc(sizeof(struct PeoInfo) * INIT_MAX);
if (pc->data == NULL)
{
printf("通讯录初始化失败:%s\n", strerror(errno));
return;
}
pc->count = 0;
pc->capacity = INIT_MAX;
//加载文件的信息到通讯录
LoadContact(pc);
}
本期有关通讯录三种版本(静态、动态和文件)的分享到这里就结束啦,涵盖了很多知识点,相信大家看完后会有所收获。路还很长,一点一点来。或许我的思路存在不足之处,欢迎各位佬们的指点。如果这篇文章对你们有所帮助,记得给博主一个三连哈~你们的支持是我创作的最大动力!