c语言实现通讯录(含菜单和保存文件功能)

发布于:2023-01-19 ⋅ 阅读:(485) ⋅ 点赞:(0)

功能较为完整的通讯录

        包含有增删查改,保存文件,以及排序和打印功能(包含菜单)

源码如下(包含有一定的注释)

谢谢点个赞喏。

         已测试运行正常。(vs2019)

目录

定义通讯录结构体

1.初始化通讯录

2.判断容量接口

3.增加成员信息

4.查找通讯录成员(以名字为凭据)

5.删除某一个成员

6.更正某一个成员的数据

7.排序通讯录(以名字首字母为凭据)

8.打印通讯录

9.保存通讯录到本地文件中

 10.销毁通讯录并保存

完整源码如下


定义通讯录结构体

#define  NAME_MAX 10
#define  SEX_MAX 5
#define  PHONE_MAX 12
#define  ADDRESS_MAX 10
#define SZ_DEFAULT 3
//定义标识符常量,便于未来的参数修改
 
 
typedef struct PeoInfo//成员的信息
{
	char name[NAME_MAX];
	int age;
	char sex[SEX_MAX];
	char phone[PHONE_MAX];
	char address[ADDRESS_MAX];
}PeoInfo;
 
typedef struct Contact
{
	PeoInfo* data;//指针域
	int size;
	int capacity;
}Contact;

1.初始化通讯录

         由于本通讯录是通过动态存储到本地文件的,因此涉及到空间的申请以及本地文件的读取问题,所以我们在初始化通讯录的时候,应该充分考虑两个问题。

        ⊙第一:本地是否存在通讯录关联的文件,若不存在,则应该进行文件的创建能力;若存在,则应该读取本地文件的信息加载的通讯录中。

        ⊙第二:通讯录是否满溢。因为在上一步中,我们可能读取了本地文件的信息,而在初始化通讯录时默认申请的空间可能不足以存储本地文件的信息,因此,我们复用一个判断通讯录是否满溢的接口,在每次读取本地文件时能够保证空间的足够。

void InitContact(Contact* p)//初始化通讯录
{
 
	p->size = 0;
	p->capacity = SZ_DEFAULT;
	p->data = (PeoInfo*)malloc(SZ_DEFAULT * sizeof(PeoInfo));
	if (p->data == NULL)
	{
		printf("InitContact::%s\n", strerror(errno));
		exit(-1);
	}
	else
	{
		LocateContact(p);//装载原始数据
	}
 
}
 
void LocateContact(Contact* p)//装载原始数据
{
	PeoInfo tmp = {0};
	FILE* pf1 = NULL;
	FILE* pf = fopen("contact.txt", "rb");//文件存在则用只读方式打开文件
	if (pf == NULL)
	{
		pf1 = fopen("contact.txt", "wb+");//当文件不存在时,新建一个二进制.txt文件
		if (pf1 == NULL)
		{
			printf("LocateContact::%s\n", strerror(errno));//新建失败,错误退出并报错
			exit(-1);
		}
		fclose(pf1);
		pf1 = NULL;
		//新建成功,但文件为空则无动作直接返回
	}
	else
	{
		while (fread(&tmp, sizeof(PeoInfo), 1, pf))
		{
			CheckContact(p);//每次读取之前应该进行容量的检查
			p->data[p->size] = tmp;
			p->size++;
		}
		fclose(pf);
		pf = NULL;
	}
	
}
 
 
 
void CheckContact(Contact* p)//检查容量,若已满则二倍申请空间
{
	assert(p);
 
	PeoInfo* ptr = p->data;
	if (p->capacity == p->size)
	{
		p->capacity = 2 * p->capacity;
 
		ptr = (PeoInfo*)realloc(p->data, sizeof(PeoInfo)*p->capacity);
		if (ptr == NULL)
		{
			printf("CheckContact::%s\n", strerror(errno));
			exit(-1);
		}
		else
		{
			p->data = ptr;
			printf("增容成功\n");
		}
	}
}

2.判断容量接口

          正如之前所说,我们通讯录是动态申请空间的,因此可能会出现通讯录占满的情况,这时候通讯录的容量capacity和当前存储数量size相等,我们就需要重新申请更大的空间来存储成员信息。为了不频繁申请空间,我们每次扩容的大小均为上一次容量的两倍。

