C语言实现通讯录(包括动态开辟内存以及写入文件操作等)

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


前言

我们模拟实现了一个通讯录,该通讯录有两个版本:
第一个版本是直接开辟一个可以存储1000个人信息的通讯录,但这样可能造成内存的浪费。当然我相信大家都不愿意看这种低配的版本,所以我们直接上第二个版本!
第二个版本是动态开辟内存,我们可以根据实际的需要来开辟相应大小的空间,这样可以避免内存的浪费。同时我们将通讯录的内容写入文件当中,避免了程序结束后,通讯录内容就消失。(注意!!!一定要输入0结束后才会保存通讯录哦! 不要直接点×关闭了我们的控制台。)
在这里插入图片描述
该通讯录中每个人的信息包括:

  • 名字、年龄、性别、电话、住址.

通讯录的功能包括:

  • 增加联系人
  • 删除联系人
  • 查找联系人
  • 修改联系人
  • 排序联系人(按照名字)
  • 查看所有联系人

一、通讯录菜单

为了更好的实现交互,我们应该输出一个菜单,方便用户选择。

void menu()
{
	printf("******************************\n");
	printf("***1.Add        2.Del	   ***\n");
	printf("***3.Search     4.Modify   ***\n");
	printf("***5.Sort       6.Print    ***\n");
	printf("***0.Exit                  ***\n");
	printf("******************************\n");
	printf("\n");
}

二、通讯录主函数

代码如下:

int main()
{
	int input = 0;
	//创建通讯录
	Contact con;
	//通讯录初始化
	Init_Contact(&con);
	do
	{
		menu();
		printf("请输入:\n");
		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 Sort:
				SortContactBy_Name(&con);
				break;
			case Print:
				PrintContact(&con);
				break;
			case Exit:
				//保存通讯录到文件中
				SaveContact(&con);
				//销毁通讯录(释放所开辟的空间)
				DestroyContact(&con);
				return 0;
				break;
			default:
				printf("输入有误,请重新输入:\n");
				break;
		}
	} while (input);
	return 0;
}

三、枚举、宏定义及头文件

或许很多人对枚举不是很了解,也不明白为什么需要用枚举。但其实很简单,使用枚举会更加的直观。在主函数中的switch case中,如果使用0、1、2、3这些数字,你需要去记住这些数字分别代表着什么,而使用枚举可以直接从名字上就读出来其表达的意思。枚举当中的Exit就是0,Add就是1,依此类推。
当然,你可以用#define 来代替枚举
代码如下:

#define Exit 0
#define Add 1
#define Del 2
#define Search 3
#define Modify 4
#define Sort 5
#define Print 6

很显然,你又需要写一大堆的#define。我相信当你了解过枚举后,会选择枚举滴。
枚举代码如下:

enum Option
{
	Exit,//0
	Add,//1
	Del,//2
	Search,//3
	Modify,//4
	Sort,//5
	Print,//6
};

怎么样,是不是比#define更加简单,比用数字1、2、3更加直观??
在这里插入图片描述
宏定义及头文件代码如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define Max_Name 20 
#define Max_Sex 10
#define Max_Tele 12
#define Max_Address 20
#define InitSize 3//表示动态开辟的初始大小
#define IncrSize 2//表示每次额外开辟大小的增量

四、定义联系人信息

要描述一个联系人的信息,很显然我们需要用到结构体。
代码如下:

typedef struct PeoInfo
{
	char Name[Max_Name];
	int Age;
	char Sex[Max_Sex];
	char Tele[Max_Tele];
	char Address[Max_Address];
}PeoInfo;

五、定义通讯录

这里我们选择在开辟一个结构体去定义我们的通讯录,那么为什么这么做呢?因为我们前面那个结构体只是定义了联系人的信息,对于我们的通讯录中拥有多少个联系人并不清楚,所以我们可以在定义一个结构体,里面的成员是我的PeoInfo结构体和我的成员数量sz。当然也可以选择不开辟通讯录这个结构体,只需要在函数传参的时候,每个函数都把sz给传过去也可以,但我认为这样比较麻烦。生为懒鬼的我,肯定选择在定义一个结构体的啦。在这里插入图片描述

