C语言通讯录(自定义类型+动态内存分配+文件操作)

引言

在上一篇文章中我们了解了扫雷的C语言实现,相信大家对于实现这样的小项目已经有了基本的思路。

在本篇文章中,我们将实现通讯录。
与实现扫雷的思路类似:
我们首先要从需求出发,需要通讯录实现哪些功能;
再将框架实现出来;
然后进行具体的函数实现:

在实现通讯录前,需要了解以下知识(了解过的可以自行跳过):

文件操作

动态内存管理

指针进阶

qsort函数的应用

声明: 本文所讲述的代码中实现了通讯录的缩容。realloc在扩容的时候会出现异地扩容的情况,不断地缩容扩容就会很影响效率,所以我删去了自动缩容的操作,本篇文章没有改动,改动后的源码会在下一篇的参考源码中体现。

思路简述

通讯录功能需求

想必大家都对通讯录的功能是非常熟悉的,这里就不再赘述了。

对于这次的通讯录,我们主要实现以下功能:
通讯录的创建、增容、减容;
联系人的增加、删除、查找、排序、打印;
联系人数据在硬盘中的存储,下次打开通讯录时能够自动导入数据。

由于网络上找不到能够直观展示出通讯录功能的图片,这里就先将参考源码的运行结果放在前面:

增加联系人(空间不足时会增容):

在这里插入图片描述
查找联系人:

在这里插入图片描述
修改联系人:
在这里插入图片描述
打印联系人:
在这里插入图片描述
排序联系人:
在这里插入图片描述
删除联系人:
在这里插入图片描述
退出并保存:
在这里插入图片描述
再次打开时会导入已保存的联系人数据:
在这里插入图片描述

实现思路

就我们现在学到的知识,是能够完成这次通讯录的实现的:

在创建通讯录时:
首先,每一个联系人的信息都包括姓名、年龄、性别、联系方式等,所以我们需要创建一个结构体变量来存放成员的数据。
然后我们需要一个数组来存放许多联系人的信息,每一个元素都是存放联系人信息的结构体变量。
然后我们需要再次定义一个结构体类型。这个结构体类型中不仅包括存放联系人信息结构体的数组,还要包括已经存储的联系人个数以及通讯录当前的容量;
为了方便对通讯录的容量进行改变,这个数组的开辟需要使用动态内存函数来开辟。

我们可以通过realloc函数实现通讯录的增容与减容。

联系人的添加、删除、查找、排序、打印,我们只需要将需要添加的联系人的信息逐个输入到结构体变量,或是打印某个特定的结构体变量即可。

对于联系人数据在硬盘中的存储,我们需要用到文件操作的知识。

我们将这次的实现分在三个文件中:contact.h(常量以及函数的声明)、contact.c(函数的实现)、test.c(测试部分)
接下来,我们就开始实现这个通讯录:

实现

主体实现

首先,我们需要两个结构体变量:
结构体people存放联系人的信息(name、sex、age、addr、tele);
结构体contact存放元素类型为people的数组data、已存联系人的个数n、通讯录的容量capacity:
(为方便,将这两个结构体重命名)

typedef struct people
{
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;
	char addr[ADDR_MAX];
	char tele[TEL_MAX];
}people;
typedef struct contact
{
	int n;
	int capacity;
	people* data;
}contact;

同时需要动态开辟一个块空间,空间中的元素类型为people(这一步在初始化函数中进行)。

在主函数部分:
同样的,我们需要打印一个目录,并通过switch语句来分支这个目录中的功能。
不同于上一次的switch语句,我们可以使用枚举常量与switch结合的方式来提高代码的可读性;

并且在退出通讯录之前,需要这个程序循环进行,所以我们使用do_while语句实现循环:

//contact.h文件中定义枚举常量

