通讯录的实现【涉及动态内存管理和文件操作】【从易到难】【详解】

news2024/10/7 20:37:12

在这里插入图片描述

本期介绍🍖
主要介绍:如何实现一个通讯录,从静态版通讯录,到动态内存版通讯录,再到文件存储版通讯录,详细讲述了每一个通讯录的实现步骤以及思维逻辑,以及通讯录的完整代码👀。


文章目录

  • 一、通讯录(静态版)🍖
    • 1.1 如何存放通讯录🍖
    • 1.2 通讯录菜单🍖
    • 1.3 初始化通讯录🍖
    • 1.4 实现添加新成员🍖
    • 1.5 打印全部成员🍖
    • 1.6 查找联系人🍖
    • 1.7 删除联系人🍖
    • 1.8 修改联系人信息🍖
    • 1.9 排序联系人🍖
  • 二、通讯录(动态内存版)🍖
    • 2.1 通讯录的结构体声明🍖
    • 2.2 通讯录的初始化🍖
    • 2.3 通讯录添加新成员🍖
    • 2.4 销毁通讯录🍖
  • 三、通讯录(文件操作版)🍖
    • 3.1 保存通讯录🍖
    • 3.2 加载通讯录🍖
  • 四、完整的代码🍖


前言

  现要求使用C语言实现一个能够存放1000个人信息的通讯录,每个人的信息包括:姓名、年龄、性别、电话、地址。且要求这个通讯录能够实现:

  1. 添加联系人
  2. 删除联系人
  3. 查找联系人
  4. 修改联系人
  5. 显示所有联系人
  6. 排序联系人

一、通讯录(静态版)🍖

1.1 如何存放通讯录🍖

  首先,我们需要来思考一下什么是通讯录,以及我们该使用什么变量来存放通讯录。通讯录就是一个由联系人组成的集合,且每一个联系人包含了5个不同类型的信息。故我们不难得出可以用一个结构体数组来存放整个通讯录。但值得注意的是,在之后应用中我们随时需要知道通讯录中已经存在几个联系人,所以可以将通讯录设置成如下的样式:

//个人信息
#define NAME_MAX 20//姓名所占字节大小
#define SEX_MAX 5//性别所占字节大小
#define TELE_MAX 12//电话所占字节大小
#define ADDR_MAX 30//地址所占字节大小
#define NUM_MAX 1000//通讯录存储人员个数大小

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[NUM_MAX];//通讯录人员信息
	int num;//通讯录种已存放人员个数
}Contact;

  这样做大大简化了函数的构思。举个例子:当一个函数需要知道通讯录当下已经存放了几个成员时,就不需要再通过函数传参、以及函数返回值来实时的更新人员个数,仅仅只需要传一个通讯录变量的地址给函数,然后在函数内部就能完成全部的操作。

  注意:这里使用#define来定义那些常量的目的是,在统一的运用后,可以统一的进行修改(只需要在#define后进行修改就可以了)。


1.2 通讯录菜单🍖

//菜单
static void menu()
{
	printf("\n");
	printf("*****************************************************\n");
	printf("********  1.增加新成员        2.删除成员      *******\n");
	printf("********  3.查找成员          4.修改成员信息  *******\n");
	printf("********  5.显示所有成员信息  6.排序          *******\n");
	printf("********  0.退出                              *******\n");
	printf("*****************************************************\n");
	printf("\n");

}

enum Option
{
	Exit,
	Add,
	Del,
	Search,
	Modify,
	Show,
	Sort
};

int main()
{
	//1.创建一个通讯录
	Contact con;
	//2.初始化通讯录
	InitContact(&con);
	//3.实现菜单
	int input = 0;
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case Add:
			Add_Contact(&con);//增加新成员
			break;
		case Del:
			Del_Contact(&con);//删除成员
			break;
		case Search:
			Search_Contact(&con);//查找成员
			break;
		case Modify:
			Modify_Contact(&con);//修改成员信息
			break;
		case Show:
			Show_Contact(&con);//显示所有成员信息
			break;
		case Sort:
			Sort_Contact(&con);//成员排序
			break;
		case Exit:
			printf("已退出通讯录\n");
			break;
		default:
			printf("输入错误请重新输入:\n");
			break;
		}
	} while (input);
	return 0;
}

  注意:这里设置一个枚举类型enum Option的目的是:在接下来的switch case语句中可以用枚举类型成员名来代替数字了,这样做跟容易联想,不需要在思考数字所对应的功能是什么了。


1.3 初始化通讯录🍖

//初始化通讯录
void InitContact(Contact* con)
{
	//初始化data
	memset(con, 0, sizeof(struct PeoInfo) * NUM_MAX);
	//初始化num
	con->num = 0;
}

  初始化通讯录其实只要将,通讯录变量中的datanum部分分别初始化就可以了。对于data部分我们是通过内存设置函数memset() 来实现的,该函数的参数为:

  1. 将要被设置内存的起始位置
  2. 每个字节被设置的值
  3. 被设置的内存一共占多少个字节

  所以不难得出,data的起始地址为con,每个字节需要被设为0data一共占用sizeof(struct PeoInfo) * NUM_MAX个字节的大小(也就是1000个联系人的大小)。


1.4 实现添加新成员🍖