动态版本因为要根据我们的已有联系人数和当前已经开辟的空间比较,如果已有联系人数大于已经开辟的空间,我们需要去在额外开辟空间来存放我们的联系人,所以这里定义了一个capacity变量。
代码如下:

//动态版本
typedef struct Contact
{
	PeoInfo* data;//指向动态开辟的一块内存
	int size;//当前通讯录中已有联系人的个数
	int capacity;//当前通讯录的容量
}Contact;

五、通讯录的初始化

代码如下:

//初始化  动态版本
void Init_Contact(Contact* pc)
{
	//使用calloc开辟一块空间并将该空间内容初始化为0
	 pc->data = (PeoInfo*)calloc(InitSize, sizeof(PeoInfo));
	if (pc->data == NULL)
	{
		perror("Init_Contact");
		return;
	}
	pc->size = 0;//初始化已有的联系人
	pc->capacity = InitSize;//初始化通讯录的容量
	//加载文件 将文件中的内容写入我们的通讯录
	LoadContact(pc);
}

六、通过名字查找联系人

为什么需要这个函数呢?
在这里插入图片描述
我们在删除联系人,查找联系人和修改联系人信息的时候都需要先通过名字找到确定的联系人,然后在进行相应的操作。
代码如下:

int FindByName(Contact* pc, char* Name)
{
	for (int i = 0; i < pc->size; i++)
		if (strcmp(pc->data[i].Name, Name) == 0)
			return i;//如果查找到了,返回该联系人的下标
	return -1;
}

七、通讯录功能的实现

(一)、增加联系人

代码如下:

//检查是否需要增容
void Check_Capacity(Contact* pc)
{
	//如果通讯录已有联系人数量和总容量相等了,那么就要考虑增容.
	if (pc->size == pc->capacity)
	{
		PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + IncrSize) * sizeof(PeoInfo));
		if (tmp == NULL)
		{
			perror("Check_Capacity");
			printf("增加联系人失败\n");
			return;
		}
		pc->data = tmp;
		//增容完成后记得总容量的值也要随着改变
		pc->capacity += IncrSize;
		printf("增容成功\n");
	}
}
//增加联系人  动态版本
void AddContact(Contact* pc)
{	
	//增加联系人之前检查通讯录容量是否足够,如若不够需要增容。
	Check_Capacity(pc);
	printf("请输入名字:\n");
	scanf("%s", pc->data[pc->size].Name);
	printf("请输入年龄:\n");
	scanf("%d", &pc->data[pc->size].Age);
	printf("请输入性别:\n");
	scanf("%s", pc->data[pc->size].Sex);
	printf("请输入电话:\n");
	scanf("%s", pc->data[pc->size].Tele);
	printf("请输入住址:\n");
	scanf("%s", pc->data[pc->size].Address);
	//每次增加完后,通讯录的已有联系人数量+1
	pc->size++;
	printf("增加成功\n");
}

(二)、删除联系人

代码如下:

//删除联系人信息
void DelContact(Contact* pc)
{
	char Name[Max_Name] = { 0 };
	printf("请输入要删除人的名字:\n");
	scanf("%s", Name);//gets(Name);
	int ret = FindByName(pc,Name);
	if (ret == -1)
	{
		printf("该联系人不存在,无法删除\n");
		return;
	}
	else
	{
		//删除联系人只需要将该联系人后面的内容全部向前移就行
		for (int i = ret; i < pc->size - 1; i++)
			pc->data[i] = pc->data[i + 1];
	}
	pc->size--;
	printf("删除成功\n");
}

(三)、查找联系人

代码如下