void CheckContact(Contact* p)//检查容量,若已满则二倍申请空间
{
	assert(p);
 
	PeoInfo* ptr = p->data;
	if (p->capacity == p->size)
	{
		p->capacity = 2 * p->capacity;
 
		ptr = (PeoInfo*)realloc(p->data, sizeof(PeoInfo)*p->capacity);
		if (ptr == NULL)
		{
			printf("CheckContact::%s\n", strerror(errno));
			exit(-1);
		}
		else
		{
			p->data = ptr;
			printf("增容成功\n");
		}
	}
}

3.增加成员信息

         由于要增加新成员,当然就要考虑容量的问题,我们之前已经封装了检查容量的接口,直接复用就好。

void AddContact(Contact* p)//增加成员
{
	assert(p);
 
	CheckContact(p);
 
	printf("请输入姓名:>");
	scanf("%s", &(&p->data[p->size])->name);
	printf("请输入年龄:>");
	scanf("%d", &(&p->data[p->size])->age);
	printf("请输入性别:>");
	scanf("%s", &(&p->data[p->size])->sex);
	printf("请输入电话:>");
	scanf("%s", &(&p->data[p->size])->phone);
	printf("请输入住址:>");
	scanf("%s", &(&p->data[p->size])->address);//要注意是取地址
	
	p->size++;//当前大小加一
}

4.查找通讯录成员(以名字为凭据)

         该接口要注意到底是返回成员的位序(以一开头),还是返回下标(第一个成员下标为0).

本接口是找到成员返回其位序,否则返回-1。

int FindByName(Contact* p, char name[])//以名字查找成员并返回位序(即第几个)
{
	assert(p);
 
	int i = 0;
	for (i = 0; i < p->size; i++)
	{
		while (strcmp(p->data[i].name, name)== 0)
		{
			return i + 1;//i是下标,注意返回的应该是位序
		}
	}
	printf("未找到该成员\n");
	return -1;//表示未找到该成员
}

5.删除某一个成员

          由我们第四步定义的查找接口可知,若存在某个成员,我们能根据他的名字找到他的位序,因此,我们只需要删除该位序上的成员即可。(注意如果pos是-1则不存在成员)

        删除成功后通讯录的大小应该减一。

        具体删除的方式是找到该位序的成员后(pos-1),我们只需要将该成员后的成员分别挪动到前一个位置即可,因为我们的通讯录应该是连续存储的。
 

void DelContact(Contact* p, char name[])//删除某一个成员(以成员名字为凭据),复用查找接口(注意是位序还是下标),本接口采用位序(从1开始计数)
{
	assert(p);
 
	int pos= FindByName(p, name);
 
	if (pos == -1)
	{
		printf("不存在该成员,无法删除\n");
		return;
	}
	else
	{
		int pos1 = pos-1;//转换成下标
		for (int j = pos1; j < p->size-1; j++)//注意j的上限应该比长度少1
		{
			p->data[j] = p->data[j + 1];
		}
		printf("删除成功\n");
		p->size--;//删除成功了通讯录大小应该减一
		return;
	}
}

6.更正某一个成员的数据

          同理,我们只需要复用查找接口就可以找到需要更改的成员所在位序(不存在pos为-1)。

分别更新新成员的信息即可

void ChangeContact(Contact* p, char name[])//复用查找接口,以名字为凭据更改通讯录
{
	assert(p);
 
	int pos= FindByName(p, name);
 
	if (pos == -1)//查找失败
	{
		printf("查找失败\n");
		return;
	}
	else
	{
		int pos1 = pos - 1;//转换为下标
 
		printf("请输入更正后的信息,原信息删除\n");
		printf("请输入姓名:>");
		scanf("%s", &(&p->data[pos1])->name);
		printf("请输入年龄:>");
		scanf("%d", &(&p->data[pos1])->age);
		printf("请输入性别:>");
		scanf("%s", &(&p->data[pos1])->sex);
		printf("请输入电话:>");
		scanf("%s", &(&p->data[pos1])->phone);
		printf("请输入住址:>");
		scanf("%s", &(&p->data[pos1])->address);//要注意是取地址
 
		printf("更正成功\n");
	}
}

7.排序通讯录(以名字首字母为凭据)

         该排序的思想是冒泡排序。

        要注意字符串的比较应该使用strcmp函数而非直接用符号比较。

        具体定义可在cplusplus.com查找。  

void SortContact(Contact* p)//排序通讯录,以名字首字母大小为凭据(相同则比较下一个字母)
{
	assert(p);
 
	//冒泡排序,每次将最大值放到最后
	int i = 0;
	for (i = 0; i < p->size - 1; i++)//应该执行size-1次冒泡排序
	{
		int flag = 0;//某趟冒泡排序中如无发生交换则直接标志排序完成
		int j = 0;
		for (j = 0; j < p->size - 1 - i; j++)//每趟冒泡排序
		{
			if (strcmp(p->data[j].name, p->data[j + 1].name) > 0)//比较名字大小,大于则发生交换
			{
				PeoInfo tmp = p->data[j];
				p->data[j] = p->data[j + 1];
				p->data[j + 1] = tmp;
				flag++;//标志着发生交换则排序未完成
			}
		}
		if (flag == 0)
		{
			return;
		}
	}
}

8.打印通讯录

   注意打印的格式,如下

                “%-15s” 解析:

                “s”:本次打印的内容应该是字符串。

                “15”:意思是本次打印的字符串应该占有15个字节。

                “-”:数字前面的负号表示以左对齐。


 

void PrintContact(Contact* p)//打印通讯录
{
	assert(p);
 
	if (p->size == 0)
	{
		printf("通讯录为空\n");
		return;
	}
	printf("%-15s%-5s%-8s%-15s%-12s\n", "姓名", "年龄", "性别", "电话", "住址");
	printf("_________________________________________________\n");
	
	for (int i = 0; i < p->size; i++)
	{
		printf("%-15s%-5d%-8s%-15s%-12s\n",
			(&p->data[i])->name,
			(&p->data[i])->age,
			(&p->data[i])->sex,
			(&p->data[i])->phone,
			(&p->data[i])->address);
	}
	
}

9.保存通讯录到本地文件中

         注意打开文件后退出函数前要关闭文件。即fopen和 fclose的用法。

        第二就是 函数 “fwrite”的用法要学会。

void SaveContact(Contact* p)//保存通讯录到文件中
{
	assert(p);
 
	FILE* pf = fopen("contact.txt", "wb");//已只写形式打开文件(之前初始化通讯录时已经建立好文件)
	if (pf == NULL)
	{
		printf("SaveContact::%s\n", strerror(errno));//这里出错只可能是因为未初始化通讯录且当前目录下无文件
		exit(-1);
	}
	int i = 0;
	for (i = 0; i < p->size; i++)
	{
		fwrite(&(p->data[i]), sizeof(PeoInfo), 1, pf);
	}
	//写文件完成,需要关闭文件
	fclose(pf);
	pf = NULL;
}

 10.销毁通讯录并保存

           

        比较简单的接口,复用保存接口即完成数据存到本地文件。

        然后就是要注意销毁通讯录的空间,因为我们是动态开辟的内存在堆上,如果不释放空间,就会造成内存泄漏。

void DestroyContact(Contact* p)//销毁通讯录并保存
{
	assert(p);//输入正确
 
	//复用保存接口进行数据的保存至文件中
	SaveContact(p);
 
	free(p->data);//销毁通讯录的数据空间
	p->data = NULL;
 
	p->data = 0;
	p->capacity = SZ_DEFAULT;//通讯录其他成员恢复初始化
 
}

完整源码如下

text.c(含菜单)

#include "Contact.h"
 
 
 
void menu()
{
	printf("*************************************\n");
	printf("******0. 退出并保存         *********\n");
	printf("******1. 增加    2. 删除    *********\n");
	printf("******3. 查找    4. 更正    *********\n");
	printf("******5. 排序    6. 打印    *********\n");
	printf("******7. 保存               *********\n");
	printf("*************************************\n");
}
 
int main()
{
	int input = 0;
	Contact Con;
	InitContact(&Con);
 
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case exit0:
		{
			DestroyContact(&Con);
			printf("退出成功\n");
			break;
		}
		case Add:
		{
			AddContact(&Con);
			break;
		}
		case Find:
		{
			char name[NAME_MAX] = { 0 };
			printf("请输入你要查找的成员名\n");
			scanf("%s", name);
			FindByName(&Con, name);
			break;
		}
		case Change:
		{
			char name[NAME_MAX] = { 0 };
			printf("请输入你要更正的成员名\n");
			scanf("%s", name);
			ChangeContact(&Con, name);
			break;
		}
		case Sort:
		{
			SortContact(&Con);
			break;
		}
		case Print:
		{
			PrintContact(&Con);
			break;
		}
		case Save:
		{
			SaveContact(&Con);
			break;
		}
		
		default:
		{
			printf("选择非法,请重新输入\n");
			break;
		}
		}
	} while (input);
 
	return 0;
}
Contact.c(接口的定义)
#include "Contact.h"
 
 
 
 
void InitContact(Contact* p)//初始化通讯录
{
 
	p->size = 0;
	p->capacity = SZ_DEFAULT;
	p->data = (PeoInfo*)malloc(SZ_DEFAULT * sizeof(PeoInfo));
	if (p->data == NULL)
	{
		printf("InitContact::%s\n", strerror(errno));
		exit(-1);
	}
	else
	{
		LocateContact(p);//装载原始数据
	}
 
}
 
