文章目录
一、项目要求
在学习了结构体、动态内存管理以及文件操作的相关知识后,我们可以根据以上基本要求来分别设计三个版本的通讯录,分别是静态版、动态版和文件版
静态版:是最基础的版本,能满足对通讯录内部联系人的增删查改、对联系人按姓名,年龄,电话或住址进行排序、显示通讯录中所有联系人、退出通讯录。
动态版:动态版的通讯录能满足静态版通讯录的所有功能,但是内部实现由固定大小改为动态增容;
文件版:文件版的通讯录能满足动态版通讯录的所有功能,但是会在程序退出时把联系人信息保存在文件中,在程序运行时把文件中的联系人信息加载到通讯录中;
- 设计框架
我们把项目封装在三个源文件中:
test.c:通讯录的总体逻辑,主要用于对通讯录各功能的测试;
contact.c:通讯录各种功能的具体实现;
contact.h:各种必要的声明,包括库函数头文件的声明、自定义结构的声明以及自定义函数的声明;
二、静态版通讯录
1. 结构体设计
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAX_NAME 10 //宏定义各种联系人信息变量的大小,方便以后修改
#define MAX_SEX 3
#define MAX_TELE 12
#define MAX_ADDR 10
#define MAX_CAP 100 //通讯录容量
typedef struct peoinfo //联系人
{
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
}peoinfo;
typedef struct con //通讯录
{
peoinfo data[MAX_CAP];
int sz; //通讯录实际联系人数目
}contact;
2、初始化通讯录
我们可以使用前面学习的 memset 函数来b把通讯录中存放联系人信息的数组的内容全部初始化为0,然后再把用于实际联系人数量置为0,完成通讯录的初始化
void InitContact(contact* pc)
{
memset(pc->data, 0, sizeof(pc->data));
pc->sz = 0;
}
3、添加联系人信息
这里由于通讯录大小是固定的,所以我们在添加联系人的时候要注意检查通讯录是否已满,如果没满才能正常添加,满了就打印提示信息并直接 return。
void AddContact(contact* pc)
{
if (pc->sz == MAX_CAP)
{
printf("通讯录已满,不能添加联系人!\n");
return;
}
printf("请输入添加联系人的姓名:");
scanf("%s", pc->data[pc->sz].name);
printf("请输入添加联系人的年龄:");
scanf("%d", &pc->data[pc->sz].age);
printf("请输入添加联系人的性别:");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入添加联系人的电话:");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入添加联系人的住址:");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("添加成功!\n");
}
4、删除联系人信息
这里有两个需要注意的地方:一是需要检查通讯录是否为空,如果为空提示后直接返回;二是我们要检查我们希望删除的这个人是否存在,所以我们需要设计一个 FindByName 函数来查找联系人,根据函数的结果来进行后续操作。
int FindByName(const contact* pc, char* name)
{
for (int i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;
}
}
return -1;
}
void DelContact(contact* pc)
{
if (pc->sz == 0)
{
printf("通讯录为空!\n");
return;
}
char name[MAX_NAME] = { 0 };
printf("请输入你要删除的联系人:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("联系人不存在!\n");
return;
}
for (int i = pos; i < MAX_CAP - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功!\n");
}
5、查找联系人(按姓名)
这里也需要调用 FindByName 函数来查找联系人,如果我们查找到了就返回该联系人所在位置的下标,但是如果找不到我们应该返回一个无意义的数,比如-1,因为数组是从0下标开始的,我们所查找的联系人可能在0位置处。
int FindByName(const contact* pc, char* name)
{
for (int i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;
}
}
return -1;
}
void SearchContact(const contact* pc)
{
char name[MAX_NAME] = { 0 };
printf("请输入你要查找的联系人:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("你要查找的人不存在!\n");
return;
}
printf("%-10s %-5d %-5s %-15s %-10s\n",
pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].sex,
pc->data[pos].tele,
pc->data[pos].addr);
}
6、修改联系人信息
int FindByName(const contact* pc, char* name)
{
for (int i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;
}
}
return -1;
}
void ModifyContact(contact* pc)
{
char name[MAX_NAME] = { 0 };
printf("请输入你要修改的联系人:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("你要修改的人不存在!\n");
return;
}
printf("请输入新联系人的姓名:");
scanf("%s", pc->data[pos].name);
printf("请输入新联系人的年龄:");
scanf("%d", &pc->data[pos].age);
printf("请输入新联系人的性别:");
scanf("%s", pc->data[pos].sex);
printf("请输入新联系人的电话:");
scanf("%s", pc->data[pos].tele);
printf("请输入新联系人的住址:");
scanf("%s", pc->data[pos].addr);
printf("修改成功!\n");
}
7、排序联系人(自选属性)
我们可以通过调用 qsort 函数,然后给定排序规则来实现对联系人信息的排序,这里实现出了除性别以外所有属性的排序方法的函数(毕竟正常人可不会按性别排序),然后将其放入函数指针数组中,最后通过回调函数的方式实现任意方式的排序。
void menu1()
{
printf("*****************************\n");
printf("***** 0. name 1. age ******\n");
printf("***** 2. tele 3. addr ******\n");
printf("*****************************\n");
}
enum cau
{
NAME,
AGE,
TELE,
ADDR
};
int cmp_age(const void* a, const void* b)
{
return (*(peoinfo*)a).age - (*(peoinfo*)b).age;
}
int cmp_name(const void* a, const void* b)
{
char* s1 = ((peoinfo*)a)->name;
char* s2 = ((peoinfo*)b)->name;
return strcmp(s1, s2);
}
int cmp_tele(const void* a, const void* b)
{
char* s1 = ((peoinfo*)a)->tele;
char* s2 = ((peoinfo*)b)->tele;
return strcmp(s1, s2);
}
int cmp_addr(const void* a, const void* b)
{
char* s1 = ((peoinfo*)a)->addr;
char* s2 = ((peoinfo*)b)->addr;
return strcmp(s1, s2);
}
void SortContact(contact* pc)
{
menu1();
int input = 0;
int (*p[4])(const void*, const void*) = { cmp_name, cmp_age, cmp_tele, cmp_addr };//函数指针数组
while (1)
{
printf("请选择以什么属性排序:> ");
scanf("%d", &input);
if (input == 0 || input == 1 || input == 2 || input == 3)
{
qsort(pc->data, pc->sz, sizeof(pc->data[0]), p[input]);
printf("排序成功!\n");
return;
}
else
{
printf("输入错误,请重新输入!\n");
}
}
}
8、显示所有联系人信息
void ShowContact(const contact* pc)
{
for (int i = 0; i < pc->sz; i++)
{
printf("%-10s %-5d %-5s %-15s %-10s\n",
pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
9、完整代码
contact.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAX_NAME 10
#define MAX_SEX 3
#define MAX_TELE 12
#define MAX_ADDR 10
#define MAX_CAP 100
typedef struct peoinfo //联系人
{
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
}peoinfo;
typedef struct con //通讯录
{
peoinfo data[MAX_CAP];
int sz;
}contact;
//初始化通讯录
void InitContact(contact* pc);
//增加联系人
void AddContact(contact* pc);
//查看通讯录
void ShowContact(const contact* pc);
//删除联系人
void DelContact(contact* pc);
//查找联系人
void SearchContact(const contact* pc);
//修改联系人
void ModifyContact(contact* pc);
//对通讯录排序
void SortContact(contact* pc);
contact.c
#include"contact.h"
void InitContact(contact* pc)
{
memset(pc->data, 0, sizeof(pc->data));
pc->sz = 0;
}
void AddContact(contact* pc)
{
if (pc->sz == MAX_CAP)
{
printf("通讯录已满,不能添加联系人!\n");
return;
}
printf("请输入添加联系人的姓名:");
scanf("%s", pc->data[pc->sz].name);
printf("请输入添加联系人的年龄:");
scanf("%d", &pc->data[pc->sz].age);
printf("请输入添加联系人的性别:");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入添加联系人的电话:");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入添加联系人的住址:");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("添加成功!\n");
}
void ShowContact(const contact* pc)
{
for (int i = 0; i < pc->sz; i++)
{
printf("%-10s %-5d %-5s %-15s %-10s\n",
pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
int FindByName(const contact* pc, char* name)
{
for (int i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;
}
}
return -1;
}
void DelContact(contact* pc)
{
if (pc->sz == 0)
{
printf("通讯录为空!\n");
return;
}
char name[MAX_NAME] = { 0 };
printf("请输入你要删除的联系人:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("联系人不存在!\n");
return;
}
for (int i = pos; i < MAX_CAP - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功!\n");
}
void SearchContact(const contact* pc)
{
char name[MAX_NAME] = { 0 };
printf("请输入你要查找的联系人:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("你要查找的人不存在!\n");
return;
}
printf("%-10s %-5d %-5s %-15s %-10s\n",
pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].sex,
pc->data[pos].tele,
pc->data[pos].addr);
}
void ModifyContact(contact* pc)
{
char name[MAX_NAME] = { 0 };
printf("请输入你要修改的联系人:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("你要修改的人不存在!\n");
return;
}
printf("请输入新联系人的姓名:");
scanf("%s", pc->data[pos].name);
printf("请输入新联系人的年龄:");
scanf("%d", &pc->data[pos].age);
printf("请输入新联系人的性别:");
scanf("%s", pc->data[pos].sex);
printf("请输入新联系人的电话:");
scanf("%s", pc->data[pos].tele);
printf("请输入新联系人的住址:");
scanf("%s", pc->data[pos].addr);
printf("修改成功!\n");
}
void menu1()
{
printf("*****************************\n");
printf("***** 0. name 1. age ******\n");
printf("***** 2. tele 3. addr ******\n");
printf("*****************************\n");
}
enum cau
{
NAME,
AGE,
TELE,
ADDR
};
int cmp_age(const void* a, const void* b)
{
return (*(peoinfo*)a).age - (*(peoinfo*)b).age;
}
int cmp_name(const void* a, const void* b)
{
char* s1 = ((peoinfo*)a)->name;
char* s2 = ((peoinfo*)b)->name;
return strcmp(s1, s2);
}
int cmp_tele(const void* a, const void* b)
{
char* s1 = ((peoinfo*)a)->tele;
char* s2 = ((peoinfo*)b)->tele;
return strcmp(s1, s2);
}
int cmp_addr(const void* a, const void* b)
{
char* s1 = ((peoinfo*)a)->addr;
char* s2 = ((peoinfo*)b)->addr;
return strcmp(s1, s2);
}
void SortContact(contact* pc)
{
menu1();
int input = 0;
int (*p[4])(const void*, const void*) = { cmp_name, cmp_age, cmp_tele, cmp_addr };//函数指针数组
while (1)
{
printf("请选择以什么属性排序:> ");
scanf("%d", &input);
if (input == 0 || input == 1 || input == 2 || input == 3)
{
qsort(pc->data, pc->sz, sizeof(pc->data[0]), p[input]);
printf("排序成功!\n");
return;
}
else
{
printf("输入错误,请重新输入!\n");
}
}
}
test.c
#include"contact.h"
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
};
void test()
{
contact con;
InitContact(&con);
menu();
int input = 0;
do
{
printf("请选择:> ");
scanf("%d", &input);
switch (input)
{
case ADD:
AddContact(&con);
break;
case DEL:
DelContact(&con);
break;
case SEARCH:
SearchContact(&con);
break;
case MODIFY:
ModifyContact(&con);
break;
case SHOW:
ShowContact(&con);
break;
case SORT:
SortContact(&con);
break;
case EXIT:
printf("退出成功!\n");
return;
break;
default:
printf("输入有误,请重新输入!\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
三、动态版通讯录
我们发现静态版的通讯录存在一个缺陷,那就是数组大小是固定的,导致当我们联系人比较少的时候会造成空间浪费,联系人比较多的时候又存储不下;所以我们通过把静态版通讯录改造成动态版通讯录来解决空间浪费与不足的问题;
动态版通讯录与静态版通讯录的实现基本相同,只是把静态数组变成了动态数组,相应需要改变的函数有:通讯录结构体、初始化函数、增加联系人的函数(需要判断是否需要增容),需要增加的函数有:检查容量函数(容量满了就增容)
1、结构体设计
存放联系人信息的结构体不变,需要改动的是通讯录结构体,我们在静态版通讯录结构体中定义了一个固定大小的联系人结构体数组,用于存放联系人信息,但是现在我们用将其改为动态增容的,所以我们需要定义一个联系人指针变量,指向动态开辟的一块空间,来存放联系人信息;同时,我们还需要一个capacity变量,来记录当前通讯录的容量,如果当前联系人数量与其相等,我们就增容。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAX_NAME 10
#define MAX_SEX 3
#define MAX_TELE 12
#define MAX_ADDR 10
#define INIT_LEN 3 //初始容量
#define EXPAN_LEN 2 //每次扩容的大小
typedef struct peoinfo //联系人
{
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
}peoinfo;
typedef struct con
{
peoinfo* data;
int sz;
int capacity;
}contact;
2、初始化通讯录
我们可以使用 malloc 函数来为 date 指针开辟空间,然后再使用 memset 函数将其内容全部初始化为0,我们也可以使用 calloc 函数,在开辟空间的同时把内存初始化为0。
void InitContact(contact* pc)
{
pc->data = (peoinfo*)malloc(INIT_LEN * sizeof(peoinfo));
if (pc->data == NULL)
{
perror("InitContact");
return;
}
memset(pc->data, 0, sizeof(pc->data));
pc->sz = 0;
pc->capacity = INIT_LEN;
}
3、检查通讯录容量
比较通讯录结构体中的实际联系人数量和当前通讯录容量,如果二者相等,就使用 realloc 函数对通讯录进行扩容,这里我们一次扩容两个。
int Expansion(contact* pc)
{
peoinfo* temp = (peoinfo*)realloc(pc->data, sizeof(peoinfo) * (EXPAN_LEN + pc->capacity));
if (temp == NULL)
{
perror("Expansion:");
return -1;
}
pc->data = temp;
pc->capacity += EXPAN_LEN;
return 1;
}
4、销毁通讯录
void DestroyContact(contact* pc)
{
assert(pc);
free(pc->data); //释放date指向的空间
pc->data = NULL; //把date置空,避免出现野指针
}
6、完整代码
动态版通讯录的完整代码我放在 Gitee 上了,有需要的可以自取
四、文件版通讯录
我们发现动态版的通讯录存在一个缺陷,那就是我们的联系人信息并没有持久化,每次我们程序重新运行的时候都需要重新添加联系人;
我们可以通过把动态版通讯录改造成文件版通讯录来解决联系人信息存储问题;
文件版通讯录在动态版的基础上增加了两个步骤:一是在销毁通讯录之前把联系人信息存放到 contact.txt 文件中,避免程序退出后联系人信息丢失;二是在初始化通讯录后,把 contact.txt 文件中的联系人信息加载到通讯录中;相应需要增加的函数有:加载联系人信息函数、保存联系人信息函数。
1. 保存联系人
void SaveContact(contact* pc)
{
FILE* pf = fopen("contact.txt", "wb");
if (pf == NULL)
{
perror("fopen");
printf("保存失败!\n");
return;
}
size_t ret = fwrite(pc->data, sizeof(peoinfo), pc->sz, pf);
if (ret == pc->sz)
{
printf("保存成功!\n");
}
fclose(pf);
pf = NULL;
}
2. 加载联系人信息
int Expansion(contact* pc)
{
peoinfo* temp = (peoinfo*)realloc(pc->data, sizeof(peoinfo) * (EXPAN_LEN + pc->capacity));
if (temp == NULL)
{
perror("Expansion:");
return -1;
}
pc->data = temp;
pc->capacity += EXPAN_LEN;
return 1;
}
void LoadContact(contact* pc)
{
FILE* pfread = fopen("contact.txt", "rb");
if (pfread == NULL)
{
perror("fopen");
printf("初始化失败!\n");
return;
}
peoinfo temp = { 0 };
while (fread(&temp, sizeof(peoinfo), 1, pfread) == 1)
{
pc->data[pc->sz] = temp;
pc->sz++;
if (pc->sz == pc->capacity)
{
Expansion(pc);
}
}
printf("初始化成功!\n");
fclose(pfread);
pfread = NULL;
}
3. 完整版代码
文件版通讯录的完整代码我放在 Gitee 上了,有需要的可以自取