(使用柔性数组)动态版通讯录

前言:

  • 博主目前实力有限,博文有什么错误,请你斧正!

  • 后面学会EasyX会重新更新本篇博客。0.0.

  • 上一篇博客更新了动态内存管理与柔性数组,因此趁热打铁,本通讯录采用动态函数与柔性数组。

  • 希望本篇博客对你有帮助。

思维导图

动态通讯录(柔性数组版)

思路分析

分析一:

我的思路是来一个元素,增加一次空间(realloc),这样不需要预留空间 了。顺便在添加元素的时侯,排序元素。0.0

分析二

通讯录中包含 很多人,而每个人又有很多类信息。因此我们创建2个结构体(一个也行,建立结构数组):一个是人信息的集合,一个记录有效的人数与人结构柔性数组的通讯录

enum whole//存放全局作用的常量
{
     
     
	Name_max=20,
	Age_max=10,
	Sex_max=20,
	Tel_max=20,
	Exit=0,
	Add,
	Del,
	Modify,
	Show,
	Search
};
	
struct peo
{
     
     
	char name[Name_max];
	char age[Age_max];
	char sex[Sex_max];
	char tel[Tel_max];
};

struct cont
{
     
     
	size_t sz;
	struct peo date[0];//柔性数组,这里用realloc
};

分析三:

在删除,修改,都需要对结构体进行元素查找(我用的二分查找法,需要数组有一定顺序),因此需要排序数组,但是什么时候排序呢?我的思路是在添加元素的时候就排序数组。而排序我用的是 模拟qsort的方法。需要2个种子。

分析四:

我们在函数中动态内存开辟时,实参可能不产生任何影响**(形参只是实参的拷贝,我只是给形式变量传一个数据)**。为了避免这种情况,在某些函数我们通过使用二级指针。可以改变实参的指向。另外

在这个函数中:我们可以先用中间变量去 realloc,产生我们想要作用之后,再用实参指向新的申请空间。

函数分析

初始化函数

  • 通过二级指针的方法,改变实参的指向。
void In_Bein(struct cont** pc)
{
     
     
	assert(pc);
	
	struct cont *p = (struct cont*)malloc(sizeof(struct cont));//这里只需要给sz申请空间就行了
	                                                            //后面增加元素的时候再未peo开辟空间
	if (NULL == p)
	{
     
     
	
		printf("%s\n", strerror(errno));
		exit(1);
	}
	p->sz = 0;
	*pc = p;
	//p是局部变量,因此函数栈帧结束后,就成为野指针,但是那快动态申请的空间没有free,
	// 但是我们通过 *pc=p,这步,在程序结束,最后free就行了。
}

打印函数

void My_Show(struct cont* pc)
{
     
     
	assert(pc);//断言防止传入NULL指针
	for (size_t i = 0; i < pc->sz; i++)
	{
     
     
		printf("姓名:%s\n", ((pc -> date)+i)->name);
		printf("年龄:%s\n", ((pc-> date)+i)->age);
		printf("性别:%s\n", ((pc->date)+i)->sex);
		printf("电话:%s\n", ((pc->date)+i)->tel);
	}

}

排序函数

  • 复写qsort的使用见我另外一篇博客

  • 在排序的时候,可能会碰到同名的情况,因此我们比较电话号码就行了,毕竟电话号码是唯一的。因此需要2个比较函数(我称谓种子)。(排序是从小到大,想从大到小,改变种子就行)

