2

精通C语言:打造高效便捷的通讯录管理系统 - Betty’sSweet

 7 months ago
source link: https://www.cnblogs.com/bett/p/18018489
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
pFp8UCq.jpg

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C语言项目
贝蒂的主页:Betty‘s blog

在我们大致学习完C语言之后,我们就可以利用目前所学的知识去做一些有意思的项目,而今天贝蒂就带大家完成一个通讯录的简易实现,

本章你可能需要的知识:

  1. 动态内存的使用:动态内存管理
  2. 文件的创建与使用:文件操作

1. 通讯录要求

  1. 通讯录包括每个人的姓名,性别,年龄,电话与地址。
  2. 玩家可以自由选择通讯录的进出。
  3. 玩家可以自由增删查改通讯录中的数据。
3371071-20240217214642394-1822277613.png

2. 多文件管理

为了方便代码的管理和保证通讯录实现逻辑的清晰性,我们将采用多文件管理的模式。

  1. 创建头文件contact.c,包含所有头文件(其他源文件只需引用它即可),以及所有通讯录功能的展现。

  2. 创建源文件contact.c,负责通讯录所有功能的具体代码实现。

  3. 创建源文件text.c,负责展现通讯录实现的总体逻辑。

3. 通讯录的准备

3.1 预处理信息

为了方便我们后续更换通讯的信息,我们可以利用来定义通讯录的具体信息的大小。

#define MAX 100//最大人数
#define MAX_NAME 20//名字最大长度
#define MAX_SEX 5//性别最大长度
#define MAX_TELE 12//电话最大长度
#define MAX_ADDR 30//地址最大长度

3.2 结构体定义

每个人的通讯录都要包含姓名,性别,年龄,电话与地址等信息,这时就需要我们创建一个结构体来方便管理。

typedef struct PeoInfo
{
	char name[MAX_NAME];//名字
	int age;//年龄
	char sex[MAX_SEX];//性别
	char tele[MAX_TELE];//电话
	char addr[MAX_ADDR];//地址
}PeoInfo;

而我们需要用这个结构体创建一个大小为100的数组,并且我们还需要知道当前通讯录的大小才能进行增删查改的操作,这两者息息相关,为了简化代码和增加代码的可读性,我们可以将这两者重新定义一个结构体。

typedef struct contact
{
	PeoInfo data[MAX];//一百个人的数据
	int sz;//通讯录的大小
}contact;

4. 简易菜单

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

画面展示:

3371071-20240217214642975-554789535.png

5. 通讯录具体功能

5.1 初始化

我们首先对通讯录进行初始化。

void InitContact(contact* pc)//初始化
{
	assert(pc);
	pc->sz = 0;
	memset(pc->data, 0, sizeof(pc->data));
}

5.2 展示联系人

当用户选择5时自动展示通讯录中的用户,并且展示用户过程中需要进行对齐,便于用户观看。

void DisplayContact(contact* pc)//打印信息
{
	assert(pc);
	printf("%-15s\t%-5s\t%-5s\t%-12s\t%-30s\n", "姓名", "年纪",
		"性别", "电话", "地址");//默认右对齐,修改为左对齐
	//中间也要留下足够的空间
	for (int i = 0; i < pc->sz; i++)
	{
		printf("%-15s\t%-5d\t%-5s\t%-12s\t%-30s\n", pc->data[i].name,
			pc->data[i].age,
			pc->data[i].sex,
			pc->data[i].tele,
			pc->data[i].addr);
	}
}

5.3 添加联系人

用户选择1可以自由添加联系人,如果通讯录已满,则提醒用户通讯录已满,请先清理通讯录。

(1) 检查通讯录是否已满

如果通讯录满了返回0,未满则返回1。

int CheckContact(contact*pc)//检查大小
{
	assert(pc);
	if (pc->sz == 100)
	{
		return 0;
	}
	return 1;
}

(2) 添加

当通讯录未满时,用户可以输入数据添加新用户。

void AddContact(contact* pc)//增加联系人
{
	assert(pc);
	int ret = CheckContact(pc);//检查是否满了
	if (ret == 0)
	{
		printf("通讯录已满,请先清理通讯录!!\n");
		return;
	}
	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);
	printf("用户添加成功!\n");
	pc->sz++;
}
3371071-20240217214643601-2046476886.png

5.4 删除联系人