//查找联系人
void SearchContact(Contact* pc)
{
	char Name[Max_Name] = { 0 };
	printf("请输入要查找联系人的名字:\n");
	scanf("%s", Name);
	int ret = FindByName(pc, Name);
	if (ret == -1)
	{
		printf("该联系人不存在\n");
		return;
	}
	else
	{
		printf("%-20s\t%-5s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "住址");
		printf("%-20s\t%-5d\t%-5s\t%-12s\t%-20s\n",
			pc->data[ret].Name,
			pc->data[ret].Age,
			pc->data[ret].Sex,
			pc->data[ret].Tele,
			pc->data[ret].Address);
	}
}

(四)、修改联系人信息

代码如下:

//修改联系人信息
void ModifyContact(Contact* pc)
{
	char Name[Max_Name] = { 0 };
	printf("请输入要修改联系人的名字:\n");
	scanf("%s", Name);
	int ret = FindByName(pc, Name);
	if (ret == -1)
	{
		printf("该用户不存在,无法修改\n");
		return;
	}
	else
	{
		printf("请输入名字:\n");
		scanf("%s", pc->data[ret].Name);
		printf("请输入年龄:\n");
		scanf("%d", &pc->data[ret].Age);
		printf("请输入性别:\n");
		scanf("%s", pc->data[ret].Sex);
		printf("请输入电话:\n");
		scanf("%s", pc->data[ret].Tele);
		printf("请输入住址:\n");
		scanf("%s", pc->data[ret].Address);
	}
	printf("修改成功\n");
}

(五)、排序联系人(按照名字)

代码如下:
这里调用了库函数qsort
可能大家并不是很了解这个库函数,这里我们做一个简单介绍。
首先,qsort是一个快速排序函数,它可以对任意类型的数据进行排序,想想你平时写的那一大串冒泡排序等,qsort是不是显得非常的轻松呢??哈哈哈赶紧了解一下叭,简直是懒人必备。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
这里在给大家配上一个例子

/* qsort example */
#include <stdio.h>      /* printf */
#include <stdlib.h>     /* qsort */

int values[] = { 40, 10, 100, 90, 20, 25 };

int compare (const void * a, const void * b)
{
  return ( *(int*)a - *(int*)b );
}

int main ()
{
  int n;
  qsort (values, 6, sizeof(int), compare);
  for (n=0; n<6; n++)
     printf ("%d ",values[n]);
  return 0;
}

其输出结果为:
在这里插入图片描述
到这里可能有人会问了,那我想逆序怎么办呢?是不是就实现不了了???
在这里插入图片描述
其实也很简单,只需要在cmppare函数的返回值中将a和b的位置换一下就可以啦。 return (*(int*)b - *(int*)a);

int  cmp(const void* a, const void* b)
{
	return strcmp(((PeoInfo*)a)->Name,((PeoInfo*)b)->Name);
}
//按照名字排序通讯录
void SortContactBy_Name(Contact* pc)
{
	qsort(pc->data, pc->size, sizeof(pc->data[0] ), cmp);
	printf("排序成功\n");
}

(六)、打印联系人信息

代码如下:

//打印联系人
void PrintContact(Contact* pc)
{
	printf("%-20s\t%-5s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "住址");
	for (int i = 0; i < pc->size; i++)
	{
		printf("%-20s\t%-5d\t%-5s\t%-12s\t%-20s\n",
			pc->data[i].Name,
			pc->data[i].Age,
			pc->data[i].Sex,
			pc->data[i].Tele,
			pc->data[i].Address);
	}
}

(七)、保存通讯录

文件不需要自己去创建,如果没有创建文件的话,fwrite函数会帮我们自动创建。
代码如下:

//保存通讯录到文件中
void SaveContact(Contact * pc)
{
	FILE* pf = fopen("Contact.dat", "w");
	if (pf == NULL)
	{
		perror("SaveContact");
		return;
	}
	//写文件
	for (int i = 0; i < pc->size; i++)
		fwrite(pc->data+i, sizeof(PeoInfo), 1, pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

(八)、销毁通讯录

代码如下:

//销毁通讯录
void DestroyContact(Contact * pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->size = 0;
}

八、完整代码

为了代码更加的清晰,我分了两个.c文件。
包含主函数的.c文件代码如下:

#include "contact.h"
void menu()
{
	printf("******************************\n");
	printf("***1.Add        2.Del	   ***\n");
	printf("***3.Search     4.Modify   ***\n");
	printf("***5.Sort       6.Print    ***\n");
	printf("***0.Exit                  ***\n");
	printf("******************************\n");
	printf("\n");
}

enum Option
{
	Exit,
	Add,
	Del,
	Search,
	Modify,
	Sort,
	Print,
};
int main()
{
	int input = 0;
	//创建通讯录
	Contact con;
	//通讯录初始化
	Init_Contact(&con);
	do
	{
		menu();
		printf("请输入:\n");
		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 Sort:
				SortContactBy_Name(&con);
				break;
			case Print:
				PrintContact(&con);
				break;
			case Exit:
				//保存通讯录到文件中
				SaveContact(&con);
				//销毁通讯录(释放所开辟的空间)
				DestroyContact(&con);
				return 0;
				break;
			default:
				printf("输入有误,请重新输入:\n");
				break;
		}
	} while (input);
	return 0;
}

另一个是contact.c,这里面包含了通讯录的部分。
代码如下:

#include "contact.h"
//检查是否需要增容 
void Check_Capacity(Contact* pc)
{
	//如果通讯录已有联系人数量和总容量相等了,那么就要考虑增容.
	if (pc->size == pc->capacity)
	{
		PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + IncrSize) * sizeof(PeoInfo));
		if (tmp == NULL)
		{
			perror("Check_Capacity");
			printf("增加联系人失败\n");
			return;
		}
		pc->data = tmp;
		//增容完成后记得总容量的值也要随着改变
		pc->capacity += IncrSize;
		printf("增容成功\n");
	}
}

//将通讯录从文件中读出来
void LoadContact(Contact* pc)
{
	FILE* pf = fopen("Contact.dat", "r");
	if (pf == NULL)
	{
		perror("LoadContact");
		return;
	}
	//读文件
	PeoInfo tmp = { 0 };
	while (fread(&tmp, sizeof(PeoInfo), 1, pf))
	{
		//是否需要增容
		Check_Capacity(pc);
		pc->data[pc->size] = tmp;
		pc->size++;
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
}


//初始化  动态版本
void Init_Contact(Contact* pc)
{
	//使用calloc开辟一块空间,并将该空间内容初始化为0
	 pc->data = (PeoInfo*)calloc(InitSize, sizeof(PeoInfo));
	if (pc->data == NULL)
	{
		//如若不了解perror 可以省略这一步。
		perror("Init_Contact");
		return;
	}
	pc->size = 0;//初始化已有的联系人
	pc->capacity = InitSize;//初始化通讯录的容量
	//加载文件 将文件中的内容写入我们的通讯录
	LoadContact(pc);
}


//增加联系人  动态版本
void AddContact(Contact* pc)
{	
	//增加联系人之前检查通讯录容量是否足够,如若不够需要增容。
	Check_Capacity(pc);
	printf("请输入名字:\n");
	scanf("%s", pc->data[pc->size].Name);
	printf("请输入年龄:\n");
	scanf("%d", &pc->data[pc->size].Age);
	printf("请输入性别:\n");
	scanf("%s", pc->data[pc->size].Sex);
	printf("请输入电话:\n");
	scanf("%s", pc->data[pc->size].Tele);
	printf("请输入住址:\n");
	scanf("%s", pc->data[pc->size].Address);
	//每次增加完后,通讯录的已有联系人数量+1
	pc->size++;
	printf("增加成功\n");
}

//保存通讯录到文件中
void SaveContact(Contact * pc)
{
	FILE* pf = fopen("Contact.dat", "w");
	if (pf == NULL)
	{
		perror("SaveContact");
		return;
	}
	//写文件
	for (int i = 0; i < pc->size; i++)
		fwrite(pc->data+i, sizeof(PeoInfo), 1, pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

//销毁通讯录
void DestroyContact(Contact * pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->size = 0;
}


//打印联系人
void PrintContact(Contact* pc)
{
	printf("%-20s\t%-5s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "住址");
	for (int i = 0; i < pc->size; i++)
	{
		printf("%-20s\t%-5d\t%-5s\t%-12s\t%-20s\n",
			pc->data[i].Name,
			pc->data[i].Age,
			pc->data[i].Sex,
			pc->data[i].Tele,
			pc->data[i].Address);
	}
}


int FindByName(Contact* pc, char* Name)
{
	for (int i = 0; i < pc->size; i++)
		if (strcmp(pc->data[i].Name, Name) == 0)
			return i;//如果查找到了,返回该联系人的下标
	return -1;
}


//删除联系人信息
void DelContact(Contact* pc)
{
	char Name[Max_Name] = { 0 };
	printf("请输入要删除人的名字:\n");
	scanf("%s", Name);//gets(Name);
	int ret = FindByName(pc,Name);
	if (ret == -1)
	{
		printf("该联系人不存在,无法删除\n");
		return;
	}
	else
	{
		//删除联系人只需要将该联系人后面的内容全部向前移就行
		for (int i = ret; i < pc->size - 1; i++)
			pc->data[i] = pc->data[i + 1];
	}
	pc->size--;
	printf("删除成功\n");
}

//查找联系人
void SearchContact(Contact* pc)
{
	char Name[Max_Name] = { 0 };
	printf("请输入要查找联系人的名字:\n");
	scanf("%s", Name);
	int ret = FindByName(pc, Name);
	if (ret == -1)
	{
		printf("该联系人不存在\n");
		return;
	}
	else
	{
		printf("%-20s\t%-5s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "住址");
		printf("%-20s\t%-5d\t%-5s\t%-12s\t%-20s\n",
			pc->data[ret].Name,
			pc->data[ret].Age,
			pc->data[ret].Sex,
			pc->data[ret].Tele,
			pc->data[ret].Address);
	}
}

//修改联系人信息
void ModifyContact(Contact* pc)
{
	char Name[Max_Name] = { 0 };
	printf("请输入要修改联系人的名字:\n");
	scanf("%s", Name);
	int ret = FindByName(pc, Name);
	if (ret == -1)
	{
		printf("该用户不存在,无法修改\n");
		return;
	}
	else
	{
		printf("请输入名字:\n");
		scanf("%s", pc->data[ret].Name);
		printf("请输入年龄:\n");
		scanf("%d", &pc->data[ret].Age);
		printf("请输入性别:\n");
		scanf("%s", pc->data[ret].Sex);
		printf("请输入电话:\n");
		scanf("%s", pc->data[ret].Tele);
		printf("请输入住址:\n");
		scanf("%s", pc->data[ret].Address);
	}
	printf("修改成功\n");
}


int  cmp(const void* a, const void* b)
{
	return strcmp(((PeoInfo*)a)->Name,((PeoInfo*)b)->Name);
}
//按照名字排序通讯录
void SortContactBy_Name(Contact* pc)
{
	qsort(pc->data, pc->size, sizeof(pc->data[0] ), cmp);
	printf("排序成功\n");
}

头文件代码如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define Max_Name 20 
#define Max_Sex 10
#define Max_Tele 12
#define Max_Address 20
#define InitSize 3//表示动态开辟的初始大小
#define IncrSize 2//表示每次额外开辟大小的增量


//联系人信息
typedef struct PeoInfo
{
	char Name[Max_Name];
	int Age;
	char Sex[Max_Sex];
	char Tele[Max_Tele];
	char Address[Max_Address];
}PeoInfo;
//

//动态版本
typedef struct Contact
{
	PeoInfo* data;//指向动态开辟的一块内存
	int size;//当前通讯录中已有联系人的个数
	int capacity;//当前通讯录的容量
}Contact;


//打印联系人
void PrintContact(Contact* pc);

//增加联系人
void Addcontact(Contact* pc);

//初始化联系人
void Init_Contact(Contact* pc);

//修改联系人信息
void ModifyContact(Contact* pc);

//查找联系人
void SearchContact(Contact* pc);

//删除联系人
void DelContact(Contact* pc);

//按名字排序
void SortContactBy_Name(Contact* pc);

//销毁通讯录
void DestroyContact(Contact* pc);

//保存信息到文件
void SaveContact(Contact * pc);

//增容通讯录
void Check_Capacity(Contact* pc);

//读取通讯录信息
void LoadContact(Contact* pc);

总结

好啦,我们的通讯录到这里就成功实现啦。不过我们的通讯录也还存在一些地方并不完善,比如如果通讯录中存在重名的情况,可能只能查找到下标在前面的那个联系人,解决这个问题的方法也有很多,我就不在这里实现了,相信聪明的读者可以轻松实现哒。(只要你想去实现的话)

在这里插入图片描述

  • 8
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值