用C语言和文本文件实现一个简单的,可保存的通讯录

我们先思考一个通讯录都有那些信息,很明显通讯录记录的是人
人有哪些信息呢
这里我就写5个吧,分别是姓名,年龄,电话,性别,地址
然后我们把他们写成一个结构体,最好定义在头文件里,这样在使用的时候更方便
我们还可以把要使用的一些常用的或者要修改的常量定义成枚举,后续有什么要添加的,直接就能在枚举中添加

//枚举的一些关键常量
enum NUM
{
	NAME = 20, //名字
	TELE = 12,  //电话
	SEX = 10,  //性别
	ADDR = 100,  //地址
	ADD = 1,DEL = 2,SEARCH = 3,MODIFY = 4,SHOW = 5,SORT = 6,EXIT = 0,CLEAR =7,//菜单
	//ARRDATA = 100
	SIZE = 3,  //通讯录初始容量大小
	SIZEADD = 2  //每次扩容的增加量
};
//人的信息
typedef struct peo
{
	char name[NAME];   //姓名
	int age;            //年龄
	char tele[TELE];  //电话
	char sex[SEX];    //性别
	char addr[ADDR];   //地址
}peo; 

这里的枚举内容我直接把参数都写里了,后续在思考的过程就不再改动了
接下来思考,光有一个人的信息是不够的,无法完整表达一个通讯录,当然,由于我们只是简单实现,我们就只再记录通讯录的人员数量和通讯录的容量
把他们简单封装成一个结构体

typedef struct contact
{
	peo* data;          //人的相关数据
	size_t count;     //成员数量
	size_t capacity;   //通讯录容量
}contact;

这里我都是进行了重命名了的,方便后续的使用
这些都是头文件里面要用的,还有一些库函数,我就在这里提前定义了

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

然后我们写主函数
我们考虑通讯录运行一定要有菜单
然后我们用switch语句写分支结构
我们需要用户主动输入选择,所以我们还需要一个变量接收,这个变量还可以用于switch语句的判断
菜单我们可以单独封装一个函数
可以实现增加联系人,删除联系人,查找联系人,修改联系人,显示联系人,按名字排序联系人,清空联系人,和退出保存,将数据保存到文件中,把这些功能都封装成函数
还得创建一个结构体变量用于存放通讯录的数据
还需要一个函数对结构体进行初始化,并把保存在文件中的信息放入到结构体中
于是主函数如下

#include"contact.h"
static void menu()   //菜单函数
{
	printf("****************************************\n");
	printf("****************************************\n");
	printf("****************************************\n");
	printf("***       1.add      2.del        ******\n");
	printf("***       3.search   4.modify     ******\n");
	printf("***       5.show     6.sort       ******\n");
	printf("***       0.exit     7.clear      ******\n");
	printf("****************************************\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:
			add_contact(&con);       //添加函数
			break;
		case DEL:
			del_contact(&con);       //删除函数
			break;
		case SEARCH:
			search_contact(&con);    //查找函数
			break;
		case MODIFY:
			modify_contact(&con);    //修改函数
			break; 
		case SHOW:
			show_contact(&con);        //显示函数
			break;
		case SORT:
			sort_contact(&con);       //排序函数
			break;
		case CLEAR:
			clear_contact(&con);   //清空函数
			break;
		case EXIT:
			save_contact(&con);       //保存函数
			destroycontact(&con);      //推出后内存的销毁
			printf("程序退出\n");
			break;
		default:
			printf("选择错误,请重新选择\n");//其他逻辑判断
			break;
		}
	} while (input)//do while的循环判断
	return 0;
}

主函数别忘了引用头文件
接下来我们封装函数
把函数单独放在一个源文件

