C语言实现简易通讯录

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

C语言实现简易通讯录

前言

在C语言学习过程中的一个练习,使用C完成一个简易的通讯录。而在完成的过程中我将它分为三个阶段。
1.完成大体的程序框架,能够实现一般通讯录所能够的功能,如增删改查。
阶段一问题:由于在第一个阶段时未考虑内存问题,导致初设内存量固定,输入数据量过少浪费内存,过多则无法进行输入。所以第二阶段的目标为。
2.更改为动态内存,即低量进行初始化。随着输入内容增加,动态开辟所需要的空间。
阶段二问题:虽然实现了内存动态增长,但此通讯录还是十分不完全的,如无法保存数据,每次都得重新进行输入。这无疑是不合适的。
3.使用文件进行对通讯录的存储。

提示:以下是本篇文章正文内容,下面案例可供参考

一、程序目标

确认需要实现的功能以便于搭建基础框架。
通讯录需要实现的基本功能有。
联系人的增删改查,展示通讯录所录入的所有信息,将录入的信息进行合理的排序。使得信息是有序的。

二、实现步骤

1.进行多文件处理

将此代码分为3个模块进行处理,即函数模块(实现函数功能),函数声明模块,通讯录模块(函数调用模块)。
这么做的好处:结构清晰明了,便于管理。
分析需求 :内容增删改查,内容显示,对数据进行排序。
1.菜单实现
由于要实现多组数据输入,程序需要多次运转。恰好通过循环可以实现这一要求。
菜单代码如下:

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

将菜单封装进一个函数,通过循环来达成反复调用。
菜单调用模块

while (1)
	{
		menu();
		printf("请选择将要进行的操作\n");
		scanf("%d", &i);
		switch (i)
		{
			case add:
				ress_add(f);//添加通讯录
				break;
			case del:
				ress_del(f);//删除通讯录
				break;
			case search:
				ress_search(f);//搜素通讯录
				break;
			case moddify:
				ress_moddify(f);//更改通讯录
				break;
			case show:
				ress_show(f);//展示通讯录
				break;
			case sort:
				ress_sort(f);//通讯录排序
				break;
			case quit:
				printf("程序结束\n");
				break;
		default:
			printf("操作错误,请重新选择操作\n");
			break;
		}
		if (!i)
		{
			free_txl(f);
			break;
		}
	}

采用switch case方法实现对功能的区分后调用不同函数。判断语句还使用了枚举常量,用以增加程序可读性,枚举代码实现如下:

enum cd
{
	quit,
	add,
	del,
	search,
	moddify,
	show,
	sort  //枚举的特性,由上到下分别是0-6
};

2.空间开辟及内容初始化

1 由于通讯录需要录入多种信息,所以采用结构体来录入。结构体创建及初始化如下

struct address
{
	char name[20];  //姓名
	int age;       //年龄
	char sex[5];     //性别
	char number[20];    //电话号码
	char location[10];    //居住地址
};
struct txl
{
	struct address *q;
	int sz;
}*f,data;
void chh(struct txl* p) //通过一个函数将数组内容进行初始化  p接收的是指针f.
{
	p->sz = 0;
	memset(p->q, 0, 100 * (sizeof(struct address)));
}

3.增删改查功能的分别实现

首先对通讯录的添加功能进行分析:要进行数据的录入,将键盘输入的数据放入已经开辟出的内存空间之中。

void ress_add(struct txl *p)   //将地址传到函数,方便存入数据。
{
	if (p->sz==max)
	{
		printf("该数组已存满");   //结构体数组大小设置为100,等于即满。无法继续存入数据
		return;
	}
	int i = p->sz;      //sz此时作用为计数器,记录已存入数据数量。
	printf("请输入信息\n");
	printf("姓名\n");
	scanf("%s", p->q[i].name);  //通过地址存入数据
	printf("年龄\n");
	scanf("%d", &(p->q[i].age));   //取地址的原因在于age为int型变量
	printf("性别\n");
	scanf("%s", p->q[i].sex);
	printf("电话号\n");
	scanf("%s", p->q[i].number);
	printf("居住地址\n");
	scanf("%s", p->q[i].location);
	p->sz += 1;
	printf("%d", p->sz);
}

删除需要实现的效果是,当一个数据被删除后,后续的数据能够进行补充,中间不会出现空置间隔。与此同时,删除过程中得先找到需要被删除的数据。所以要先将查找函数实现。查找只需要进行遍历即可。删除模块代码实现如下:

//查找是否存在搜素的信息
int cz(struct txl* p, char* name)    //调用此函数根据返回值来判断是否存在查找的数据
{
	int i = 0;
	for (i = 0; i < p->sz; i++)   //遍历查找此信息
	{
		if (0 == strcmp(p->q[i].name, name))
		{
			return i;     //当存在时,返回其对应的计数值
		}
	}
	return -1;
}
//删除数据
void ress_del(struct txl* p)
{;
	char name[20] = { 0 };
	printf("请输入将要删除的人的名字\n");   //根据姓名进行查找,
	scanf("%s", name);        //可以根据自己需求对此查找信息进行更改,如改为号码
	int i = cz(p, name);
	if (i != -1)
	{
		int j = i;
		for (j; j < p->sz - 1; j++)   //循环条件为被删除数据到总数据量-1
		{
			p->q[j] = p->q[j + 1];    //此操作实现将数据间隔消失。
		}
		printf("成功删除指定联系人\n");
		p->sz--;
	}
	else
		printf("该数据不存在\n");
}

当信息录入,删除之后。我们想要再次检测。前面的操作是否成功实现应当如何做呢?搜索模块应运而生,主要功能:输入想要查找的数据信息,存在进行此数据的输出,否则输出不存在此数据。代码实现如下:

void ress_search(struct txl* p) 
{
	char name[20] = { 0 };
	printf("请输入将要搜查的人的名字\n");   
	scanf("%s", name);
	int i = cz(p, name);        //获取了查找函数的返回值。
	if (i != -1)      
	{
		printf("信息如下\n");
		printf("%-20s\t%-5s\t%-5s\t%-20s\t%-10s\n", "姓名", "年龄", "性别", "号码", "地址");
		printf("%-20s\t%-5d\t%-5s\t%-20s\t%-10s\n",
			p->q[i].name,    //对查找到的信息进行输出。
			p->q[i].age,
			p->q[i].sex,
			p->q[i].number,
			p->q[i].location);
	}
	else
		printf("查无此人\n");
}```

> 接下来实现的函数模块为更改已录入的数据,当已经录入的数据需要进行更改时,也是先找到。才能进行更改。更改相当于一个重新录入的过程,所以与添加有所相同。代码实现如下:
> 

```c
void ress_moddify(struct txl* p)
{
	char name[20] = { 0 };
	printf("请输入将要修改的人的名字\n");
	scanf("%s", name);
	int i = cz(p, name);   //获取了查找函数的返回值。
	if (i != -1)     //判断是否存在需要进行修改的信息、
	{                               
		printf("请重新录入信息\n");   //存在则进行重新录入信息
		printf("姓名\n");
		scanf("%s", p->q[i].name);
		printf("年龄\n");
		scanf("%d", &(p->q[i].age));
		printf("性别\n");
		scanf("%s", p->q[i].sex);
		printf("电话号\n");
		scanf("%s", p->q[i].number);
		printf("居住地址\n");
		scanf("%s", p->q[i].location);
		printf("信息修改成功\n");
	}
	else
		printf("查无此人\n");    //不存在即显示查无此人,跳回菜单栏。
}

4.信息展示模块

到此处,我们已经完成了通讯录的增删改查功能。既然录入了信息,当然也应该能做到查看这些信息。而搜索信息只能看到一个人的。我们需要一个可以展示所有数据的功能。接下来,完成展示信息的模块。

//查看所有数据
void ress_show(struct txl* p)
{
	int i = 0;
	printf("%-20s\t%-5s\t%-5s\t%-20s\t%-10s\n", "姓名", "年龄", "性别", "号码", "地址");
	for (i = 0; i < p->sz; i++)  //进行遍历操作,停止条件为计数器上限。
	{
		printf("%-20s\t%-5d\t%-5s\t%-20s\t%-10s\n",  //%后的数字作用为调整输出间隔,使格式美观
			p->q[i].name,
			p->q[i].age,
			p->q[i].sex, 
			p->q[i].number,
			p->q[i].location);
	}
}

5.对录入的信息进行排序

在实现了上述的功能后,还有一个问题。在日常使用通讯录时。整体信息的排列是有序的。大概可以分为两种
1 . 按照姓名首字母进行排序
2. 按照电话号码对信息进行排序
我选用的实现方法是第二种。

//qsort函数参数
int cmpress(const void* e1, const void* e2) //对qsort函数参数进行配置。
{
	return strcmp(((struct address*)e1)->number,((struct address*)e2)->number);
}
//通讯录排序
void ress_sort(struct txl* p)
{
	printf("根据电话号码进行排序\n");
	qsort(p->q,p->sz,sizeof(struct address),cmpress);//进行qsort函数调用。
}

将上述代码结合起来,也就完成了一个简单的通讯录系统。但是还存在了一部分问题。
如开篇所说:开辟内存已是固定量了,当数据少时,内存浪费。过多时,又无法存储下这些内容。
十分的尴尬。那应该怎样对程序进行优化?在C语言中,有一个realloc函数。它的作用就是对一个内存进行扩容。正好适用于此情况。

二阶段代码修改