用户可以选择2清理通讯录,删除指定联系人

(1) 寻找下标

在删除指定练习人时我们需通过其姓名寻找该联系人的下标。找到返回其下标,否则返回-1、

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

(2) 删除

通过寻找到的下标,我们可以利用后面的数据依次覆盖来达到删除的目的。

注意:我们不能覆盖最后一个数据否则就会发生数组越界,这时我们只需减去通讯录此时的大小就好了。

void DeleteContact(contact* pc)//删除联系人
{
	assert(pc);
         assert(pc->sz >= 0);
	char name[MAX_NAME];
	printf("请输入需要删除人的姓名:> ");
	scanf("%s", name);
	int pos = FindName(pc, name);
	if (pos == -1)
	{
		printf("通讯录中并没有这个人!!\n");
		return;
	}
	for (int i = pos; i < pc->sz - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];//覆盖
	}
    	printf("删除成功\n");
	pc->sz--;
}

3371071-20240217214643998-1308632762.png

3371071-20240217214644394-437837819.png

5.5 查找联系人

通过选择3查找指定联系人,没有找到则提醒用户没有该用户,查找到就打印其信息。

void SearchContact(contact* pc)//查找联系人
{
	assert(pc);
	char name[MAX_NAME];
	printf("请输入需要查找人的姓名:> ");
	scanf("%s", name);
	int pos = FindName(pc, name);
	if (pos == -1)
	{
		printf("通讯录中并没有这个人!!\n");
		return;
	}
	printf("%-15s\t%-5s\t%-5s\t%-12s\t%-30s\n", "姓名", "年纪",
		"性别", "电话", "地址");
	printf("%-15s\t%-5d\t%-5s\t%-12s\t%-30s", pc->data[pos].name,
		pc->data[pos].age,
		pc->data[pos].sex,
		pc->data[pos].tele,
		pc->data[pos].addr);
}

3371071-20240217214644967-579242267.png

5.6 修改联系人

我们可以通过选择4修改指定联系人的信息。

void ModifyContact(contact* pc)//修改联系人
{
	assert(pc);
	char name[MAX_NAME];
	printf("请输入需要修改人的姓名:> ");
	scanf("%s", name);
	int pos = FindName(pc, name);
	if (pos == -1)
	{
		printf("通讯录中并没有这个人!!\n");
		return;
	}
	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].tele);
	printf("请输入联系人的地址:> ");
	scanf("%s", pc->data[pos].addr);
	printf("修改成功\n");
}

3371071-20240217214643998-1308632762.png

3371071-20240217214645661-1791485951.png

5.7 排序联系人

我们可以选择6对通讯录进行排序,可以按照姓名,年纪,性别,电话,地址排序。

int cmp1(const void* p1, const void* p2)
{
	return strcmp(((PeoInfo*)p1)->name, ((PeoInfo*)p2)->name);
}
int cmp2(const void* p1, const void* p2)
{
	return ((PeoInfo*)p1)->age- ((PeoInfo*)p2)->age;
}
int cmp3(const void* p1, const void* p2)
{
	return strcmp(((PeoInfo*)p1)->sex, ((PeoInfo*)p2)->sex);
}
int cmp4(const void* p1, const void* p2)
{
	return strcmp(((PeoInfo*)p1)->tele, ((PeoInfo*)p2)->tele);
}
int cmp5(const void* p1, const void* p2)
{
	return strcmp(((PeoInfo*)p1)->addr, ((PeoInfo*)p2)->addr);
}
void SortByName(contact* pc)
{
	qsort(pc, pc->sz, sizeof(PeoInfo), cmp1);
}
void SortByAge(contact* pc)
{
	qsort(pc, pc->sz, sizeof(PeoInfo), cmp2);
}
void SortBySex(contact* pc)
{
	qsort(pc, pc->sz, sizeof(PeoInfo), cmp3);
}
void SortByTele(contact* pc)
{
	qsort(pc, pc->sz, sizeof(PeoInfo), cmp4);
}
void SortByAddr(contact* pc)
{
	qsort(pc, pc->sz, sizeof(PeoInfo), cmp4);
}
void SortContact(contact* pc)//排序联系人
{
	assert(pc);
	printf("请选择如何排序:> ");
	char input[20];
	scanf("%s", input);
	if (strcmp(input, "姓名")==0)
	{
		SortByName(pc);//按姓名排序
	}
	else if (strcmp(input, "年龄") == 0)
	{
		SortByAge(pc);//按年龄排序
	}
	else if (strcmp(input, "性别") == 0)
	{
		SortBySex(pc);//按性别排序
	}
	else if (strcmp(input, "电话") == 0)
	{
		SortByTele(pc);//按电话排序
	}
	else if (strcmp(input, "地址") == 0)
	{
		SortByAddr(pc);//按地址排序
	}
	else
	{
		printf("输入非法,请重新输入\n");
	}
}

