C语言进阶之路--详解通讯录的实现(静态版+动态版+文件版)

嗨嗨大家~我们又见面啦!今天为大家带来的内容是:保姆级通讯录的实现,包括了三个版本(静态、动态,文件版)。

在写代码之前,我们必须要思考:实现通讯录的步骤及其功能是怎样的。想象一下,平时在手机上关于通讯录的功能分别有:添加联系人,删除联系人,查询联系人信息,修改联系人信息,显示通讯录,通讯录名字的排序等等。那么现在我们就正式开始吧!


目录

一、静态通讯录

1 代码分装

2 代码实现 

(1)main主函数的实现

         test.c--测试功能:

(2) 创建通讯录

(3) 初始化通讯录 

(4) 添加联系人信息

(5) 显示联系人信息

(6) 删除联系人信息

(7) 查找指定联系人信息 

(8) 修改联系人信息 

(9) 整理(排序)通讯录 

二、动态通讯录 

代码实现

(1)优化思路

(2)代码实现

三、文件通讯录 

代码实现

(1)优化思路

(2)代码实现


一、静态通讯录

1 代码分装

 为避免在一个源文件中过于冗杂,我们选择使用两个源文件(一个用于功能函数的运用:test.c;一个用于写各功能函数具体实现:contact.c),一个头文件(声明函数:contact.h)。

源文件:

  • test.c: 测试通讯录的基本功能
  • contact.c: 实现通讯录的相关函数

头文件:

  • contact.h: 相关函数和类型的声明,整个代码要引用的头文件以及宏定义

2 代码实现 

(1)main主函数的实现

  • 在test.c中定义一个main函数打印菜单,提示我们进行选择。
  • scanf函数用来接收我们所选择的数据项,并用switch判断,针对判断进行详细操作。为了让用户完成一个功能操作后在进行下一个操作,这里需要使用do while语句将各个选择包装起来。
  • 使用枚举来一一列举菜单选择的变量名称,以便我们使用时知晓其意,增加代码的可读性。
test.c--测试功能:
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");
}
 
enum option  //枚举常量
{
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT
};
 
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请输入你的选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			break;
		case DEL:
			break;
		case SEARCH:
			break;
		case MODIFY:
			break;
		case SHOW:
			break;
		case SORT:
			break;
		case EXIT:
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误,重新选择\n");
			break;
		}
	} while (input);
	return 0;
}

(2) 创建通讯录

首先我们要理清思路,通讯录里所储存的是多个联系人的信息,包括姓名、年龄、性别、电话、地址,因此我们可以创建两个结构体,一个储存联系人的信息,一个储存通讯录。与此同时,还需要一个变量来定义通讯录的大小(即通讯录人数多少)。

创建结构体类型 PeoInfo,用于描述人的信息,还需要创建 Contact 结构体包含 PeoInfo 和个数,使用宏#define定义的变量,以便后续代码的更改(只需更改宏定义的变量)。到这步一个通讯录结构体类型便创建成功了。

#define MAX 100
#define NAME_MAX 20
#define SEX_MAX 5
#define TELE_MAX 12
#define ADDR_MAX 30
 
 
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 count;
}Contact;

(3) 初始化通讯录 

使用memset函数初始化通讯录: 

//初始化通讯录函数
void InitContact(Contact* pc)
{
	pc->count = 0;
	memset(pc->data, 0, sizeof(pc->data));
}

(4) 添加联系人信息

使用assert 进行断言操作,作用是对传入的指针进行判断,防止对空指针进行操作

//添加联系人到通讯录
void addContact(Contact* pc)
{
	assert(pc);

	//判断通讯录是否已经满员
	if (pc->count == MAX)
	{
		printf("\n通讯录已满,无法添加\n");
			return;
	}
 
	//添加信息
	printf("\n请输入名字:>");

	//每次放进去的信息都是放到data下标为count的数组中
	scanf("%s", pc->data[pc->count].name);
 
	printf("\n请输入年龄:>");

	//因为name是存放在数组中,数组名本身就是地址,不需要再取地址
	//这里的年龄是int型的变量,需要取地址
	scanf("%d", &(pc->data[pc->count].age));
 
	printf("\n请输入性别:>");
	scanf("%s", pc->data[pc->count].sex);
 
	printf("\n请输入电话:>");
	scanf("%s", pc->data[pc->count].tele);
 
	printf("\n请输入地址:>");
	scanf("%s", pc->data[pc->count].addr);
 
	pc->count++;
	printf("\n增加成功\n");
 
}

为了检查所添加的功能是否正确,需要显示通讯录信息,因此我们来实现下一个函数ShowContact:

(5) 显示联系人信息

