介绍
通讯录是在学习C语言中很经典的题目了,他与写学生管理系统一样综合性比较强,这次我将介绍三种通讯录写法,分别是普通通讯录,动态内存版通讯录和文件版通讯录,后面两者均是在普通通讯录上做的修改。
教程
首先,一个通讯录,我们需要他能够做到以下操作
1.可以存放100个人的信息
2.每个人的信息:
名字
性别
年龄
电话
地址
3.增加删除联系人
4.查找指定联系人
5.修改指定联系人
6.显示联系人信息
7.排序联系人(按照年龄/名字)
通讯录需要头文件:
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
我们可以发现,要做到这些功能,显然需要运用结构体创建变量:
//表示一个人的信息
typedef struct PeoInfo
{
char name[20];
int age;
char sex[5];
char tel[12];
char addr[30];
}PeoInfo;
但是,如果全部定义成数字的话,如果我们的通讯录要涉及到更改容量或其他数据,就会变得很麻烦,需要一个一个的找到对应数据进行修改,所以,在这里定义变量时,我们可以用#define定义几个常变量,放到数组当中,这样一来,当我们需要修改数据时,只需要修改define定义的变量的值:
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TEL 12
#define MAX_ADDR 30
#define DEFAULT_SZ 3
#define INC_SZ 2
typedef struct PeoInfo
{
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tel[MAX_TEL];
char addr[MAX_ADDR];
}PeoInfo;
在题目中我们假设的是可以存放100个人的通讯录,这里我们只是把一个人所要显示的消息的变量创建了出来,因此,我们还需要设置一个结构体来存放数据和表示人数:
typedef struct Contact
{
// PeoInfo data[100];//存放数据
PeoInfo data[MAX];
int sz;//记录通讯录有效信息个数
}Contact,//* pContact;
可以注意到,最后重命名结构体类型变量时,注释了一个* pContact,其实,如果不用Contact直接重命名的话,我们在括号后添加一个“*”,使整体变为结构体指针,而现在重命名的变量就变成了结构体指针变量,那么,再后面定义函数设置参数时,我们就不用写成Contact*,而是直接写pContact
做好准备工作后,我们可以开始写函数了,我们可以创建两个源文件,一个专门用来写通讯录函数,实现功能,一个则负责调用函数和主菜单界面,对于主菜单界面和调用函数的设置在这里不过多赘述,请看代码:
void menu()
{
printf("**************************************\n");
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");
printf("**************************************\n");
}
enum Option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SHOW,
SORT
};
int main()
{
int input = 0;
PeoInfo data[100];
int sz = 0;*///记录通讯录有几个人的信息
//两者一同增加显得麻烦,盒装到一个结构体中
Contact con;//通讯录
//初始化通讯录
InitContact(&con);
do
{
menu();
printf("请输入选项:");
scanf_s("%d", &input);
switch (input)
{
case ADD:
Add(&con);
break;
case DEL:
Del(&con);
break;
case SEARCH:
Search(&con);
break;
case MODIFY:
Modify(&con);
break;
case SHOW:
Show(&con);//不用修改,只是打印,但传地址效率更高
break;
case SORT:
Sort(&con);
break;
case EXIT:
printf("退出通讯录\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
PS:这里运用枚举是为了让代码看起来更美观,可读性更强。
打开另一个源文件,首先我们要写一个初始化通讯录的代码,其实很简单,就是将数组初始化成0:
void InitContact(Contact* pc)
{
memset(pc->data, 0, sizeof(pc->data));//将数组初始化为0
}
初始化完成后,就可以开始使用通讯录的功能了,创建一个添加数据的函数,在函数中设置一个if,用来检查是否达到了存储上限:
void Add(Contact* pc)
{
if (pc->sz == MAX)
{
printf("通讯录已满,无法增加\n");
return;
}
printf("请输入名字:");
//将数据输入到下标为sz的数组元素中
scanf("%s", pc->data[pc->sz].name);
//name是数组名,地址,所以不需要scanf取地址操作
printf("请输入年龄:");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入性别:");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入电话:");
scanf("%s", pc->data[pc->sz].tel);
printf("请输入地址:");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("添加成功\n");
}
添加函数写好后,我们继续写一个显示函数,这个函数也很简单,只需要把各类数据打印出来就好了:
void Show(const Contact* pc)
{
int i = 0;
printf("%-10s %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
for (i = 0; i < pc->sz; i++)
{
printf("%-10s %-5d %-5s %-12s %-30s\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tel, pc->data[i].addr);
}
}
接着是查找和删除函数,我们可以发现,这两个函数都有一个共同点:都要查找,查找函数先查找再显示,删除函数先查找再删除,在删除和查找函数中都使用了名字查找功能,代码一样,所以分装成一个名字查找函数,那我们可以写一个FindByName函数,并且只用于辅助删除和查找函数,那么用static修饰,使得只能在该函数所在的文件中使用,最后,在两个函数中调用FindByName,当然,我们也应该考虑到通讯录为空时,要删除的数据不存在时,要查找的数据不存在时的特殊情况。
static int FindByName(const Contact* pc, char name[])
{
int i = 0;
int pos = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;
}
}
return -1;
}
void Del(Contact* pc)
{
char name[MAX_NAME] = { 0 };
if (pc->sz == 0)
{
printf("通讯录为空,无法删除\n");
return;
}
printf("输入要删除的名字:");
scanf("%s", name);
int pos = FindByName(pc, name);
int i = 0;
if (pos == -1)
{
printf("要删除的人不存在");
return;
}
for (i = pos; i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功\n");
}
void Search(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 %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-10s %-5d %-5s %-12s %-30s\n", pc->data[pos].name, pc->data[pos].age, pc->data[pos].sex, pc->data[pos].tel, pc->data[pos].addr);
}
如果在使用过程中,出现了写入数据错误,要修改数据的情况该怎么办呢?这就要用到修改功能,修改功能也涉及到查找数据,所以仍然可以调用FindByName函数,找到对应数据后,在相应位置进行修改
void Modify(Contact* pc)
{
char name[MAX_NAME];
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].tel);
printf("请输入地址:");
scanf("%s", pc->data[pos].addr);
printf("修改成功\n");
}
实现这些功能后,最后还有一个排序功能,排序函数我们可以运用qsort函数排序,不过使用qsort需要自己再写一个比较函数,假设我们按名字来排序,那么就写一个cmp_by_name函数作为比较函数
int cmp_by_name(const void* e1, const void* e2)
{
return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}
void Sort(const Contact* pc)
{
qsort(pc->data, pc->sz, sizeof(PeoInfo), cmp_by_name);
Show(pc);
printf("排序成功\n");
}
到此为止,最基础的通讯录就实现完毕了,我们可以发现,这个通讯录有很多缺陷:他不能自己扩容,同时输入数据后无法保存,程序关闭后数据便销毁了,要解决这两种问题,就涉及到用动态内存和文件操作
动态内存修改
动态内存,即malloc,calloc,realloc函数,通过运用这些函数我们可以达到通讯录扩容的需求,解决了通讯录容量不够的问题。
要达到这个效果,我们需要从结构体定义开始修改:
//动态版本
//默认能存放3个人
//不够每次增加2个人的信息
typedef struct Contact
{
// PeoInfo data[100];//静态存放数据
PeoInfo* data;//data 指向存放数据空间
int sz;//记录通讯录有效信息个数
int rl;//通讯录容量
}Contact; //* pContact;
接着我们修改初始化函数,用malloc函数初始化通讯录的数据空间data
void InitContact(Contact* pc)
{
pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
if (pc->data == NULL)
{
printf("通讯录初始化失败:%s",strerror(errno));
return;
}
pc->sz = 0;
pc->rl = DEFAULT_SZ;
}
当然,使用动态内存时需要判断是否使用开辟内存成功。初始化完成后,我们可以开始填写数据,但是我们要怎样知道他的容量不够需要扩容呢?我们可以在添加函数中设置一个检查容量的函数,如果容量不够就扩容,容量够就退出函数开始写入数据,写入完成后,动态内存就应该释放了,因此,我们需要写一个释放函数,将使用完的动态内存释放,避免出错,当然,这个操作是在我们退出通讯录时做的:
void Destory(Contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->rl = 0;
pc->sz = 0;
}
//扩容失败返回0
//扩容成功或不扩容返回1
Check(Contact* pc)
{
if (pc->sz == pc->rl)
{
PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->rl + INC_SZ) * sizeof(PeoInfo));
if (ptr == NULL)
{
printf("Check:%s", strerror(errno));
return 0;
}
else
{
pc->data = ptr;
pc->rl += INC_SZ;
return 1;
}
}
return 1;
}
void Add(Contact* pc)
{
Check(pc);//检查扩充容量
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].tel);
printf("请输入地址:");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("添加成功\n");
}
主函数修改:
int main()
{
int input = 0;
Contact con;
InitContact(&con);
do
{
menu();
printf("请输入选项:");
scanf_s("%d", &input);
switch (input)
{
case ADD:
Add(&con);
break;
case DEL:
Del(&con);
break;
case SEARCH:
Search(&con);
break;
case MODIFY:
Modify(&con);
break;
case SHOW:
Show(&con);
break;
case SORT:
Sort(&con);
break;
case EXIT:
Destory(&con);
//销毁释放内存
printf("退出通讯录\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
动态内存处理到这里就写完了,现在通讯录可以进行扩容操作,接下来我们假如文件指针函数,使得这个通讯录可以存储和读取信息
文件操作
我们知道,保存的数据时需要读取才能显示的,所以我们可以在通讯录初始化的时候就读取数据,如果有就读取成功,没有则显示没有数据,在读取时,我们需要再调用一遍容量检查函数,在这个通讯录中,初始容量为3,所以当我们读取到数据并想要放入通讯录时,需要进行容量检查并扩容,但是该函数已经在后面进行了编译和调用,而这个函数在它之前,应该怎么使用呢?我们可以在该函数之前进行一个函数声明,这样我们就能提前正常使用了
int Check(Contact* pc);//声明以提前使用函数
void Load(Contact* pc)
{
FILE* pf = fopen("contact.txt", "rb");
if (pf == NULL);
{
perror("Load::fopen");//fopen较多的情况下,使用::明确是load中的fopen,避免出错
return;
}
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf))
{
//不能直接用读文件到pc,pc只有三个元素,如果文件中有多个元素就无法读取成功
//所以创建相同类型的临时变量tmp
Check(pc);
pc->data[pc->sz] = tmp;
pc->sz++;
}
fclose(pf);
pf = NULL;
}
初始化函数就需要修改
void InitContact(Contact* pc)
{
pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
if (pc->data == NULL)
{
printf("通讯录初始化失败:%s",strerror(errno));
return;
}
pc->sz = 0;
pc->rl = DEFAULT_SZ;
//加载文件信息到通讯录
Load(pc);
}
最后是存储函数,用fwrite将数据存储到对应的文件即可:
void Save(Contact* pc)
{
FILE* pf = fopen("contact.txt", "wb");
if (pf == NULL)
{
perror("Save::fopen");
return;
}
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->data + i, sizeof(struct PeoInfo), 1, pf);
}
fclose(pf);
pf = NULL;
}
对主函数进行修改:
int main()
{
int input = 0;
Contact con;
//加载文件到通讯录
InitContact(&con);
do
{
menu();
printf("请输入选项:");
scanf_s("%d", &input);
switch (input)
{
case ADD:
Add(&con);
break;
case DEL:
Del(&con);
break;
case SEARCH:
Search(&con);
break;
case MODIFY:
Modify(&con);
break;
case SHOW:
Show(&con);
break;
case SORT:
Sort(&con);
break;
case EXIT:
Save(&con);
Destory(&con);
printf("退出通讯录\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
到此为止,完整版的通讯录就写好了,当然,不要忘了我们创建的函数需要在头文件中进行声明:
//初始化通讯录
void InitContact(Contact* pc);
//增加指定联系人
void Add(Contact* pc);
//显示联系人消息
void Show(const Contact* pc);
//删除指定联系人
void Del(Contact* pc);
//void Del(pContact pc);
//查找指定联系人
void Search(const Contact* pc);
//修改通讯录
void Modify(Contact* pc);
//排序通讯录元素
void Sort(const Contact* pc);
//销毁通讯录
void Destory(Contact* pc);
//保存数据到文件
void Save(Contact* pc);
//读取数据
void Load(Contact* pc);
完整代码
头文件:
#pragma once
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TEL 12
#define MAX_ADDR 30
#define DEFAULT_SZ 3
#define INC_SZ 2
typedef struct PeoInfo
{
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tel[MAX_TEL];
char addr[MAX_ADDR];
}PeoInfo;
typedef struct Contact
{
// PeoInfo data[100];
PeoInfo* data;//data
int sz;
int rl;
}Contact;
//初始化通讯录
void InitContact(Contact* pc);
//增加指定联系人
void Add(Contact* pc);
//显示联系人消息
void Show(const Contact* pc);
//删除指定联系人
void Del(Contact* pc);
//void Del(pContact pc);
//查找指定联系人
void Search(const Contact* pc);
//修改通讯录
void Modify(Contact* pc);
//排序通讯录元素
void Sort(const Contact* pc);
//销毁通讯录
void Destory(Contact* pc);
//保存数据到文件
void Save(Contact* pc);
//读取数据
void Load(Contact* pc);
源文件1:
int Check(Contact* pc);
void Load(Contact* pc)
{
FILE* pf = fopen("contact.txt", "rb");
if (pf == NULL);
{
perror("Load::fopen");
return;
}
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf))
{
Check(pc);
pc->data[pc->sz] = tmp;
pc->sz++;
}
fclose(pf);
pf = NULL;
}
void InitContact(Contact* pc)
{
pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
if (pc->data == NULL)
{
printf("通讯录初始化失败:%s",strerror(errno));
return;
}
pc->sz = 0;
pc->rl = DEFAULT_SZ;
//memset(pc->data, 0, sizeof(pc->data));
Load(pc);
}
void Destory(Contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->rl = 0;
pc->sz = 0;
}
Check(Contact* pc)
{
if (pc->sz == pc->rl)
{
PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->rl + INC_SZ) * sizeof(PeoInfo));
if (ptr == NULL)
{
printf("Check:%s", strerror(errno));
return 0;
}
else
{
pc->data = ptr;
pc->rl += INC_SZ;
return 1;
}
}
return 1;
}
void Add(Contact* pc)
{
Check(pc);
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].tel);
printf("请输入地址:");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("添加成功\n");
}
void Show(const Contact* pc)
{
int i = 0;
printf("%-10s %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
for (i = 0; i < pc->sz; i++)
{
printf("%-10s %-5d %-5s %-12s %-30s\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tel, pc->data[i].addr);
}
}
static int FindByName(const Contact* pc, char name[])
{
int i = 0;
int pos = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;
}
}
return -1;
}
void Del(Contact* pc)
{
char name[MAX_NAME] = { 0 };
if (pc->sz == 0)
{
printf("通讯录为空,无法删除\n");
return;
}
printf("输入要删除的名字:");
scanf("%s", name);
int pos = FindByName(pc, name);
int i = 0;
if (pos == -1)
{
printf("要删除的人不存在");
return;
}
for (i = pos; i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功\n");
}
void Search(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 %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-10s %-5d %-5s %-12s %-30s\n", pc->data[pos].name, pc->data[pos].age, pc->data[pos].sex, pc->data[pos].tel, pc->data[pos].addr);
}
void Modify(Contact* pc)
{
char name[MAX_NAME];
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].tel);
printf("请输入地址:");
scanf("%s", pc->data[pos].addr);
printf("修改成功\n");
}
int cmp_by_name(const void* e1, const void* e2)
{
return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}
void Sort(const Contact* pc)
{
qsort(pc->data, pc->sz, sizeof(PeoInfo), cmp_by_name);
Show(pc);
printf("排序成功\n");
}
void Save(Contact* pc)
{
FILE* pf = fopen("contact.txt", "wb");
if (pf == NULL)
{
perror("Save::fopen");
return;
}
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->data + i, sizeof(struct PeoInfo), 1, pf);
}
fclose(pf);
pf = NULL;
}
源文件2:
void menu()
{
printf("**************************************\n");
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");
printf("**************************************\n");
}
enum Option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SHOW,
SORT
};
int main()
{
int input = 0;
Contact con;
InitContact(&con);
do
{
menu();
printf("请输入选项:");
scanf_s("%d", &input);
switch (input)
{
case ADD:
Add(&con);
break;
case DEL:
Del(&con);
break;
case SEARCH:
Search(&con);
break;
case MODIFY:
Modify(&con);
break;
case SHOW:
Show(&con);
break;
case SORT:
Sort(&con);
break;
case EXIT:
Save(&con);
Destory(&con);
printf("退出通讯录\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}