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


  • 博客主页:江池俊的博客
  • ⏩收录专栏: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.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");
}

通讯录详解(静态版,动态版,文件版)_第2张图片

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
#include//memset、strcmp
#include//assert
#include//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,表示成功增加了一个联系人,并打印一条成功消息。

通讯录详解(静态版,动态版,文件版)_第3张图片

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. 打印一条成功消息,表示删除成功。

通讯录详解(静态版,动态版,文件版)_第4张图片

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. 如果找到了联系人,它会显示该联系人的信息;否则,它会打印一条错误消息。

通讯录详解(静态版,动态版,文件版)_第5张图片

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");
}

通讯录详解(静态版,动态版,文件版)_第6张图片

(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. 最后输出修改成功的提示信息。

通讯录详解(静态版,动态版,文件版)_第7张图片

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);
	}
}

通讯录详解(静态版,动态版,文件版)_第8张图片

1.2.7 排序通讯录

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

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

通讯录详解(静态版,动态版,文件版)_第9张图片

(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. 如果用户输入的选择不在有效范围内,会打印错误提示并重新显示排序菜单。

通讯录详解(静态版,动态版,文件版)_第10张图片

1.3 静态通讯录源代码

1.3.1 Contact.h 文件

#pragma once

#include
#include
#include
#include//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
#include
#include
#include//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
#include
#include
#include //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;
}

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

你可能感兴趣的:(C语言进阶之路,c语言,学习,经验分享,通讯录)