【C语言】通讯录

介绍 

通讯录是在学习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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值