enum operate
{
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT
};
#include"contact.h"
void menu()
{
	printf("******************\n");
	printf("****  通讯录  ****\n"); 
	printf("****  1. add  ****\n");
	printf("****  2. del  ****\n");
	printf("**** 3.search ****\n");
	printf("**** 4.modify ****\n");
	printf("****  5.show  ****\n");
	printf("****  6.sort  ****\n");
	printf("****  0.exit  ****\n");
	printf("******************\n");
}
int main()
{
	int input = 0;
	contact con;//定义contact结构体
	do
	{
		menu();
		printf("请选择:\n");
		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;
}

然后,我们将需要实现的函数填充到main函数与switch语句的分支中,它们分别为:
初始化通讯录:InitCon(contact* pcon);
添加联系人: AddPeo(contact* pcon);
删除联系人: DelPeo(contact* pcon);
打印查找的联系人:ShowSearchPeo(const contact* pcon);
打印全部联系人: ShowAllPeo(const contact* pcon);
修改联系人: ModifyPeo(contact* pcon);
排序联系人: SortContact(contact* pcon);
保存数据:SaveContact(contact* pcon);
销毁通讯录: DestroyCon(contact* pcon);
(见参考代码test.c部分)

接下来就详细介绍这些函数的实现:

函数实现

在实现这这些函数时,需要一个完备的逻辑。
这里我尽量将这个逻辑串起来,这就导致逻辑的顺序与代码的顺序不同,大家可以在明白逻辑之后在参考源码部分进行行汇总。

初始化通讯录 InitCon

我们已经定义了一个结构体变量contact,其中包括data指针、n、capacity。但是这些成员中的内容是人一直,所以需要对其初始化。
我们可以封装一个函数对这个结构体变量初始化:

在初始化函数InitCon中:

需要注意的是:包括InitCon在内,这些函数的参数都是contact*型的,也就是结构体指针。这样传参会更加节省空间)

首先assert断言结构体指针的有效性;

将capacity设置为初始的容量值,这个值在contact.h中定义:#define INIT_VALE 3
将n设置为0;
为数组data动态开辟空间(用calloc动态开辟空间时,会将空间内的数据初始化为0)。

最后,如果上次已经在硬盘中存储了一些数据,就需要将这些数据导入到内存中,并且封装为一个函数LoadCon(为不影响主要的逻辑,这个函数在后面介绍)。

void InitCon(contact* pcon)
{
	assert(pcon);
	pcon->n = 0;
	pcon->capacity = INIT_VALE;
	people* ptr = (people*)calloc(INIT_VALE, sizeof(people));
	if (ptr == NULL)
	{
		perror("InitCon::calloc");
		return;
	}
	pcon->data = ptr;
	LoadCon(pcon);
}

添加联系人 AddPeo

接下来实现通讯录的第一个功能:添加联系人:

这个函数的实现是比较简单的。

首先assert断言结构体指针的有效性;
再只需要通过contact*型的结构体指针找到其中的data数组,并给数组中的第n个结构体的每一个成员赋值即可。

但是,在这之前,我们需要通过比较已经存储的联系人个数n与data的容量capacity来判断是否需要增容。当个数与容量相同时即已经存满,需要增容。

我们可以将这个增容的实现封装为一个函数ChangeData:

增容/减容通讯录 ChangeData

在这个更改容量的函数中,我们不仅需要实现增容,也要能实现减容。
那我们应该怎样区分这两个不同的操作呢?
我们给这个ChangeData函数再增加一个参数state,这个参数可以表明需要执行的状态。state参数有两个可选择的常量值:MORE与LESS,这两个常量值在头文件contact.h中定义:

enum change
{
	LESS,
    MORE
};

然后,我们就可以通过realloc函数来更改data的大小了:

int ChangeData(contact* pcon,enum change state)
{
	//state不等于0时扩容,等于0时减容
	people* ptr = NULL;
	if (state)
	{
		ptr = (people*)realloc(pcon->data, (pcon->capacity + ADD_VALE)*sizeof(people));
		if (ptr == NULL)
		{
			perror("ChangeData::more::realloc");
			return 0;
		}
		else
		{
			pcon->data = ptr;
			pcon->capacity += ADD_VALE;
			return 1;
		}
	}
	else
	{
		ptr = (people*)realloc(pcon->data, (pcon->capacity - ADD_VALE)*sizeof(people));
		if (ptr == NULL)
		{
			perror("ChangeData::less::realloc");
			return 0;
		}
		else
		{
			pcon->data = ptr;
			pcon->capacity -= ADD_VALE;
			return 1;
		}
	}
}