void LocateContact(Contact* p)//装载原始数据
{
	PeoInfo tmp = {0};
	FILE* pf1 = NULL;
	FILE* pf = fopen("contact.txt", "rb");//文件存在则用只读方式打开文件
	if (pf == NULL)
	{
		pf1 = fopen("contact.txt", "wb+");//当文件不存在时,新建一个二进制.txt文件
		if (pf1 == NULL)
		{
			printf("LocateContact::%s\n", strerror(errno));//新建失败,错误退出并报错
			exit(-1);
		}
		fclose(pf1);
		pf1 = NULL;
		//新建成功,但文件为空则无动作直接返回
	}
	else
	{
		while (fread(&tmp, sizeof(PeoInfo), 1, pf))
		{
			CheckContact(p);//每次读取之前应该进行容量的检查
			p->data[p->size] = tmp;
			p->size++;
		}
		fclose(pf);
		pf = NULL;
	}
	
}
 
 
 
void CheckContact(Contact* p)//检查容量,若已满则二倍申请空间
{
	assert(p);
 
	PeoInfo* ptr = p->data;
	if (p->capacity == p->size)
	{
		p->capacity = 2 * p->capacity;
 
		ptr = (PeoInfo*)realloc(p->data, sizeof(PeoInfo)*p->capacity);
		if (ptr == NULL)
		{
			printf("CheckContact::%s\n", strerror(errno));
			exit(-1);
		}
		else
		{
			p->data = ptr;
			printf("增容成功\n");
		}
	}
}
 
void AddContact(Contact* p)//增加成员
{
	assert(p);
 
	CheckContact(p);
 
	printf("请输入姓名:>");
	scanf("%s", &(&p->data[p->size])->name);
	printf("请输入年龄:>");
	scanf("%d", &(&p->data[p->size])->age);
	printf("请输入性别:>");
	scanf("%s", &(&p->data[p->size])->sex);
	printf("请输入电话:>");
	scanf("%s", &(&p->data[p->size])->phone);
	printf("请输入住址:>");
	scanf("%s", &(&p->data[p->size])->address);//要注意是取地址
	
	p->size++;//当前大小加一
}
 
int FindByName(Contact* p, char name[])//以名字查找成员并返回位序(即第几个)
{
	assert(p);
 
	int i = 0;
	for (i = 0; i < p->size; i++)
	{
		while (strcmp(p->data[i].name, name)== 0)
		{
			return i + 1;//i是下标,注意返回的应该是位序
		}
	}
	printf("未找到该成员\n");
	return -1;//表示未找到该成员
}
 
void DelContact(Contact* p, char name[])//删除某一个成员(以成员名字为凭据),复用查找接口(注意是位序还是下标),本接口采用位序(从1开始计数)
{
	assert(p);
 
	int pos= FindByName(p, name);
 
	if (pos == -1)
	{
		printf("不存在该成员,无法删除\n");
		return;
	}
	else
	{
		int pos1 = pos-1;//转换成下标
		for (int j = pos1; j < p->size-1; j++)//注意j的上限应该比长度少1
		{
			p->data[j] = p->data[j + 1];
		}
		printf("删除成功\n");
		p->size--;//删除成功了通讯录大小应该减一
		return;
	}
}
 
void ChangeContact(Contact* p, char name[])//复用查找接口,以名字为凭据更改通讯录
{
	assert(p);
 
	int pos= FindByName(p, name);
 
	if (pos == -1)//查找失败
	{
		printf("查找失败\n");
		return;
	}
	else
	{
		int pos1 = pos - 1;//转换为下标
 
		printf("请输入更正后的信息,原信息删除\n");
		printf("请输入姓名:>");
		scanf("%s", &(&p->data[pos1])->name);
		printf("请输入年龄:>");
		scanf("%d", &(&p->data[pos1])->age);
		printf("请输入性别:>");
		scanf("%s", &(&p->data[pos1])->sex);
		printf("请输入电话:>");
		scanf("%s", &(&p->data[pos1])->phone);
		printf("请输入住址:>");
		scanf("%s", &(&p->data[pos1])->address);//要注意是取地址
 
		printf("更正成功\n");
	}
}
 
void SortContact(Contact* p)//排序通讯录,以名字首字母大小为凭据(相同则比较下一个字母)
{
	assert(p);
 
	//冒泡排序,每次将最大值放到最后
	int i = 0;
	for (i = 0; i < p->size - 1; i++)//应该执行size-1次冒泡排序
	{
		int flag = 0;//某趟冒泡排序中如无发生交换则直接标志排序完成
		int j = 0;
		for (j = 0; j < p->size - 1 - i; j++)//每趟冒泡排序
		{
			if (strcmp(p->data[j].name, p->data[j + 1].name) > 0)//比较名字大小,大于则发生交换
			{
				PeoInfo tmp = p->data[j];
				p->data[j] = p->data[j + 1];
				p->data[j + 1] = tmp;
				flag++;//标志着发生交换则排序未完成
			}
		}
		if (flag == 0)
		{
			return;
		}
	}
}
 
void PrintContact(Contact* p)//打印通讯录
{
	assert(p);
 
	if (p->size == 0)
	{
		printf("通讯录为空\n");
		return;
	}
	printf("%-15s%-5s%-8s%-15s%-12s\n", "姓名", "年龄", "性别", "电话", "住址");
	printf("_________________________________________________\n");
	
	for (int i = 0; i < p->size; i++)
	{
		printf("%-15s%-5d%-8s%-15s%-12s\n",
			(&p->data[i])->name,
			(&p->data[i])->age,
			(&p->data[i])->sex,
			(&p->data[i])->phone,
			(&p->data[i])->address);
	}
	
}
 
void SaveContact(Contact* p)//保存通讯录到文件中
{
	assert(p);
 
	FILE* pf = fopen("contact.txt", "wb");//已只写形式打开文件(之前初始化通讯录时已经建立好文件)
	if (pf == NULL)
	{
		printf("SaveContact::%s\n", strerror(errno));//这里出错只可能是因为未初始化通讯录且当前目录下无文件
		exit(-1);
	}
	int i = 0;
	for (i = 0; i < p->size; i++)
	{
		fwrite(&(p->data[i]), sizeof(PeoInfo), 1, pf);
	}
	//写文件完成,需要关闭文件
	fclose(pf);
	pf = NULL;
}
void DestroyContact(Contact* p)//销毁通讯录并保存
{
	assert(p);//输入正确
 
	//复用保存接口进行数据的保存至文件中
	SaveContact(p);
 
	free(p->data);//销毁通讯录的数据空间
	p->data = NULL;
 
	p->data = 0;
	p->capacity = SZ_DEFAULT;//通讯录其他成员恢复初始化
 
}
 
 
 
 
 

Contact.h(接口的声明)

#define _CRT_SECURE_NO_WARNINGS 1
 
#pragma once
 
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
 
 
#define  NAME_MAX 10
#define  SEX_MAX 5
#define  PHONE_MAX 12
#define  ADDRESS_MAX 10
#define SZ_DEFAULT 3
 
enum
{
	exit0,
	Add,
	Del,
	Find,
	Change,
	Sort,
	Print,
	Save
};
 
typedef int ConData;
 
typedef struct PeoInfo
{
	char name[NAME_MAX];
	int age;
	char sex[SEX_MAX];
	char phone[PHONE_MAX];
	char address[ADDRESS_MAX];
}PeoInfo;
 
typedef struct Contact
{
	PeoInfo* data;
	int size;
	int capacity;
}Contact;
 
void InitContact(Contact* p);//初始化通讯录
 
void LocateContact(Contact* p);//装载原始数据
 
void CheckContact(Contact* p);//检查容量,若已满则二倍申请空间
 
void AddContact(Contact* p);//增加成员
 
int FindByName(Contact* p, char name[]);//以名字查找成员并返回位序(即第几个)
 
void DelContact(Contact* p, char name[]);//删除某一个成员(以成员名字为凭据),复用查找接口(注意是位序还是下标),本接口采用位序(从1开始计数)
 
void ChangeContact(Contact* p, char name[]);//复用查找接口,以名字为凭据更改通讯录
 
void SortContact(Contact* p);//排序通讯录,以名字首字母大小为凭据(相同则比较下一个字母)
 
void PrintContact(Contact* p);//打印通讯录
 
void SaveContact(Contact* p);//保存通讯录到文件中
 
void DestroyContact(Contact* p);//销毁通讯录并保存

        新手上路,自娱自乐。

        都看到这里了点个赞吧(⸝⸝⸝⋅⃘᷄ ·̭ ⋅⃘᷅⸝⸝⸝)♡

本文含有隐藏内容,请 开通VIP 后查看