优化思路:初始开辟空间减小,当内存被占满时,使用realloc进行增容。
需要进行更改的模块有,初始化模块,通讯录结构体,添加一个增容模块。在数据添加过程中对是否需要增容进行判断。话不多说,上代码。

//动态版本通讯录结构体
struct txl
{
	struct address* q;
	int sz;      //当前所用空间
	int inmax;   //最大空间容量
}*f, data;
//动态数组初始化
void chh(struct txl* p)
{
	p->sz = 0;
	p->inmax = 3;
	struct address* i;
	if ((i = (struct address*)malloc(3 * sizeof(struct address))) != NULL)
	{
		p->q = i;
		memset(p->q, 0, 3 * (sizeof(struct address))); //初始化内存空间
	}
	else
	{
		exit(0);
	}
}
//添加数据实现  动态版本
void ress_add(struct txl* p)
{
	if (add_inmax(p))   //判断是否需要增容
	{
		printf("请输入下列信息\n");
	}
	else
	{
		printf("容量增如失败\n");
	}
	int i = p->sz;
	printf("姓名\n");
	scanf("%s", p->q[i].name);
	printf("年龄\n");
	scanf("%d", &(p->q[i].age));
	printf("性别\n");
	scanf("%s", p->q[i].sex);
	printf("电话号\n");
	scanf("%s", p->q[i].number);
	printf("居住地址\n");
	scanf("%s", p->q[i].location);
	p->sz += 1;
}
//动态内存开辟新空间
int add_inmax(struct txl* p)    
{
	struct address* k;
	if (p->sz == p->inmax)   //判断是否需要开辟新空间,当前占用与最大容量相等时。增容。
	{
		if ((k = (struct address*)realloc(p->q, (p->inmax+add_in) * sizeof(struct address))) != NULL)       //add_in是宏定义的一个增容量大小为2
		{
			p->q = k;
			p->inmax += 2;
			printf("增容成功\n");
			return 1;
		}
		else
			return 0;
	}
	return 1;
}

上述代码完成后,通讯录的内存空间便已经是随着输入数据的增加进行动态增长。不必考虑空间的过度浪费以及空间不够的问题。
那现在的代码足够完善了吗?
不够,因为当前还有一个最严重的问题。就是数据无法进行保存。打开程序需要重新录入,关闭再次打开之后,相当于没有留下任何痕迹。那C语言怎样才能够进行数据的存储。以我目前的能力来讲,只能使用文件。将已经输入的内容写入一个文件,当打开程序时,将文件的内容读出到内存当中。接下来,就是实现这个功能。代码在以上基础进行一定的修改即可。

三阶段代码修改

当数据录入完毕后,程序将要退出时。完成将数据写入文件的函数。代码如下:

//将通讯录内容存放在文件当中
void perserve(struct txl* f)
{
	FILE* p_write = fopen("data.txt", "wb");  //打开文件。不存在即创建该文件。
	if (p_write == NULL)
	{
		perror("perserve;; fopen");
		exit(1);
	}
	int i = 0;
	for (i = 0; i < f->sz; i++)
	{
		fwrite(f->q+i, sizeof(struct address), 1, p_write);
	}
	fclose(p_write);  //关闭文件。
	p_write = NULL;
}

打开程序时完成对已写入文件的内容进行读写

//读取文件中的内容,初始化通讯录
void readfile(struct txl* f)
{
	FILE* p_read = fopen("data.txt", "rb");
	if (p_read == NULL)
	{
		perror("readfile::fopen");
		return;
	}
	struct address p = { 0 };
	//int i = 0;
	while (fread(&p, sizeof(struct address), 1, p_read))
	{
		//考虑增容问题
		add_inmax(f);   //调用增容函数,读取过程中可对空间进行开辟。
		f->q[f->sz] = p;
		f->sz++;
	}
	fclose(p_read);   //关闭文件。
	p_read = NULL;
}

直至此处,通讯录已经完成到我目前能力范围内所能做到最好的程度。功能也相对完善。
最终运行效果图如下:
在这里插入图片描述
读取文件中已存在的数据,展示所有数据。
在这里插入图片描述
数据的录入,以及动态增容。

在这里插入图片描述
确认数据录入情况。

在这里插入图片描述根据电话号码对数据进行了排序

在这里插入图片描述
退出程序,并将数据存入文件。
在这里插入图片描述
程序初始化时读取文件中存在的信息。

总结

以上就是今天我想要分享的内容,本文仅介绍了我对使用C语言实现通讯录的思路,以及我的一部分代码实现过程。我更想分享的是我的解题思路。本博客中的代码缺少一些小细节。全部粘贴会导致文章过于繁琐就取消了这个念头。其中对内存空间的释放,以及主函数对菜单模块的调用我都没有进行详细的解释。大家需要注意这两点。
因为本篇博客是我写的第一篇博客,所以难免存在不少疏漏之处。如果大家发现了,请在评论区指出。我会尽快进行更改。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值