void Add_Contact(Contact* con)
{
	assert(con);
	printf("添加新成员\n");
	//输入一个人的信息
	printf("请输入姓名:");
	scanf("%s", con->data[con->num].name);
	printf("请输入年龄:");
	scanf("%d", &(con->data[con->num].age));
	printf("请输入性别:");
	scanf("%s", con->data[con->num].sex);
	printf("请输入电话:");
	scanf("%s", con->data[con->num].tele);
	printf("请输入地址:");
	scanf("%s", con->data[con->num].addr);
	//通讯录成员个数加一
	con->num++;
}

1.5 打印全部成员🍖

//显示所有成员信息
void Show_Contact(const Contact* con)
{
	assert(con);
	if (con->num == 0)
	{
		printf("通信录为空\n");
		return;
	}
	int i = 0;
	for (i = 0; i < con->num; i++)
	{
		printf("%20s %5d %5s %15s %30s\n", 
			con->data[i].name,
			con->data[i].age,
			con->data[i].sex,
			con->data[i].tele,
			con->data[i].addr);
	}
}

在这里插入图片描述


1.6 查找联系人🍖

//查找函数
int Search(const Contact* con, char name[])
{
	assert(con);
	int i = 0;
	for (i = 0; i < con->num; i++)
	{
		if (0 == strcmp(con->data[i].name, name))
			return i;
	}
	return -1;
}

//查找联系人
void Search_Contact(const Contact* con)
{
	assert(con);
	char name[20] = { 0 };
	int flag = 0;
	printf("请输入需要查找的姓名:");
	scanf("%s", name);

	//Search函数为查找函数
	//若没有找到则返回-1,若找到了则返回该成员所在下标
	flag = Search(con, name);

	if (flag < 0)
	{
		printf("未找到该成员\n");
	}
	else
	{
		printf("%20s %5d %5s %15s %30s\n",
			con->data[flag].name,
			con->data[flag].age,
			con->data[flag].sex,
			con->data[flag].tele,
			con->data[flag].addr);
	}
}

在这里插入图片描述

  注意:这里的查找函数int Search(const Contact* con, char name[])会在之后的删除功能修改功能中用到,因为要想删除和修改前提条件是能够找到呀。


1.7 删除联系人🍖

//删除成员
void Del_Contact(Contact* con)
{
	assert(con);
	char name[20] = { 0 };
	int flag = 0;
	printf("请输入要删除人的姓名:");
	scanf("%s", name);

	//1.查找确定这个人是否存在
	flag = Search(con, name);
	if (flag < 0)
	{
		printf("未找到此人\n");
		return;
	}

	//2.实施删除操作
	con->data[flag] = con->data[con->num - 1];
	con->num--;
	printf("完成删除操作\n");
}

在这里插入图片描述

  注意:此处是通过现找到需要被删除的联系人,然后用最后一个联系人覆盖掉它,最后将现有联系人个数num--。那么下一次录入新成员时就会将之前的最后一个联系人覆盖掉,这样通过覆盖的方式不就间接的实现的删除操作了嘛!


1.8 修改联系人信息🍖

//修改成员信息
void Modify_Contact(Contact* con)
{
	assert(con);
	char name[20] = { 0 };
	int flag = 0;
	printf("请输入需要被修改的成员:");
	scanf("%s", name);
	
	//1.查找这个人是否存在
	flag = Search(con, name);
	if (flag < 0)
	{
		printf("未找到该此人\n");
		return;
	}

	//2.对该成员进行修改
	printf("开始修改\n");
	//输入一个人的信息
	printf("请输入姓名:");
	scanf("%s", con->data[flag].name);
	printf("请输入年龄:");
	scanf("%d", &(con->data[flag].age));
	printf("请输入性别:");
	scanf("%s", con->data[flag].sex);
	printf("请输入电话:");
	scanf("%s", con->data[flag].tele);
	printf("请输入地址:");
	scanf("%s", con->data[flag].addr);
}

在这里插入图片描述


1.9 排序联系人🍖

  要想排序联系人我们可以通过5种不同的方式来进行排序:姓名、年龄、性别、电话、地址。所以这里需要让使用者来进行选择,然后再通过不同的选择实现不同的排序。但问题来了,该怎么编写代码才不会出现冗余现象呢?我们发现排序函数的框架是不需要改变的(这里的框架表示:冒泡排序),只需要对元素比较部分进行不同样式的修改,就可以实现同一个函数不同样式的排序了,因为排列的先后顺序就是由比较函数所决定的举个例子:比较的姓名,那么排列的顺序必然是按照姓名顺序排列的。给出两个建议:

  1. 使用回调函数(函数指针)
  2. 使用转移表(函数指针数组)

  这里我是使用转移表来实现的,转移表就是通过将函数指针数组的下标排序功能的序号联系起来,从而做到选择什么排序就能过通过函数指针数组找到所选择的排序函数,最终实现输入什么就按照该功能进行排序。就譬如这里,我选择按照姓名排序,输入:1,就可以找到下标为1的函数指针数组所对应的比较函数(比较两个PeoInfo结构体中姓名的大小),最后将该比较函数的地址传递给排序函数,完成通讯录的排序。

//成员排序