#include"contact.h"
//初始化函数
//对于初始化我们不仅要初始化结构体内容,还要把之前保存在文件中的信息放入到结构体中,当然还涉及到一个动态扩容的问题,我们的结构体初始设计的是有容量的,如果里面放的信息多了,还要涉及到扩容的问题,
这些都要设计函数
static void expandCapacity(contact* con)  //扩容
{
	assert(con);  //断言防止空指针
	if (con->capacity == con->count)   //容量判断
	{
		void* p = 0;
		p = realloc(con->data, (con->capacity + SIZEADD) * sizeof(peo));//追加信息容量
		if (NULL == p) //报错判断
		{
			printf("expandCapacity::%s\n", strerror(errno));
			return;
		}
		con->capacity += SIZEADD;//修改容量标记
		con->data = (peo*)p;       //修改指针位置(其实没变)
		printf("增容成功\n");
	}
}
static int read_conact(contact* con)   //读取文件中保存的通讯录信息
{
	static void expandCapacity(contact * con);   //函数声明
	FILE* p = fopen("contact.txt", "r");      //打开文件
	if (p == NULL)                          //报错判断
	{
		return 1;
	}
	peo a = { 0 };
	while (fread(&a, sizeof(peo), 1, p) == 1)//循环提取信息,这里取决于保存函数的写法
	{
		expandCapacity(con);  //扩容
		con->data[con->count] = a;//赋值
		(con->count)++;//成员计数加一
	}
	fclose(p);
	p = NULL;//关闭文件,指针空置
	return 0;
}
void init_contact(contact* con)    //初始化函数
{
	static int read_conact(contact * con);//函数调用声明
	assert(con);////断言防止空指针
	con->count = 0;//初始化人数
	void* p = 0;//内存指针
	p = calloc(SIZE, sizeof(peo));//创建内存
	if (NULL == con->data)//报错判断
	{
		printf("init_contact:calloc:%s\n", strerror(errno));
		return;
	}
	con->data = (peo*)p;//初始化人信息指针
	con->capacity = SIZE;//初始化容量
	if (read_conact(con) == 1)//读取之前的数据
	{
		perror("读取旧数据失败");//问题判断
		return;
	}
}

//初始化我们已经完成了
//接下来我们写添加函数
void add_contact(contact* con)
{
	static void expandCapacity(contact * con);  //函数声明
	assert(con);//断言防止空指针
	expandCapacity(con);//扩容判断

	printf("请输入姓名\n");
	scanf("%s", (con->data+con->count)->name);
	printf("请输入年龄\n");
	scanf("%d", &((con->data + con->count)->age));
	printf("请输入电话\n");
	scanf("%s", con->data[con->count].tele);
	printf("请输入性别\n");
	scanf("%s", con->data[con->count].sex);
	printf("请输入地址\n");
	scanf("%s", con->data[con->count].addr);//这些都是信息是输入
	con->count++;//计数加一
	printf("增加成功\n");
}


//接下来我们写显示函数
void show_contact(contact* con)
{
	assert(con);//断言防止空指针
	int i = 0;
	printf("%-20s\t%-5s\t%5s\t%12s\t%30s\n", "姓名", "年龄", "电话", "性别", "地址");
	//显示时方便观察,打印一个标签
	for (i = 0; i < con->count; i++)//循环打印人员信息
	{
		printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n",
			con->data[i].name,
			con->data[i].age,
			con->data[i].tele,
			con->data[i].sex,
			con->data[i].addr);
	}
}

//查找函数
//查找函数不仅要能查找,还要能显示,我们分成两个函数,因为删除也需要查找
//查找部分
static int find_by_name(const contact* con,const char* arr)  //依据名字查找
{
	assert(con && arr);//断言防止空指针
	int i = 0;
	for (i = 0; i < con->count; i++)//循环遍历查找
	{
		if (0 == strcmp(con->data[i].name, arr))
		{
			return i;//找到返回下标
		}
	}
	return -1;//找不到返回-1
}

//查找
void search_contact(contact* con)
{
	static int find_by_name(const contact * con, const char* arr);//函数声明
	assert(con);//断言防止空指针
	if (con->count == 0)//无联系人情况
	{
		printf("无联系人可查找\n");
		return;
	}
	printf("请输入要查找的人的姓名\n");
	char arr[NAME] = { 0 };
	scanf("%s", arr);
	int a = find_by_name(con, arr);//套用查找函数
	if (a == -1)
	{
		printf("要查找的人不存在\n");
		return;
	}
	printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "姓名", "年龄", "电话", "性别", "地址");
	printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n",
			con->data[a].name,
			con->data[a].age,
			con->data[a].tele,
			con->data[a].sex,
			con->data[a].addr);//显示部分
}



