揽括c语言百分之九十基础知识的小项目——数据持久化通讯录,大一学生快看过来

号称揽括c语言百分之九十基础知识的小项目——数据持久化通讯录,非常适合大一学生巩固和加强c语言,提高我们的代码能力,一起来看看吧。

该通讯录具备了一般通讯录的功能,例如添加,删除,修改,查找,排序(按名字字母大小排序),显示通讯录。
除此之外,该通讯录所占空间大小不是一成不变的,而是调用了c语言中的内存函数,随着你添加的人数越多,会自动开辟扩容空间,节省了空间上的浪费;
值得一提的是,该通讯录是数据持久化的,数据不会随着程序结束而消失,而是调用文件函数,生成文件,把数据保存起来;下一次运行程序,也会继承生成文件原数据,继续使用通讯录。因此,该通讯录除了无页面,基本上与手机上的通讯录无异。
当然,也不可能尽善尽美,有什么不好的地方,请大家多多指教哈·。

通讯录实现过程逻辑

我们上来就先浅创建两个结构体,一个是个人信息,另一个则是通讯录,注释如下

typedef struct peo
{
	char name[NAME_MAX];//姓名
	int age;			//年龄
	char sex[SEX_MAX];	//性别
	char tele[TELE_MAX];//电话
	char addr[ADDR__MAX];//地址
}peo;

typedef struct contact
{
	peo* data;
	//创建一个指向结构体(个人信息)指针;
	//后面可以以结构体数组的形式使用
	int sz;
	//通讯录当前人数
	int moren;
	//通讯录默认人数,可开辟空间增加
}contact;

变量的大小我们都是先define定义好,以便我们后期可能修改大小,这样就不需要在整个代码里进行修改。

#define MAX 1000
#define NAME_MAX 15
#define ADDR__MAX 15
#define SEX_MAX 10
#define TELE_MAX 15
#define MOREN_SE 2

整个通讯录的实现逻辑也是比较简单易懂的,使用do…while 语句,上来就给菜单进行选择,选择则使用switch语句;选择0就退出程序,其余选择就进入一个函数,使用通讯录的对应功能。代码注释如下,包括函数作用。

int main()
{
	int input;
	contact con = { 0 };//创建一个通讯录结构体变量
	InitContact(&con);
	//初始化通讯录,包括开辟空间和继承上次运行数据
	do
	{
		menu();
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			addinput(&con);
			//添加联系人函数
			
			break;
		case 2:
			delcontact(&con);
			//删除联系人函数
			break;
		case 3:
			
			searchcontact(&con);
			//查找联系人函数
			break;
		case 4:
			
			modifycontact(&con);
			//修改联系人信息函数
			break;
		case 5:
			paixucontact_byname(&con);
			//对联系人进行排序函数
			break;
		case 6:
			show(&con);
			break;
			
		case 0:
			SaveContact(&con);
			DestroyContact(&con);
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误\n");
		}
	} while (input);
}

通讯录不同功能的函数实现

一.开辟空间和继承上次运行数据

InitContact(&con);这是初始化通讯录,包括开辟空间和继承上次运行数据,函数参数是一个结构体指针,指向我们刚才创建的那个通讯录结构体 con;

void InitContact(contact*pc)
{
	assert(pc);
//判断传进来的指针是否为空,空指针的话整个程序会崩溃
	pc->moren = MOREN_SE;
	//初始化通讯录默认人数
	peo* tmp = (peo*)malloc(sizeof(peo) * pc->moren);
	//malloc是库函数,用于开辟空间,下面我会再介绍
		if (NULL != tmp)
		{
		//空间如果开辟失败,会返回一个空指针;
			pc->data = tmp;
		//空间开辟成功,我们则把通讯录指针指向这个空间
		}
		else
		{
			printf("%s", strerror(errno));
			//报出空间失败原因
		}
		LoadContact(pc);//继承上次运行得到的数据
}

malloc开辟空间函数,参数是要开辟空间的大小,单位为字节,返回一个指针,指向这个刚开辟的空间。
在这里插入图片描述

LoadContact(pc);这个函数用于继承上次运行得到的数据,这里先不介绍,等会和保存数据到文件一起介绍。