//菜单
static void menu()
{
	putchar('\n');
	printf("*****************************\n");
	printf("******  1. 按姓名排序  ******\n");
	printf("******  2. 按年龄排序  ******\n");
	printf("******  3. 按性别排序  ******\n");
	printf("******  4. 按电话排序  ******\n");
	printf("******  5. 按地址排序  ******\n");
	printf("*****************************\n");
	putchar('\n');
}

//比较函数
int CmpName(PeoInfo* s1, PeoInfo* s2)
{
	assert(s1 && s2);
	return strcmp(s1->name, s2->name);
}

int CmpAge(PeoInfo* s1, PeoInfo* s2)
{
	assert(s1 && s2);
	return s1->age - s2->age;
}

int CmpSex(PeoInfo* s1, PeoInfo* s2)
{
	assert(s1 && s2);
	return strcmp(s1->sex, s2->sex);
}

int CmpTele(PeoInfo* s1, PeoInfo* s2)
{
	assert(s1 && s2);
	return strcmp(s1->tele, s2->tele);
}

int CmpAddr(PeoInfo* s1, PeoInfo* s2)
{
	assert(s1 && s2);
	return strcmp(s1->addr, s2->addr);
}

//排序函数
void peo_sort(Contact* con, int cmp(PeoInfo* s1, PeoInfo* s2))
{
	int i = 0;
	int flag = 0;//记录是否以有序
	//趟数
	for (i = 0; i < con->num - 1; i++)
	{
		int j = 0;
		//每趟的交换的次数
		for (j = 0; j < con->num - 1 - i; j++)
		{
			//升序排放
			if (cmp(&(con->data[j]), &(con->data[j + 1])) > 0)
			{
				//交换两个元素
				PeoInfo tmp = con->data[j];
				con->data[j] = con->data[j + 1];
				con->data[j + 1] = tmp;

				//此时数组任然是乱序的
				flag = 1;
			}
		}
		//若flag在一趟排序中始终没有置1,则说明此时数组已然有序
		if (flag == 0)
			break;
	}
}

void Sort_Contact(Contact* con)
{
	//创建函数指针数组(用于实现转移表)
	int (*cmp_arr[6])(PeoInfo*, PeoInfo*) = { 0, CmpName, CmpAge, CmpSex, CmpTele, CmpAddr };
	int input = 0;
	do
	{
		menu();
		printf("请选择要以什么方式进行排序:");
		scanf("%d", &input);
		if (input >= 1 && input <= 5)
		{
			//冒泡排序
			peo_sort(con, cmp_arr[input]);
			printf("完成排序\n");
			break;
		}
		else
		{
			printf("输入错误请重新输入\n");
		}
	} while (input);
}

姓名排序
在这里插入图片描述
年龄排序
在这里插入图片描述
性别排序
在这里插入图片描述
电话排序
在这里插入图片描述
地址排序
在这里插入图片描述


二、通讯录(动态内存版)🍖

  上面实现的(静态版)通讯录对于内存的使用太死板不够灵活为什么这么说呢?举个例子:由于上面实现的通讯录一开始就开辟了能够存放1000人信息的空间,当仅需使用该通讯录来存放3个成员信息时,剩余的997个空间不就浪费掉了,而且及其奢侈,而我们又无法消除这种浪费;同样如果需要存放的成员信息多于1000个时,该通讯录就无法适用了,因为初始化时向内存申请的空间是固定死的,只有1000个。

  那怎样才能解决上述难题呢? 答案是使用动态内存来实现这个通讯录,如此就可以做到,内存不够用时对内存进行扩容,内存过大时对内存进行削减,实现对内存的利用最大化。总结,动态内存管理就是通过对内存的动态变化,使得内存的利用率变高的方法


2.1 通讯录的结构体声明🍖

  静态版通讯录结构体由两部份组成,其一是1000个成员信息data,其二是已经存放人数num,如下所示。

//通讯录
typedef struct Contact
{
	PeoInfo data[NUM_MAX];//通讯录人员信息
	int num;//通讯录中已存放人员个数
}Contact;

  而这种声明方式必然会导致上述难题的出现,而为了能够灵活使用内存,我们应将通讯录声明设计成如下形式:

//通讯录
typedef struct Contact
{
	PeoInfo* data;//通讯录人员信息
	int num;//通讯录中已存放人员个数
	int Capacity;//通讯录容量
}Contact;

  其中data为一个指针,该指针是用来维护之后动态申请的那块空间的。而多出来的这个成员变量Capacity用以表示当前通讯录的容量。对应的内存布局如下图所示:

在这里插入图片描述


2.2 通讯录的初始化🍖

  首先需要用calloc函数向内存申请一块能够存放3个成员信息的空间,并使得data指针来维护这块空间。然后将通讯录中已存放人员个数num和通讯录容量capacity初始化一下。

void InitContact(Contact* con)
{
	//向内存申请一块能够存放3个成员的空间,并使得data指针来维护这块空间
	con->data = (PeoInfo*)calloc(CON_NUM, sizeof(PeoInfo));
	if (con->data == NULL)
	{
		printf("InitContact:%s\n", strerror(errno));
		return;
	}
	//初始化num和Capacity
	con->num = 0;
	con->Capacity = CON_NUM;//宏定义CON_NUM = 3;
}

2.3 通讯录添加新成员🍖

  初始化时设定的通讯录容量为3个,当存放第4个成员信息前,就必须对通讯录的容量进行增容。而增容分为两部分:其一,对动态开辟的空间进行调整;其二,对结构体成员Capacity增加2。接着才能继续录入人员信息,直至下一次容量不够用时再对其扩容,如此往复。代码如下:

void CheckCapacity(Contact* con)
{
	assert(con);
	//判断已存放人数是否已超出容量
	if (con->num == con->Capacity)
	{
		//通讯录扩容(扩容2个成员大小)
		PeoInfo* ptr = (PeoInfo*)realloc(con->data, (con->Capacity + INC_SZ) * sizeof(PeoInfo));
		if (ptr == NULL)
		{
			printf("CheckCapacity::%s\n", strerror(errno));
			return;
		}
		else
		{
			con->data = ptr;
			con->Capacity += INC_SZ;//宏定义INC_SZ = 2;
			printf("增容成功\n");
		}
	}
}

//添加新成员
void Add_Contact(Contact* con)
{
	assert(con);
	//检查通讯录
	CheckCapacity(con);

	printf("添加新成员\n");
	//输入一个人的信息
	printf("请输入姓名:");
	scanf("%s", con->data[con->num].name);
	printf("请输入年龄:");
	scanf("%d", &(con->data[con->num].age));
	printf("请输入性别:");
	scanf("%s", con->data[con->num].sex);
	printf("请输入电话:");
	scanf("%s", con->data[con->num].tele);
	printf("请输入地址:");
	scanf("%s", con->data[con->num].addr);
	//通讯录成员个数加一
	con->num++;
}

在这里插入图片描述


2.4 销毁通讯录🍖

  当我们结束对通讯录的操作后,是需要把之前动态内存开辟的空间还给操作系统,也就是把通讯录结构体中成员变量data指向的那块空间释放掉。如图所示,在test.c文件末尾加上销毁通讯录的操作。

在这里插入图片描述

//销毁通讯录
void DestoryContact(Contact* con)
{
	assert(con);
	free(con->data);
	con->data = NULL;
}

三、通讯录(文件操作版)🍖

  之前实现的(动态内存版)通讯录确实提高了内存的利用率,但是不知道大家在有没有发现一个问题:当程序运行起来后,我们向通讯录中增加了一些成员变量,而且确确实实的保存到通讯录中了,接着把通讯录关掉然后再次打开,就会发现其中一个成员的信息都没有了。为什么呢

  因为当程序运行起来后,如果我们没有将这些数据存储起来的话,它们只是在内存中暂时的存放。一旦程序结束,程序运行时所占用的空间就会被回收,那些数据自然相当于销毁了。 当下一次再运行时里面当然没有数据了,故我们又得重新录入数据,但这是不是有点太不人性化了呀!所以为了能够持久化的使用数据,不会因为程序运行的结束而使得数据丢失,故我们需要将它们保存下来,保存到文件中去。下面我就来带着大家实现一下(文件版)通讯录,当然是在动态内存版基础上实现的。


3.1 保存通讯录🍖

  首先思考一个问题:我们因该把保存通讯录的代码写在哪里? 是不是放在程序的最后面,且在销毁通讯录之前。如下图所示:

在这里插入图片描述

  至于该怎么实现,我们应先以二进制写的形式打开文件,然后使用fwrite()函数将现在通讯录中存在的所用成员信息输出到文件当中去,最后再关闭文件。注意:为什么我们要以“”的形式打开文件?因为如此做后,每一次向文件中写数据都会覆盖掉原先存在的数据,起到了数据的更新迭代。而不会因为每次都是在上一次的基础上追加,而导致数据的冗余。代码如下:

//保存通讯录
void Save_Contact(const Contact* con)
{
	assert(con);
	//1.打开文件
	FILE* pfWrite = fopen("contact.txt", "wb");
	if (pfWrite == NULL)
	{
		perror("Save_Contact");
		return;
	}
	//2.保存所有人员信息
	fwrite(con->data, sizeof(PeoInfo), con->num, pfWrite);
	//3.关闭文件
	fclose(pfWrite);
	pfWrite = NULL;
}

  当该程序运行结束后,到通讯录项目下去找我们会发现那个我们创建的文件contact.txt

在这里插入图片描述


3.2 加载通讯录🍖

  上面我们已经实现了对通讯录数据的存储,那么再思考一下:该怎么使用这些被保存下来的数据呢? 我们是不是希望在关闭通讯录后下一次再打开时,能够看到之前的数据啊。所以我们应该在通讯录初始化时将保存的数据载入通讯录中,这样在使用通讯录之前就使得数据存在于通讯录中了。

  那该怎么实现呢? 首先我们并不知道内存中方有几个人员的信息,所以不能一次性的读取,只能一个一个的往里读,直到遇见文件的结尾或者没信息可读时才算结束。但值得注意的是,我们还得考虑通讯录容量不够的情况,所以必须在将数据放入通讯录前加一个容量的判断,若不够则扩容。下面是代码的实现:

//检查通讯录容量
void CheckCapacity(Contact* con)
{
	assert(con);
	//判断已存放人数是否已超出容量
	if (con->num == con->Capacity)
	{
		//通讯录扩容(扩容2个成员大小)
		PeoInfo* ptr = (PeoInfo*)realloc(con->data, (con->Capacity + INC_SZ) * sizeof(PeoInfo));
		if (ptr == NULL)
		{
			printf("CheckCapacity::%s\n", strerror(errno));
			return;
		}
		else
		{
			con->data = ptr;
			con->Capacity += INC_SZ;
			printf("增容成功\n");
		}
	}
}

//加载通讯录
void Load_Contact(Contact* con)
{
	assert(con);
	//1.以二进制读的形式打开文件
	FILE* pfRead = fopen("contact.txt", "rb");
	if (pfRead == NULL)
	{
		perror("Load_Contact");
		return;
	}
	//2.读取数据
	PeoInfo tmp = { 0 };
	while (fread(&tmp, sizeof(PeoInfo), 1, pfRead) == 1)
	{
		//判断是否需要扩容
		CheckCapacity(con);
		//将临时存放在tmp中的数据放入通讯录中
		con->data[con->num] = tmp;
		con->num += 1;
	}
	//3.关闭文件
	fclose(pfRead);
	pfRead = NULL;
}

//初始化通讯录
void InitContact(Contact* con)
{
	assert(con);
	//向内存申请一块能够存放3个成员的空间,并使得data指针来维护这块空间
	con->data = (PeoInfo*)calloc(CON_NUM, sizeof(PeoInfo));
	if (con->data == NULL)
	{
		perror("InitContact");
		return;
	}
	//初始化num和Capacity
	con->num = 0;
	con->Capacity = CON_NUM;
	//加载通讯录
	Load_Contact(con);
}

在这里插入图片描述

  在重新打开通讯录后,我们发现程序成功载入了上一次保存在文件中的数据。

  注意:代码中while循环的判断条件是fread()函数的返回值,该函数的返回值为成功读取到数据的个数,而这里我们是一个一个读的,所以只有两种情况要么为1要么为0。如此就可以是实现训话读数据,直到再也读不到数据时停下来。


四、完整的代码🍖

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

#define NAME_MAX 20//姓名所占字节大小
#define SEX_MAX 5//性别所占字节大小
#define TELE_MAX 12//电话所占字节大小
#define ADDR_MAX 30//地址所占字节大小
#define CON_NUM 3//初始容量
#define INC_SZ 2//扩容大小

//个人信息
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;//通讯录人员信息
	int num;//已存放人员个数
	int Capacity;//通讯录容量
}Contact;

//初始化通讯录
void InitContact(Contact* con);

//销毁通讯录
void DestoryContact(Contact* con);

//增加新成员
void Add_Contact(Contact* con);

//显示所有成员信息
void Show_Contact(const Contact* con);

//查找成员
void Search_Contact(const Contact* con);

//删除成员
void Del_Contact(Contact* con);

//修改成员信息
void Modify_Contact(Contact* con);

//成员排序
void Sort_Contact(Contact* con);

//保存通讯录
void Save_Contact(const Contact* con);

//加载通讯录
void Load_Contact(Contact* con);

//检查通讯录容量
void CheckCapacity(Contact* con)
{
	assert(con);
	//判断已存放人数是否已超出容量
	if (con->num == con->Capacity)
	{
		//通讯录扩容(扩容2个成员大小)
		PeoInfo* ptr = (PeoInfo*)realloc(con->data, (con->Capacity + INC_SZ) * sizeof(PeoInfo));
		if (ptr == NULL)
		{
			printf("CheckCapacity::%s\n", strerror(errno));
			return;
		}
		else
		{
			con->data = ptr;
			con->Capacity += INC_SZ;
			printf("增容成功\n");
		}
	}
}

//加载通讯录
void Load_Contact(Contact* con)
{
	assert(con);
	//1.以二进制读的形式打开文件
	FILE* pfRead = fopen("contact.txt", "rb");
	if (pfRead == NULL)
	{
		perror("Load_Contact");
		return;
	}
	//2.读取数据
	PeoInfo tmp = { 0 };
	while (fread(&tmp, sizeof(PeoInfo), 1, pfRead) == 1)
	{
		//判断是否需要扩容
		CheckCapacity(con);
		//将临时存放在tmp中的数据放入通讯录中
		con->data[con->num] = tmp;
		con->num += 1;
	}
	//3.关闭文件
	fclose(pfRead);
	pfRead = NULL;
}

//初始化通讯录
void InitContact(Contact* con)
{
	assert(con);
	//向内存申请一块能够存放3个成员的空间,并使得data指针来维护这块空间
	con->data = (PeoInfo*)calloc(CON_NUM, sizeof(PeoInfo));
	if (con->data == NULL)
	{
		perror("InitContact");
		return;
	}
	//初始化num和Capacity
	con->num = 0;
	con->Capacity = CON_NUM;
	//加载通讯录
	Load_Contact(con);
}

//保存通讯录
void Save_Contact(const Contact* con)
{
	assert(con);
	//1.打开文件
	FILE* pfWrite = fopen("contact.txt", "wb");
	if (pfWrite == NULL)
	{
		perror("Save_Contact");
		return;
	}
	//2.保存所有人员信息
	fwrite(con->data, sizeof(PeoInfo), con->num, pfWrite);
	//3.关闭文件
	fclose(pfWrite);
	pfWrite = NULL;
}

//销毁通讯录
void DestoryContact(Contact* con)
{
	assert(con);
	//释放data所指向的动态开辟空间
	free(con->data);
	con->data = NULL;
}

//查找成员
int Search(const Contact* con, char name[])
{
	assert(con);
	int i = 0;
	for (i = 0; i < con->num; i++)
	{
		if (0 == strcmp(con->data[i].name, name))
			return i;
	}
	return -1;
}

//添加新成员
void Add_Contact(Contact* con)
{
	assert(con);
	//检查通讯录容量
	CheckCapacity(con);

	printf("添加新成员\n");
	//输入一个人的信息
	printf("请输入姓名:");
	scanf("%s", con->data[con->num].name);
	printf("请输入年龄:");
	scanf("%d", &(con->data[con->num].age));
	printf("请输入性别:");
	scanf("%s", con->data[con->num].sex);
	printf("请输入电话:");
	scanf("%s", con->data[con->num].tele);
	printf("请输入地址:");
	scanf("%s", con->data[con->num].addr);
	//通讯录成员个数加一
	con->num++;
}

//显示所有成员信息
void Show_Contact(const Contact* con)
{
	assert(con);
	if (con->num == 0)
	{
		printf("通信录为空\n");
		return;
	}
	int i = 0;
	for (i = 0; i < con->num; i++)
	{
		printf("%20s %5d %5s %15s %30s\n", 
			con->data[i].name,
			con->data[i].age,
			con->data[i].sex,
			con->data[i].tele,
			con->data[i].addr);
	}
}

//查找成员
void Search_Contact(const Contact* con)
{
	assert(con);
	char name[20] = { 0 };
	int flag = 0;
	printf("请输入需要查找的姓名:");
	scanf("%s", name);

	//Search函数为查找函数
	//若没有找到则返回-1,若找到了则返回该成员所在下标
	flag = Search(con, name);

	if (flag < 0)
	{
		printf("未找到该成员\n");
	}
	else
	{
		printf("%20s %5d %5s %15s %30s\n",
			con->data[flag].name,
			con->data[flag].age,
			con->data[flag].sex,
			con->data[flag].tele,
			con->data[flag].addr);
	}
}

//删除成员
void Del_Contact(Contact* con)
{
	assert(con);
	char name[20] = { 0 };
	int flag = 0;
	printf("请输入要删除人的姓名:");
	scanf("%s", name);

	//1.查找确定这个人是否存在
	flag = Search(con, name);
	if (flag < 0)
	{
		printf("未找到此人\n");
		return;
	}

	//2.实施删除操作
	con->data[flag] = con->data[con->num - 1];
	con->num--;
	printf("完成删除操作\n");
}

//修改成员信息
void Modify_Contact(Contact* con)
{
	assert(con);
	char name[20] = { 0 };
	int flag = 0;
	printf("请输入需要被修改的成员:");
	scanf("%s", name);
	
	//1.查找这个人是否存在
	flag = Search(con, name);
	if (flag < 0)
	{
		printf("未找到该此人\n");
		return;
	}

	//2.对该成员进行修改
	printf("开始修改\n");
	//输入一个人的信息
	printf("请输入姓名:");
	scanf("%s", con->data[flag].name);
	printf("请输入年龄:");
	scanf("%d", &(con->data[flag].age));
	printf("请输入性别:");
	scanf("%s", con->data[flag].sex);
	printf("请输入电话:");
	scanf("%s", con->data[flag].tele);
	printf("请输入地址:");
	scanf("%s", con->data[flag].addr);
}

//成员排序

//菜单
static void menu()
{
	putchar('\n');
	printf("*****************************\n");
	printf("******  1. 按姓名排序  ******\n");
	printf("******  2. 按年龄排序  ******\n");
	printf("******  3. 按性别排序  ******\n");
	printf("******  4. 按电话排序  ******\n");
	printf("******  5. 按地址排序  ******\n");
	printf("*****************************\n");
	putchar('\n');
}

//比较函数
int CmpName(PeoInfo* s1, PeoInfo* s2)
{
	assert(s1 && s2);
	return strcmp(s1->name, s2->name);
}

int CmpAge(PeoInfo* s1, PeoInfo* s2)
{
	assert(s1 && s2);
	return s1->age - s2->age;
}

int CmpSex(PeoInfo* s1, PeoInfo* s2)
{
	assert(s1 && s2);
	return strcmp(s1->sex, s2->sex);
}

int CmpTele(PeoInfo* s1, PeoInfo* s2)
{
	assert(s1 && s2);
	return strcmp(s1->tele, s2->tele);
}

int CmpAddr(PeoInfo* s1, PeoInfo* s2)
{
	assert(s1 && s2);
	return strcmp(s1->addr, s2->addr);
}

//排序函数
void peo_sort(Contact* con, int cmp(PeoInfo* s1, PeoInfo* s2))
{
	int i = 0;
	int flag = 0;//记录是否以有序
	//趟数
	for (i = 0; i < con->num - 1; i++)
	{
		int j = 0;
		//每趟的交换的次数
		for (j = 0; j < con->num - 1 - i; j++)
		{
			//升序排放
			if (cmp(&(con->data[j]), &(con->data[j + 1])) > 0)
			{
				//交换两个元素
				PeoInfo tmp = con->data[j];
				con->data[j] = con->data[j + 1];
				con->data[j + 1] = tmp;

				//此时数组任然是乱序的
				flag = 1;
			}
		}
		//若flag在一趟排序中始终没有置1,则说明此时数组已然有序
		if (flag == 0)
			break;
	}
}

void Sort_Contact(Contact* con)
{
	//创建函数指针数组(用于实现转移表)
	int (*cmp_arr[6])(PeoInfo*, PeoInfo*) = { 0, CmpName, CmpAge, CmpSex, CmpTele, CmpAddr };
	int input = 0;
	do
	{
		menu();
		printf("请选择要以什么方式进行排序:");
		scanf("%d", &input);
		if (input >= 1 && input <= 5)
		{
			peo_sort(con, cmp_arr[input]);
			printf("完成排序\n");
			break;
		}
		else
		{
			printf("输入错误请重新输入\n");
		}
	} while (input);
}

//菜单
static void menu()
{
	printf("\n");
	printf("*****************************************************\n");
	printf("********  1.增加新成员        2.删除成员      *******\n");
	printf("********  3.查找成员          4.修改成员信息  *******\n");
	printf("********  5.显示所有成员信息  6.排序          *******\n");
	printf("********  0.退出                              *******\n");
	printf("*****************************************************\n");
	printf("\n");

}

enum Option
{
	Exit,
	Add,
	Del,
	Search,
	Modify,
	Show,
	Sort
};

int main()
{
	//1.创建一个通讯录
	Contact con;
	//2.初始化通讯录
	InitContact(&con);
	//4.实现菜单
	int input = 0;
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case Add:
			Add_Contact(&con);//增加新成员
			break;
		case Del:
			Del_Contact(&con);//删除成员
			break;
		case Search:
			Search_Contact(&con);//查找成员
			break;
		case Modify:
			Modify_Contact(&con);//修改成员信息
			break;
		case Show:
			Show_Contact(&con);//显示所有成员信息
			break;
		case Sort:
			Sort_Contact(&con);//成员排序
			break;
		case Exit:
			//保存通讯录
			Save_Contact(&con);
			//销毁通讯录
			DestoryContact(&con);
			printf("已退出通讯录\n");
			break;
		default:
			printf("输入错误请重新输入:\n");
			break;
		}
	} while (input);
	return 0;
}

在这里插入图片描述

这份博客👍如果对你有帮助,给博主一个免费的点赞以示鼓励欢迎各位🔎点赞👍评论收藏⭐️,谢谢!!!
如果有什么疑问或不同的见解,欢迎评论区留言欧👀。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/19792.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Springboot+mybatis+mysql+html图书管理系统

基于Springbootmybatismysqlhtml图书管理系统一、系统介绍二、功能展示1.用户登陆2.图书管理3.读者管理4.借还管理5.密码修改6.图书查询&#xff08;读者&#xff09;7.个人信息&#xff08;读者&#xff09;8.我的借还&#xff08;读者&#xff09;一、系统介绍 系统主要功能…

深究为啥Vue管理的函数不能是箭头函数

首先明确一点&#xff0c;箭头函数的this指向是根据上下文作用域确定的 Vue框架中&#xff0c;容易搞错的一点就是认为对象也有作用域 了解作用域与作用域链这个问题就迎刃而解了 假设Vue管理的函数是箭头函数时&#xff1a; 此时this是windows&#xff0c;Vue中data与metho…

生物识别技术在汽车领域带来了巨大变革

智能汽车时代 2022年10月28日&#xff0c;工信部发布《道路机动车辆生产准入许可管理条例&#xff08;征求意见稿&#xff09;》&#xff08;“《准入管理条例草案》”&#xff09;。包含了更全面的汽车准入管理规定&#xff0c;同时较为系统地增加了针对智能汽车的准入管理规定…

更简单的读取和存储对象

在上一篇文章中我们已经介绍在XML文件注册Bean的具体步骤,这一篇文章将会介绍使用更加简洁的方式(使用注解)来存储和读取Bean.这也是最常用的方法. 1. 创建并配置好Spring项目 和上一篇的步骤相同,下面就相当于复习如何创建Spring项目吧 创建一个 Maven 项目为 Spring 项目…

微信小程序 | 酷炫时钟样式整理【附源码】

&#x1f4cc;个人主页&#xff1a;个人主页 ​&#x1f9c0; 推荐专栏&#xff1a;小程序开发成神之路 --【这是一个为想要入门和进阶小程序开发专门开启的精品专栏&#xff01;从个人到商业的全套开发教程&#xff0c;实打实的干货分享&#xff0c;确定不来看看&#xff1f; …

对给定的数组进行重新排列numpy.random.shuffle()和numpy.random.permutation()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 对给定的数组进行重新排列 numpy.random.shuffle()和 numpy.random.permutation() [太阳]选择题 请问对以下Python代码说法错误的是&#xff1f; import numpy as np anp.arange(6) print(【…

零基础带你基于vue2架构搭建qiankun父子项目微前端架构

这里建议大家用 14版本左右的node版本 我们先创建一个目录 就叫qiankun 然后在终端打开 qiankun 目录 在终端输入指令 vue create vue-qiankun-base创建一个叫 vue-qiankun-base的vue项目 版本大家先选择vue2 vue-qiankun-base项目将作为我们的基座 然后在终端输入 vue …

SpringBoot SpringBoot 开发实用篇 5 整合第三方技术 5.3 手机验证码案例 - 生成验证码

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇5 整合第三方技术5.3 手机验证码案例 - 生成验证码5.3.1 SpringBoot …

众焱公司网络平台建设-传输网的规划与设计

目 录 摘 要 I Abstract II 第一章 项目概述 1 1.1 项目目标 1 1.1.1 总体目标 1 1.1.2 阶段目标 1 1.2 设计原则 2 1.3总体拓扑图设计 3 第二章 应用分析 4 2.1 应用分类 4 2.1.1 应用系统总体框架 4 2.1.2 业务系统应用分类 5 2.1.3 信息管理系统应用分类 6 2.2 数据中心及分…

数据结构:栈和队列

栈是一种特殊的线性结构&#xff0c;只允许在栈顶进行进行插入和删除操作。 进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出&#xff08;先进后出&#xff09;LIFO&#xff08;Last In First Out&#xff09;的原则。 类比成将子…

小学生python游戏编程arcade----爆炸粒子类

小学生python游戏编程arcade----爆炸粒子类前言1.1 参数设置粒子加速下降的速度。如果不需要&#xff0c;则为0粒子退出的速度粒子移动的速度。范围为2.5<-->5&#xff0c;设置为2.5和2.5。每次爆炸有多少粒子粒子直径多大粒子颜色列表我们有可能将纹理翻转为白色&#x…

芒果改进YOLOv7系列:首发改进特征融合网络BiFPN结构,融合更多有效特征

💡统一使用 YOLOv7 代码框架,结合不同模块来构建不同的YOLO目标检测模型。文章目录 一、BiFPN论文理论部分代码部分YOLOv7+BiFPN在这篇文章中,将BiFPN结构加入到 YOLOv7 结构中 一、BiFPN论文理论部分 EfficientDet: Scalable and Efficient Object Detection BiFPN与P…

芯天下在创业板过会:预计全年收入将达到10亿元,净利润约2亿元

11月18日&#xff0c;深圳证券交易所创业板披露的信息显示&#xff0c;芯天下技术股份有限公司&#xff08;下称“芯天下”&#xff09;获得上市委会议通过&#xff0c;即IPO过会。据贝多财经了解&#xff0c;芯天下于2022年4月28日在创业板递交上市申请材料。 本次冲刺创业板上…

vins-mono初始化代码分析

大体流程 初始化主要分成2部分&#xff0c;第一部分是纯视觉SfM优化滑窗内的位姿&#xff0c;然后在融合IMU信息。 这部分代码在estimator::processImage()最后面。 主函数入口&#xff1a; void Estimator::processImage(const map<int, vector<pair<int, Eigen:…

maven大全(概述、maven安装配置、IDEA配置maven、IDEA创建maven项目并如何使用)

目录 一、概述 1.什么是maven&#xff1f; 2.maven有什么作用&#xff1f; &#xff08;1&#xff09;提供了一套标准化的项目结构 &#xff08;2&#xff09;提供了标准化的构建流程&#xff08;编译、测试、打包、发布&#xff09; &#xff08;3&#xff09;提供了一套…

Java -- 每日一问:后台服务出现明显“变慢”,谈谈你的诊断思路?

典型回答 首先&#xff0c;需要对这个问题进行更加清晰的定义: 服务是突然变慢还是长时间运行后观察到变慢&#xff1f;类似问题是否重复出现&#xff1f;“慢”的定义是什么&#xff0c;我能够理解是系统对其他方面的请求的反应延时变长吗? 第二&#xff0c;理清问题的症状…

【计算机考研必备常识】24考研你开始准备了吗?

前言 23考研只剩下一个多月了&#xff0c;准备 【24考研】 的小伙伴是否有一丝丝焦虑了呢&#xff1f; 对于考研相关的常识问题&#xff0c;你又是否有了解呢&#xff1f;考研全流程&#xff1f;计算机考研考什么&#xff1f;学硕和专硕怎么选 … 一系列考研相关的常识问题博…

JWT和token是什么?如何利用token进行身份验证?

什么是token&#xff1f;什么是JWT&#xff1f;如何基于token进行身份验证&#xff1f; 我们都知道session信息需要保存一份在服务器端。这种方式会带来一些麻烦&#xff0c;比如需要我们保证保存session信息服务器的可用性、不适合移动端等。 有没有一种不需要自己存放sessi…

五、DMSQL

五、数据类型与操作符和常用DMSQL语句 1、数据类型与操作符介绍 达梦数据库支持的数据类型有很多&#xff0c;具体如下&#xff1a; 其中&#xff1a; 常规数据类型 数值数据类型字符数据类型多媒体数据类型日期时间数据类型 一般日期时间类型时区数据类型时间间隔数据类型 B…

辰奕智能在创业板过会:计划募资约4亿元,约有五成来自境外

11月18日&#xff0c;深圳证券交易所创业板披露的信息显示&#xff0c;广东辰奕智能科技股份有限公司&#xff08;下称“辰奕智能”&#xff09;获得上市委会议通过&#xff0c;即IPO过会。据贝多财经了解&#xff0c;辰奕智能于2021年12月31日在创业板递交上市申请材料。 本次…