【C语言进阶】6. 通讯录的思路与代码实现

通讯录的主题思想,将100个人的信息存放在通讯录当中,对其进行增、删、改、查、排序等操作

1.静态版本

gitee码仓:静态版本实现通讯录的具体代码
首先人是复杂对象,需要用结构体进行存储

1.1 声明结构体对象

#define NAME_MAX 20
#define SEX_MAX 8
#define TELE_MAX 12
#define ADDR_MAX 30
#define DATA_MAX 100

// 存储人的信息
typedef struct Peo_info
{
	char name[NAME_MAX];
	int age;
	char sex[SEX_MAX];
	char tele[TELE_MAX];
	char addr[ADDR_MAX];
}Peo_info;

// 存放通讯录的信息
typedef struct Contact
{
	// 记录100个人的信息
	Peo_info data[DATA_MAX];
	// 通讯录当中人数
	int count;
}Contact;

分别对 struct Peo_info 和 struct Contact 进行重命名操作,将元素空间用 #define 定义宏 增加代码可读性

1.2 基本框架

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

int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择要进行的操作:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			break;
		case 2:
			break;
		case 3:
			break;
		case 4:
			break;
		case 5:
			break;
		case 6:
			break;
		case 0:
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
		}
	} while (input);
	return 0;
}

1.3 创建并初始化结构体对象

void InitContact(Contact* pc)
{
	// 对pc指针进行断言
	assert(pc);
	// 将通讯录中的人数设置成0
	pc->count = 0;
	// 将通讯录的数据全部置成0
	memset(pc->data, 0, sizeof(pc->data));
}

memset函数 将pc->data个字节的空间全部用0覆盖,赋初值
接下来就是各个功能模块的实现

1.4 模块化划分(核心)

1.4.1 AddContact

		case 1:
			AddContact(&con);
			break;

case 1: 进行Add操作

// 往通讯录中增加
void AddContact(Contact* pc)
{
	assert(pc);
	if (pc->count == DATA_MAX)
	{
		printf("通讯录人数已满,无法添加\n");
		return;
	}
	printf("当前人数:%d\n", pc->count);
	// 往下标为count的位置添加信息
	printf("请输入添加对象的姓名:");
	scanf("%s", pc->data[pc->count].name);
	printf("请输入添加对象的年龄:");
	// scanf函数接收的是地址,所以要对age值取地址
	// 其他元素都是数组,数组名就是首元素地址
	scanf("%d", &(pc->data[pc->count].age));
	printf("请输入添加对象的性别:");
	scanf("%s", pc->data[pc->count].sex);
	printf("请输入添加对象的电话:");
	scanf("%s", pc->data[pc->count].tele);
	printf("请输入添加对象的地址:");
	scanf("%s", pc->data[pc->count].addr);
	printf("添加成功\n");
	// 人数++ 确保下一次操作正确进行
	pc->count++;
}

1.4.2 DelContact

		case 2:
			DelContact(&con);
			break;

case 2: 进行删除操作

// 将函数设置成静态的,只允许在当前的.c文件中访问
static int Find_by_name(Contact* pc, char* name)
{
	int i = 0;
	// 遍历整个数组当中的名字
	for (i = 0; i < pc->count; i++)
	{
		// 两字符串比较相等用strcmp函数
		if (strcmp(pc->data[pc->count].name, name))
		{
			// 将元素的下标返回
			return i;
		}
	}
	//没找到 返回-1
	return -1;
}
// 删除信息
void DelContact(Contact* pc)
{
	// 要想删除先要找到对象是否在通讯录当中
	char name[NAME_MAX] = { 0 };
	printf("请输入要删除人的姓名:>");
	scanf("%s", name);
	//查找名字
	//拿 ret 接收返回值
	int ret = Find_by_name(pc, name);
	if (ret == -1)
	{
		printf("要删除的对象不在通讯录中\n");
		return;
	}
	else
	{
		//删除就是将后面的对象往前覆盖,count再--
		// 这里ret小于pc—>count -1为了防止越界访问
		// 返回下标的作用就体现出来了
		for (ret; ret < (pc->count)-1; ret++)
		{
			pc->data[ret] = pc->data[ret + 1];
		}
		pc->count--;
		printf("删除完毕\n");
	}
}

1.4.3 SearchContact

		case 3:
			SearchContact(&con);
			break;

case 3 :进行查找操作

void SearchContact(Contact* pc)
{
	// 这里可以引用上述Find_by_name代码
	char name[NAME_MAX] = { 0 };
	printf("请输入要查找人的姓名:>");
	scanf("%s", name);
	int ret = Find_by_name(pc, name);
	if (ret == -1)
	{
		printf("要查找的对象不在通讯录中\n");
		return;
	}
	else
	{
		//查找到对象要将其信息打印出来
		printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
		printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n", pc->data[ret].name,
			pc->data[ret].age,
			pc->data[ret].sex,
			pc->data[ret].tele,
			pc->data[ret].addr);
	}
}

1.4.4 ModifyContact

		case 4:
			ModifyContact(&con);
			break;

case 4:进行修改操作

void ModifyContact(Contact* pc)
{
	//同样要先找到所要修改的对象
	char name[NAME_MAX] = { 0 };
	printf("请输入所要修改人的姓名:>");
	scanf("%s", name);
	int ret = Find_by_name(pc, name);
	if (ret == -1)
	{
		printf("要修改的对象不在通讯录中\n");
		return;
	}
	//怎么修改?
	// 将下标为ret的元素重新输入一遍,即为修改
	printf("请输入修改后的姓名:");
	scanf("%s", pc->data[ret].name);
	printf("请输入修改后的年龄:");
	scanf("%d", &(pc->data[ret].age));
	printf("请输入修改后的性别:");
	scanf("%s", pc->data[ret].sex);
	printf("请输入修改后的电话:");
	scanf("%s", pc->data[ret].tele);
	printf("请输入修改后的地址:");
	scanf("%s", pc->data[ret].addr);
	printf("修改成功\n");
}

1.4.5 ShowContact

		case 5:
			ShowContact(&con);
			break;

case 5: 进行展示操作

void ShowContact(Contact* pc)
{
	// 判断通讯录当中是否有对象
	if (pc->count == DATA_MIN)
	{
		printf("通讯录中没有元素,无法显示\n");
		return;
	}
	//有元素
	int i = 0;
	printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
	for (i = 0; i < pc->count; i++)
	{
		printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n", pc->data[i].name,
			pc->data[i].age,
			pc->data[i].sex,
			pc->data[i].tele,
			pc->data[i].addr);
	}
}

1.4.6 SortContact

		case 6:
			SortContact(&con);
			break;

case 6 :进行排序操作

int cmp_by_name(const void *e1,const void *e2)
{
	//比较字符串用strcmp
	return strcmp(((Peo_info*)e1)->name, ((Peo_info*)e2)->name);
}
void SortContact(Contact* pc)
{
	//对通讯录中的对象进行排序
	// 这里采取对名字排序
	// 快排
	qsort(pc->data, pc->count, sizeof(Peo_info), cmp_by_name);
	printf("排序成功\n");
}

使用快排,快排的参数 要比较的起始位置,比较的个数,所比较元素的大小,比较方法
模块划分实现完毕
在这里插入图片描述
具体操作的实现,到这里基本的静态版本通讯录就完成了

2.动态版本

gitee码仓:动态版本实现通讯录的具体代码
动态版本与静态版本最大的区别就是动态开辟内存(根据需要开辟内存空间)
在这里插入图片描述
既然选择动态开辟,那么struct Contact 里面就不能用数组来固定元素个数,而应该用指针的形式(再通过malloc开辟内存)

2.1 修改结构体成员

在这里插入图片描述
结构体类型进行了修改,那么通讯录的初始化也要进行相应的修改

2.2 修改初始化函数

// 动态版本
int InitContact(Contact* pc)
{
	// 对pc指针进行断言
	assert(pc);
	// 将通讯录中的人数设置成0
	pc->count = 0;
	pc->data = (Peo_info*)calloc(3, sizeof(Peo_info));
	// 如果没开辟成功返回错误码信息
	if (pc->data == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	// 开辟成功
	//容量设置成默认大小:当前为3
	pc->capacity = DEFAULT_SZ;
	//通常C语言中没有错误返回的是0,有错误返回1
	return 0;
}

通过calloc开辟并初始化内存空间
同样的,Add函数涉及增容的概念,如果空间开辟的不够要进一步增加空间

2.3 修改Add函数

void CheckCapacity(Contact* pc)
{
	//既然是动态开辟的就不存在人数满的情况
	//if (pc->count == DATA_MAX)
	//{
	//	printf("通讯录人数已满,无法添加\n");
	//	return;
	//}

	// 容量等于当前人数,需要扩容
	if (pc->count == pc->capacity)
	{
		// 用realloc 函数调整空间大小,每次增长2个Peo_info的空间
		// realloc 调整空间后同样需要指针接收 ptr
		Peo_info* ptr = (Peo_info*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(Peo_info));
		//增容失败,报错
		if (ptr == NULL)
		{
			printf("AddContact::%s\n", strerror(errno));
		}
		else
		{
			// 接收ptr,指向增容后的空间
			pc->data = ptr;
			pc->capacity += INC_SZ;
			printf("增容成功\n");
		}
	}
}
//动态版本
void AddContact(Contact* pc)
{
	assert(pc);
	// 将增容封装成函数
	CheckCapacity(pc);
	//....后续内容不变

在这里插入图片描述

2.4 修改退出选项

退出通讯录就需要free进行空间的释放,因为通讯录中的data都是通过malloc,realloc开辟得来的,需要释放

void DestroyContact(Contact* pc)
{
	assert(pc);
	//释放data空间
	free(pc->data);
	//设置成0 pc->data被free后成为野指针,置成NULL才安全
	pc->data = NULL;
}
		case 0:
			DestroyContact(&con);
			printf("退出通讯录\n");
			break;

在case 0 中添加free释放空间,退出程序

动态的版本到这里也就实现完成了

3.文件版本

gitee码仓:文件版本实现通讯录的具体代码

3.1 保存数据到文件

首先在退出通讯录的时候需要能实现将数据写入文件当中(存入磁盘当中,而不是只能存在于内存)
所以在case 0:DesroyContact之前加入一个保存模块SaveContact

case 0:
			SaveContact(&con);
			DestroyContact(&con);
			printf("退出通讯录\n");
			break;

Save函数的代码实现:
在这里插入图片描述

3.2 将数据读入通讯录中

存储在文件中的数据需要能展现出来,(在内存:控制台中可以看见数据)从磁盘中读入内存
在初始化InitContact中不仅要实现动态开辟内存,而且需要把之前存在磁盘文件中的数据存入通讯录中
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
CheckCapacity 检查空间是否足够(在动态版本中实现的检查空间判断增容函数)
在这里插入图片描述
到这里文件版本实现通讯录也就完成了
但是我们知道在这里插入图片描述
相较于文件,数据库存储数据更具优势,当然这部分的知识还未深入学习…
那么一整个通讯录的实现到这也就完全实现喽!!!撒花✿✿ヽ(°▽°)ノ✿

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值