二.添加联系人及动态内存分配

addinput(&con);添加联系人函数,同样传过去一个结构体指针,指向我们刚才创建的那个通讯录结构体 con;添加联系人之前我们得先检查当前通讯录人数是否等于默认人数,如果是,就得扩容空间,是通讯录空间变大,所以我们再创建一个函数进行检查。

void addinput(contact* pc)
{
	int flag = 0;
	cheak_sz(pc);
	//检查人数函数
		printf("请输入名字:>");
		scanf("%s", pc->data[pc->sz].name);
		          //con指针指向peo指针所对应内容
		printf("请输入年龄:>");
		scanf("%d", &(pc->data[pc->sz].age));
		printf("请输入性别:>");
		scanf("%s", pc->data[pc->sz].sex);
		printf("请输入电话:>");
		scanf("%s", pc->data[pc->sz].tele);
		printf("请输入地址:>");
		scanf("%s", pc->data[pc->sz].addr);
		pc->sz++;
		//增加联系人成功,通讯录当前人数sz+1
		printf("增加联系人成功\n");
	
	
}

cheak_sz(pc);检查是否需要扩容空间,如果通讯录人数等于默认人数,则对通讯录空间进行扩容,这涉及到动态内存分配,我们调用realloc库函数对通讯录空间进行扩容

void cheak_sz(contact* pc)
{
	assert(pc);
	if (pc->sz == pc->moren)
	{
//如果通讯录人数等于默认人数,则对通讯录空间进行扩容
		peo*tmp = (peo*)realloc(pc->data, sizeof(peo) * (pc->moren + 2));
//对通讯录空间进行扩容,realloc函数下面会介绍
		if (tmp != NULL)
		{
			pc->data= tmp;
			pc->moren += 2;//默认人数+2
			//
			printf("通讯录人数已满,正在扩容...增容成功\n");
		}
		else
		{
			printf("%s", strerror(errno));
		}
	}
}

realloc(pc->data, sizeof(peo) * (pc->moren + 2))
这个函数第一个参数是指针,你想为谁扩容,指针就指向谁;第二个参数则是空间要该改变成多少,单位也是字节;返回值是一个void指针,我们用一个结构体指针接收,返回值也要强制类型转换成对应的指针

在这里插入图片描述

三.删除联系人

输入联系人名字进行查找,然后删除对应联系人,删除结束后,我们得把通讯录后面的内容,一个一个往前挪移回来。所谓删除,其实也就是把后面的内容往前覆盖。

void delcontact(contact* pc)
{
	assert(pc);
	printf("请输入联系人名字进行删除\n");
	char name[20];
	int i = 0;
	scanf("%s", name);
	int ret=search(pc, name);//查找联系人函数
	if (ret != -1)
	{
		for (i = ret; i < pc->sz - 1; i++)
		{
			pc->data[i] = pc->data[i + 1];
			//
		}
		pc->sz--;
		printf("删除联系人成功\n");

	}
	else
	{
		printf("不存在该联系人\n");
	}
}

查找联系人函数也很简单,对所有联系人名字遍历查找一下,找到返回对应下标

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

四.修改联系人信息

同样输入联系人名字进行查找,查找联系人函数在上面已介绍,如果找到则进行修改。这些都相对比较简单。

void modifycontact(contact* pc)
{
	assert(pc);
	char name[20];
	printf("请输入联系人名字进行修改\n");
	scanf("%s", name);
	int ret = search(pc, name);
	if (ret != -1)
	{
		printf("请修改名字:>");
		scanf("%s", pc->data[ret].name);
		printf("请修改年龄:>");
		scanf("%d", &(pc->data[ret].age));
		printf("请修改性别:>");
		scanf("%s", pc->data[ret].sex);
		printf("请修改电话:>");
		scanf("%s", pc->data[ret].tele);
		printf("请修改地址:>");
		scanf("%s", pc->data[ret].addr);
		
		printf("修改联系人成功\n");
	}
	else
	{
		printf("不存在该联系人\n");
	}
}

五.查找联系人信息

这个也相对简单,同样输入名字,调用查找联系人,找到联系人就打印出全部信息。

void searchcontact(contact* pc)
{
	assert(pc);
	char name[20];
	printf("请输入联系人名字进行查找\n");
	scanf("%s", name);
	int ret = search(pc, name);
	if (ret != -1)
	{
		printf("%-15s\t%-5d\t%-10s\t%-15s\t%-15s\n",
			pc->data[ret].name, pc->data[ret].age, pc->data[ret].sex,
			pc->data[ret].tele, pc->data[ret].addr);
	}
	else
	{
		printf("通讯录不存在该联系人\n");
	}
}

六.按名字字母对联系人进行排序显示

这个排序使用了c语言中的排序函数qsort,不知道你们对这个函数了解?

在这里插入图片描述
qsort排序函数有四个函数,第一个是指针,你想对什么类型变量进行排序,就传该类型的指针指向对应内容空间;第二个是进行排序的变量有多少个;第三个是每个变量所占内存空间大小,单位字节;比较麻烦的是第四个参数,其实是传过去一个比较参数,这个函数定义了两个变量之间的比较方式,我这里其实是对字符串进行比较,所以直接调用strcmp函数,这里就不介绍strcmp函数了。

int compare(const void* arg1, const void* arg2)
{
	return strcmp((*(peo*)arg1).name,(*(peo*)arg2).name);
}
void paixucontact_byname(contact* pc)
{
	qsort(pc->data, pc->sz, sizeof(pc->data[1]), compare);
	show(pc);//排序后会展示一下当前通讯录内容
}

七.将通讯录内容打印显示

这一步也不难,把所有人的信息遍历一下,然后打印出来


void show(contact* pc)
{
	int i = 0;
	printf("%-15s\t%-5s\t%-10s\t%-15s\t%-15s\n", 
		"名字", "年龄", "性别", "电话", "地址");
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-15s\t%-5d\t%-10s\t%-15s\t%-15s\n",
			pc->data[i].name, pc->data[i].age, pc->data[i].sex, 
			pc->data[i].tele, pc->data[i].addr );
	}
}

八.程序结束后保存数据

我们先打开一个文件,如果不存在该文件,则会先创建一个文件,我们以二进制写文件的方式先打开文件。
在这里插入图片描述
fopen(“contact.txt”, “wb”),第一个函数参数是文件名或者文件地址,第二个函数参数是以什么方式打开。这里’wb’是指用二进制的方式打开进行导入数据。

void SaveContact(contact* pc)
{
	
	FILE* pf = fopen("contact.txt", "wb");
	if (pf == NULL)
	{
		printf("SaveContact:: %s\n", strerror(errno));
		//判断文件打开是否成功
		return;
	}
	
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fwrite(pc->data + i, sizeof(peo), 1, pf);
		//一次循环导入一个联系人的信息
	}


	fclose(pf);
	pf = NULL;
}

如果文件打开成功,我们则调用fwrite函数把联系人的数据一个一个写进去,这里是以二进制的方式导入数据,我们打开生成的文件也是乱码的。
简单介绍一下fwrite函数
在这里插入图片描述
fwrite(pc->data + i, sizeof(peo), 1, pf);
该函数有四个参数,第一个参数是指针,你想给文件写入什么内容,就把一个指向该内容的指针传过去;第二个是每次要写入的空间大小,单位是字节;第三个参数要读入多少个这样的空间。第四个是一个文件指针,指向需要写入内容的文件

需要注意的是,保存数据后整个函数已接近运行结束,结束之前我们最好把开辟的动态内存先手动释放,还给系统。这里我也有这么做,可以参考一下

void DestroyContact(contact* pc)
{
	assert(pc);
	free(pc->data);
	//free函数释放指针指向开辟的动态内存空间
	pc->data = NULL;
	pc->sz = 0;
	pc->moren = 0;
}

九.程序开始运行时下载导入上次的数据

LoadContact(pc);这个函数用于继承上次运行得到的数据,内嵌于初始化函数void InitContact(contact*pc);中。
同样我们先打开一个文件,'rb’是以二进制的方式访问文件,打开成功后,则把文件内容加载进程序运行内存中。
这里调用fread库函数把文件内容导入

void LoadContact(contact* pc)
{
	FILE* pf = fopen("contact.txt", "rb");
	if (pf == NULL)
	{
		printf("InitContact:: open for reading : %s\n", strerror(errno));
		return;
	}
	
	peo buf = { 0 };
	
	while (fread(&buf, sizeof(peo), 1, pf))
	{
		cheak_sz(pc);

		pc->data[pc->sz] = buf;
		pc->sz++;
	}

	
	fclose(pf);
	pf = NULL;
}

这里也简单介绍一下fread函数;
fread(&buf, sizeof(peo), 1, pf)
该函数有四个参数,第一个参数是指针,你想从文件导入什么内容,就把一个指针传过去用于接收数据;第二个是每次要从文件下载内容的空间大小,单位是字节;第三个参数是要下载多少个这样的空间;第四个参数是文件指针,你想从哪个文件下载数据,就给对应的文件指针。
在这里插入图片描述

通讯录完整代码

contact.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
#include<windows.h>
#include<assert.h>
#include<errno.h>

#define MAX 1000
#define NAME_MAX 15
#define ADDR__MAX 15
#define SEX_MAX 10
#define TELE_MAX 15
#define MOREN_SE 2

typedef struct peo
{
	char name[NAME_MAX];//姓名
	int age;			//年龄
	char sex[SEX_MAX];	//性别
	char tele[TELE_MAX];//电话
	char addr[ADDR__MAX];//地址
}peo;

typedef struct contact
{
	peo* data;
	//创建一个结构体(个人信息)指针;
	int sz;
	//通讯录当前人数
	int moren;
	//通讯录默认人数,可开辟空间增加
}contact;


void InitContact(contact* pc);
void cheak_sz(contact* pc);
void addinput(contact* pc);
void show(contact* pc);
void SaveContact(contact* pc);
void LoadContact(contact* pc);
void DestroyContact(contact* pc);
void delcontact(contact* pc);
int search(contact* pc, char name[]);
void modifycontact(contact* pc);
void searchcontact(contact* pc);
int compare(const void* arg1, const void* arg2);
void paixucontact_byname(contact* pc);

test.c

#include"contact.h"

void menu()
{
	printf("\n*******************************************\n");
	printf("*********    1.添加      2.删除      *******\n");
	printf("*********    3.查找      4.修改      *******\n");
	printf("*********    5.排序      6.显示      *******\n");
	printf("*********    0.退出并保存            *******\n");
	printf("********************************************\n");
}
int main()
{
	int input;
	contact con = { 0 };
	InitContact(&con);
	//初始化通讯录,包括开辟空间和继承上次运行数据
	do
	{
		menu();
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			addinput(&con);
			//添加联系人函数
			
			break;
		case 2:
			delcontact(&con);
			//删除联系人函数
			break;
		case 3:
			
			searchcontact(&con);
			//查找联系人函数
			break;
		case 4:
			
			modifycontact(&con);
			//修改联系人信息函数
			break;
		case 5:
			paixucontact_byname(&con);
			//对联系人进行排序函数
			break;
		case 6:
			show(&con);
			break;
			
		case 0:
			SaveContact(&con);
			DestroyContact(&con);
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误\n");
		}
	} while (input);
}

contact.c

#include"contact.h"


void LoadContact(contact* pc)
{
	FILE* pf = fopen("contact.txt", "rb");
	if (pf == NULL)
	{
		printf("InitContact:: open for reading : %s\n", strerror(errno));
		return;
	}
	
	peo buf = { 0 };
	
	while (fread(&buf, sizeof(peo), 1, pf))
	{
		cheak_sz(pc);

		pc->data[pc->sz] = buf;
		pc->sz++;
	}

	
	fclose(pf);
	pf = NULL;
}

void InitContact(contact*pc)
{
	assert(pc);
	pc->moren = MOREN_SE;
	peo* tmp = (peo*)malloc(sizeof(peo) * pc->moren);
		if (NULL != tmp)
		{
			pc->data = tmp;
		}
		else
		{
			printf("%s", strerror(errno));
		}
		LoadContact(pc);
}
void cheak_sz(contact* pc)
{
	assert(pc);
	if (pc->sz == pc->moren)
	{
		peo*tmp = (peo*)realloc(pc->data, sizeof(peo) * (pc->moren + 2));


		if (tmp != NULL)
		{
			pc->data= tmp;
			pc->moren += 2;
			printf("通讯录人数已满,正在扩容...增容成功\n");
		}
		else
		{
			printf("%s", strerror(errno));
		}
	}
}


void addinput(contact* pc)
{
	int flag = 0;
	cheak_sz(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].tele);
		printf("请输入地址:>");
		scanf("%s", pc->data[pc->sz].addr);
		pc->sz++;
		printf("增加联系人成功\n");
	
	/*printf("请选择\n");
	printf("1:继续添加联系人\n");
	printf("2:退出添加联系人\n");
	scanf("%d", &flag);
	if (flag == 1)
	{
		addinput(pc);
	}
	else
	{
		return;
	}*/
}

void show(contact* pc)
{
	int i = 0;
	printf("%-15s\t%-5s\t%-10s\t%-15s\t%-15s\n", 
		"名字", "年龄", "性别", "电话", "地址");
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-15s\t%-5d\t%-10s\t%-15s\t%-15s\n",
			pc->data[i].name, pc->data[i].age, pc->data[i].sex, 
			pc->data[i].tele, pc->data[i].addr );
	}
}


void SaveContact(contact* pc)
{
	
	FILE* pf = fopen("contact.txt", "wb");
	if (pf == NULL)
	{
		printf("SaveContact:: %s\n", strerror(errno));
		return;
	}
	
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fwrite(pc->data + i, sizeof(peo), 1, pf);
	}


	fclose(pf);
	pf = NULL;
}

void searchcontact(contact* pc)
{
	assert(pc);
	char name[20];
	printf("请输入联系人名字进行查找\n");
	scanf("%s", name);
	int ret = search(pc, name);
	if (ret != -1)
	{
		printf("%-15s\t%-5d\t%-10s\t%-15s\t%-15s\n",
			pc->data[ret].name, pc->data[ret].age, pc->data[ret].sex,
			pc->data[ret].tele, pc->data[ret].addr);
	}
	else
	{
		printf("通讯录不存在该联系人\n");
	}
}
int search(contact* pc,char name[])
{
	int i;
	for (i = 0; i < pc->sz; i++)
	{
		if (strcmp(name, pc->data[i].name) == 0)
		{
			return i;
		}
	}
	return -1;
}
void modifycontact(contact* pc)
{
	assert(pc);
	char name[20];
	printf("请输入联系人名字进行修改\n");
	scanf("%s", name);
	int ret = search(pc, name);
	if (ret != -1)
	{
		printf("请修改名字:>");
		scanf("%s", pc->data[ret].name);
		printf("请修改年龄:>");
		scanf("%d", &(pc->data[ret].age));
		printf("请修改性别:>");
		scanf("%s", pc->data[ret].sex);
		printf("请修改电话:>");
		scanf("%s", pc->data[ret].tele);
		printf("请修改地址:>");
		scanf("%s", pc->data[ret].addr);
		
		printf("修改联系人成功\n");
	}
	else
	{
		printf("不存在该联系人\n");
	}
}
void delcontact(contact* pc)
{
	assert(pc);
	printf("请输入联系人名字进行删除\n");
	char name[20];
	int i = 0;
	scanf("%s", name);
	int ret=search(pc, name);
	if (ret != -1)
	{
		for (i = ret; i < pc->sz - 1; i++)
		{
			pc->data[i] = pc->data[i + 1];
		}
		pc->sz--;
		printf("删除联系人成功\n");

	}
	else
	{
		printf("不存在该联系人\n");
	}
}

int compare(const void* arg1, const void* arg2)
{
	return strcmp((*(peo*)arg1).name,(*(peo*)arg2).name);
}
void paixucontact_byname(contact* pc)
{
	qsort(pc->data, pc->sz, sizeof(pc->data[1]), compare);
	show(pc);
}



void DestroyContact(contact* pc)
{
	assert(pc);
	free(pc->data);
	pc->data = NULL;
	pc->sz = 0;
	pc->moren = 0;
}

最后 求鼓励 哈哈哈

这个通讯还是存在蛮多不足的,还请大家多多指教,评论区不啬赐教,谢谢您了;
码字不易,也请动动小手点点赞!一起提升技术能力!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值