需要注意的是,在增加或减少容量后,需要更改capacity的值。

函数嵌套后就得到了AddPeo函数:

void AddPeo(contact* pcon)
{
	int i = 0;
	assert(pcon);
	if (pcon->n == pcon->capacity)
	{
		i = ChangeData(pcon, MORE);//这里在需要时只会进行扩容,所以直接将MORE作为参数
		if (i)
		{
			printf("增容成功\n");
		}
		else
		{
			printf("增容失败。。。\n");
		}
	}
	printf("请输入姓名:");
	scanf("%s", (pcon->data[pcon->n]).name);
	printf("请输入性别:");
	scanf("%s", (pcon->data[pcon->n]).sex);
	printf("请输入年龄:");
	scanf("%d", &((pcon->data[pcon->n]).age));
	printf("请输入地址:");
	scanf("%s", (pcon->data[pcon->n]).addr);
	printf("请输入联系方式:");
	scanf("%s", (pcon->data[pcon->n]).tele);
	//存入数据后,要加一
	(pcon->n)++;
	printf("添加成功\n");
}

删除联系人 DelPeo

接下来实现第二个操作:删除联系人。

这个操作的实现也是比较简单的。
但是,删除联系人的前提是要找到指定的联系人。
(不仅在这里,在后面查找并打印指定联系人与修改联系人的操作时也需要先查找指定联系人,所以我们将查找联系人的操作也封装为一个函数SearchPeo)

查找联系人 SearchPeo

在查找联系人部分,我们通过逐一对比输入的联系人姓名与已经录入的列表中的每一个联系人的姓名而确认是否存在该联系人。
若存在该联系人,返回该联系人在data数组中的下标,否则返回-1:

int SearchPeo(const char* Fpeo,const contact* pcon)
{
	assert(Fpeo&&pcon);
	int i = 0;
	for (i = 0; i < pcon->n; i++)
	{
		int j = strcmp(pcon->data[i].name, Fpeo);
		if (j == 0)
		{
			return i;
		}
	}
	return -1;
}

在获取了需要查找的联系人的位置后,就可以将其删除。
我们可以通过将这个位置后一个位置联系人的信息拷贝到前一个联系人的位置的方式来实现删除联系人的操作。

当然,当通讯录当前的联系人数量为0时也是无法实现删除的。

void DelPeo(contact* pcon)
{
	assert(pcon);
	//若pcon->n为0则通讯录为空
	if (pcon->n == 0)
	{
		printf("通讯录为空,无法删除。。。\n");
		return;
	}
	int i = 0;
	char Fpeo[NAME_MAX] = { 0 };
	printf("请输入要删除的人的姓名\n");
	scanf("%s", Fpeo);
	i = SearchPeo(Fpeo, pcon);
	if (i != -1)
	{
		while (i < (pcon->n) - 1)
		{
			pcon->data[i] = pcon->data[i + 1];
			i++;
		}
		//删除数据后,要减一
		(pcon->n)--;
		printf("删除成功\n");
	}
	else
	{
		printf("查无此人。。。\n");
	}
	if ((pcon->capacity) - (pcon->n) > ADD_VALE)
	{
		i = ChangeData(pcon, LESS);
		if (i)
		{
			printf("减容成功\n");
		}
		else
		{
			printf("减容失败。。。\n");
		}
	}
}

修改联系人 ModifyPeo

在上面已经实现封装了查找联系人的操作后,我们就可以直接实现修改联系人的操作:

修改联系人的实现也比较简单,我们只需要将查找到的联系人该位置的数据重新输入即可:

当然,当通讯录当前的联系人数量为0时也是无法实现删除的。