为了让通讯录的信息更加清晰地显示,这里进行了格式化处理:在%后加上负数,为信息预留了空间,如下代码:

 void ShowContact(Contact* pc)
{
	int i = 0;
	printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	
	for (i = 0; i < pc->count; i++)
	{
		printf("%-10s %-4d %-5s %-12s %-30s\n",
        pc->data[i].name,										
        pc->data[i].age, 
		pc->data[i].sex, 
		pc->data[i].tele, 
		pc->data[i].addr);
	}
}

(6) 删除联系人信息

我们需要先找到想要删除的联系人,再进行相应操作,代码如下:

//查找联系人的函数,这个函数不需要进行声明,它是代码内部逻辑的需要
static int findByName(Contact* pc, char name[])
{
	assert(pc);
	int i = 0;
	for (i = 0; i < pc->count; i++)
	{
		if (0 == strcmp(pc->data[i].name, name))
			return i;
	}
	return -1;
}
 
//删除指定联系人
void delContact(Contact* pc)
{
	char name[MAX_NAME] = { 0 };
	assert(pc);
	int i = 0;
	if (pc->count == 0)
	{
		printf("\n通讯录为空\n");
		return;
	}
	printf("\n请输入要删除的人名字:>");
	scanf("%s", name);

	//进行删除
	//1.查找,因为各种操作都需要先进行查找,可以独立分装一个函数
	int flag = findByName(pc, name);
	if (flag == -1)
	{
		printf("要删除的人不存在\n");
		return;
	}

	//2.删除
	for (i = flag; i < pc->count-1; i++)
	{
			//用i+1位置的数据覆盖掉pos位置的数据,完成删除
			pc->data[i] = pc->data[i + 1];//i+1最大99
	}
		pc->count--;
		//这里是为了解决当要删除最后一个人的信息时,
		//后面没有数据来覆盖最后一个联系人的数据。
		//count--;最后一个元素为99,访问不到最后一个元素了,从而起到删除的作用
		printf("\n删除成功\n");
}

(7) 查找指定联系人信息 

在该代码的实现过程中,同样使用上面的查找函数来查找此人的下标并返回,格式化输出此人信息。

void SearchContact(Contact* pc)
{
	if (pc->count == 0)
	{
		printf("通讯录为空\n");
	}
	char name[NAME_MAX];
	printf("请输入要查找联系人的姓名:");
	scanf("%s", &SearchName);
	int flag = findByName(pc, name);
	if (flag == -1)
	{
		printf("要查找的人不存在\n");
		return;
	}
	printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	printf("%-10s %-4d %-5s %-12s %-30s\n",
    pc->data[flag].name,											
    pc->data[flag].age,
	pc->data[flag].sex,
	pc->data[flag].tele,
	pc->data[flag].addr);
}

(8) 修改联系人信息 

 我们还是要调用上面的查找函数找到该人的信息,然后进行更改其内容(若不存在则结束函数运行)。

//修改指定联系人
void ModifyContact(Contact* pc)
{
	char name[MAX_NAME] = { 0 };
	assert(pc);
	printf("\n请输入要修改的人名字:>");
	scanf("%s", name);
 
	//1.查找
	int flag = findByName(pc, name);
	if (flag == -1)
	{
		printf("要修改的人不存在\n");
		return;
	}
 
	//2.修改
	printf("\n请输入名字:>");

	//每次放进去的信息都是放进data 下标为count的数组
	scanf("%s", pc->data[flag].name);
 
	printf("\n请输入年龄:>");

	//因为name是存放在数组中,数组名本身就是地址,不需要再取地址
	//这里的年龄是int 型变量,需要取地址
	scanf("%d", &(pc->data[flag].age));
 
	printf("\n请输入性别:>");
	scanf("%s", pc->data[flag].sex);
 
	printf("\n请输入电话:>");
	scanf("%s", pc->data[flag].tele);
 
	printf("\n请输入地址:>");
	scanf("%s", pc->data[flag].addr);
 
	printf("\n修改成功\n");
 
}

(9) 整理(排序)通讯录 

这里是根据年龄进行了排序:

int cmp_age(const void* e1, const void* e2)
{
	return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}
 
void SortContact(Contact* pc)
{
	assert(pc);
	if (pc->count == 0)
	{
		printf("通讯录为空,无需排序\n");
		return;
	}
	qsort(pc, pc->count, sizeof(PeoInfo), cmp_age);
	printf("排序成功\n");
}

学到这里通讯录的实现就基本完成了,但是我们可以注意到,此通讯录还存在两个问题:

  •  通讯录的大小是固定的100个元素,最多只能存放100个人。当信息太少时,就会导致空间剩余过大浪费空间,而当信息太多时空间又太小了无法进行存入,而解决这个问题需要使用到动态内存管理的知识。如果大家对动态内存管理没有深入研究,可以到博主的 http://t.csdnimg.cn/w7Du3 这篇文章中学习。
  • 录入的信息,等程序结束后就不存在了,这是因为数据只存放到了内存中。为了解决这个问题,需要使用到C语言文件存储的相关知识。 如果你们对C语言文件存储不太了解,可以看博主的 http://t.csdnimg.cn/akhKd 这篇博客。