int compare1(const void* e1, const void* e2)//种子1 比较名字
								//比较结构体2元素中成员 名字
{
     
     
	assert(e1 && e2);//防止传入NULL指针
	return strcmp(((struct peo*)e1)->name, ((struct peo*)e2)->name);

}
int compare2(const void* e1, const void* e2)//种子2 比较电话
{
     
     
	assert(e1 && e2);
	return strcmp(((struct peo*)e1)->tel, ((struct peo*)e2)->tel);

}
void swap(void* e1, void* e2, size_t width)//交换2元素所占内存字节中的内容
{
     
     

	assert(e1 && e2);
	for (size_t i = 0; i < width; i++)//依次交换e1,e2中的字节内容
	{
     
     
		unsigned char tmp = *((unsigned char*)e1 + i);
		*((unsigned char*)e1 + i) = *((unsigned char*)e2 + i);
		*((unsigned char*)e2 + i) = tmp;
	}

}
void my_qsort(void* base,
	size_t num,size_t width	)
{
     
     
	assert(base);//防止传入NULL指针
	
	for (size_t i = 0; i < num-1; i++)
	{
     
     
		size_t min = i;
		for (size_t j = i + 1; j < num; j++)
		{
     
      //传入的是date,因此强转(char*)后利用width就行了
			if (0 < compare1( (char *)base + min * width, (char*)base + j * width))
			{
     
     
				min = j;
			}
			else if (0 == compare1((char*)base + min * width, (char*)base + j * width))//一旦重名就检测电话
			{
     
     

				if (0 < compare2((char*)base + min * width, (char*)base + j * width))
				{
     
     
					min = j;
				}
			}
		}
		if (min != i)
		{
     
     
			swap((char *)base + i * width, (char *)base + min * width,width);
		
		}

	}
}


添加元素函数

  • 添加元素,必然需要重新开辟空间,需要realloc。
  • 我们要使实参改变指向,必然二级指针。
void My_Add(struct cont** pc)
{
     
     
	assert(pc);//断言防止传入NULL指针
	struct  cont* p = (struct cont*)realloc(*pc, sizeof(struct cont) + ((*pc)->sz+1) * sizeof(struct peo));
	if (NULL == p)
	{
     
     
		printf("%s\n", strerror(errno));//打印错误信息
		exit(1);
	}
	printf("请输入姓名:");
	scanf("%s", (p->date + p->sz)->name);
	printf("\n");
	printf("请输入性别:");
	scanf("%s", (p->date + p->sz)->sex);
	printf("\n");

	printf("请输入年龄:");
	scanf("%s", (p->date + p->sz)->age);
	printf("\n");

	printf("请输入电话:");
	scanf("%s", (p->date + p->sz)->tel);
	p->sz++;
	*pc = p;
	my_qsort(p->date, p->sz, sizeof(struct peo));//排序新增后的元素
}
void My_Add(struct cont** pc)
{
     
     
	assert(pc);//断言防止传入NULL指针
	struct  cont* p = (struct cont*)realloc(*pc, sizeof(struct cont) + ((*pc)->sz+1) * sizeof(struct peo));
	if (NULL == p)
	{
     
     
		printf("%s\n", strerror(errno));//打印错误信息
		exit(1);
	}
	printf("请输入姓名:");
	scanf("%s", (p->date + p->sz)->name);
	printf("\n");
	printf("请输入性别:");
	scanf("%s", (p->date + p->sz)->sex);
	printf("\n");

	printf("请输入年龄:");
	scanf("%s", (p->date + p->sz)->age);
	printf("\n");

	printf("请输入电话:");
	scanf("%s", (p->date + p->sz)->tel);
	p->sz++;
	*pc = p;
	my_qsort(p->date, p->sz, sizeof(struct peo));
}

查找元素函数

  • 二分查找必然需要数组一定顺序.

  • 返回一个值的原因是为了其它函数的实现。

int  My_Search(struct cont* pc,const char *str1,const char *str2)//二分查找
{
     
     
	assert(pc && str1 && str2);//断言防止传入NULL指针
	size_t left = 0;
	size_t right = pc->sz - 1;
	while (left <= right)
	{
     
     
		size_t mid = (left + right) / 2;
		if ((strcmp((pc->date + mid)->name, str1)==0)&& (strcmp((pc->date + mid)->tel, str2) == 0))
		{
     
     
			printf("找到了\n");
			printf("%d\n", mid);
			return mid;
		}
		else if((strcmp((pc->date + mid)->name, str1)<0)||((strcmp((pc->date + mid)->name, str1)==0)  && (strcmp((pc->date + mid)->tel, str2)<0)))
		{
     
     
			left = mid + 1;
		
		}
		else if ((strcmp((pc->date + mid)->name, str1) > 0) || ((strcmp((pc->date + mid)->name, str1) == 0 && (strcmp((pc->date + mid)->tel, str2) > 0))))
		{
     
     
			right = mid - 1;
		}
	}

	printf("查无此人\n");
	return -1;
}

删除元素函数

  • 删除必然需要查找是否存在。而查找又需要排序。哈哈~~~0.0.