void ModifyPeo(contact* pcon)
{
	assert(pcon);
	if (pcon->n == 0)
	{
		printf("通讯录为空,无法修改。。。\n");
		return;
	}
	int i = 0;
	char Fpeo[NAME_MAX] = { 0 };
	printf("请输入要修改的人的姓名:\n");
	scanf("%s", Fpeo);
	i = SearchPeo(Fpeo, pcon);
	if (i != -1)
	{
		printf("请输入修改后姓名:\n");
		scanf("%s", ((pcon->data)[i]).name);
		printf("请输入修改后性别:\n");
		scanf("%s", ((pcon->data)[i]).sex);
		printf("请输入修改后年龄\n");
		scanf("%d", &(((pcon->data)[i]).age));
		printf("请输入修改后地址\n");
		scanf("%s", ((pcon->data)[i]).addr);
		printf("请输入修改后联系方式:\n");
		scanf("%s", ((pcon->data)[i]).tele);
		printf("修改完成\n");
	}
	else
	{
		printf("查无此人。。。\n");
	}
}

需要注意的是:删除联系人后需要将n减1。

打印查找的联系人 ShowSearchPeo

之后,我们紧接着就来实现打印查找的联系人。

当我们通过SearchPeo获取到需要查找的联系人的位置后,就分别打印联系人的每一个信息即可(people结构体中的成员)。

这里在分享一种思路:
我们在打印people结构体中的变量时,写的代码比较多,并且在后面的打印所有成员的操作中也需要将打印的代码写一遍,会比较冗余。所以,我们可以将这个打印操作封装为一个函数ShowNumPeo。
这个函数可以实现将从指定位置开始的num个结构体元素打印在屏幕上:

打印指定个数联系人 ShowNumPeo

这个函数接收三个参数:
第一个参数const contact* pcon,表示结构体con的指针;
第二个参数int i,表示打印的起始位置;
第三个参数int num,表示从指定位置后打印num个元素。

打印时:
%-nd可以实现这个数据打印时向左对齐n位,"\t"为水平制表符。

void ShowNumPeo(const contact* pcon, int i, int num)
{
	assert(pcon);
	int k = 0;//用于循环
	//打印表头
	printf("%-2s\t%-10s\t%-7s\t%-10s\t%-20s\t%-12s\n", "  ", "姓名", "性别", "年龄", "地址", "电话");
	//打印数据
	for (k = 0; k < num; k++)
	{
		printf("%-2d\t%-10s\t%-7s\t%-10d\t%-20s\t%-12s\n", i + 1, (pcon->data[i]).name,
			(pcon->data[i]).sex,
			(pcon->data[i]).age,
			(pcon->data[i]).addr,
			(pcon->data[i]).tele);
		i++;
	}
}

有了这个函数,我们就可以很轻松的打印指定的某一个联系人的信息(num为1):

void ShowSearchPeo(const contact* pcon)
{
	assert(pcon);
	//若pcon->n为0则通讯录为空
	if (pcon->n == 0)
	{
		printf("通讯录为空,无法查询。。。\n");
		return;
	}
	char Fpeo[NAME_MAX] = { 0 };
	int i = 0;
	printf("请输入要查询的人的姓名:\n");
	scanf("%s", Fpeo);
	i = SearchPeo(Fpeo, pcon);
	if (i != -1)
	{
		//用ShowNumPeo函数打印
		ShowNumPeo(pcon, i, 1);
	}
	else
	{
		printf("查无此人。。。\n");
	}
}

打印全部联系人 ShowAllPeo

同时,借助ShowNumPeo函数,我们也可以很轻松的实现打印全部的联系人(num与已存储的联系人数量相同):

void ShowAllPeo(const contact* pcon)
{
	assert(pcon);
	//用ShowNumPeo函数打印
	ShowNumPeo(pcon, 0, pcon->n);
}

排序联系人 SortContact

接下来实现排序联系人的操作:

排序联系人需要借助库函数sqort,并且我们要实现4个排序的标准(姓名、性别、年龄、地址)。这就要求我们实现4中cmp函数作为判断依据。