6. 改进通讯录

6.1 动态内存开辟

上述通讯录有一个致命的缺点——通讯录大小固定,为了解决这个问题我可以使用我们前面学过的动态内存开辟

(1) 新增变量

为了方便我们知道此时的容量大小,我们将在结构体中加入新的变量。

typedef struct contact
{
	PeoInfo *data;//动态开辟的数据
	int sz;//通讯录的大小
	int capacity;//通讯录的容量
}contact;

(2) 初始化

void InitContact(contact* pc)//初始化
{
	assert(pc);
	pc->sz = 0;
	pc->data = (PeoInfo*)calloc(2, sizeof(PeoInfo));
	if (pc->data == NULL)
	{
		perror("InitContact:");
		return;
	}
	pc->capacity = 2;
	/*memset(pc->data, 0, sizeof(pc->data));*/
}

(3) 增容

当通讯录用户数量满时增加内存空间。

void CheckCapacity(contact* pc)//增容
{
	if (pc->sz == pc->capacity)
	{
		PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + 2) * sizeof(PeoInfo));
		if (tmp != NULL)
		{
			pc->data = tmp;
		}
		else
		{
			perror("CheckCapacity:");
			return;
		}
		pc->capacity += 2;
		printf("增容成功\n");
	}
}
3371071-20240217214646119-663179786.png

6.2 文件保存

我们发现每当我们关闭程序时我们写入的数据会被清空,我们要想保存上次写入的数据就应该使用文件操作

(1) 加载数据

每次初始化时就要将上次文件的数据导入进来。

void LoadContact(contact* pc)//加载上次数据
{
	PeoInfo tmp = { 0 };
	FILE* pf = fopen("contact.txt", "rb");
	if (pf == NULL)
	{
		return;
	}
	//读取文件,存放到通讯录
	while (fread(&tmp, sizeof(PeoInfo), 1, pf))
	//返回值为0就是遇见文件末尾
	{
		CheckCapacity(pc);
		pc->data[pc->sz] = tmp;
		pc->sz++;
	}

	fclose(pf);
	pf = NULL;
}

(2) 保存数据

当程序正常结束时保存数据。

void SaveContact(contact* pc)//保存数据
{
	FILE* pfWrite = fopen("contact.txt", "wb");
	if (pfWrite == NULL)
	{
		perror("fopen:");
		return;
	}
	//写通讯录中数据到文件中
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fwrite(&(pc->data[i]), sizeof(PeoInfo), 1, pfWrite);
	}
	fclose(pfWrite);
	pfWrite = NULL;
	printf("保存成功\n");

}

7. 通讯录逻辑的搭建

  1. 用户通过数字选择功能。
  2. 每次选择完之后返回菜单。
  3. 选择退出之后保存数据,程序结束。
enum Option
	//利用枚举增加代码的可读性
{
	EXIT,//0
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	DISPLAY,
	SORT
};
void test()
{
	int input = 0;
	contact con;
	InitContact(&con);
	do
	{
		menu();
		printf("请输入你的选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			AddContact(&con);
			break;
		case DEL:
			DeleteContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case MODIFY:
			ModifyContact(&con);
			break;
		case DISPLAY:
			DisplayContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case EXIT:
			SaveContact(&con);//退出保存数据
		    DestroyContact(&con);
			break;
		default:
			printf("非法输入,请重新输入\n");
			break;
		}
	} while (input);
}
int main()
{
	test();
	return 0;
}

8. 完整代码

