【C语言】通讯录小程序的实现(动态内存版)

前言

在学完结构体、动态内存管理、文件操作之后,我们可以用C语言来实现一个通讯录,通讯录的功能有:

1.可以存储足够数量的联系人,每个联系人的信息包括:姓名、年龄、性别、住址、电话;
2.功能有:增加、删除、查找、修改、查看、保存、排序联系人。
3.联系人信息保存在电脑文件中,下次程序运行联系人信息仍存在。

一、创建文件

对于这种具有实际应用功能的程序,我们采用分模块的编程思想,创建三个文件来分别存放对应功能的代码。

test.c:主程序,功能的调用
contact.c:通讯录的具体功能实现
contact.h:工程需要的各种头文件、函数声明以及宏定义

这样做的好处:提高代码的可读性,方便后续调试,条理清晰,使主程序看起来简洁

二、test.c文件

menu()打印菜单

0~7选择:退出、增加、删除、查找、修改、查看、手动保存、排序。

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

main()主函数

int main()
{
	int input = 0;
	Contact con;
	InitCon(&con);
	do
	{
		menu();
		printf("请选择->:");
		scanf("%d", &input);
		switch (input)
		{
		case EXIT:
			SaveCon(&con);
			DestroyCon(&con);
			printf("已退出通讯录\n");
			break;
		case ADD:
			AddCon(&con);
			break;
		case DEL:
			DelCon(&con);
			break;
		case SEARCH:
			SearchCon(&con);
			break;
		case MODIFY:
			MoldifyCon(&con);
			break;
		case SHOW:
			ShowCon(&con);
			break;
		case SORT:
			SortCon(&con);
			break;
		case SAVE:
			SaveCon(&con);
			break;
		default:
			printf("输入错误,请重新输入->:\n");
			break;
		}
	} while (input);
	return 0;
}

分支过多,使用枚举,方便理解

enum Choose
{
	EXIT,  //0
	ADD,   //1
	DEL,   //2
	SEARCH,//3
	MODIFY,//4
	SHOW,  //5
	SORT,  //6
	SAVE   //7
};

三、contact.h文件

头文件

#pragma once//防止头文件重复调用
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<errno.h>

定义联系人和通讯录

//存放联系人信息
typedef struct Pepe
{
	char name[Name_Max];
	int age;
	char sex[Sex_Max];
	char addr[Addr_Max];
	char tele[Tele_Max];
}Pepe;

//通讯录  结构体嵌套
typedef struct Contact
{
	Pepe* data;	 //指向存放人的信息的空间
	int sz;		 //当前已经存放联系人的个数	
	int capacity;//当前通讯录的最大容量
}Contact;

宏定义变量

方便随时一次性修改,不用再从函数内部修改

#define Name_Max 15
#define Sex_Max 10
#define Addr_Max 30
#define Tele_Max 12
#define Init_Sz 10   //通讯录初始开辟空间大小
#define Inc_Sz 5	 //每次空间不够 新增空间大小

主要功能函数的声明

采用址传递,避免结构体过大使得临时拷贝负担过重,从而导致性能下降。

//初始化通讯录
void InitCon(Contact* pc);
//释放空间
void DestroyCon(Contact* pc);
//增加联系人
void AddCon(Contact* pc);
//显示打印通讯录信息
void ShowCon(const Contact* pc);
//删除联系人
void DelCon(Contact* pc);
//查找指定联系人
void SearchCon(const Contact* pc);
//修改指定联系人
void MoldifyCon(Contact* pc);
//按姓名排序通讯录
void SortCon(struct Contact* ps);
//保存通讯录信息到文件中
void SaveCon(Contact* pc);
//加载文件信息到通讯录
void LoadCon(Contact* pc);

四、contact.c文件

初始化通讯录

在写函数的时候,先采用断言处理,防止指针为空从而导致后续操作非法,同时方便找错误。
每次运行程序,通讯录都会自动读取上次保存的内容。