//删除
void del_contact(contact* con)
{
	static int find_by_name(const contact * con, const char* arr);//函数声明
	assert(con);//断言防止空指针
	char arr[NAME] = { 0 };
	if (con->count == 0)//无联系人情况
	{
		printf("无联系人可删除\n");
		return;
	}
	printf("请输入要删除人的名字\n");
	scanf("%s", arr);
	int a = find_by_name(con,arr);//函数回调
	if (a == -1)
	{
		printf("要删除的人不存在\n");
		return;
	}
	for (; a < con->count - 1; a++)//删除本质就是把后面的信息提前,覆盖掉这个下标的信息
	//当然也还有其他的写法
	{
		con->data[a] = con->data[a + 1];
	}
	con->count--;
	printf("删除成功\n");
}


//修改函数
//修改也要先找

void modify_contact(contact* con)
{
	static int find_by_name(const contact * con, const char* arr);//函数声明
	assert(con);//断言防止空指针
	if (con->count == 0)
	{
		printf("无联系人可修改\n");//没有的情况判断
		return;
	}
	printf("请输入要修改的人的姓名\n");
	char arr[NAME] = { 0 };
	scanf("%s", arr);
	int a = find_by_name(con, arr);//调用函数
	if (a == -1)
	{
		printf("要修改的人不存在\n");
		return;
	}
	printf("请输入修改姓名\n");
	scanf("%s", con->data[a].name);
	printf("请输入修改年龄\n");
	scanf("%d", &(con->data[a].age));
	printf("请输入修改电话\n");
	scanf("%s", con->data[a].tele);
	printf("请输入修改性别\n");
	scanf("%s", con->data[a].sex);
	printf("请输入修改地址\n");
	scanf("%s", con->data[a].addr);
	printf("修改成功\n");//信息的修改
}


//排序函数
//这里我使用了qsort
static int cmp_peo_by_name(const void* e1, const void* e2)//qsort调用的排序函数
{
	return strcmp(((const peo*)e1)->name, ((const peo*)e2)->name);
}

void sort_contact(contact* con)//本体
{
	static int cmp_peo_by_name(const void* e1, const void* e2);//函数调用
	assert(con);//断言
	if (con->count == 0)
	{
		printf("无联系人可排序\n");//没人的情况
		return;
	}
	printf("正在按姓名排序\n");
	qsort(con->data, con->count, sizeof(peo), cmp_peo_by_name);//qsort排序
	printf("排序成功\n");
	show_contact(con);//这里不声明是因为这些函数都会在头文件中声明,但内些静态函数不会
}

//销毁,保存,清空
void destroycontact(contact* con)//释放内存
{
	assert(con);
	free(con->data);
	con->data = NULL;
}


void save_contact(const contact* con)//保存信息
{
	assert(con);
	FILE* p = fopen("contact.txt", "wb");
	if (p == NULL)
	{
		perror("保存失败:save_contact:fopen");
		return;
	}
	fwrite(con->data, sizeof(peo), con->count, p);
	fclose(p);
	p = NULL;
}

void clear_contact(contact* con)//清空文件信息和当前结构体信息
{
	assert(con);
	FILE* p = fopen("contact.txt", "w");
	if (p == NULL)
		return;
	fclose(p);
	p = NULL;
	con->count = 0;
	memset(con->data,0, sizeof(peo));
	printf("已全部清除\n");
}

最后我们再在头文件中声明一下这些函数,为了调用

//初始化通讯录
void init_contact(contact* con);

//增加联系人
void add_contact(contact* con);

//显示
void show_contact(contact* con);

//删除
void del_contact(contact* con);

//查找
void search_contact(contact* con);

//修改
void modify_contact(contact* con);

//排序
void sort_contact(contact* con);

//退出后内存销毁
void destroycontact(contact* con);

//退出后的保存
void save_contact(const contact*con);

//清空通讯录
void clear_contact(contact*con);

静态函数不用声明,主要和一些商业习惯有关

好了,这样就结束了,比较简陋,旨在练手,大佬勿喷

posted @ 2025-11-28 22:32  好想成为人类啊  阅读(7)  评论(0)    收藏  举报