8.1 contact.h

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#define MAX_NAME 20//名字最大长度
#define MAX_SEX 5//性别最大长度
#define MAX_TELE 12//电话最大长度
#define MAX_ADDR 30//地址最大长度
typedef struct PeoInfo
{
	char name[MAX_NAME];//名字
	int age;//年龄
	char sex[MAX_SEX];//性别
	char tele[MAX_TELE];//电话
	char addr[MAX_ADDR];//地址
}PeoInfo;
typedef struct contact
{
	PeoInfo *data;//动态开辟的数据
	int sz;//通讯录的大小
	int capacity;//通讯录的容量
}contact;
void InitContact(contact* pc);//初始化
void DisplayContact(contact* pc);//打印信息
void AddContact(contact* pc);//增加联系人
void DeleteContact(contact* pc);//删除联系人
void SearchContact(contact* pc);//查找联系人
void ModifyContact(contact* pc);//修改联系人
void SortContact(contact* pc);//排序联系人
void SaveContact(contact* pc);//保存数据
void DestroyContact(contact* pc);//销毁内存

8.2 contact.c

#include"contact.h"
void CheckCapacity(contact* pc)//增容
{
	if (pc->sz == pc->capacity)
	{
		PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + 2) * sizeof(PeoInfo));
		if (tmp != NULL)
		{
			pc->data = tmp;
		}
		else
		{
			perror("CheckCapacity:");
			return;
		}
		pc->capacity += 2;
		printf("增容成功\n");
	}
}
void LoadContact(contact* pc)//加载上次数据
{
	PeoInfo tmp = { 0 };
	FILE* pf = fopen("contact.txt", "rb");
	if (pf == NULL)
	{
		return;
	}
	//读取文件,存放到通讯录
	while (fread(&tmp, sizeof(PeoInfo), 1, pf))
	//返回值为0就是遇见文件末尾
	{
		CheckCapacity(pc);
		pc->data[pc->sz] = tmp;
		pc->sz++;
	}

	fclose(pf);
	pf = NULL;
}

void InitContact(contact* pc)//初始化
{
	assert(pc);
	pc->sz = 0;
	pc->data = (PeoInfo*)calloc(2, sizeof(PeoInfo));
	if (pc->data == NULL)
	{
		perror("InitContact:");
		return;
	}
	pc->capacity = 2;
	LoadContact(pc);
	/*memset(pc->data, 0, sizeof(pc->data));*/
}
void DisplayContact(contact* pc)//打印信息
{
	assert(pc);
	printf("%-15s\t%-5s\t%-5s\t%-12s\t%-30s\n", "姓名", "年纪",
		"性别", "电话", "地址");//默认右对齐,修改为左对齐
	//中间也要留下足够的空间
	for (int i = 0; i < pc->sz; i++)
	{
		printf("%-15s\t%-5d\t%-5s\t%-12s\t%-30s\n", pc->data[i].name,
			pc->data[i].age,
			pc->data[i].sex,
			pc->data[i].tele,
			pc->data[i].addr);
	}
}

