C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C语言通讯录

基于C语言打造高效通讯录的示例代码

作者:努力学习游泳的鱼

本文主要介绍了如何使用C语言实现一个通讯录。实现通讯录的过程中,会大量用到C语言的知识点,包括但不限于:函数、自定义类型、指针、动态内存管理、文件操作,感兴趣的可以了解一下

本篇博客会讲解如何使用C语言实现一个通讯录。实现通讯录的过程中,会大量用到C语言的知识点,包括但不限于:函数、自定义类型、指针、动态内存管理、文件操作,这些知识点在我的其他博客中都有讲解过,欢迎大家阅读,这里就不进行系统的复习了。

先来梳理下需求:

1.通讯录能够存储的联系人的信息有:姓名、年龄、性别、电话、住址。

2.这个通讯录不能是“静态的”,而应该是“动态的”,也就是说,需要用到动态内存管理的知识。这是因为,静态的通讯录的容量是固定的,空间太大可能浪费,太小了又不够存。

3.由于当程序开始运行后,通讯录的数据是存储在内存中的,一旦程序运行结束,执行完main函数的return 0;后,空间就被操作系统回收了,相当于数据就丢了。为了能够实现“永久保存”的效果,我们要在程序退出前,把数据保存到文件中,这又涉及到文件操作的相关知识点。

4.类似顺序表这种数据结构的基本操作,通讯录要能做到:增删查改+排序+打印,即增加联系人、删除联系人、查找联系人、修改联系人、排序联系人、打印联系人等等。

下面我们开始吧!

准备工作

以下是菜单里的一些选项,声明成枚举类型是比较合适的。

// 菜单里的不同选项
enum Option
{
    EXIT,   // 退出
    ADD,    // 增加联系人
    DEL,    // 删除联系人
    SEARCH, // 查找联系人
    MODIFY, // 修改联系人
    SHOW,   // 显示联系人
    SORT    // 排序
};

由于联系人的姓名、性别、电话和住址都是字符串,要存储在字符数组中,最好先声明它们的容量。

// 各信息的存储容量
#define MAX_NAME 20 // 名字
#define MAX_SEX 5   // 性别
#define MAX_TELE 12 // 电话
#define MAX_ADDR 30 // 住址

我们后面在进行动态内存管理时,需要知道初始的容量和每次扩容的容量,也声明一下:

// 动态内存默认存储的数据
#define DEFAULT_SZ 3
// 若不够存,每次扩容的数量
#define INC_SZ 2

再声明一个结构体,表示一个人的信息,包括姓名、年龄、性别、电话、住址。

// 表示一个人的信息
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; // data指向了存放数据的空间
    int sz;        // 记录通讯录中的有效信息个数
    int capacity;  // 通讯录当前的容量
}Contact;

下面我们开始实现程序的主体逻辑。先从主函数写起,把主要的功能都封装成函数:

// 打印菜单
void menu()
{
	printf("************************************\n");
	printf("*****    1. add     2. del       ***\n");
	printf("*****    3. search  4. modify    ***\n");
	printf("*****    5. show    6. sort      ***\n");
	printf("*****    0. exit                 ***\n");
	printf("************************************\n");
}
int main()
{
	int input = 0; // 存储用户输入的数据
	Contact con; // 通讯录
	// 初始化通讯录
	// 加载文件的信息到通讯录中
	InitContact(&con);
	do
	{
		menu(); // 菜单
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case ADD: // 添加联系人
			AddContact(&con);
			break;
		case DEL: // 删除联系人
			DelContact(&con);
			break;
		case SEARCH: // 查找指定联系人
			SearchContact(&con);
			break;
		case MODIFY: // 修改指定联系人的信息
			ModifyContact(&con);
			break;
		case SHOW: // 展示联系人信息
			ShowContact(&con);
			break;
		case SORT: // 排序
			SortContact(&con);
			break;
		case EXIT: // 退出通讯录
			// 保存通讯录到文件中
			SaveContact(&con);
			// 销毁通讯录
			DestroyConact(&con);
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

初始化通讯录

先定义一个函数InitContact,它的作用是初始化通讯录。函数的参数是一个指向Contact结构体的指针。函数的具体实现如下:

void InitContact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);
	// 先开辟默认的容量
	pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
	// 检查开辟空间是否成功
	if (pc->data == NULL)
	{
		// 开辟空间失败
		printf("通讯录初始化失败:%s\n", strerror(errno));
		return;
	}
	// 开辟空间成功
	pc->sz = 0;
	pc->capacity = DEFAULT_SZ;
	//加载文件的信息到通讯录
	LoadContact(pc);
}

