手搓通讯录——C语言思路和代码(详解)

一. 前言

当我们学习C语言学到动态内容管理和结构体这一块后,就能利用所学习的知识来写一个通讯录项目。那么本片博客就来介绍如何实现通讯录的具体步骤。

二. 模块化编程

我们在平时练习写代码的时候,所有的函数均放在main.c一个源文件里,若使用的函数比较多,则一个文件内会有很多的代码,不利于代码的组织和管理,而且很影响我们的思路。所以我们要学会模块化编程。

把各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数的声明、宏定义和结构体。其它.c文件想使用其中的代码时,只需要包含.h头文件即#include "XXX.h"文件即可。使用模块化编程可极大的提高代码的可阅读性、可维护性、可移植性等。

三. 通讯录功能以及实现逻辑

通讯录的功能:

1.增加联系人

2.删除联系人

3.查找联系人

4.更改联系人信息

5.给联系人排序

6.显示联系人信息

7.退出通讯录

四. 详细步骤

1. 定义结构体

1.定义个人信息结构体。包含个人的姓名、性别、年龄、电话、住址。为了方便更改,这些字符数组的大小,定义成常量

arduino 复制代码
#define NAME_MAX 10
#define SEX_MAX 6
#define TEL_MAX 14
#define ADDR_MAX 10

typedef struct PeoInfo
{
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;
	char tel[TEL_MAX];
	char addr[ADDR_MAX];
}PeoInfo;

2.定义通讯录结构体。通讯录里面包含用户的个人信息,还有用户个数,还有通讯录容量信息

arduino 复制代码
typedef struct Contact
{
	PeoInfo* data;
	int sz;
	int capacity;
}Contact;

2. 打印菜单

我们利用菜单显示通讯录里面的功能,也就是上边我们所展示的。这一步的实现比较简单,就是写一个menu函数,在进入通讯录的时候显示到屏幕上

swift 复制代码
void menu()
{
	printf("\n************************\n");
	printf("****  1.增加联系人  ****\n");
	printf("****  2.删除联系人  ****\n");
	printf("****  3.查找联系人  ****\n");
	printf("*** 4.更改联系人信息 ***\n");
	printf("****  5.排序联系人  ****\n");
	printf("****  6.显示联系人  ****\n");
	printf("****  0.退出通讯录  ****\n");
	printf("************************\n\n");

}

3. 定义和初始化通讯录变量

1.在前面我们已经定义好了通讯录的结构体,我们在主函数main里边定义一通讯录变量。然后再写一个函数用来初始化通讯录。我们要更改通讯录里面的内容,所以要传结构体的地址过去。

ini 复制代码
Contact con;
InitContact(&con);
  1. 初始化通讯录函数:
ini 复制代码
void InitContact(Contact* pc)
{
	//开辟10个存放联系人信息的空间
	PeoInfo* tmp = (PeoInfo*)malloc(sizeof(PeoInfo) * 10);
	if (tmp == NULL)
	{
		perror("InitContact->malloc failed");
		exit(-1);
	}
	pc->data = tmp;
	pc->capacity = 10;
	pc->sz = 0;
}

4. 检查通讯录容量函数

1.在增加联系人之前,要检查通讯录的容量。如果通讯录的容量与通讯联系人个数相等即pc->capacity==pc->sz,执行增容代码。利用realloc函数增加容量到原来的二倍,然后更新通讯录容量

ini 复制代码
void CheckContact(Contact* pc)
{
	if (pc->capacity == pc->sz)
	{
		PeoInfo* tmp = (PeoInfo*)realloc(pc->data, sizeof(PeoInfo) * pc->capacity * 2);
		if (tmp == NULL)
		{
			perror("CheckContact->mallco failed");
			exit(-1);
		}
		pc->data = tmp;
		pc->capacity *= 2;
	}
}

5. 增加联系人

写一个增加联系人的函数,对联系人的信息逐个进行scanf录入

scss 复制代码
void AddContact(Contact* pc)
{
	CheckContact(pc);
	printf("请依次输入联系人信息:\n");
	printf("姓名:>");
	scanf("%s", pc->data[pc->sz].name);
	printf("性别:>");
	scanf("%s", pc->data[pc->sz].sex);
	printf("年龄:>");
	scanf("%d", &(pc->data[pc->sz].age));
	printf("电话:>");
	scanf("%s", pc->data[pc->sz].tel);
	printf("地址:>");
	scanf("%s", pc->data[pc->sz].addr);
	printf("添加成功!\n");
	pc->sz++;
}