void AddContact(contact* pc)//增加联系人
{
	assert(pc);
	CheckCapacity(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);
	printf("用户添加成功!\n");
	pc->sz++;
}
int FindName(contact* pc, char name[])
{
	assert(pc&&name);
	for (int pos = 0; pos < pc->sz; pos++)
	{
		if (strcmp(pc->data[pos].name, name) == 0)
		{
			return pos;
		}
	}
	return -1;
}
void DeleteContact(contact* pc)//删除联系人
{
	assert(pc);
	assert(pc->sz >= 0);
	char name[MAX_NAME];
	printf("请输入需要删除人的姓名:> ");
	scanf("%s", name);
	int pos = FindName(pc, name);
	if (pos == -1)
	{
		printf("通讯录中并没有这个人!!\n");
		return;
	}
	for (int i = pos; i < pc->sz - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];//覆盖
	}
	printf("删除成功\n");
	pc->sz--;
}
void SearchContact(contact* pc)//查找联系人
{
	assert(pc);
	char name[MAX_NAME];
	printf("请输入需要查找人的姓名:> ");
	scanf("%s", name);
	int pos = FindName(pc, name);
	if (pos == -1)
	{
		printf("通讯录中并没有这个人!!\n");
		return;
	}
	printf("%-15s\t%-5s\t%-5s\t%-12s\t%-30s\n", "姓名", "年纪",
		"性别", "电话", "地址");
	printf("%-15s\t%-5d\t%-5s\t%-12s\t%-30s", pc->data[pos].name,
		pc->data[pos].age,
		pc->data[pos].sex,
		pc->data[pos].tele,
		pc->data[pos].addr);
}
void ModifyContact(contact* pc)//修改联系人
{
	assert(pc);
	char name[MAX_NAME];
	printf("请输入需要修改人的姓名:> ");
	scanf("%s", name);
	int pos = FindName(pc, name);
	if (pos == -1)
	{
		printf("通讯录中并没有这个人!!\n");
		return;
	}
	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].tele);
	printf("请输入联系人的地址:> ");
	scanf("%s", pc->data[pos].addr);
	printf("修改成功\n");
}
int cmp1(const void* p1, const void* p2)
{
	return strcmp(((PeoInfo*)p1)->name, ((PeoInfo*)p2)->name);
}
int cmp2(const void* p1, const void* p2)
{
	return ((PeoInfo*)p1)->age- ((PeoInfo*)p2)->age;
}
int cmp3(const void* p1, const void* p2)
{
	return strcmp(((PeoInfo*)p1)->sex, ((PeoInfo*)p2)->sex);
}
int cmp4(const void* p1, const void* p2)
{
	return strcmp(((PeoInfo*)p1)->tele, ((PeoInfo*)p2)->tele);
}
int cmp5(const void* p1, const void* p2)
{
	return strcmp(((PeoInfo*)p1)->addr, ((PeoInfo*)p2)->addr);
}
void SortByName(contact* pc)
{
	qsort(pc, pc->sz, sizeof(PeoInfo), cmp1);
}
void SortByAge(contact* pc)
{
	qsort(pc, pc->sz, sizeof(PeoInfo), cmp2);
}
void SortBySex(contact* pc)
{
	qsort(pc, pc->sz, sizeof(PeoInfo), cmp3);
}
void SortByTele(contact* pc)
{
	qsort(pc, pc->sz, sizeof(PeoInfo), cmp4);
}
void SortByAddr(contact* pc)
{
	qsort(pc, pc->sz, sizeof(PeoInfo), cmp4);
}
void SortContact(contact* pc)//排序联系人
{
	assert(pc);
	printf("请选择如何排序:> ");
	char input[20] = { 0 };
	scanf("%s", input);
	if (strcmp(input, "姓名")==0)
	{
		SortByName(pc);//按姓名排序
	}
	else if (strcmp(input, "年龄") == 0)
	{
		SortByAge(pc);//按年龄排序
	}
	else if (strcmp(input, "性别") == 0)
	{
		SortBySex(pc);//按性别排序
	}
	else if (strcmp(input, "电话") == 0)
	{
		SortByTele(pc);//按电话排序
	}
	else if (strcmp(input, "地址") == 0)
	{
		SortByAddr(pc);//按地址排序
	}
	else
	{
		printf("输入非法,请重新输入\n");
	}
}
void DestroyContact(contact* pc)
{
	free(pc->data);
	pc->data= NULL;
	pc->capacity = 0;
	pc->sz = 0;
	printf("销毁成功\n");
}
void SaveContact(contact* pc)//保存数据
{
	FILE* pfWrite = fopen("contact.txt", "wb");
	if (pfWrite == NULL)
	{
		perror("fopen:");
		return;
	}
	//写通讯录中数据到文件中
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fwrite(&(pc->data[i]), sizeof(PeoInfo), 1, pfWrite);
	}
	fclose(pfWrite);
	pfWrite = NULL;
	printf("保存成功\n");

}

8.3 text.c

#include"contact.h"

enum Option
	//利用枚举增加代码的可读性
{
	EXIT,//0
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	DISPLAY,
	SORT
};

void menu()
{
    printf("***********************************\n");
    printf("**    1.add          2.delete    **\n");
    printf("**    3.search       4.modify    **\n");
    printf("**    5.display      6.sort      **\n");
    printf("**             0.exit            **\n");
    printf("***********************************\n");
}
void test()
{
	int input = 0;
	contact con;
	InitContact(&con);
	do
	{
		menu();
		printf("请输入你的选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			AddContact(&con);
			break;
		case DEL:
			DeleteContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case MODIFY:
			ModifyContact(&con);
			break;
		case DISPLAY:
			DisplayContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case EXIT:
			SaveContact(&con);//退出保存数据
		    DestroyContact(&con);
			break;
		default:
			printf("非法输入,请重新输入\n");
			break;
		}
	} while (input);
}

int main()
{
	test();
	return 0;
}


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK