通讯录详解(静态版,动态版,文件版)

news2024/12/28 23:10:35

  • 💓博客主页:江池俊的博客
  • ⏩收录专栏:C语言进阶之路
  • 👉专栏推荐:✅C语言初阶之路 ✅数据结构探索✅C语言刷题专栏
  • 💻代码仓库:江池俊的代码仓库
  • 🎉欢迎大家点赞👍评论📝收藏⭐

在这里插入图片描述

文章目录

      • 前言
  • ♨️一、静态通讯录
    • 🚀1.1 通讯录的前期准备
      • 🍁1.1.1 创建菜单
      • 🍁1.1.2 创建结构体
      • 🍁1.1.3 头文件的包含和#define定义常量
      • 🍁1.1.4 接口函数的声明
      • 🍁1.1.5 实现通讯录菜单选项的对接
    • 🚀1.2 通讯录函数功能的实现
      • 📌1.2.1 初始化通讯录
      • 📌1.2.2 添加联系人
      • 📌1.2.3 删除联系人
      • 📌1.2.4 查找联系人
      • 📌1.2.5 修改联系人信息
      • 📌1.2.6 打印通讯录信息
      • 📌1.2.7 排序通讯录
    • 🚀1.3 静态通讯录源代码
      • 🧩1.3.1 Contact.h 文件
      • 🧩1.3.2 Contact.c 文件
      • 🧩1.3.3 test.c 文件
  • ♨️二、通讯录优化之`动态通讯录`
    • 🚀2.1 通讯录结构体的优化
    • 🚀2.2 通讯录初始化函数的优化
    • 🚀2.3 添加扩容函数
    • 🚀2.4 释放动态开辟的内存
    • 🚀2.5 动态通讯录源代码
      • 🧩2.5.1 Contact.h 文件
      • 🧩2.5.2 Contact.c 文件
      • 🧩2.5.3 test.c 文件
  • ♨️三、通讯录优化之`文件版通讯录`
    • 🚀3.1 保存通讯录信息到文件
    • 🚀3.2 在初始化时加载文件信息到通讯录
    • 🚀3.3 文件版通讯录源代码
      • 🧩3.3.1 Contact.h 文件
      • 🧩3.3.2 Contact.c 文件
      • 🧩3.3.3 test.c 文件


前言

在现代社会中,通讯录已经成为了我们生活中不可或缺的一部分。无论是工作还是生活,我们都需要一个可靠的通讯录来记录和管理我们的联系人信息。

本文将介绍用C语言来实现一个通讯录管理系统,其中主要存储了若干联系人的信息,每个人的信息包括他们的姓名、年龄、性别、电话号码、住址等。并且该通讯录包括以下功能:

  1. 增加联系人
  2. 删除联系人
  3. 查找联系人
  4. 修改联系人
  5. 打印通讯录
  6. 排序通讯录
  7. 退出程序

在写通讯录前,我们需要创建工程,这里为了让大家养成模块化的好习惯,我们尽量将代码分成三个文件来写。这里我打开的编译器是 vs 2022,在资源管理器的 头文件 中创建 Contact.h 文件,此文件作用主要是为了存储各种头文件和通讯录各个功能的函数的声明以及联系人信息和通讯录结构体的创建;在源文件中创建 Contact.c 文件用来实现通讯录各大功能的函数,Test.c 文件用来测试通讯录的功能。具体如下图所示:

在这里插入图片描述


♨️一、静态通讯录

🚀1.1 通讯录的前期准备

🍁1.1.1 创建菜单

再创建通讯录之前,我们需要先建立一个完整的菜单,从而来实现用户与计算机的交互,方便用户快速查找和访问通讯录中的联系人信息,以实现快捷拨号、发送短信、查看联系人信息等功能。

代码:

void menu()
{
	printf("------------------------------\n");
	printf("----     1.添加联系人     ----\n");
	printf("----     2.删除联系人     ----\n");
	printf("----     3.查找联系人     ----\n");
	printf("----     4.修改联系人     ----\n");
	printf("----     5.打印通讯录     ----\n");
	printf("----     6.排序通讯录     ----\n");
	printf("----     0.退出通讯录     ----\n");
	printf("------------------------------\n");
}

在这里插入图片描述

🍁1.1.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[MAX];//存放联系人的信息
	int sz;//记录当前通讯录存放的联系人个数
}Contact;//通讯录

定义两个结构体类型:PeoInfoContact
PeoInfo结构体类型包含了5个成员变量,分别是:

  • name:一个字符数组,用于存储联系人姓名,其最大长度为NAME_MAX
  • age:一个整型变量,用于存储联系人年龄。
  • sex:一个字符数组,用于存储联系人性别,其最大长度为SEX_MAX
  • tele:一个字符数组,用于存储联系人电话号码,其最大长度为TELE_MAX
  • addr:一个字符数组,用于存储联系人地址,其最大长度为ADDR_MAX

这个结构体类型代表了一个联系人的信息。

Contact结构体类型包含了两个成员变量:

  • data:一个PeoInfo类型的数组,用于存储多个联系人的信息,其最大长度为MAX
  • sz:一个整型变量,用于记录当前通讯录中存放的联系人个数。

这个结构体类型代表了一个通讯录,其中可以存储多个联系人的信息,并且记录了当前通讯录中联系人的个数。

通过这两个结构体类型,我们可以方便地存储和管理联系人的信息和通讯录的状态。

🍁1.1.3 头文件的包含和#define定义常量

#include<stdio.h>
#include<string.h>//memset、strcmp
#include<assert.h>//assert
#include<stdlib.h>//qsort函数、malloc、realloc

#define NAME_MAX 20 //姓名
#define SEX_MAX 5 //性别
#define TELE_MAX 12 //电话
#define ADDR_MAX 30 //地址
#define MAX 100 //通讯录中可记录联系人数目的最大值

🍁1.1.4 接口函数的声明

声明实现通讯录各个功能的函数,如初始化通讯录,增加联系人,显示联系人,删除联系人,查找联系人等功能的函数。

//初始化通讯录
void InitContact(Contact* pc);
//增加联系人
void AddContact(Contact* pc);
//显示联系人信息
void ShowContact(Contact* pc);//这里形参也可以是结构体变量,因为打印不改变结构体内容
//删除联系人
void DelContact(Contact* pc);
//查找联系人
void SearchContact(Contact* pc);
//修改指定联系人
void ModifyContact(Contact* pc);
//排序通讯录
void SortContact(Contact* pc);
//按姓名排序
void SortContact_by_name(Contact* pc);
//按年龄排序
void SortContact_by_age(Contact* pc);

🍁1.1.5 实现通讯录菜单选项的对接

创建一个通讯录程序的框架,为了增加代码的可读性和维护性,使用枚举类型定义各个功能函数的选择。在主函数中,首先创建了一个通讯录对象,并初始化该对象。然后通过一个循环来显示菜单并获取用户的输入,根据用户的选择调用相应的函数来执行相应的操作。

enum Option //枚举常量对应各个函数的选择
{
	EXIT, //0
	ADD, //1
	DEL, //2
	SEARCH, //3
	MODIFY, //4
	SHOW, //5
	SORT //6  
};