这里我使用了函数指针数组来存放实现的4中种判断依据int(*cmp[4])(const void*, const void*) = { cmp_name, cmp_sex, cmp_age, cmp_addr };。这样可以大大减少代码的冗余。

int cmp_name(const void* e1, const void* e2)
{
	return memcmp(((people*)e1)->name, ((people*)e2)->name, NAME_MAX);
}
int cmp_sex(const void* e1, const void* e2)
{
	return memcmp(((people*)e1)->sex, ((people*)e2)->sex, SEX_MAX);
}
int cmp_age(const void* e1, const void* e2)
{
	return ((people*)e1)->age - ((people*)e2)->age;
}
int cmp_addr(const void* e1, const void* e2)
{
	return memcmp(((people*)e1)->addr, ((people*)e2)->addr, ADDR_MAX);
}

void SortContact(contact* pcon)
{
	assert(pcon);
	int i = 0;
	int(*cmp[4])(const void*, const void*) = { cmp_name, cmp_sex, cmp_age, cmp_addr };
	do
	{
		printf("请选择排序依据:\n");
		printf("----------------\n");
		printf("---- 1.姓名 ----\n");
		printf("---- 2.性别 ----\n");
		printf("---- 3.年龄 ----\n");
		printf("---- 4.地址 ----\n");
		printf("----------------\n");
		scanf("%d", &i);
		if (i > 4 || i < 1)
		{
			printf("非法选择,请重新输入:\n");
		}
		else
		{
			qsort(pcon->data, pcon->n, sizeof(pcon->data[0]), cmp[i - 1]);
			printf("排序完成\n");
			i = 0;
			break;
		}

	} while (i);
}

文件操作部分

在实现了对联系人的所有操作后,我们就要实现将内存中的数据存储到硬盘的文件中
在下次打开程序后,硬盘中的数据能够自动加载到内存中。

保存数据 SaveContact

首先,"wb"新建一个二进制文件;
然后使用fwrite函数将内存中的数据写入到文件中;
最后fclose关闭文件。

void SaveContact(contact* pcon)
{
	//写数据
	//打开文件
	FILE* pf = fopen("contact.txt", "wb");
	if (pf == NULL)
	{
		perror("SaveContact");
	}
	else
	{
		int i = 0;
		for (i = 0; i < pcon->n; i++)
		{
			fwrite(pcon->data + i, sizeof(people), 1, pf);
		}
		//关闭文件
		fclose(pf);
		pf = NULL;
		printf("保存成功\n");
	}
}
加载通讯录 LoadCon

然后,我们需要实现将文件中的数据导入到内存中:

首先,"rb"打开二进制文件;
然后使用库函数fread将联系人逐个写入内存中(注意,这里需要逐个写入的原因是需要判断之前的联系人个数需不需要增容,以实现及时增容);
并且在每读取一个联系人的数据后,需要将n加1;
最后,关闭文件。

void LoadCon(contact* pcon)
{
	//读数据
	//打开文件
	FILE* pf = fopen("contact.txt", "rb");
	if (pf == NULL)
	{
		perror("LoadCon::fopen");
	}
	else
	{
		people temp = { 0 };
		int i = 0;
		while (fread(&temp, sizeof(people), 1, pf))//读取的数据不足1个时结束循环
		{
			if (pcon->n == pcon->capacity)
			{
				ChangeData(pcon, MORE);
			}
			pcon->data[i] = temp;
			pcon->n++;
			i++;
		}
		//关闭文件
		fclose(pf);
	}
}

销毁通讯录 DestroyCon

在进行完毕所以的操作后,不要忘了我们的空间是动态分配的,所以需要使用库函数free释放动态空间,以免出现内存泄漏。

void DestroyCon(contact* pcon)
{
	pcon->capacity = 0;
	pcon->n = 0;
    free(pcon->data);
	pcon->data = NULL;
	pcon = NULL;
}

到此,这个小项目的介绍就结束了。
当然,contact.h文件中还声明了一些其他的常量,这些常量被用于创建数组等,大家可以在参考源码部分看到。

参考源码

//contact.h

#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>



#define INIT_VALE 3
#define ADD_VALE 2
#define NAME_MAX 20
#define SEX_MAX 7
#define ADDR_MAX 20
#define TEL_MAX 12



enum operate
{
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT
};
enum change
{
	LESS,
    MORE
};
typedef struct people
{
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;
	char addr[ADDR_MAX];
	char tele[TEL_MAX];
}people;
typedef struct contact
{
	int n;
	int capacity;
	people* data;
}contact;


//初始化通讯录
void InitCon(contact* pcon);
//加载通讯录
void LoadCon(contact* pcon);
//增容/减容通讯录
int ChangeData(contact* pcon,enum change state);
//查找联系人
int SearchPeo(const char* Fpeo, const contact* pcan);
//打印指定个数联系人
void ShowNumPeo(const contact* pcon, int i, int num);
//添加联系人
void AddPeo(contact* pcon);
//删除联系人
void DelPeo(contact* pcon);
//打印查找的联系人
void ShowSearchPeo(const contact* pcon);
//打印全部联系人
void ShowAllPeo(const contact* pcon);
//修改联系人
void ModifyPeo(contact* pcon);
//排序联系人
void SortContact(contact* pcon);
//保存数据
void SaveContact(contact* pcon);
//销毁通讯录
void DestroyCon(contact* pcon);

//contact.c

#include"contact.h"

void InitCon(contact* pcon)
{
	assert(pcon);
	pcon->n = 0;
	pcon->capacity = INIT_VALE;
	people* ptr = (people*)calloc(INIT_VALE, sizeof(people));
	if (ptr == NULL)
	{
		perror("InitCon::calloc");
		return;
	}
	pcon->data = ptr;
	LoadCon(pcon);
}

void LoadCon(contact* pcon)
{
	//读数据
	//打开文件
	FILE* pf = fopen("contact.txt", "rb");
	if (pf == NULL)
	{
		perror("LoadCon::fopen");
	}
	else
	{
		people temp = { 0 };
		int i = 0;
		while (fread(&temp, sizeof(people), 1, pf))//读取的数据不足1个时结束循环
		{
			if (pcon->n == pcon->capacity)
			{
				ChangeData(pcon, MORE);
			}
			pcon->data[i] = temp;
			pcon->n++;
			i++;
		}
		//关闭文件
		fclose(pf);
	}
}

int ChangeData(contact* pcon,enum change state)
{
	//state不等于0时扩容,等于0时减容
	people* ptr = NULL;
	if (state)
	{
		ptr = (people*)realloc(pcon->data, (pcon->capacity + ADD_VALE)*sizeof(people));
		if (ptr == NULL)
		{
			perror("ChangeData::more::realloc");
			return 0;
		}
		else
		{
			pcon->data = ptr;
			pcon->capacity += ADD_VALE;
			return 1;
		}
	}
	else
	{
		ptr = (people*)realloc(pcon->data, (pcon->capacity - ADD_VALE)*sizeof(people));
		if (ptr == NULL)
		{
			perror("ChangeData::less::realloc");
			return 0;
		}
		else
		{
			pcon->data = ptr;
			pcon->capacity -= ADD_VALE;
			return 1;
		}
	}
}
int SearchPeo(const char* Fpeo,const contact* pcon)
{
	assert(Fpeo&&pcon);
	int i = 0;
	for (i = 0; i < pcon->n; i++)
	{
		int j = strcmp(pcon->data[i].name, Fpeo);
		if (j == 0)
		{
			return i;
		}
	}
	return -1;
}

void ShowNumPeo(const contact* pcon, int i, int num)
{
	assert(pcon);
	int k = 0;//用于循环
	//打印表头
	printf("%-2s\t%-10s\t%-7s\t%-10s\t%-20s\t%-12s\n", "  ", "姓名", "性别", "年龄", "地址", "电话");
	//打印数据
	for (k = 0; k < num; k++)
	{
		printf("%-2d\t%-10s\t%-7s\t%-10d\t%-20s\t%-12s\n", i + 1, (pcon->data[i]).name,
			(pcon->data[i]).sex,
			(pcon->data[i]).age,
			(pcon->data[i]).addr,
			(pcon->data[i]).tele);
		i++;
	}
}