void My_Del(struct cont** pc)
{
     
     
	assert(pc);//断言防止传入NULL指针
	char You_name[Name_max] = {
     
     0};
	char You_tel[Tel_max] = {
     
     0};
	printf("请输入要删除的姓名与电话\n");
	scanf("%s", You_name);
	scanf("%s", You_tel);
	int ret = My_Search(*pc,You_name,You_tel);
	if (ret!=-1)
	{
     
     

		for (size_t i = ret; i < (*pc)->sz-1; i++)
		{
     
     
			*((*pc)->date + i) = *((*pc) -> date+i + 1);
		
		}
		struct cont* p = (struct cont*)realloc(*pc, ((*pc)->sz - 1) * sizeof(struct peo));
		if (NULL == p)
		{
     
     
			printf("%s\n", strerror(errno));
			exit(1);
		}
		p->sz--;
		*pc = p;
	}
	else
	{
     
     
		printf("请重新输入\n");
	}
}

修改元素函数

  • 修改必然查找…(套娃–哈哈 ~ ~~)
void My_Modify(struct cont* pc)
{
     
     

	assert(pc);//断言防止传入NULL指针
	char You_name[Name_max] = {
     
     0};//要初始化下,strcpy的原因
	char You_tel[Tel_max] = {
     
     0};
	char You_sex[Sex_max] = {
     
     0};
	char You_age[Age_max] = {
     
     0};

	while (1)
	{
     
     
		printf("请输入要修改的姓名与电话\n");
		scanf("%s", You_name);
		scanf("%s", You_tel);
		int ret = My_Search(pc, You_name, You_tel);
		if (ret != -1)
		{
     
     
			printf("请输入要修改的姓名:");
			scanf("%s", You_name);
			strcpy((pc->date + ret)->name, You_name);

			printf("请输入要修改的年龄:");
			scanf("%s", You_age);
			strcpy((pc->date + ret)->age , You_age);

			printf("请输入要修改的性别:");
			scanf("%s", You_sex);
			strcpy((pc->date + ret)->sex, You_sex);

			printf("请输入要修改的电话:");
			scanf("%s", You_tel);
			strcpy((pc->date + ret)->tel, You_tel);
			printf("修改成功\n")return;
		}
		else
		{
     
     
			printf("%s\t%s\n", You_name, You_tel);
		
		}

	}
}

全部代码:

text.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "contact.h"

void menu()//提示菜单
{
     
     
	printf("****************************************************\n");
	printf("*************动态通讯录*****************************\n");
	printf("****************************************************\n");
	printf("*******1.Add(添加)               2. Del(删除)*******\n");
	printf("****** 3.Modify(纠正)            4.Show(打印)*******\n");
	printf("*******5.Search(寻找)              *******\n");
	printf("*******0.Exit(退出)                     ************\n");
	printf("****************************************************\n");
}

int main ()
{
     
     

	int Input = 0;
	struct cont* pc = NULL;//悬挂pc
	In_Bein(&pc);//因为pc NULL的原因,我们必须让它指向一段动态的内存,并初始化。
	do//先打印提示菜单,供玩家判断。
	{
     
     
		menu();
		printf("请输入你的选择:\n");
			scanf("%d", &Input);
		switch (Input)
		{
     
     
		case Add:My_Add(&pc);
			break;
		case Del:My_Del(&pc);
			break;
		case Modify:My_Modify(pc);
			break;
		case Show:My_Show(pc);
			break;
		case Search:
		{
     
     
			char You_name[Name_max] = {
     
     0};
			char You_tel[Tel_max] = {
     
     0};
			printf("请输入要找的的姓名与电话\n");
			scanf("%s", You_name);
			scanf("%s", You_tel);
			My_Search(pc, You_name, You_tel);
		};
		   break;
		case Exit://exit是C语言内置的关键字,因此Exit,而不是 exit.
		{
     
     
			system("cls");//清空屏幕
			printf("\t\t~~~~~~~感谢使用博主的动态通许录!!!!~~~~~\n");
		};
		   break;
		default:printf("输入错误。请重新输入\n");
			break;
		}

	} while (Input);
	free(pc);
	pc = NULL;
	return 0;
}

contact.h

#pragma once