void InitCon(Contact* pc)
{
	assert(pc);
	pc->sz = 0;//当前联系人个数为0
	Pepe* ptr = (Pepe*)calloc(Init_Sz, sizeof(Pepe));//动态开辟空间,并初识化0
	if (ptr == NULL)
	{
		perror("InitCon::calloc\n");
		return;
	}
	pc->data = ptr;
	pc->capacity = Init_Sz;
	//每次运行程序,加载文件信息到通讯录
	LoadCon(pc);
}

释放通讯录空间

每次手动退出通讯录,要将开辟的空间释放掉,防止造成内存泄漏。

void DestroyCon(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->sz = pc->capacity = 0;//个数和容量清零
	pc = NULL;	//pc不是动态开辟,只用置空,不用free
}

增加联系人

每次增加联系人时,首先检查容量是否已满,若已满则新增容量,避免越界访问。

void AddCon(Contact* pc)
{
	assert(pc);
	check_capacity(pc);//检查容量
	
	printf("请输入名字->:");
	scanf("%s", pc->data[pc->sz].name);
	printf("请输入年龄->:");
	scanf("%d", &pc->data[pc->sz].age);
	printf("请输入性别->:"); 
	scanf("%s", pc->data[pc->sz].sex);
	printf("请输入地址->:");
	scanf("%s", pc->data[pc->sz].addr);
	printf("请输入电话->:");
	scanf("%s", pc->data[pc->sz].tele);
	pc->sz++;
}

判断开辟的空间是否已满,若满了则新开辟Inc_Sz个空间,避免浪费。

void check_capacity(Contact* pc)
{
	if (pc->sz == pc->capacity)//联系人个数和容量相等
	{
		//增加容量
		Pepe* ptr = (Pepe*)realloc(pc->data, (pc->capacity + Inc_Sz) * sizeof(Pepe));
		if (ptr == NULL)
		{
			perror("check_capacity::realloc\n");
			return;
		}
		pc->data = ptr;
		pc->capacity += Inc_Sz;
	}
}

删除联系人

后面会用到的子函数:通过联系人姓名找到下标

int Find_Name(Contact* pc, char name[])
{
	for (int i = 0; i < pc->sz; i++)
	{
		if (strcmp(pc->data[i].name, name) == 0)
		{
			return i;
		}
	}
	return -1;
}

通过名字找到下标,再将该坐标后面的所有联系人往前移一位

void DelCon(Contact* pc)
{
	assert(pc);
	char name[Name_Max] = { 0 };
	if (pc->sz == 0)
	{
		printf("通讯录为空,无法删除\n");
		return;
	}
	printf("请输入需要删除的联系人姓名\n");
	scanf("%s", name);
	int pos = Find_Name(pc, name);//通过名字查找联系人,并返回下标
	if (pos == -1)
	{
		printf("要删除的联系人不存在\n");
		return;
	}
	for (int i = pos; i < pc->sz - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];
	}
	pc->sz--;
	printf("删除成功\n");
}

查找联系人

通过名字找到联系人下标并打印信息。

void SearchCon(const Contact* pc)
{
	assert(pc);
	char name[Name_Max] = { 0 };
	printf("请输入需要查找的联系人姓名\n");
	scanf("%s", name);
	int pos = Find_Name(pc, name);//通过名字查找联系人,并返回下标
	if (pos == -1)
	{
		printf("要查找的联系人不存在\n");
		return;
	}
	printf("已查找到该联系人\n");
	printf("%-15s\t%-4s\t%-10s\t%-20s\t%-12s\n", 
		"姓名", "年龄", "性别", "地址", "电话");
	printf("%-15s\t%-4d\t%-10s\t%-20s\t%-12s\n", 
		pc->data[pos].name, 
		pc->data[pos].age, 
		pc->data[pos].sex, 
		pc->data[pos].addr, 
		pc->data[pos].tele);
}

修改联系人