从文件中加载信息

再定义一个函数LoadContact,用于从文件中读取联系人信息并存储到内存中。函数的参数是一个指向Contact结构体的指针,表示要将读取到的联系人信息存储到哪个数据结构中。函数的具体实现如下:

void LoadContact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);
	// 打开文件
	FILE* pf = fopen("contact.dat", "rb");
	// 检查打开文件是否成功
	if (pf == NULL)
	{
		// 打开文件失败,可能是第一次运行通讯录,并没有数据文件
		//perror("LoadContact::fopen");
		return;
	}
	// 读文件
	PeoInfo tmp = { 0 }; // 存储读取到的数据
	// 每次读一个数据
	while (fread(&tmp, sizeof(PeoInfo), 1, pf))
	{
		// 检查容量,不够的话要扩容
		CheckCapacity(pc);
		// 存储从文件读取到的数据
		pc->data[pc->sz] = tmp;
		pc->sz++;
	}
	// 关闭文件
	fclose(pf);
	pf = NULL;
}

检查容量

再定义一个函数CheckCapacity,用于检查并扩容动态数组。函数的参数是一个指向Contact结构体的指针。函数的具体实现如下:

// 扩容失败,返回0
// 扩容成功,不需要扩容,返回1
static int CheckCapacity(Contact* pc)
{
	// 检查指针有效性
	assert(pc);
	// 检查是否需要扩容
	if (pc->sz == pc->capacity)
	{
		// 需要扩容
		PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
		// 检查是否扩容成功
		if (ptr == NULL)
		{
			// 扩容失败
			printf("CheckCapacity:%s\n", strerror(errno));
			return 0;
		}
		else
		{
			// 扩容成功
			// 更新数组的起始位置
			pc->data = ptr;
			// 更新容量
			pc->capacity += INC_SZ;
			printf("增容成功,当前容量:%d\n", pc->capacity);
		}
	}
	return 1;
}

销毁通讯录

再定义一个函数DestroyContact,用来销毁通讯录。函数的参数是一个指向Contact结构体的指针。函数的具体实现如下:

void DestroyConact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);
	// 释放内存空间
	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->sz = 0;
	printf("释放内存.....\n");
}

添加联系人

接着定义一个函数AddContact,参数仍然是一个指向Contact结构体的指针,实现向通讯录中添加联系人的功能。具体实现如下:

void AddContact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);
	// 扩容,同时检查是否成功
	if (0 == CheckCapacity(pc))
	{
		// 扩容失败
		printf("空间不够,扩容失败\n");
		return;
	}
	else
	{
		// 输入联系人信息
		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");
	}
}

打印数据

接下来定义一个函数 ShowContact,用于打印联系人信息。函数接受一个指向Contact结构体的指针pc。具体实现如下:

void ShowContact(const Contact* pc)
{
	// 检查指针有效性
	assert(pc);
	// 打印效果
	// 姓名      年龄      性别     电话      地址
	// zhangsan 20        男      123456    北京
	//
	// 打印标题
	printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	// 打印数据
	for (int i = 0; i < pc->sz; ++i)
	{
		printf("%-10s %-4d %-5s %-12s %-30s\n",
			pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
	}
}

删除联系人

接下来实现通讯录的删除联系人功能。具体实现如下:

FindByName函数:根据指定名字,在通讯录中查找联系人信息。函数参数为指向Contact结构体的指针和要查找的名字。函数返回值为查找到的联系人在通讯录中的下标,如果没找到则返回-1。

DelContact函数:删除通讯录中指定联系人。函数参数为指向Contact结构体的指针。函数实现如下:

// 根据指定名字,查找联系人信息
static int FindByName(const Contact* pc, char name[])
{
	// 检查指针有效性
	assert(pc);
	assert(name);
	// 遍历数组
	for (int i = 0; i < pc->sz; ++i)
	{
		// 检查名字是否匹配
		if (0 == strcmp(pc->data[i].name, name))
		{
			// 找到了
			return i;
		}
	}
	// 没找到
	return -1;
}
void DelContact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);
	// 检查是否还有数据
	if (pc->sz == 0)
	{
		// 通讯录已空
		printf("通讯录为空,无法删除\n");
		return;
	}
	char name[MAX_NAME] = { 0 }; // 存储用户输入的数据
	// 用户输入信息
	printf("输入要删除人的名字:>");
	scanf("%s", name);
	// 找到要删除的人的下标
	int pos = FindByName(pc, name);
	// 检查是否找到
	if (pos == -1)
	{
		// 没找到
		printf("要删除的人不存在\n");
		return;
	}
	// 删除pos位置上的数据
	// 挪动pos后面的数据,覆盖pos
	memmove(pc->data + pos, pc->data + pos + 1, sizeof(PeoInfo) * (pc->sz - pos - 1));
	// 更新有效数据个数
	pc->sz--;
	printf("删除成功\n");
}

查找联系人

接着实现在通讯录中根据姓名查找联系人的功能。具体实现如下:

void SearchContact(const Contact* pc)
{
	// 检查指针有效性
	assert(pc);
	char name[MAX_NAME] = { 0 }; // 存储用户输入的信息
	// 用户输入数据
	printf("请输入要查找人的名字:>");
	scanf("%s", name);
	// 查找
	int pos = FindByName(pc, name);
	// 检查是否找到
	if (pos == -1)
	{
		// 没找到
		printf("要查找的人不存在\n");
		return;
	}
	// 打印标题行
	printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	// 打印数据
	printf("%-10s %-4d %-5s %-12s %-30s\n",
		pc->data[pos].name,
		pc->data[pos].age,
		pc->data[pos].sex,
		pc->data[pos].tele,
		pc->data[pos].addr);
}

修改联系人

再下来实现一个函数,函数的参数仍然是一个指向通讯录结构体类型Contact的指针,用于修改通讯录中的联系人信息。具体实现如下:

void ModifyContact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);
	char name[MAX_NAME] = { 0 }; // 存储用户输入的信息
	// 用户输入数据
	printf("请输入要修改人的名字:>");
	scanf("%s", name);
	// 查找
	int pos = FindByName(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 CmpByName(const void* p1, const void* p2)
{
	// 检查指针有效性
	assert(p1 && p2);
	// 比较名字字符串大小
	return strcmp(((PeoInfo*)p1)->name, ((PeoInfo*)p2)->name);
}
void SortContact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);
	// 根据名字来排序
	qsort(pc->data, pc->sz, sizeof(PeoInfo), CmpByName);
	printf("排序成功\n");
}

保存通讯录

最后实现将联系人信息保存到文件中的功能。具体实现如下:

void SaveContact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);
	// 打开文件
	FILE* pf = fopen("contact.dat", "wb");
	// 检查是否打开成功
	if (pf == NULL)
	{
		// 打开文件失败
		perror("SaveContact::fopen");
		return;
	}
	// 写数据,一次写一个
	for (int i = 0; i < pc->sz; ++i)
	{
		fwrite(pc->data + i, sizeof(struct PeoInfo), 1, pf);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	printf("保存成功...\n");
}

总结

以上就是基于C语言打造高效通讯录的示例代码的详细内容,更多关于C语言通讯录的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文