6. 显示通讯录信息

把联系人的信息打印到屏幕上。逻辑很简单,遍历打印,在打印的时候为了美观,要先排好各个信息的左右间距

ini 复制代码
void PrintContact(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空!\n");
        return;
	}
	printf("  %-6s%-2s  %-2s     %-12s %-6s\n", "姓名", "性别", "年龄", "电话", "地址");
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		printf(" %-6s  %-2s    %-2d   %-12s    %-6s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age, pc->data[i].tel, pc->data[i].addr);
	}
}

7. 通过名字搜索联系人

这个是通讯录的一个功能,但是另外的删除、更改也都需要用到一个搜索名字查找联系人的功能,所以在这里为了实现这个功能,我们单独写一个函数出来,如果找到搜索的联系人返回下标,否则返回-1.

ini 复制代码
//找到返回下标,否则返回-1
int SearchContact(Contact* pc, char*name)
{
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (strcmp(pc->data[i].name, name) == 0)
		{
			return i;
		}
	}
	return -1;
}

8. 删除联系人

要删除,就得先搜索到这个联系人,所以直接调用前面的利用名字搜索联系人的函数,定义ret接收返回值,如果为-1,则查无此人,否则就是对应的下标,把下标后面的联系人数据统一往前移,覆盖掉要删除的联系人,最后把通讯录的联系人个数减1

ini 复制代码
void DeleteContact(Contact* pc)
{
    //判断通讯录是否为空
	if (pc->sz == 0)
	{
		printf("通讯录为空\n");
		return;
	}
	char name[NAME_MAX];
	printf("请输入删除联系人的姓名:>");
	scanf("%s", name);
	int ret = SearchContact(pc, name);
	if (ret != -1)
	{
		int i = 0;
		for (i = ret; i < pc->sz - 1; i++)
		{
			pc->data[i] = pc->data[i + 1];
		}
		pc->sz--;
		printf("删除成功!\n");
	}
	else
	{
		printf("查无此人!\n");
	}
}

9. 更改联系人信息

这个函数逻辑就是先搜索要更改的联系人的姓名,找到以后,再重新录入联系人的信息

scss 复制代码
void ChangeContact(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空\n");
		return;
	}
	char name[NAME_MAX];
	printf("请输入需要更改的联系人的姓名:>");
	scanf("%s", name);
	int ret = SearchContact(pc, name);
	if (ret != -1)
	{
		printf("  %-6s%-2s  %-2s     %-12s %-6s\n", "姓名", "性别", "年龄", "电话", "地址");
		int i = ret;
		printf(" %-6s  %-2s    %-2d   %-12s    %-6s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age, pc->data[i].tel, pc->data[i].addr);
		printf("请依次输入联系人信息:\n");
		printf("姓名:>");
		scanf("%s", pc->data[i].name);
		printf("性别:>");
		scanf("%s", pc->data[i].sex);
		printf("年龄:>");
		scanf("%d", &(pc->data[i].age));
		printf("电话:>");
		scanf("%s", pc->data[i].tel);
		printf("地址:>");
		scanf("%s", pc->data[i].addr);
		printf("修改成功!\n");
	}
	else
	{
		printf("查无此人!\n");
	}
}

10. 利用年龄排序通讯录

利用qsort函数排序整个联系人数组,为了使用qsort函数,我们要专门写一个按照联系人年龄排序的函数,把这个函数的地址传给qsort

c 复制代码
int cmp_by_age(const void* e1, const void* e2)
{
	return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}

void SortContactAge(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空\n");
		return;
	}
	qsort(pc->data, pc->sz, sizeof(PeoInfo), cmp_by_age);
	printf("按照年龄排序成功!\n");
}

11. 保存通讯录信息

利用通讯录,当然就是为了保存联系人信息方便查看,我们利用文件操作把动态内存里面的联系人信息保存到一个专门的文件中。

ini 复制代码
void SaveContact(Contact* pc)
{
	FILE* pf = fopen("contact.data", "w");
	if (pf == NULL)
	{
		perror("SaveContact->fopen failed");
		exit(-1);
	}
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fwrite(&(pc->data[i]), sizeof(PeoInfo), 1, pf);
	}
	fclose(pf);
	pc = NULL;
	printf("数据保存成功!\n");
}

12. 加载通讯录信息

在打开通讯录的时候,要读取本地存储的联系人数据,但是在读取数据的时候还要注意,当通讯录中的联系人数量等于通讯录容量的时候也要增容

