写点什么

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

作者:EquatorCoco
  • 2024-02-18
    福建
  • 本文字数:8562 字

    阅读完需:约 28 分钟

引言:


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

本章你可能需要的知识:

  1. 动态内存的使用:动态内存管理

  2. 文件的创建与使用:文件操作


1. 通讯录要求


  1. 通讯录包括每个人的姓名,性别,年龄,电话与地址。

  2. 玩家可以自由选择通讯录的进出。

  3. 玩家可以自由增删查改通讯录中的数据。



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");}
复制代码


画面展示:



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++;}
复制代码



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--;}
复制代码


删除前:



删除后 :



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);}
复制代码



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");}
复制代码


修改前:



修改后:



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");	}}
复制代码



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;}
复制代码


文章转载自:Betty’sSweet

原文链接:https://www.cnblogs.com/bett/p/18018489

体验地址:http://www.jnpfsoft.com/?from=001

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
精通C语言:打造高效便捷的通讯录管理系统_算法_EquatorCoco_InfoQ写作社区