int main()
{
	int input = 0;
	//创建通讯录
	Contact con;//通讯录
	//初始化通讯录
	InitContact(&con);

	int choice = 0;//排序方式的选择
	do
	{
		menu();
		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"); 
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	return 0;
}

🚀1.2 通讯录函数功能的实现

📌1.2.1 初始化通讯录

//初始化通讯录
void InitContact(Contact* pc)
{
	assert(pc);

	pc->sz = 0;
	memset(pc->data, 0, sizeof(pc->data));
}
  1. 首先使用 assert 宏来检查传入的指针是否为非空。如果指针为空,则程序会在这里终止并输出错误信息。
  2. 接下来,将结构体的 sz 成员变量初始化为 0,这是表示通讯录中联系人数目的变量。
  3. 然后,使用 memset 函数将结构体的 data 成员数组(该数组存放的是联系人的信息)初始化为 0sizeof(pc->data) 确定了要清零的字节数,确保整个数组都被清零。

📌1.2.2 添加联系人

//增加联系人
void AddContact(Contact* pc)
{
	assert(pc);
	if (pc->sz == MAX)
	{
		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");
}
  1. 首先,使用断言(assert)确保传入的指针不为空。
  2. 然后,检查通讯录是否已满(即 sz 等于 MAX)。如果已满,则打印一条错误消息并返回,不执行后续操作。
  3. 如果通讯录未满,则提示用户输入要增加的联系人的姓名、年龄、性别、电话和地址。
  4. 使用 scanf 函数从标准输入读取用户的输入,并将其存储在 Contact 结构体中相应的字段中。
  5. 最后,将 sz 的值加 1,表示成功增加了一个联系人,并打印一条成功消息。

在这里插入图片描述

📌1.2.3 删除联系人

//删除联系人
void DelContact(Contact* pc)
{
	char name[NAME_MAX];
	assert(pc);
	if (pc->sz == 0)
	{
		printf("通讯录为空,无法删除\n");
		return;
	}
	printf("请输入要删除的人的名字:");
	scanf("%s", name);
	//找到名字为name的人
	int ret = FindByName(pc, name);
	if (ret == -1)
	{
		printf("要删除的人不存在\n");
		return;
	}
	//删除这个人的信息
	for (int i = ret; i < pc->sz - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];
	}
	pc->sz--;
	printf("删除成功!\n");
}
  1. 首先,声明一个字符数组name,用于存储要删除的人的名字。
  2. 使用断言(assert)确保传入的指针不为空。
  3. 如果通讯录为空(即sz等于0),则打印一条错误消息并返回,不执行后续操作。
  4. 提示用户输入要删除的人的名字,并将其存储在name数组中。
  5. 调用FindByName函数来查找名字为name的人的位置,将结果存储在变量ret中。
  6. 如果ret等于-1,表示要删除的人不存在,则打印一条错误消息并返回。
  7. 如果找到了要删除的人,则通过循环将后面的联系人信息向前移动一位,覆盖掉要删除的人的信息。
  8. 将通讯录的大小减1,表示成功删除了一个联系人。
  9. 打印一条成功消息,表示删除成功。

在这里插入图片描述

📌1.2.4 查找联系人

在查找联系人并打印信息之前我们需要先找到联系人的下标,以便后续对于该联系人信息的获取和修改等操作。所以先创建一个FindByName 的函数,该函数的作用是查找联系人,若找到返回联系人的下标,否者,返回 -1

(1)FindByName 函数

//查找联系人并返回下标
int FindByName(Contact* pc, char name[])
{
	assert(pc);
	for (int i = 0; i < pc->sz; i++)
	{
		if (strcmp(name, pc->data[i].name) == 0)
		{
			return i;//找到了,返回下标
		}
	}
	return -1;//没找到,返回-1
}
  1. 首先,使用assert(pc)确保传入的联系人列表指针不为空。
  2. 然后,使用一个循环遍历联系人列表中的每个元素。循环变量i0 开始,直到pc->sz - 1(联系人列表的大小)。
  3. 在循环中,使用strcmp(name, pc->data[i].name) == 0判断当前联系人的名称是否与要查找的名称相同。这里使用了strcmp函数进行字符串比较。
  4. 如果找到了匹配的联系人,即strcmp的结果为 0,则返回当前下标i
  5. 如果循环结束后仍未找到匹配的联系人,则返回 -1

(2)SearchContact 函数

//查找联系人并打印信息
void SearchContact(Contact* pc)
{
	char name[NAME_MAX];
	assert(pc);
	printf("请输入要查找的人的姓名:");
	scanf("%s", &name);
	int ret = FindByName(pc, name);
	if (ret == -1)
	{
		printf("要查找的人不存在\n");
		return;
	}
	//显示找到的人的信息
	printf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");
	printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",
		ret + 1,
		pc->data[ret].name,
		pc->data[ret].age,
		pc->data[ret].sex,
		pc->data[ret].tele,
		pc->data[ret].addr);
}
  1. 此函数接受一个指向Contact 结构体的指针作为参数,然后提示用户输入要查找的人的姓名。
  2. 接着,调用 FindByName 函数来查找该联系人,并将结果存储在变量ret中。
  3. 如果找到了联系人,它会显示该联系人的信息;否则,它会打印一条错误消息。

在这里插入图片描述

📌1.2.5 修改联系人信息

由于联系人有多个信息,我们无法确定要修改的信息是哪一项,所以在修改联系人信息前需要先创建一个修改联系人信息的菜单,从而供用户选择对相应的信息进行修改。

(1)ModifyMenu 函数

void ModifyMenu()
{
	printf("------------------------\n");
	printf("----   0.退出修改   ----\n");
	printf("----   1.修改姓名   ----\n");
	printf("----   2.修改年龄   ----\n");
	printf("----   3.修改性别   ----\n");
	printf("----   4.修改电话   ----\n");
	printf("----   5.修改地址   ----\n");
	printf("------------------------\n");
}

在这里插入图片描述

(2)ModifyContact 函数

//修改指定联系人
void ModifyContact(Contact* pc)
{
	char name[NAME_MAX];
	assert(pc);
	printf("请输入要修改的人的姓名:");
	scanf("%s", &name);
	int ret = FindByName(pc, name);
	if (ret == -1)
	{
		printf("要修改的人不存在\n");
		return;
	}
	int input = 0;
	do
	{
		ModifyMenu();//修改信息的菜单
		printf("请选择你要修改的信息->:");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			printf("退出修改!\n");
			break;
		case 1:
			printf("请输入修改后的姓名:");
			scanf("%s", pc->data[ret].name);
			break;
		case 2:
			printf("请输入修改后的年龄:");
			scanf("%d", &(pc->data[ret].age));
			break;
		case 3:
			printf("请输入修改后的性别:");
			scanf("%s", pc->data[ret].sex);
			break;
		case 4:
			printf("请输入修改后的电话:");
			scanf("%s", pc->data[ret].tele);
			break;
		case 5:
			printf("请输入修改后的地址:");
			scanf("%s", pc->data[ret].addr);
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	printf("修改成功!\n");
}
  1. 首先通过输入要修改的人的姓名来查找该联系人
  2. 如果找到了就进入一个循环,显示修改信息的菜单并让用户选择要修改的信息,然后根据用户的选择进行相应的修改操作。
  3. 最后输出修改成功的提示信息。

在这里插入图片描述

📌1.2.6 打印通讯录信息

遍历通讯录,逐个打印出联系人的序号、姓名、年龄、性别、电话和地址信息。如果通讯录为空,则则打印提示信息并结束函数。

//显示联系人信息
void ShowContact(Contact* pc)//这里形参也可以是结构体变量,因为打印不改变结构体内容
{
	assert(pc);
	if (pc->sz == 0)
	{
		printf("通讯录为空,无需打印\n");
		return;
	}
	int i = 0;
	//序号  名字  年龄  性别  电话    地址
	//xxx   xxx   xxx   xxx    xxx    xxx
	printf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");
	for (i = 0; i < pc->sz; i++)
	{
		//打印每个人的信息
		printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",
			i + 1, pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
	}
}

在这里插入图片描述

📌1.2.7 排序通讯录

这里可以使用两种方式进行排序,第一种是按照联系人姓名排序,第二种则是按照联系人年龄来排序。两种排序都是基于 qsort 函数来实现。qsort 函数的具体使用方法见《深入理解回调函数qsort:从入门到模拟实现》
qsort 函数的关键是第四个参数,该参数是一个函数指针,用来指向一个比较函数,故排序的方式也是由它来决定的,比如:想要按姓名来排序就要写一个按姓名来比较的函数,如果想要按年龄来排序就要写一个按年龄来比较的函数。
(1)SortMenu 函数
该函数打印的是通讯录排序的方式,方便与用户进行交互,让用户来选择排序通讯录的方式。

void SortMenu()
{
	printf("-------------------------\n");
	printf("----   0.退出排序    ----\n");
	printf("----   1.按姓名排序  ----\n");
	printf("----   2.按年龄排序  ----\n");
	printf("-------------------------\n");
}

在这里插入图片描述

(2)比较函数 + 排序函数

//按名字比较
int cmp_by_name(const void* e1, const void* e2)
{
	return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}

//按年龄比较
int cmp_by_age(const void* e1, const void* e2)
{
	return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}

//按姓名排序 --- qsort
void SortContact_by_name(Contact* pc)
{
	qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_name);
}
//按年龄排序 --- qsort
void SortContact_by_age(Contact* pc)
{
	qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_age);
}

这段代码是用于对联系人进行排序的。它包含了两个比较函数(cmp_by_namecmp_by_age)以及两个排序函数(SortContact_by_nameSortContact_by_age)。

  1. cmp_by_name 函数用于按名字比较两个联系人。它接受两个 const void* 类型的参数 e1e2,然后将它们转换为 PeoInfo 类型的指针,并使用 strcmp 函数比较它们的 name 成员。如果 e1 的名字在字母顺序上排在 e2 之前,则返回负数;如果相等,则返回零;如果 e1 的名字在字母顺序上排在 e2 之后,则返回正数。
  2. cmp_by_age 函数用于按年龄比较两个联系人。它接受两个 const void* 类型的参数 e1e2,然后将它们转换为 PeoInfo 类型的指针,并计算它们的 age 成员之差。如果 e1 的年龄小于 e2的年龄,则返回负数;如果相等,则返回零;如果 e1 的年龄大于 e2 的年龄,则返回正数。
  3. SortContact_by_name 函数用于按姓名对联系人数组进行排序。它接受一个 Contact* 类型的参数 pc,然后使用 qsort 函数对 pc->data 数组进行排序。qsort 函数的第四个参数是一个比较函数指针,这里传入的是 cmp_by_name 函数。这样,当 qsort函数需要比较两个元素时,就会调用 cmp_by_name 函数来进行比较。
  4. SortContact_by_age 函数用于按年龄对联系人数组进行排序。它与 SortContact_by_name 函数类似,只是比较函数改为了 cmp_by_age 函数。

(3)SortContact 函数

//排序通讯录
void SortContact(Contact* pc)
{
	int inpuct = 0;
	if (pc->sz == 0)
	{
		printf("通讯录为空,不用排序!\n");
		return;
	}

	do
	{
		SortMenu();//排序通讯录菜单
		printf("请选择排序方式 ->:");
		scanf("%d", &inpuct);
		switch (inpuct)
		{
		case 0:
			printf("退出排序!\n");
			break;
		case 1:
			SortContact_by_name(pc);
			printf("按姓名排序后:\n");
			ShowContact(pc);
			break;
		case 2:
			SortContact_by_age(pc);
			printf("按年龄排序后:\n");
			ShowContact(pc);
			break;
		default:
			printf("输入错误,请重新输入");
			break;
		}
	} while (inpuct);
}
  1. 首先,代码检查联系人数组是否为空,如果为空则打印提示信息并返回。
  2. 接下来,进入一个循环,显示排序菜单并等待用户输入选择。根据用户的输入,执行相应的排序操作:
    • 如果用户选择0,退出排序;
    • 如果用户选择1,调用 SortContact_by_name 函数按姓名对联系人进行排序,并打印排序后的结果;
    • 如果用户选择2,调用 SortContact_by_age 函数按年龄对联系人进行排序,并打印排序后的结果。
  3. 如果用户输入的选择不在有效范围内,会打印错误提示并重新显示排序菜单。

在这里插入图片描述

🚀1.3 静态通讯录源代码

🧩1.3.1 Contact.h 文件

#pragma once

#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>//qsort函数、malloc、realloc

#define NAME_MAX 20 //姓名
#define SEX_MAX 5 //性别
#define TELE_MAX 12 //电话
#define ADDR_MAX 30 //地址
#define MAX 100 //通讯录中可记录联系人数目的最大值

//类型声明
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[MAX];//存放联系人的信息
	int sz;//记录当前通讯录存放的联系人个数
}Contact;//通讯录


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

//增加联系人
void AddContact(Contact* pc);

//显示联系人信息
void ShowContact(Contact* pc);//这里形参也可以是结构体变量,因为打印不改变结构体内容

//删除联系人
void DelContact(Contact* pc);

//查找联系人
void SearchContact(Contact* pc);

//修改指定联系人
void ModifyContact(Contact* pc);

//排序通讯录
void SortContact(Contact* pc);

//按姓名排序
void SortContact_by_name(Contact* pc);

//按年龄排序
void SortContact_by_age(Contact* pc);

🧩1.3.2 Contact.c 文件

#define _CRT_SECURE_NO_WARNINGS 1

#include "Contact.h"

void ModifyMenu()
{
	printf("------------------------\n");
	printf("----   0.退出修改   ----\n");
	printf("----   1.修改姓名   ----\n");
	printf("----   2.修改年龄   ----\n");
	printf("----   3.修改性别   ----\n");
	printf("----   4.修改电话   ----\n");
	printf("----   5.修改地址   ----\n");
	printf("------------------------\n");
}

void SortMenu()
{
	printf("-------------------------\n");
	printf("----   0.退出排序    ----\n");
	printf("----   1.按姓名排序  ----\n");
	printf("----   2.按年龄排序  ----\n");
	printf("-------------------------\n");
}

//初始化通讯录
void InitContact(Contact* pc)
{
	assert(pc);

	pc->sz = 0;
	memset(pc->data, 0, sizeof(pc->data));
}

//增加联系人
void AddContact(Contact* pc)
{
	assert(pc);
	if (pc->sz == MAX)
	{
		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(Contact* pc)//这里形参也可以是结构体变量,因为打印不改变结构体内容
{
	assert(pc);
	if (pc->sz == 0)
	{
		printf("通讯录为空,无需打印\n");
		return;
	}
	int i = 0;
	//序号  名字  年龄  性别  电话    地址
	//xxx   xxx   xxx   xxx    xxx    xxx
	printf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");
	for (i = 0; i < pc->sz; i++)
	{
		//打印每个人的信息
		printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",
			i + 1, pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
	}
}

//查找联系人并返回下标
int FindByName(Contact* pc, char name[])
{
	assert(pc);
	for (int i = 0; i < pc->sz; i++)
	{
		if (strcmp(name, pc->data[i].name) == 0)
		{
			return i;//找到了,返回下标
		}
	}
	return -1;//没找到,返回-1
}

//删除联系人
void DelContact(Contact* pc)
{
	char name[NAME_MAX];
	assert(pc);
	if (pc->sz == 0)
	{
		printf("通讯录为空,无法删除\n");
		return;
	}
	printf("请输入要删除的人的名字:");
	scanf("%s", name);
	//找到名字为name的人
	int ret = FindByName(pc, name);
	if (ret == -1)
	{
		printf("要删除的人不存在\n");
		return;
	}
	//删除这个人的信息
	for (int i = ret; i < pc->sz - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];
	}
	pc->sz--;
	printf("删除成功!\n");
}

//查找联系人并打印信息
void SearchContact(Contact* pc)
{
	char name[NAME_MAX];
	assert(pc);
	printf("请输入要查找的人的姓名:");
	scanf("%s", &name);
	int ret = FindByName(pc, name);
	if (ret == -1)
	{
		printf("要查找的人不存在\n");
		return;
	}
	//显示找到的人的信息
	printf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");
	printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",
		ret + 1,
		pc->data[ret].name,
		pc->data[ret].age,
		pc->data[ret].sex,
		pc->data[ret].tele,
		pc->data[ret].addr);
}

//修改指定联系人
void ModifyContact(Contact* pc)
{
	char name[NAME_MAX];
	assert(pc);
	printf("请输入要修改的人的姓名:");
	scanf("%s", &name);
	int ret = FindByName(pc, name);
	if (ret == -1)
	{
		printf("要修改的人不存在\n");
		return;
	}
	int input = 0;
	do
	{
		ModifyMenu();//修改信息的菜单
		printf("请选择你要修改的信息->:");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			printf("退出修改!\n");
			break;
		case 1:
			printf("请输入修改后的姓名:");
			scanf("%s", pc->data[ret].name);
			break;
		case 2:
			printf("请输入修改后的年龄:");
			scanf("%d", &(pc->data[ret].age));
			break;
		case 3:
			printf("请输入修改后的性别:");
			scanf("%s", pc->data[ret].sex);
			break;
		case 4:
			printf("请输入修改后的电话:");
			scanf("%s", pc->data[ret].tele);
			break;
		case 5:
			printf("请输入修改后的地址:");
			scanf("%s", pc->data[ret].addr);
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	printf("修改成功!\n");
}

//按名字比较
int cmp_by_name(const void* e1, const void* e2)
{
	return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}

//按年龄比较
int cmp_by_age(const void* e1, const void* e2)
{
	return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}

//按姓名排序 --- qsort
void SortContact_by_name(Contact* pc)
{
	qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_name);
}
//按年龄排序 --- qsort
void SortContact_by_age(Contact* pc)
{
	qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_age);
}

//排序通讯录
void SortContact(Contact* pc)
{
	int inpuct = 0;
	if (pc->sz == 0)
	{
		printf("通讯录为空,不用排序!\n");
		return;
	}

	do
	{
		SortMenu();//排序通讯录菜单
		printf("请选择排序方式 ->:");
		scanf("%d", &inpuct);
		switch (inpuct)
		{
		case 0:
			printf("退出排序!\n");
			break;
		case 1:
			SortContact_by_name(pc);
			printf("按姓名排序后:\n");
			ShowContact(pc);
			break;
		case 2:
			SortContact_by_age(pc);
			printf("按年龄排序后:\n");
			ShowContact(pc);
			break;
		default:
			printf("输入错误,请重新输入");
			break;
		}
	} while (inpuct);
}

🧩1.3.3 test.c 文件

#define _CRT_SECURE_NO_WARNINGS 1
//测试通讯录基本功能

#include "Contact.h"

void menu()
{
	printf("------------------------------\n");
	printf("----     1.添加联系人     ----\n");
	printf("----     2.删除联系人     ----\n");
	printf("----     3.查找联系人     ----\n");
	printf("----     4.修改联系人     ----\n");
	printf("----     5.打印通讯录     ----\n");
	printf("----     6.排序通讯录     ----\n");
	printf("----     0.退出通讯录     ----\n");
	printf("------------------------------\n");
}

enum Option //枚举常量对应各个函数的选择
{
	EXIT, //0
	ADD, //1
	DEL, //2
	SEARCH, //3
	MODIFY, //4
	SHOW, //5
	SORT //6  
};

int main()
{
	int input = 0;
	//创建通讯录
	Contact con;//通讯录
	//初始化通讯录
	InitContact(&con);

	int choice = 0;//排序方式的选择
	do
	{
		menu();
		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"); 
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	return 0;
}

♨️二、通讯录优化之动态通讯录

上述静态通讯录我们不难发现它的一个致命的缺点,在通讯录结构体的创建时,我们将通讯录的大小定义为100,当通讯录存满100个人的信息时我们如果想要继续存储,则会发生越界,程序报错,因此,我们需要优化通讯录使得我们能够手动增加通讯录的大小,于是就需要利用动态内存分配来定义通讯录结构体内的联系人数组的大小。

🚀2.1 通讯录结构体的优化

typedef struct Contact
{
	PeoInfo* data;//存放联系人的信息
	int sz;//当前通讯录存放的联系人的个数
	int capacity;//当前通讯录的最大容量
}Contact;

定义了一个名为Contact的结构体,用于表示通讯录。该结构体包含以下成员:

  • PeoInfo* data;:指向存放联系人信息的指针。
  • int sz;:当前通讯录中存放的联系人的个数。
  • int capacity;:当前通讯录的最大容量。

🚀2.2 通讯录初始化函数的优化

使用 malloc 动态开辟联系人数组的空间,初始空间大小为 INIT_DATA,再使用 memset 将数组中的值都置为0

//初始化通讯录
void InitContact(Contact* pc)
{
	assert(pc);
	pc->data = (PeoInfo*)malloc(sizeof(PeoInfo) * INIT_DATA);
	if (pc->data == NULL)
	{
		perror("InitContact->malloc");
		return;
	}
	memset(pc->data, 0, sizeof(pc->data));
	pc->sz = 0;
	pc->capacity = INIT_DATA;
}

🚀2.3 添加扩容函数

创建一个用于检查并扩容通讯录的函数。它接受一个指向Contact结构体的指针作为参数,并根据当前容量和最大容量进行判断是否需要扩容。

如果当前容量等于最大容量,那么就会执行以下操作:

  1. 使用realloc函数对data成员进行重新分配内存空间。新的内存空间大小为原容量加上一个常量ADD_DATA
  2. 如果重新分配失败,会打印错误信息并返回。
  3. 如果重新分配成功,将新分配的内存地址赋值给data成员,并将最大容量增加ADD_DATA
  4. 打印"增容成功"的消息。

如果当前容量没有达到最大容量,则不执行任何操作。

//扩容
void CheckCapacity(Contact* pc)
{
	//1.如果满了,就增容
	if (pc->sz == pc->capacity)
	{
		Contact* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + ADD_DATA) * sizeof(PeoInfo));
		if (ptr == NULL)
		{
			perror("CheckCapacity->realloc");
			return;
		}
		pc->data = ptr;
		pc->capacity += ADD_DATA;
		printf("增容成功\n");
	}
	//2.如果没满,啥也不干
}

🚀2.4 释放动态开辟的内存

创建一个销毁通讯录的函数(其目的是在程序结束前释放动态开辟的内存空间)。它接受一个指向Contact结构体的指针作为参数,并执行以下操作:

  1. 使用free函数释放data成员所指向的内存空间。
  2. data成员设置为NULL,以避免悬挂指针。
  3. sz成员设置为0,表示当前通讯录中没有联系人。
  4. capacity成员设置为0,表示通讯录的最大容量为0。
  5. 使用printf函数输出"销毁通讯录成功"的消息。

通过调用这个函数,可以释放通讯录所占用的内存空间,并将相关成员变量重置为初始状态。

//销毁通讯录
void DestroyContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->sz = 0;
	pc->capacity = 0;
	printf("销毁通讯录成功\n"); 
}

🚀2.5 动态通讯录源代码

🧩2.5.1 Contact.h 文件

#pragma once

#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>//qsort函数、malloc、realloc

#define NAME_MAX 20 //姓名
#define SEX_MAX 5 //性别
#define TELE_MAX 12 //电话
#define ADDR_MAX 30 //地址
//#define MAX 100 //通讯录记录的人的信息的最大值

#define INIT_DATA 4 //通讯录的初始大小
#define ADD_DATA 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[MAX];//存放信息
//	int sz;//记录当前通讯录存放的人的信息个数
//}Contact;

typedef struct Contact
{
	PeoInfo* data;//存放联系人的信息
	int sz;//当前通讯录存放的联系人的个数
	int capacity;//当前通讯录的最大容量
}Contact;

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

//增加联系人
void AddContact(Contact* pc);

//显示联系人信息
void ShowContact(Contact* pc);//这里形参也可以是结构体变量,因为打印不改变结构体内容

//删除联系人
void DelContact(Contact* pc);

//查找联系人
void SearchContact(Contact* pc);

//修改指定联系人
void ModifyContact(Contact* pc);

//排序通讯录
void SortContact(Contact* pc);

//按姓名排序
void SortContact_by_name(Contact* pc);

//按年龄排序
void SortContact_by_age(Contact* pc);

//销毁通讯录
void DestroyContact(Contact* pc);

🧩2.5.2 Contact.c 文件

#define _CRT_SECURE_NO_WARNINGS 1

#include "Contact.h"

void CheckCapacity(Contact* pc);

void ModifyMenu()
{
	printf("------------------------\n");
	printf("----   0.退出修改   ----\n");
	printf("----   1.修改姓名   ----\n");
	printf("----   2.修改年龄   ----\n");
	printf("----   3.修改性别   ----\n");
	printf("----   4.修改电话   ----\n");
	printf("----   5.修改地址   ----\n");
	printf("------------------------\n");
}

void SortMenu()
{
	printf("-------------------------\n");
	printf("----   0.退出排序    ----\n");
	printf("----   1.按姓名排序  ----\n");
	printf("----   2.按年龄排序  ----\n");
	printf("-------------------------\n");
}

//初始化通讯录
void InitContact(Contact* pc)
{
	assert(pc);
	pc->data = (PeoInfo*)malloc(sizeof(PeoInfo) * INIT_DATA);
	if (pc->data == NULL)
	{
		perror("InitContact->malloc");
		return;
	}
	memset(pc->data, 0, sizeof(pc->data));
	pc->sz = 0;
	pc->capacity = INIT_DATA;
}

//扩容
void CheckCapacity(Contact* pc)
{
	//1.如果满了,就增容
	if (pc->sz == pc->capacity)
	{
		Contact* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + ADD_DATA) * sizeof(PeoInfo));
		if (ptr == NULL)
		{
			perror("CheckCapacity->realloc");
			return;
		}
		pc->data = ptr;
		pc->capacity += ADD_DATA;
		printf("增容成功\n");
	}
	//2.如果没满,啥也不干
}

//增加联系人
void AddContact(Contact* pc)
{
	assert(pc);
	//检查通讯录当前的容量
	//1.如果满了,就增容
	//2.如果没满,啥也不干
	CheckCapacity(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].tele);
	printf("请输入增加联系人的地址:");
	scanf("%s", pc->data[pc->sz].addr);

	pc->sz++;
	printf("增加联系人成功!\n");
}

//显示联系人信息
void ShowContact(Contact* pc)//这里形参也可以是结构体变量,因为打印不改变结构体内容
{
	assert(pc);
	if (pc->sz == 0)
	{
		printf("通讯录为空,无需打印\n");
		return;
	}
	int i = 0;
	printf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");
	for (i = 0; i < pc->sz; i++)
	{
		//打印每个人的信息
		printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",
			i + 1, pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
	}
}

//查找联系人并返回下标
int FindByName(Contact* pc, char name[])
{
	assert(pc);
	for (int i = 0; i < pc->sz; i++)
	{
		if (strcmp(name, pc->data[i].name) == 0)
		{
			return i;//找到了,返回下标
		}
	}
	return -1;//没找到,返回-1
}

//删除联系人
void DelContact(Contact* pc)
{
	char name[NAME_MAX];
	assert(pc);
	if (pc->sz == 0)
	{
		printf("通讯录为空,无法删除\n");
		return;
	}
	printf("请输入要删除的人的名字:");
	scanf("%s", name);
	//找到名字为name的人
	int ret = FindByName(pc, name);
	if (ret == -1)
	{
		printf("要删除的人不存在\n");
		return;
	}
	//删除这个人的信息
	for (int i = ret; i < pc->sz - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];
	}
	pc->sz--;
	printf("删除成功!\n");
}

//查找联系人并打印信息
void SearchContact(Contact* pc)
{
	char name[NAME_MAX];
	assert(pc);
	printf("请输入要查找的人的姓名:");
	scanf("%s", &name);
	int ret = FindByName(pc, name);
	if (ret == -1)
	{
		printf("要查找的人不存在\n");
		return;
	}
	//显示找到的人的信息
	printf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");
	printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",
		ret + 1, 
		pc->data[ret].name, 
		pc->data[ret].age, 
		pc->data[ret].sex, 
		pc->data[ret].tele, 
		pc->data[ret].addr);
}

//修改指定联系人
void ModifyContact(Contact* pc)
{
	char name[NAME_MAX];
	assert(pc);
	printf("请输入要修改的人的姓名:");
	scanf("%s", &name);
	int ret = FindByName(pc, name);
	if (ret == -1)
	{
		printf("要修改的人不存在\n");
		return;
	}
	int input = 0;
	do
	{
		ModifyMenu();
		printf("请选择你要修改的信息->:");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			printf("退出修改!\n");
			break;
		case 1:
			printf("请输入修改后的姓名:");
			scanf("%s", pc->data[ret].name);
			break;
		case 2:
			printf("请输入修改后的年龄:");
			scanf("%d", &(pc->data[ret].age));
			break;
		case 3:
			printf("请输入修改后的性别:");
			scanf("%s", pc->data[ret].sex);
			break;
		case 4:
			printf("请输入修改后的电话:");
			scanf("%s", pc->data[ret].tele);
			break;
		case 5:
			printf("请输入修改后的地址:");
			scanf("%s", pc->data[ret].addr);
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	printf("修改成功!\n");
}

//按名字比较
int cmp_by_name(const void* e1, const void* e2)
{
	return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}

//按年龄比较
int cmp_by_age(const void* e1, const void* e2)
{
	return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}

//按姓名排序
void SortContact_by_name(Contact* pc)
{
	qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_name);
}
//按年龄排序
void SortContact_by_age(Contact* pc)
{
	qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_age);
}

//排序通讯录
void SortContact(Contact* pc)
{
	int inpuct = 0;
	if (pc->sz == 0)
	{
		printf("通讯录为空,不用排序!\n");
		return;
	}

	do
	{
		SortMenu();
		printf("请选择排序方式 ->:");
		scanf("%d", &inpuct);
		switch (inpuct)
		{
		case 0:
			printf("退出排序!\n");
			break;
		case 1:
			SortContact_by_name(pc);
			printf("按姓名排序后:\n");
			ShowContact(pc);
			break;
		case 2:
			SortContact_by_age(pc);
			printf("按年龄排序后:\n");
			ShowContact(pc);
			break;
		default:
			printf("输入错误,请重新输入");
			break;
		}
	} while (inpuct);
}

//销毁通讯录
void DestroyContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->sz = 0;
	pc->capacity = 0;
	printf("销毁通讯录成功\n"); 
}

🧩2.5.3 test.c 文件

#define _CRT_SECURE_NO_WARNINGS 1
//测试通讯录基本功能

#include "Contact.h"

void menu()
{
	printf("------------------------\n");
	printf("----     1.add      ----\n");
	printf("----     2.del      ----\n");
	printf("----     3.search   ----\n");
	printf("----     4.modify   ----\n");
	printf("----     5.show     ----\n");
	printf("----     6.sorts    ----\n");
	printf("----     0.exit     ----\n");
	printf("------------------------\n");
}

enum Option
{
	EXIT, //0
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT
};

int main()
{
	int input = 0;
	//创建通讯录
	Contact con;//通讯录
	//初始化通讯录
	InitContact(&con);

	int choice = 0;//排序方式的选择
	do
	{
		menu();
		printf("请输入你的选择 ->:");
		scanf("%d", &input);
		switch (input)
		{
		case EXIT:
			DestroyContact(&con);
			printf("退出通讯录\n");
			break;
		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;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	return 0;
}

♨️三、通讯录优化之文件版通讯录

在实现动态通讯录后,我们发现虽然解决了通讯录空间不足的问题,但是对于已经加入通讯录中的信息在退出程序后无法保存来,即退出程序后之前添加的联系人信息消失不见,无法找到。这是因为程序运行后信息是存储在内存上的,退出程序后,计算机会清除内存上的信息,但是文件中的信息是存储在磁盘上的,即使程序退出,磁盘上的信息仍然存在,不会消失。

所以为了使通讯录中的信息保存下来我们需要采用文件操作的方法实现。

🚀3.1 保存通讯录信息到文件

创建一个函数 SaveContact,将通讯录中的数据保存到名为 "Contact.text" 的文件中。

//保存通讯录中的数据到文件中
void SaveContact(Contact* pc)
{
	FILE* pf = fopen("Contact.text", "wb");
	if (pf == NULL)
	{
		perror("SaveContact");
		return;
	}
	//写信息到文件
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		//fwrite(&(pc->data[i]), sizeof(PeoInfo), 1, pf);
		fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);
	}
	//关闭文件
	fclose(pf);
	pf = NULL; 
}
  1. 首先,使用 fopen 函数以二进制模式打开文件,并将文件指针存储在变量 pf 中。如果无法打开文件(即 pfNULL),则会调用 perror 函数打印错误信息,并直接返回。
  2. 接下来,代码使用一个循环来遍历通讯录中的每个联系人。循环变量 i0 开始,直到 pc->sz(通讯录的大小)减 1。在每次循环中,通过 fwrite 函数将当前联系人的信息写入文件中。这里使用了指针运算符 + 来获取当前联系人的地址,并将其作为第一个参数传递给 fwrite 函数。第二个参数是每个联系人信息的大小(单位是字节),第三个参数是要写入的次数,第四个参数是文件指针。
  3. 最后,代码使用 fclose 函数关闭文件,并将文件指针设置为 NULL,以确保不会再次使用该指针。

🚀3.2 在初始化时加载文件信息到通讯录

创建一个 LoadContact 函数,将文件中的信息加载到通讯录中。在初始化时调用此函数即可将文件 "Contact.text" 中的信息加载到通讯录中。

//将文件信息加载到通讯录
void LoadContact(Contact* pc)
{
	FILE* pf = fopen("Contact.text", "rb");
	if (pf == NULL)
	{
		perror("LoadContact");
		return;
	}
	//加载文件信息(读文件)
	PeoInfo temp = { 0 }; //临时存储从文件中读取的数据
	while (fread(&temp, sizeof(PeoInfo), 1, pf)) //读取成功返回1,否则返回0
	{
		//检查容量,如果没满才能加载文件中的数据到通讯录
		CheckCapacity(pc);
		pc->data[pc->sz++] = temp;
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
}
  1. 首先,使用fopen函数以二进制只读模式打开文件,并将文件指针保存在变量pf中。如果文件打开失败,函数会调用perror函数打印错误信息,并直接返回。
  2. 接下来,函数创建一个临时变量temp,并将其初始化为全零。然后,它进入一个循环,使用fread函数从文件中读取数据,每次读取一个PeoInfo结构体的大小。如果读取成功,fread函数返回1,否则返回0。在循环中,函数首先调用CheckCapacity函数来检查通讯录是否还有空间容纳新的联系人信息。如果有空间,就将读取到的联系人信息存储到通讯录的相应位置,并将通讯录的大小加
    1
  3. 最后,函数使用fclose函数关闭文件,并将文件指针设置为 NULL,以避免后续操作时出现错误。

🚀3.3 文件版通讯录源代码

🧩3.3.1 Contact.h 文件

#pragma once

#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h> //qsort函数、malloc、realloc

#define NAME_MAX 20 //姓名
#define SEX_MAX 5 //性别
#define TELE_MAX 12 //电话
#define ADDR_MAX 30 //地址
//#define MAX 100 //通讯录记录的人的信息的最大值

#define INIT_DATA 4 //通讯录的初始大小
#define ADD_DATA 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[MAX];//存放信息
//	int sz;//记录当前通讯录存放的人的信息个数
//}Contact;

typedef struct Contact
{
	PeoInfo* data;//存放联系人的信息
	int sz;//当前通讯录存放的联系人的个数
	int capacity;//当前通讯录的最大容量
}Contact;

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

//增加联系人
void AddContact(Contact* pc);

//显示联系人信息
void ShowContact(Contact* pc);//这里形参也可以是结构体变量,因为打印不改变结构体内容

//删除联系人
void DelContact(Contact* pc);

//查找联系人
void SearchContact(Contact* pc);

//修改指定联系人
void ModifyContact(Contact* pc);

//排序通讯录
void SortContact(Contact* pc);

//按姓名排序
void SortContact_by_name(Contact* pc);

//按年龄排序
void SortContact_by_age(Contact* pc);

//销毁通讯录
void DestroyContact(Contact* pc);

//保存通讯录中的数据到文件中
void SaveContact(Contact* pc);

🧩3.3.2 Contact.c 文件

#define _CRT_SECURE_NO_WARNINGS 1

#include "Contact.h"

//检查通讯录的容量是否已满
void CheckCapacity(Contact* pc);

void ModifyMenu()
{
	printf("------------------------\n");
	printf("----   0.退出修改   ----\n");
	printf("----   1.修改姓名   ----\n");
	printf("----   2.修改年龄   ----\n");
	printf("----   3.修改性别   ----\n");
	printf("----   4.修改电话   ----\n");
	printf("----   5.修改地址   ----\n");
	printf("------------------------\n");
}

void SortMenu()
{
	printf("-------------------------\n");
	printf("----   0.退出排序    ----\n");
	printf("----   1.按姓名排序  ----\n");
	printf("----   2.按年龄排序  ----\n");
	printf("-------------------------\n");
}

//将文件信息加载到通讯录
void LoadContact(Contact* pc)
{
	FILE* pf = fopen("Contact.text", "rb");
	if (pf == NULL)
	{
		perror("LoadContact");
		return;
	}
	//加载文件信息(读文件)
	PeoInfo temp = { 0 }; //临时存储从文件中读取的数据
	while (fread(&temp, sizeof(PeoInfo), 1, pf)) //读取成功返回1,否则返回0
	{
		//检查容量,如果没满才能加载文件中的数据到通讯录
		CheckCapacity(pc);
		pc->data[pc->sz++] = temp;
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
}

//文件版本的初始化通讯录
void InitContact(Contact* pc)
{
	assert(pc);
	pc->data = (PeoInfo*)malloc(sizeof(PeoInfo) * INIT_DATA);
	if (pc->data == NULL)
	{
		perror("InitContact->malloc");
		return;
	}
	memset(pc->data, 0, sizeof(pc->data));
	pc->sz = 0;
	pc->capacity = INIT_DATA;
	//加载文件中的信息到通讯录
	LoadContact(pc); 
}

动态版本的初始化通讯录
//void InitContact(Contact* pc)
//{
//	assert(pc);
//	pc->data = (PeoInfo*)malloc(sizeof(PeoInfo) * INIT_DATA);
//	if (pc->data == NULL)
//	{
//		perror("InitContact->malloc");
//		return;
//	}
//	memset(pc->data, 0, sizeof(pc->data));
//	pc->sz = 0;
//	pc->capacity = INIT_DATA; 
//}

//扩容
void CheckCapacity(Contact* pc)
{
	//1.如果满了,就增容
	if (pc->sz == pc->capacity) 
	{
		Contact* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + ADD_DATA) * sizeof(PeoInfo)); 
		if (ptr == NULL)
		{
			perror("CheckCapacity->realloc");
			return;
		}
		pc->data = ptr;
		pc->capacity += ADD_DATA;
		printf("增容成功\n");
	}
	//2.如果没满,啥也不干
}