void MoldifyCon(Contact* pc)
{
	assert(pc);
	char name[Name_Max] = { 0 };
	printf("请输入需要修改的联系人姓名\n");
	scanf("%s", name);
	int pos = Find_Name(pc, name);
	if (pos == -1)
	{
		printf("要修改的联系人不存在\n");
		return;
	}
	printf("请输入修改后联系人的信息:\n");
	printf("请输入名字->:");
	scanf("%s", pc->data[pos].name);
	printf("请输入年龄->:");
	scanf("%d", &pc->data[pos].age);
	printf("请输入性别->:");
	scanf("%s", pc->data[pos].sex);
	printf("请输入地址->:");
	scanf("%s", pc->data[pos].addr);
	printf("请输入电话->:");
	scanf("%s", pc->data[pos].tele);
}

查看所有联系人

每增加一个函数模块,记得进行打印调试。

void ShowCon(const Contact* pc)
{
	assert(pc);
	printf("%-15s\t%-4s\t%-10s\t%-20s\t%-12s\n", //负号代表左对齐,数字代表间距
		"姓名", "年龄", "性别", "地址", "电话");
	for (int i = 0; i < pc->sz; i++)
	{
		printf("%-15s\t%-4d\t%-10s\t%-20s\t%-12s\n",
			pc->data[i].name, 
			pc->data[i].age, 
			pc->data[i].sex, 
			pc->data[i].addr, 
			pc->data[i].tele);
	}
}

按名字排序通讯录

通过名字的字典序对联系人进行排序并打印

int cmp_name(const void* e1, const void* e2)
{
	return strcmp(((Pepe*)e1)->name, ((Pepe*)e2)->name);
}

void SortCon(struct Contact* pc)
{
	assert(pc);
	if (pc->sz == 0) 
	{
		printf("通讯录中没有联系人,请添加!\n");
		return;
	}
	qsort(pc->data, pc->sz, sizeof(Pepe), cmp_name);
	printf("排序成功!\n");
	ShowCon(pc);
}

保存和加载联系人

选择7手动保存联系人信息到文件中或者选择0退出通讯录自动保存

void SaveCon(Contact* pc)
{
	FILE* pf = fopen("contact.txt", "wb");//以“只写”的方式  打开二进制文件
	if (pf == NULL)
	{
		perror("fopen");
	}
	else
	{
		for (int i = 0; i < pc->sz; i++)
		{
			//将每个联系人信息保存到文件中
			fwrite(&pc->data[i], sizeof(Pepe), 1, pf);
		}
		fclose(pf);
		pf = NULL;
		printf("保存成功!\n");
	}
}

每次运行程序,从文件加载联系人,并存到通讯录中;
因为每次只加载一次,所以放在初始化函数中一起执行。

void LoadCon(Contact* pc)
{
	FILE* pf = fopen("contact.txt", "rb");//以“只读”的方式  打开二进制文件
	if (pf == NULL)
	{
		perror("fopen");
	}
	else
	{
		Pepe tmp = { 0 };
		int i = 0;
		while (fread(&tmp, sizeof(Pepe), 1, pf)) //每次从文件中读取一个联系人加载到通讯录中
		{
			check_capacity(pc);
			pc->data[i++] = tmp;
			pc->sz++;
		}
	}
	fclose(pf);
	pf = NULL;
}

五、功能演示

添加联系人

在这里插入图片描述

打印所有联系人

在这里插入图片描述

查找联系人

在这里插入图片描述

修改联系人

在这里插入图片描述删除联系人

按名字排序联系人

在这里插入图片描述

删除联系人

在这里插入图片描述

选择7保存联系人或者选择0退出通讯录并自动保存,下次运行程序自动加载联系人信息
联系人信息以二进制文本形式存到电脑文件中

在这里插入图片描述
在这里插入图片描述

六、完整代码

完整代码上传在gitee

点此跳转通讯录gitee源码

以上就是通讯录的全部功能了,有些地方可能不是很完善,细心的小伙伴可以帮忙指正哦。
对您有用的话可以三连支持博主,您的点赞和关注是我最大的动力,感谢您的观看和支持!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值