void AddPeo(contact* pcon)
{
	int i = 0;
	assert(pcon);
	if (pcon->n == pcon->capacity)
	{
		i = ChangeData(pcon, MORE);
		if (i)
		{
			printf("增容成功\n");
		}
		else
		{
			printf("增容失败。。。\n");
		}
	}
	printf("请输入姓名:");
	scanf("%s", (pcon->data[pcon->n]).name);
	printf("请输入性别:");
	scanf("%s", (pcon->data[pcon->n]).sex);
	printf("请输入年龄:");
	scanf("%d", &((pcon->data[pcon->n]).age));
	printf("请输入地址:");
	scanf("%s", (pcon->data[pcon->n]).addr);
	printf("请输入联系方式:");
	scanf("%s", (pcon->data[pcon->n]).tele);
	//存入数据后,要加一
	(pcon->n)++;
	printf("添加成功\n");
}

void DelPeo(contact* pcon)
{
	assert(pcon);
	//若pcon->n为0则通讯录为空
	if (pcon->n == 0)
	{
		printf("通讯录为空,无法删除。。。\n");
		return;
	}
	int i = 0;
	char Fpeo[NAME_MAX] = { 0 };
	printf("请输入要删除的人的姓名\n");
	scanf("%s", Fpeo);
	i = SearchPeo(Fpeo, pcon);
	if (i != -1)
	{
		while (i < (pcon->n) - 1)
		{
			pcon->data[i] = pcon->data[i + 1];
			i++;
		}
		//删除数据后,要减一
		(pcon->n)--;
		printf("删除成功\n");
	}
	else
	{
		printf("查无此人。。。\n");
	}
	if ((pcon->capacity) - (pcon->n) > ADD_VALE)
	{
		i = ChangeData(pcon, LESS);
		if (i)
		{
			printf("减容成功\n");
		}
		else
		{
			printf("减容失败。。。\n");
		}
	}
}

void ShowSearchPeo(const contact* pcon)
{
	assert(pcon);
	//若pcon->n为0则通讯录为空
	if (pcon->n == 0)
	{
		printf("通讯录为空,无法查询。。。\n");
		return;
	}
	char Fpeo[NAME_MAX] = { 0 };
	int i = 0;
	printf("请输入要查询的人的姓名:\n");
	scanf("%s", Fpeo);
	i = SearchPeo(Fpeo, pcon);
	if (i != -1)
	{
		//用ShowNumPeo函数打印
		ShowNumPeo(pcon, i, 1);
	}
	else
	{
		printf("查无此人。。。\n");
	}
}

void ShowAllPeo(const contact* pcon)
{
	assert(pcon);
	//用ShowNumPeo函数打印
	ShowNumPeo(pcon, 0, pcon->n);
}

void ModifyPeo(contact* pcon)
{
	assert(pcon);
	if (pcon->n == 0)
	{
		printf("通讯录为空,无法修改。。。\n");
		return;
	}
	int i = 0;
	char Fpeo[NAME_MAX] = { 0 };
	printf("请输入要修改的人的姓名:\n");
	scanf("%s", Fpeo);
	i = SearchPeo(Fpeo, pcon);
	if (i != -1)
	{
		printf("请输入修改后姓名:\n");
		scanf("%s", ((pcon->data)[i]).name);
		printf("请输入修改后性别:\n");
		scanf("%s", ((pcon->data)[i]).sex);
		printf("请输入修改后年龄\n");
		scanf("%d", &(((pcon->data)[i]).age));
		printf("请输入修改后地址\n");
		scanf("%s", ((pcon->data)[i]).addr);
		printf("请输入修改后联系方式:\n");
		scanf("%s", ((pcon->data)[i]).tele);
		printf("修改完成\n");
	}
	else
	{
		printf("查无此人。。。\n");
	}
}
int cmp_name(const void* e1, const void* e2)
{
	return memcmp(((people*)e1)->name, ((people*)e2)->name, NAME_MAX);
}
int cmp_sex(const void* e1, const void* e2)
{
	return memcmp(((people*)e1)->sex, ((people*)e2)->sex, SEX_MAX);
}
int cmp_age(const void* e1, const void* e2)
{
	return ((people*)e1)->age - ((people*)e2)->age;
}
int cmp_addr(const void* e1, const void* e2)
{
	return memcmp(((people*)e1)->addr, ((people*)e2)->addr, ADDR_MAX);
}