#include <stdio.h>
#include <stdlib.h> 
#include <assert.h>
#include <string.h>
enum whole//存放全局作用的常量
{
     
     
	Name_max=20,
	Age_max=10,
	Sex_max=20,
	Tel_max=20,
	Exit=0,
	Add,
	Del,
	Modify,
	Show,
	Search
};
	

struct peo
{
     
     
	char name[Name_max];
	char age[Age_max];
	char sex[Sex_max];
	char tel[Tel_max];
};

struct cont
{
     
     
	size_t sz;
	struct peo date[0];//柔性数组,这里用realloc
};

void In_Bein(struct cont** pc);
void My_Show(struct cont* pc);
void My_Add(struct cont** pc);
void My_Del(struct cont** pc);
int  My_Search(struct cont* pc,const char*str1,const char *str2);
void My_Modify(struct cont* pc);
int compare1(const void* e1, const void* e2);//种子1 比较名字
								//比较结构体2元素中成员 名字 
int compare2(const void* e1, const void* e2);//种子2 比较学号
void swap(void* e1, void* e2, size_t width);
void my_qsort(void* base,size_t num,size_t width);//有2个种子就不需要 再来个参数了




contact.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "contact.h"
void In_Bein(struct cont** pc)
{
     
     
	assert(pc);//断言防止传入NULL指针

	struct cont* p = (struct cont*)malloc(sizeof(struct cont));//这里只需要给sz申请空间就行了
																//后面增加元素的时候再未peo开辟空间
	if (NULL == p)
	{
     
     

		printf("%s\n", strerror(errno));
		exit(1);
	}
	p->sz = 0;
	*pc = p;
	//p是局部变量,因此函数栈帧结束后,就成为野指针,但是那快动态申请的空间没有free,
	// 但是我们通过 *pc=p,这步,在程序结束,最后free就行了。
}
void My_Show(struct cont* pc)
{
     
     
	assert(pc);//断言防止传入NULL指针
	for (size_t i = 0; i < pc->sz; i++)
	{
     
     
		printf("姓名:%s\n", ((pc -> date)+i)->name);
		printf("年龄:%s\n", ((pc-> date)+i)->age);
		printf("性别:%s\n", ((pc->date)+i)->sex);
		printf("电话:%s\n", ((pc->date)+i)->tel);
	}

}
void My_Add(struct cont** pc)
{
     
     
	assert(pc);//断言防止传入NULL指针
	struct  cont* p = (struct cont*)realloc(*pc, sizeof(struct cont) + ((*pc)->sz+1) * sizeof(struct peo));
	if (NULL == p)
	{
     
     
		printf("%s\n", strerror(errno));//打印错误信息
		exit(1);
	}
	printf("请输入姓名:");
	scanf("%s", (p->date + p->sz)->name);
	printf("\n");
	printf("请输入性别:");
	scanf("%s", (p->date + p->sz)->sex);
	printf("\n");

	printf("请输入年龄:");
	scanf("%s", (p->date + p->sz)->age);
	printf("\n");

	printf("请输入电话:");
	scanf("%s", (p->date + p->sz)->tel);
	p->sz++;
	*pc = p;
	my_qsort(p->date, p->sz, sizeof(struct peo));//排序新增后的元素
}

int  My_Search(struct cont* pc,const char *str1,const char *str2)//二分查找
{
     
     
	assert(pc && str1 && str2);//断言防止传入NULL指针
	size_t left = 0;
	size_t right = pc->sz - 1;
	while (left <= right)
	{
     
     
		size_t mid = (left + right) / 2;
		if ((strcmp((pc->date + mid)->name, str1)==0)&& (strcmp((pc->date + mid)->tel, str2) == 0))
		{
     
     
			printf("找到了\n");
			printf("%d\n", mid);
			return mid;
		}
		else if((strcmp((pc->date + mid)->name, str1)<0)||((strcmp((pc->date + mid)->name, str1)==0)  && (strcmp((pc->date + mid)->tel, str2)<0)))
		{
     
     
			left = mid + 1;
		
		}
		else if ((strcmp((pc->date + mid)->name, str1) > 0) || ((strcmp((pc->date + mid)->name, str1) == 0 && (strcmp((pc->date + mid)->tel, str2) > 0))))
		{
     
     
			right = mid - 1;
		}
	}

	printf("查无此人\n");
	return -1;
}