下面就来优化一下通讯录:

二、动态通讯录 

 我们知道,静态通讯录的空间是有限的,如果我们需要储存的联系人过多,那便无法存下联系人。对于这个问题,我们就可以使用 malloc 和 realloc 两个函数来实现动态通讯录。

代码实现

(1)优化思路

      首先,使用malloc函数对通讯录的数据开辟空间,那么数组data就需要是指针类型;

      其次,使用realloc函数进行扩容,需要一个变量来记录函数当前最大储存容量,通过与联系人多少比较来决定是否要扩容;

      最后,释放所开辟的空间。

      综上,我们需要更改的就只有三个地方,更改通讯录结构体,更改初识化通讯录函数,更改添加联系人函数,在退出时释放空间。

(2)代码实现

//宏定义——加在contact.h文件中
#define INIT_MAX 3
#define CAPACITY_ADD 2
 
 
//通讯录结构体的更改
typedef struct Contact
{
	PeoInfo *data;//存放人的信息
	int count;    //记录当前通讯录有多少人的信息
	int capacity; //记录当前通讯录容量
}Contact;
 
 
//通讯录初始化的更改
void InitContact(Contact* pc)
{
	pc->data = malloc(sizeof(PeoInfo) * INIT_MAX);
	if (pc->data == NULL)
	{
		printf("通讯录初始化失败:%s\n", strerror(errno));
		return;
	}
	pc->count = 0;
	pc->capacity = INIT_MAX;
	
}
 
 
//通讯录添加联系人的更改
void CheckCapacity(Contact* pc)
{
	if (pc->capacity == pc->count)
	{
		PeoInfo *ptr = realloc(pc->data, (pc->capacity + 2) * sizeof(PeoInfo));
		if (ptr == NULL)
		{
			printf("CheckCapacity:%s\n", strerror(errno));
		}
		pc->capacity += CAPACITY_ADD;
		printf("增容成功,当前容量%d\n", pc->capacity);
	}
}
 
void AddContact(Contact* pc)
{
	CheckCapacity(pc);
 
	printf("请输入姓名:");
	scanf("%s", pc->data[pc->count].name);
	printf("请输入年龄:");
	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);

    pc->count++;
    printf("添加成功\n");
}

//释放空间--销毁通讯录
void DestroyContact(Contact* pc)
{
    assert(pc);
    free(pc->data);
    pc->data=NULL;
}

三、文件通讯录 

文件通讯录的实现是为了方便我们在程序退出后,也能查找到相应信息。也就是说,文件操作处理通讯录,可以存档联系人的信息。这样就不需要每一次都重新输入数据。

代码实现

(1)优化思路

      首先,我们需要创建文件,将联系人的数据写到文件中;

      其次,在通讯录初始化时,将文件的内容提前写入到通讯录中。

下面我们来优化处理通讯录:

(2)代码实现

//通讯录写入文件中
void SaveContact(Contact* pc)
{
	FILE* pf = fopen("contact.dat", "wb");
	if (pf == NULL)
	{
		perror("SaveContact::fopen");
		return;
	}
	//写数据
	int i = 0;
	for (i = 0; i < pc->count; i++)
	{
		fwrite(pc->data + i, sizeof(struct PeoInfo), 1, pf);
	}
 
	//关闭文件
	fclose(pf);
	pf = NULL;
	printf("保存成功\n");
 
}
 
//加载文件信息到通讯录中
//注意:函数内使用了CheckCapacity(pc)来增容,如果写在此函数前面,需要声明此函数
void LoadContact(Contact* pc)
{
	//打开文件
	FILE* pf = fopen("Contact.dat", "rb");
	if (pf == NULL)
	{
		perror("LoadContact::fopen");
		return;
	}
	//读文件
	PeoInfo tmp = { 0 };
	while (fread(&tmp, sizeof(PeoInfo), 1, pf))
	{
		CheckCapacity(pc);
		pc->data[pc->count] = tmp;
		pc->count++;
	}
 
	//关闭文件
	fclose(pf);
	pf = NULL;
}
 
void InitContact(Contact* pc)
{
	pc->data = malloc(sizeof(struct PeoInfo) * INIT_MAX);
 
	if (pc->data == NULL)
	{
		printf("通讯录初始化失败:%s\n", strerror(errno));
		return;
	}
	pc->count = 0;
	pc->capacity = INIT_MAX;
	
	//加载文件的信息到通讯录
	LoadContact(pc);
}

 本期有关通讯录三种版本(静态、动态和文件)的分享到这里就结束啦,涵盖了很多知识点,相信大家看完后会有所收获。路还很长,一点一点来。或许我的思路存在不足之处,欢迎各位佬们的指点。如果这篇文章对你们有所帮助,记得给博主一个三连哈~你们的支持是我创作的最大动力!

  • 22
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值