ini 复制代码
void LoadContactData(Contact* pc)
{
	FILE* pf = fopen("contact.data", "r");
	if (pf == NULL)
	{
		perror("LoadContactData->fopen failed");
		exit(-1);
	}
	PeoInfo tmp;
	while (fread(&tmp, sizeof(tmp), 1, pf))
	{
		CheckContact(pc);
		pc->data[pc->sz] = tmp;
		pc->sz++;
	}
	fclose(pf);
	pc = NULL;
	printf("数据加载完毕!\n");
}

13. 销毁通讯录

在退出通讯录时候,为了防止内存泄漏,我们要释放掉动态开辟的内存,所以所以又专门写了一个销毁通讯录的函数

ini 复制代码
void DestroyContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->sz = 0;
}

五. 结果演示

六. 模块化代码实现

①main.c

测试通讯录逻辑

arduino 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"Contact.h"



void menu()
{
	printf("\n************************\n");
	printf("****  1.增加联系人  ****\n");
	printf("****  2.删除联系人  ****\n");
	printf("****  3.查找联系人  ****\n");
	printf("*** 4.更改联系人信息 ***\n");
	printf("****  5.排序联系人  ****\n");
	printf("****  6.显示联系人  ****\n");
	printf("****  0.退出通讯录  ****\n");
	printf("************************\n\n");

}
enum Option
{
	EXIT,
	ADD,
	DELETE, 
	FIND,
	CHANGE,
	SORT,
	SHOW,
};
int main()
{
	int input = 0;

	Contact con;
	InitContact(&con);
	LoadContactData(&con);
	do
	{
		menu();
		printf("请输入你的选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			AddContact(&con);
			break;
		case DELETE:
			DeleteContact(&con);
			break;
		case FIND:
			FindContactName(&con);
			break;
		case CHANGE:
			ChangeContact(&con);
			break;
		case SORT:
			SortContactAge(&con);
			break;
		case SHOW:
			PrintContact(&con);
			break;
		case EXIT:
			SaveContact(&con);
			DestroyContact(&con);
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	return 0;
}

②Contact.h

头文件的包含、函数的声明、结构体的定义和宏定义等

arduino 复制代码
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define NAME_MAX 10
#define SEX_MAX 6
#define TEL_MAX 14
#define ADDR_MAX 10


typedef struct PeoInfo
{
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;
	char tel[TEL_MAX];
	char addr[ADDR_MAX];
}PeoInfo;

typedef struct Contact
{
	PeoInfo* data;
	int sz;
	int capacity;
}Contact;


void CheckContact(Contact* pc);
void InitContact(Contact* pc);
void LoadContactData(Contact* pc);
void AddContact(Contact* pc);
void DeleteContact(Contact* pc);
void FindContactName(Contact* pc);
void ChangeContact(Contact* pc);
void SortContactAge(Contact* pc);
void PrintContact(Contact* pc);
void SaveContact(Contact* pc);
void DestroyContact(Contact* pc);

③Contact.c

相关函数的实现

ini 复制代码
#include"Contact.h"


void InitContact(Contact* pc)
{
	//开辟10个存放联系人信息的空间
	PeoInfo* tmp = (PeoInfo*)malloc(sizeof(PeoInfo) * 10);
	if (tmp == NULL)
	{
		perror("InitContact->malloc failed");
		exit(-1);
	}
	pc->data = tmp;
	pc->capacity = 10;
	pc->sz = 0;
}

void CheckContact(Contact* pc)
{
	if (pc->capacity == pc->sz)
	{
		PeoInfo* tmp = (PeoInfo*)realloc(pc->data, sizeof(PeoInfo) * pc->capacity * 2);
		if (tmp == NULL)
		{
			perror("CheckContact->mallco failed");
			exit(-1);
		}
		pc->data = tmp;
		pc->capacity *= 2;
	}
}

void AddContact(Contact* pc)
{
	CheckContact(pc);
	printf("请依次输入联系人信息:\n");
	printf("姓名:>");
	scanf("%s", pc->data[pc->sz].name);
	printf("性别:>");
	scanf("%s", pc->data[pc->sz].sex);
	printf("年龄:>");
	scanf("%d", &(pc->data[pc->sz].age));
	printf("电话:>");
	scanf("%s", pc->data[pc->sz].tel);
	printf("地址:>");
	scanf("%s", pc->data[pc->sz].addr);
	printf("添加成功!\n");
	pc->sz++;
}

void PrintContact(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空!\n");
		return;
	}
	printf("  %-6s%-2s  %-2s     %-12s %-6s\n", "姓名", "性别", "年龄", "电话", "地址");
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		printf(" %-6s  %-2s    %-2d   %-12s    %-6s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age, pc->data[i].tel, pc->data[i].addr);
	}
}

void LoadContactData(Contact* pc)
{
	FILE* pf = fopen("contact.data", "r");
	if (pf == NULL)
	{
		perror("LoadContactData->fopen failed");
		exit(-1);
	}
	PeoInfo tmp;
	while (fread(&tmp, sizeof(tmp), 1, pf))
	{
		CheckContact(pc);
		pc->data[pc->sz] = tmp;
		pc->sz++;
	}
	fclose(pf);
	pc = NULL;
	printf("数据加载完毕!\n");
}


//找到返回下标,否则返回-1
int SearchContact(Contact* pc, char*name)
{
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (strcmp(pc->data[i].name, name) == 0)
		{
			return i;
		}
	}
	return -1;
}