void My_Del(struct cont** pc)
{
     
     
	assert(pc);//断言防止传入NULL指针
	char You_name[Name_max] = {
     
     0};
	char You_tel[Tel_max] = {
     
     0};
	printf("请输入要删除的姓名与电话\n");
	scanf("%s", You_name);
	scanf("%s", You_tel);
	int ret = My_Search(*pc,You_name,You_tel);
	if (ret!=-1)
	{
     
     

		for (size_t i = ret; i < (*pc)->sz-1; i++)
		{
     
     
			*((*pc)->date + i) = *((*pc) -> date+i + 1);
		
		}
		struct cont* p = (struct cont*)realloc(*pc, ((*pc)->sz - 1) * sizeof(struct peo));
		if (NULL == p)
		{
     
     
			printf("%s\n", strerror(errno));
			exit(1);
		}
		p->sz--;
		*pc = p;
	}
	else
	{
     
     
		printf("请重新输入\n");
	}
}

void My_Modify(struct cont* pc)
{
     
     

	assert(pc);//断言防止传入NULL指针
	char You_name[Name_max] = {
     
     0};//要初始化下,strcpy的原因
	char You_tel[Tel_max] = {
     
     0};
	char You_sex[Sex_max] = {
     
     0};
	char You_age[Age_max] = {
     
     0};

	while (1)
	{
     
     
		printf("请输入要修改的姓名与电话\n");
		scanf("%s", You_name);
		scanf("%s", You_tel);
		int ret = My_Search(pc, You_name, You_tel);
		if (ret != -1)
		{
     
     
			printf("请输入要修改的姓名:");
			scanf("%s", You_name);
			strcpy((pc->date + ret)->name, You_name);

			printf("请输入要修改的年龄:");
			scanf("%s", You_age);
			strcpy((pc->date + ret)->age , You_age);

			printf("请输入要修改的性别:");
			scanf("%s", You_sex);
			strcpy((pc->date + ret)->sex, You_sex);

			printf("请输入要修改的电话:");
			scanf("%s", You_tel);
			strcpy((pc->date + ret)->tel, You_tel);
			printf("修改成功\n")return;
		}
		else
		{
     
     
			printf("%s\t%s\n", You_name, You_tel);
		
		}

	}
}


int compare1(const void* e1, const void* e2)//种子1 比较名字
								//比较结构体2元素中成员 名字
{
     
     
	assert(e1 && e2);//防止传入NULL指针
	return strcmp(((struct peo*)e1)->name, ((struct peo*)e2)->name);

}
int compare2(const void* e1, const void* e2)//种子2 比较电话
{
     
     
	assert(e1 && e2);
	return strcmp(((struct peo*)e1)->tel, ((struct peo*)e2)->tel);

}
void swap(void* e1, void* e2, size_t width)//交换2元素所占内存字节中的内容
{
     
     

	assert(e1 && e2);
	for (size_t i = 0; i < width; i++)//依次交换e1,e2中的字节内容
	{
     
     
		unsigned char tmp = *((unsigned char*)e1 + i);
		*((unsigned char*)e1 + i) = *((unsigned char*)e2 + i);
		*((unsigned char*)e2 + i) = tmp;
	}

}
void my_qsort(void* base,
	size_t num,size_t width	)
{
     
     
	assert(base);//防止传入NULL指针
	
	for (size_t i = 0; i < num-1; i++)
	{
     
     
		size_t min = i;
		for (size_t j = i + 1; j < num; j++)
		{
     
      //传入的是date,因此强转(char*)后利用width就行了
			if (0 < compare1( (char *)base + min * width, (char*)base + j * width))
			{
     
     
				min = j;
			}
			else if (0 == compare1((char*)base + min * width, (char*)base + j * width))//一旦重名就检测电话
			{
     
     

				if (0 < compare2((char*)base + min * width, (char*)base + j * width))
				{
     
     
					min = j;
				}
			}
		}
		if (min != i)
		{
     
     
			swap((char *)base + i * width, (char *)base + min * width,width);
		
		}

	}
}


效果展示

image-20211007222514767

image-20211007222539589

总结

弄清思路,代码的实现只是时间与思考的问题。
动态函数的语法容易理解,但是实操却会有很多问题坚持,调试,思考,测试。反复这个400行的小程序,你也可以

猜你喜欢

转载自blog.csdn.net/qq_55439426/article/details/120642931