//增加联系人
void AddContact(Contact* pc)
{
	assert(pc);
	//检查通讯录当前的容量
	//1.如果满了,就增容
	//2.如果没满,啥也不干
	CheckCapacity(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].tele);
	printf("请输入增加联系人的地址:");
	scanf("%s", pc->data[pc->sz].addr);

	pc->sz++;
	printf("增加联系人成功!\n");
}

//显示联系人信息
void ShowContact(Contact* pc)//这里形参也可以是结构体变量,因为打印不改变结构体内容
{
	assert(pc);
	if (pc->sz == 0)
	{
		printf("通讯录为空,无需打印\n");
		return;
	}
	int i = 0;
	printf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");
	for (i = 0; i < pc->sz; i++)
	{
		//打印每个人的信息
		printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",
			i + 1, pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
	}
}

//查找联系人并返回下标
int FindByName(Contact* pc, char name[])
{
	assert(pc);
	for (int i = 0; i < pc->sz; i++)
	{
		if (strcmp(name, pc->data[i].name) == 0)
		{
			return i;//找到了,返回下标
		}
	}
	return -1;//没找到,返回-1
}

//删除联系人
void DelContact(Contact* pc)
{
	char name[NAME_MAX];
	assert(pc);
	if (pc->sz == 0)
	{
		printf("通讯录为空,无法删除\n");
		return;
	}
	printf("请输入要删除的人的名字:");
	scanf("%s", name);
	//找到名字为name的人
	int ret = FindByName(pc, name);
	if (ret == -1)
	{
		printf("要删除的人不存在\n");
		return;
	}
	//删除这个人的信息
	for (int i = ret; i < pc->sz - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];
	}
	pc->sz--;
	printf("删除成功!\n");
}

//查找联系人并显示联系人的信息
void SearchContact(Contact* pc)
{
	char name[NAME_MAX];
	assert(pc);
	printf("请输入要查找的人的姓名:");
	scanf("%s", &name);
	int ret = FindByName(pc, name);
	if (ret == -1)
	{
		printf("要查找的人不存在\n");
		return;
	}
	//显示找到的人的信息
	printf("%-5s %-10s %-5s %-5s %-12s %-30s\n", "序号", "姓名", "年龄", "性别", "电话", "地址");
	printf("%-5d %-10s %-5d %-5s %-12s %-30s\n",
		ret + 1,
		pc->data[ret].name,
		pc->data[ret].age,
		pc->data[ret].sex,
		pc->data[ret].tele,
		pc->data[ret].addr);
}

//修改指定联系人
void ModifyContact(Contact* pc)
{
	char name[NAME_MAX];
	assert(pc);
	printf("请输入要修改的人的姓名:");
	scanf("%s", &name);
	int ret = FindByName(pc, name);
	if (ret == -1)
	{
		printf("要修改的人不存在\n");
		return;
	}
	int input = 0;
	do
	{
		ModifyMenu();
		printf("请选择你要修改的信息->:");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			printf("退出修改!\n");
			break;
		case 1:
			printf("请输入修改后的姓名:");
			scanf("%s", pc->data[ret].name);
			break;
		case 2:
			printf("请输入修改后的年龄:");
			scanf("%d", &(pc->data[ret].age));
			break;
		case 3:
			printf("请输入修改后的性别:");
			scanf("%s", pc->data[ret].sex);
			break;
		case 4:
			printf("请输入修改后的电话:");
			scanf("%s", pc->data[ret].tele);
			break;
		case 5:
			printf("请输入修改后的地址:");
			scanf("%s", pc->data[ret].addr);
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	printf("修改成功!\n");
}

//销毁通讯录
void DestroyContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->sz = 0;
	pc->capacity = 0;
}

//按名字比较
int cmp_by_name(const void* e1, const void* e2)
{
	return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}

//按年龄比较
int cmp_by_age(const void* e1, const void* e2)
{
	return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}

//按姓名排序
void SortContact_by_name(Contact* pc)
{
	qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_name);
}
//按年龄排序
void SortContact_by_age(Contact* pc)
{
	qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_age);
}

//排序通讯录
void SortContact(Contact* pc)
{
	int inpuct = 0;
	if (pc->sz == 0)
	{
		printf("通讯录为空,不用排序!\n");
		return;
	}

	do
	{
		SortMenu();
		printf("请选择排序方式 ->:");
		scanf("%d", &inpuct);
		switch (inpuct)
		{
		case 0:
			printf("退出排序!\n");
			break;
		case 1:
			SortContact_by_name(pc);
			printf("按姓名排序后:\n");
			ShowContact(pc);
			break;
		case 2:
			SortContact_by_age(pc);
			printf("按年龄排序后:\n");
			ShowContact(pc);
			break;
		default:
			printf("输入错误,请重新输入");
			break;
		}
	} while (inpuct);
}

//保存通讯录中的数据到文件中
void SaveContact(Contact* pc)
{
	FILE* pf = fopen("Contact.text", "wb");
	if (pf == NULL)
	{
		perror("SaveContact");
		return;
	}
	//写信息到文件
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		//fwrite(&(pc->data[i]), sizeof(PeoInfo), 1, pf);
		fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);
	}
	//关闭文件
	fclose(pf);
	pf = NULL; 
}

🧩3.3.3 test.c 文件

#define _CRT_SECURE_NO_WARNINGS 1
//测试通讯录基本功能

#include "Contact.h"

void menu()
{
	printf("------------------------\n");
	printf("----     1.add      ----\n");
	printf("----     2.del      ----\n");
	printf("----     3.search   ----\n");
	printf("----     4.modify   ----\n");
	printf("----     5.show     ----\n");
	printf("----     6.sorts    ----\n");
	printf("----     0.exit     ----\n");
	printf("------------------------\n");
}

enum Option
{
	EXIT, //0
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT
};