void SortContact(contact* pcon)
{
	assert(pcon);
	int i = 0;
	int(*cmp[4])(const void*, const void*) = { cmp_name, cmp_sex, cmp_age, cmp_addr };
	do
	{
		printf("请选择排序依据:\n");
		printf("----------------\n");
		printf("---- 1.姓名 ----\n");
		printf("---- 2.性别 ----\n");
		printf("---- 3.年龄 ----\n");
		printf("---- 4.地址 ----\n");
		printf("----------------\n");
		scanf("%d", &i);
		if (i > 4 || i < 1)
		{
			printf("非法选择,请重新输入:\n");
		}
		else
		{
			qsort(pcon->data, pcon->n, sizeof(pcon->data[0]), cmp[i - 1]);
			printf("排序完成\n");
			i = 0;
			break;
		}

	} while (i);
}

void SaveContact(contact* pcon)
{
	//写数据
	//打开文件
	FILE* pf = fopen("contact.txt", "wb");
	if (pf == NULL)
	{
		perror("SaveContact");
	}
	else
	{
		int i = 0;
		for (i = 0; i < pcon->n; i++)
		{
			fwrite(pcon->data + i, sizeof(people), 1, pf);
		}
		//关闭文件
		fclose(pf);
		pf = NULL;
		printf("保存成功\n");
	}
}

void DestroyCon(contact* pcon)
{
	pcon->capacity = 0;
	pcon->n = 0;
    free(pcon->data);
	pcon->data = NULL;
	pcon = NULL;
}


//test.c

#include"contact.h"
void menu()
{
	printf("******************\n");
	printf("****  通讯录  ****\n"); 
	printf("****  1. add  ****\n");
	printf("****  2. del  ****\n");
	printf("**** 3.search ****\n");
	printf("**** 4.modify ****\n");
	printf("****  5.show  ****\n");
	printf("****  6.sort  ****\n");
	printf("****  0.exit  ****\n");
	printf("******************\n");
}
int main()
{
	int input = 0;
	contact con;
	InitCon(&con);
	do
	{
		menu();
		printf("请选择:\n");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			AddPeo(&con);
			break;
		case DEL:
			DelPeo(&con);
			break;
		case SEARCH:
			ShowSearchPeo(&con);
			break;
		case MODIFY:
			ModifyPeo(&con);
			break;
		case SHOW:
			ShowAllPeo(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case EXIT:
			//保存通讯录信息到文件中
			SaveContact(&con);
			//释放
			DestroyCon(&con);
			printf("退出通讯录。\n");
			break;
		default:
			printf("选择非法,请重新输入。。。\n");
			break;
		}
	} while (input);
	
	return 0;
}

源码的运行结果在前面的部分已经展示过了,在这里就不再重复粘贴。

总结

又是一篇小项目的介绍结束了
相信大家在了解了通讯录的实现后会对之前所学过的知识理解更加深刻

尽管我在很多的方面简化了说明,最后的篇幅依然很长
如果大家认为我对某一部分没有讲解清楚或者某一部分出了问题,欢迎大家在评论区提出

代码在我的测试中目前还没有发现问题,若大家在测试过程中发现了任何的bug请在评论区指出,谢谢大家(参考源码在文末有展示,稍后会再将源码发布,方便大家测试)

如果本文对你有帮助,希望一键三连哦

希望与大家共同进步哦

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿qiu不熬夜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值