void DeleteContact(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空\n");
		return;
	}
	char name[NAME_MAX];
	printf("请输入删除联系人的姓名:>");
	scanf("%s", name);
	int ret = SearchContact(pc, name);
	if (ret != -1)
	{
		int i = 0;
		for (i = ret; i < pc->sz - 1; i++)
		{
			pc->data[i] = pc->data[i + 1];
		}
		pc->sz--;
		printf("删除成功!\n");
	}
	else
	{
		printf("查无此人!\n");
	}
}


void FindContactName(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空\n");
		return;
	}
	char name[NAME_MAX];
	printf("请输入需要搜索联系人的姓名:>");
	scanf("%s", name);
	int ret = SearchContact(pc, name);
	if (ret != -1)
	{
		printf("  %-6s%-2s  %-2s     %-12s %-6s\n", "姓名", "性别", "年龄", "电话", "地址");
		int i = ret;
		printf(" %-6s  %-2s    %-2d   %-12s    %-6s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age, pc->data[i].tel, pc->data[i].addr);
	}
	else
	{
		printf("查无此人!\n");
	}
}


void ChangeContact(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空\n");
		return;
	}
	char name[NAME_MAX];
	printf("请输入需要更改的联系人的姓名:>");
	scanf("%s", name);
	int ret = SearchContact(pc, name);
	if (ret != -1)
	{
		printf("  %-6s%-2s  %-2s     %-12s %-6s\n", "姓名", "性别", "年龄", "电话", "地址");
		int i = ret;
		printf(" %-6s  %-2s    %-2d   %-12s    %-6s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age, pc->data[i].tel, pc->data[i].addr);
		printf("请依次输入联系人信息:\n");
		printf("姓名:>");
		scanf("%s", pc->data[i].name);
		printf("性别:>");
		scanf("%s", pc->data[i].sex);
		printf("年龄:>");
		scanf("%d", &(pc->data[i].age));
		printf("电话:>");
		scanf("%s", pc->data[i].tel);
		printf("地址:>");
		scanf("%s", pc->data[i].addr);
		printf("修改成功!\n");
	}
	else
	{
		printf("查无此人!\n");
	}
}

int cmp_by_age(const void* e1, const void* e2)
{
	return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}

void SortContactAge(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空\n");
		return;
	}
	qsort(pc->data, pc->sz, sizeof(PeoInfo), cmp_by_age);
	printf("按照年龄排序成功!\n");
}

void SaveContact(Contact* pc)
{
	FILE* pf = fopen("contact.data", "w");
	if (pf == NULL)
	{
		perror("SaveContact->fopen failed");
		exit(-1);
	}
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fwrite(&(pc->data[i]), sizeof(PeoInfo), 1, pf);
	}
	fclose(pf);
	pc = NULL;
	printf("数据保存成功!\n");
}

void DestroyContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->sz = 0;
}

感谢你能够看到这里!希望这篇文章对你有所帮助,如果不对的地方还望指正哦ovo~

相关推荐
求知若饥10 分钟前
NestJS 项目实战-权限管理系统开发(六)
后端·node.js·nestjs
gb42152871 小时前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶1 小时前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
颜淡慕潇1 小时前
【K8S问题系列 |19 】如何解决 Pod 无法挂载 PVC问题
后端·云原生·容器·kubernetes
向前看-9 小时前
验证码机制
前端·后端
超爱吃士力架10 小时前
邀请逻辑
java·linux·后端
AskHarries12 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
isolusion13 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp14 小时前
Spring-AOP
java·后端·spring·spring-aop