int main()
{
	int input = 0;
	//创建通讯录
	Contact con;//通讯录
	//初始化通讯录
	InitContact(&con);

	int choice = 0;//排序方式的选择
	do
	{
		menu();
		printf("请输入你的选择 ->:");
		scanf("%d", &input);
		switch (input)
		{
		case EXIT:
			//保存通讯录中的数据到文件中
			SaveContact(&con);
			//销毁通讯录
			DestroyContact(&con);
			printf("退出通讯录\n");
			break;
		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;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	return 0;
}

💖今天的分享就到此结束了,如有疑问或是文章有不足之处,还请大家在评论区与我讨论或者私信我,创作不易,如果文章对你有帮助的话,还请三连支持一下咯🔥

在这里插入图片描述

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

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

相关文章

第三章:boundary-value analysis

文章目录 Boundary-value Analysiscomputational faults 计算错误boundary shift 边界偏移boundary value analysis 的优势Path condition, domain, and domain boundary (路径条件、域和域边界)Open and closed boundaries (闭合边界 / 开放边界)on / off pointGuidelinestr…

浅析LiveMedia智能视频网关的AI识别技术及应用场景

一、行业背景 &#xff08;1&#xff09;AI技术在安防领域大量落地应用 随着近几年人工智能的快速发展&#xff0c;深度学习方法及性能日益提升&#xff0c;计算机视觉、图像处理、视频结构化和大数据分析等技术也不断完善&#xff0c;使得安防产品逐步走向智能化。在技术成熟…

redis五种数据类型

Redis支持五种数据类型&#xff1a;string&#xff08;字符串&#xff09;&#xff0c;hash&#xff08;哈希&#xff09;&#xff0c;list&#xff08;列表&#xff09;&#xff0c;set&#xff08;集合&#xff09;及zset(sorted set&#xff1a;有序集合)。 1.String&#…

Rust结构体和枚举类

文章目录 元组结构体结构体枚举类 Rust初步上手⚙所有权 元组结构体 元组结构体是最简单的结构体&#xff0c;可以粗暴地理解为是有名字的元组&#xff0c;二者的区别如下。 let tup: (i32, f64, u8) (500, 6.4, 1);struct Test(i32, f64, u8); let t Test(500,6.4,1)第一…

操作系统——文件在外存中的分配方式(王道视频p61 P62)

1.总体概述&#xff1a; 连续分配 —— 链接分配 —— 索引分配 &#xff08;1&#xff09;对于顺序分配&#xff0c;这种方式 基本不会使用了&#xff0c; 因为 它存在一个 核心的问题就是 没法更新&#xff1b;不过&#xff0c;还是要注意它的 “文件目录”——其中存放了…

Memcached构建缓存服务器

Memcache介绍 1、特点 内置存储方式----------为了提高性能&#xff0c;memcached中保存的数据都存储在memcache内置的内存存储空间中。由于数据仅存在于内存中&#xff0c;重启操作系统会导致全部数据消失 简单key/value存储--------------服务器不关心数据本身的意义及结构&…

NSSCTF web刷题记录4

文章目录 [NSSRound#4 SWPU]1zweb(revenge)[强网杯 2019]高明的黑客[BJDCTF 2020]Cookie is so subtle![MoeCTF 2021]fake game[第五空间 2021]PNG图片转换器[ASIS 2019]Unicorn shop[justCTF 2020]gofs[UUCTF 2022 新生赛]phonecode[b01lers 2020]Life On Mars[HZNUCTF 2023 f…

【qemu逃逸】GACTF2020-babyqemu

前言 虚拟机用户名&#xff1a;root 无密码 设备逆向 题目去掉的符号&#xff0c;经过逆向分析&#xff0c;实例结构体如下&#xff1a; 可以看到 arr_int_8 数组后面存在一个函数指针&#xff0c;不用想基本上就是劫持该函数指针了。 denc_mmio_read 函数 这里存在越界读…

.net core 到底行不行!超高稳定性和性能的客服系统:性能实测

业余时间用 .net core 写了一个升讯威在线客服系统。并在博客园写了一个系列的文章&#xff0c;介绍了这个开发过程。 我把这款业余时间写的小系统丢在网上&#xff0c;陆续有人找我要私有化版本&#xff0c;我都给了&#xff0c;毕竟软件业的初衷就是免费和分享&#xff0c;后…

最新知识付费变现小程序源码/独立后台知识付费小程序源码/修复登录接口

最新知识付费变现小程序源码&#xff0c;独立后台知识付费小程序源码&#xff0c;最新版修复登录接口。 主要功能 会员系统&#xff0c;用户登录/注册购买记录 收藏记录 基本设置 后台控制导航颜色 字体颜色 标题等设置 流量主广告开关小程序广告显示隐藏 广告主审核过审核…

VS2022创建win32汇编项目

文章目录 一、下载安装win32环境1.1、下载网址&#xff1a;https://masm32.com/1.2、解压缩安装1.3、安装路径1.4、安装masm32 SDK1.5、安装成功1.6、导入lib1.7、配置默认&#xff0c;可以根据自己需求修改1.8、启动界面二、vs2022 安装过程略过。。。2.1、创建项目2.2、填写项…

高通Android 8.1 扫码枪无法扫sn包含2或者全部是2的问题

背景&#xff1a;由于近期工厂生产&#xff0c;测试突然反馈扫码枪扫sn总是丢失2&#xff0c;比如 AXB2SHS822009997/LSXG 结果显示是 AXBSHS800997/LSX 于是我叫测试找了之前可以版本然后抓日志进行对比发现&#xff0c;确实只有2这个数字无法扫&#xff0c;如果把2这一位改成…

California Science Museum

文章目录 1. University of Southern California(USC)2. NASA航天飞机3. 返回舱4. Others彩蛋1: Paris, capital of France彩蛋2: Switzerland(瑞士)1. University of Southern California(USC) 2. NASA航天飞机

运动耳机品牌排行榜,推荐几款优秀的运动耳机

​说起耳机&#xff0c;相信大家都比较熟悉&#xff0c;特别是对于喜欢运动的爱好人士来说&#xff0c;那更是随身携带着。随着运动耳机的增长&#xff0c;大家都不知道该如何选择了。对于运动耳机除了需要佩戴稳固舒适之外&#xff0c;还有就是音质表现、防水性能、通话质量等…

外汇天眼实勘功能升级,带你沉浸式“云”穿交易商现场!

最近&#xff0c;外汇天眼新出了一个功能&#xff0c;这个功能可了不得了&#xff0c;不管你在国外还是在国内&#xff0c;它都能带你走进交易商现场。不过在介绍该功能之前&#xff0c;天眼君先问大家几个问题&#xff1a;在进行外汇交易前&#xff0c;你对自己的交易平台了解…

项目启动∣得益乳业引进企企通采购供应链管理+智采商城平台,切实提升供应链效率

近日&#xff0c;山东得益乳业股份有限公司&#xff08;以下简称“得益乳业”&#xff09;与企企通成功召开采购供应链管理智采商城双项目启动会。双方高层领导及项目团队关键成员&#xff0c;一同出席本次启动会。 本次合作以企企通数字化采购解决方案为基础&#xff0c;结合得…

基于 golang 从零到一实现时间轮算法 (三)

引言 本文参考小徐先生的相关博客整理&#xff0c;项目地址为&#xff1a; https://github.com/xiaoxuxiansheng/timewheel/blob/main/redis_time_wheel.go。主要是完善流程以及记录个人学习笔记。 分布式版实现 本章我们讨论一下&#xff0c;如何基于 redis 实现分布式版本的…

Java零基础手把手保姆级教程_类和对象(超详细)

文章目录 Java零基础手把手保姆级教程_类和对象&#xff08;超详细&#xff09;1. 类和对象1.1 类和对象的理解1.2 类的定义1.3 对象的使用1.4 学生对象-练习1.5测测你掌握了没&#xff1f; 2. 对象内存图2.1 单个对象内存图2.2 多个对象内存图2.3 多个对象指向相同内存图 3. 成…

从首届中国测绘地理信息大会,解读2023年度国产GIS创新关键词

创新是什么&#xff1f;这是各行各业持续思考的问题。 第一届中国测绘地理信息大会已进入倒计时&#xff01;这是中国测绘学会、中国地理信息产业协会和中国卫星导航定位协会共同主办的全国性高端盛会。据悉&#xff0c;本次大会将有1个主论坛、38场分论坛&#xff0c;近2万平…

YOLOv8改进有效涨点系列->多位置替换可变形卷积(DCNv1、DCNv2、DCNv3)

本文介绍 这篇文章主要给大家讲解如何在多个位置替换可变形卷积&#xff0c;它有三个版本分别是DCNv1、DCNv2、DCNv3&#xff0c;在本篇博文中会分别进行介绍同时进行对比&#xff0c;通过本文你可以学会在YOLOv8中各个位置添加可变形卷积包括(DCNv1、DCNv2、DCNv3)&#xff0…