带你轻松实现通讯录(C语言版)

带你轻松实现通讯录(C语言版)_第1张图片


  •   ‍个人主页:简 料

  •   所属专栏:C语言

  •   个人社区:越努力越幸运社区

  •   简       介:简料简料,简单有料~在校大学生一枚,专注C/C++/GO的干货分享,立志成为您的好帮手 ~


C/C++学习路线 (点击解锁)(●’◡’●)
❤️C语言
❤️初阶数据结构与算法
❤️C++
❤️高阶数据结构
❤️Linux系统编程与网络编程

文章目录

  • 前言
  • 通讯录初始化
  • 通讯录运行的基本框架和菜单
  • 增添联系人
  • 删除联系人
  • 查找联系人
  • 修改联系人信息
  • 展示通讯录
  • 通讯录联系人个数
  • 排序通讯录
  • 文件操作储存通讯录信息
  • 销毁通讯录
  • 整体代码
    • Contacts.h
    • Contacts.c
    • test.c
  • 写在最后


前言

学习C语言的小伙伴,相信都要经历实现通讯录这一关吧,接下来就带你手把手实现自己的通讯录!


通讯录初始化

  • 整个程序我们需要分三个文件,一个是头文件:Contacts.h:用来存放宏,结构体以及函数声明,还有需要用的库函数。一个是Contacts.c:用来实现各个接口函数的功能,还有一个是test.c:用来布局功能的框架以及测试代码。

  • 首先我们需要两个结构体,一个表示联系人的信息,一个为通讯录的信息:

typedef struct PeoInfo
{
	char name[NAME_MAX];   // 名字
	int age;               // 年龄
	char sex[SEX_MAX];     // 性别
	char adds[ADDS_MAX];   // 地址
	char tele[TELE_MAX];   // 电话
}Info;

typedef struct contact
{
	Info* data;    // data 为Info指针
	int size;      // 存放联系人的个数
	int capacity;  // 存放联系人的空间容量
}Con;

上面类似于NAME_MAX的东西为宏定义,表示一个联系人的名字最多占多少空间,下面为所有的宏定义:

#define NAME_MAX 20  // 名字的最大长度
#define SEX_MAX 5  // 性别的最大长度
#define ADDS_MAX 20  // 地址的最大长度 
#define TELE_MAX 12  // 电话号码的最大长度
#define NEW_SIZE 4 // 每次扩容增加的容量
  • 对于初始化由于需要插入数据,牵扯到一个扩容的问题,所以我们先开几个空间,到时候满了再动态扩容,开空间使用malloccalloc都可以,区别只在于一个没初始化,一个全部初始化0

  • 并且初始的size要为0,而capacity则为初始开辟的空间的大小INIT_CAPACITY,具体初始化代码如下:

// 初始化
void ConInit(Con* pc)
{
	// 用calloc开辟空间,空间里的数据初始化为0
	// 先开辟 NEW_SIZE 个 , 后面会使用realloc动态增容
	Info* tmp = (Info*)calloc(NEW_SIZE, sizeof(Info));
	if (tmp == NULL)
	{
		perror("calloc fail");
		exit(-1);
	}

	pc->data = tmp;
	pc->size = 0;
	pc->capacity = NEW_SIZE;

	// 拿之前保存的数据
	GetConData(pc);
}

当然GetConData(pc)这一步可以先不看,所以这整个代码就是对通讯录的一个初始化过程。

  • 宏定义,与结构体都是定义再在Contacts.h头文件当中,除此之外,还有各个函数的接口的声明,所以整个头文件的代码段如下:
#define NAME_MAX 20  // 名字的最大长度
#define SEX_MAX 5  // 性别的最大长度
#define ADDS_MAX 20  // 地址的最大长度 
#define TELE_MAX 12  // 电话号码的最大长度
#define NEW_SIZE 4 // 每次扩容增加的容量

#include 
#include 
#include 
#include 

typedef struct PeoInfo
{
	char name[NAME_MAX];   // 名字
	int age;               // 年龄
	char sex[SEX_MAX];     // 性别
	char adds[ADDS_MAX];   // 地址
	char tele[TELE_MAX];   // 电话
}Info;

typedef struct contact
{
	Info* data;    
	int size;      // 存放联系人的个数
	int capacity;  // 存放联系人的空间容量
}Con;

// 初始化
void ConInit(Con* pc);
// 初始化但是不拿之前的数据,销毁当中用
void ConInitNoInfo(Con* pc);

// 展示通讯录
void ConShow(Con* pc);

// 添加联系人
void ConAdd(Con* pc);

// 删除联系人
void ConDel(Con* pc);

// 查找联系人
void ConFind(Con* pc);

// 修改联系人的信息
void ConModify(Con* pc);

// 此时通讯录里联系人的个数
int ConSize(Con* pc);

// 销毁通讯录
void ConDestory(Con* pc);

// 排序
void ConSort(Con* pc);

// 保存通讯录
void SaveContact(Con* pc);

// 拿之前保存的通讯录数据
void GetConData(Con* pc);

通讯录运行的基本框架和菜单

  • 有了头文件的接口,这里我们设计一个菜单,以便于我们在进行操作的时候以输入数字的方式就可以完成操作,这样很是方便。

  • 首先,我们可以先通过枚举 (提高代码可读性) 来规定每一个接口运行的代号数字,整个定义如下:

enum select
{
	EXIT, // 0 退出
	ADD, // 1 增加
	DEL, // 2 删除
	FIND, // 3 查找
	MODIFY, // 4 修改
	SORT, // 5 排序
	SHOW, // 6 展示通讯录
	CLEAR, // 7 清屏
	CONSIZE, // 8 通讯录联系人个数
	DESTORY // 9 销毁通讯录(要释放)
};

根据此枚举的名字以及对应的数字,我们可以设计这样的一个菜单:

void menu()
{
	printf("***********************************************\n");
	printf("**********      1.add     2.del      **********\n");
	printf("**********      3.find    4.modify   **********\n");
	printf("**********      5.sort    6.show     **********\n");
	printf("**********      7.clear   8.ConSize  **********\n");
	printf("**********      9.destory 0.exit     **********\n");
	printf("***********************************************\n");
}

带你轻松实现通讯录(C语言版)_第2张图片

  • 然后在main函数中使用do while循环来控制整个程序的运行,在do while 循环里使用switch case分支语句来控制接口的选项,这样整个框架就差不多了。下面是test.c文件对整个程序框架建立的代码:
#include "Contacts.h"

void menu()
{
	printf("***********************************************\n");
	printf("**********      1.add     2.del      **********\n");
	printf("**********      3.find    4.modify   **********\n");
	printf("**********      5.sort    6.show     **********\n");
	printf("**********      7.clear   8.ConSize  **********\n");
	printf("**********      9.destory 0.exit     **********\n");
	printf("***********************************************\n");
}

enum select
{
	EXIT, // 0 退出
	ADD, // 1 增加
	DEL, // 2 删除
	FIND, // 3 查找
	MODIFY, // 4 修改
	SORT, // 5 排序
	SHOW, // 6 展示通讯录
	CLEAR, // 7 清屏
	CONSIZE, // 8 通讯录联系人个数
	DESTORY // 9 销毁通讯录(要释放)
};
	
int main()
{
	int input = 0;
	Con con;
	ConInit(&con);

	do
	{
	    // 每次根据菜单进行选择
		menu();
		printf("请选择:");
		scanf("%d", &input);

		switch (input)
		{
		case EXIT: // 0
			SaveContact(&con);
			printf("退出程序>\n");
			break;
		case ADD:  // 1
			ConAdd(&con);
			break;
		case DEL:  // 2
			ConDel(&con);
			break;
		case FIND:  // 3
			ConFind(&con);
			break;
		case MODIFY: // 4
			ConModify(&con);
			break;
		case SORT: // 5
			ConSort(&con);
			break;
		case SHOW: // 6
			ConShow(&con);
			break;
		case CLEAR: // 7
			system("cls");
			break;
		case CONSIZE:  // 8
			printf("现在通讯录里联系人的个数为:%d\n", ConSize(&con));
			break;
		case DESTORY: // 9
			ConDestory(&con);

			int n = 0;
			printf("是否需要重新初始化通讯录?\n1.YES : 0.NO >>> ");
			scanf("%d", &n);

			if (n)
			{
				ConInit(&con);
				printf("重新初始化成功>\n");
			}
			else
			{
				input = 0;
				printf("通讯录进程关闭,退出程序>>>>>> \n");
			}

			break;
		default:
			printf("选择错误,请重新选择>\n");
			break;
		}
	} while (input);

	return 0;
}

有了这样的框架来控制整个程序的运行,接下来,就是对每一个接口的功能的实现了。


增添联系人

  • 有了前面的铺垫,第一步当然就是添加联系人了。

  • 添加之前还要进行的操作是看是否需要扩容,因为如果空间添加满了,在添加就会出现越界非法访问的问题了,因此这里要写个扩容函数,使用的是realloc,判断条件是如果联系人个数(size)等于容量(capacity),就增加容量。

  • 添加联系人是在末尾添加,并且要依次输入该联系人的各个信息。

添加联系人接口代码如下:

// 添加联系人
void ConAdd(Con* pc)
{
	assert(pc);

	// 需判断容量够不够用,不够则需要扩容
	Jud_Exp(pc);

	printf("请输入联系人的姓名:");
	scanf("%s", pc->data[pc->size].name);
	printf("请输入联系人的年龄:");
	scanf("%d", &pc->data[pc->size].age);
	printf("请输入联系人的性别:");
	scanf("%s", pc->data[pc->size].sex);
	printf("请输入联系人的地址:");
	scanf("%s", pc->data[pc->size].adds);
	printf("请输入联系人的电话:");
	scanf("%s", pc->data[pc->size].tele);

	pc->size++;
	printf("添加成功>\n");
}

删除联系人

  • 有添加就有删除,删除当然是要指定删除哪位联系人,因此需要输入要删除的联系人的名字,然后再根据名字在通讯录里面找到该联系人,将他删除。

  • 删除的方式是通过挪动数组来实现的,以覆盖的形式,依次将后一个联系人数据往前挪动一位(很容易发现,这里的效率不是很高),起到删除的效果,当然最后size要减一表示联系人少了一位 。

  • 在删除之前也要考虑通讯录里面是否有数据的情况,如果通讯录是空的,那也就没有删的必要了,这里采用assert断言直接暴力毒打。

  • 如果要删除的联系人在通讯录里面没有找到与之对应的,这时就打印删除失败,程序继续运行。

首先是输入要删除的联系人的名字,通过这个名字查找该联系人是否在通讯录里面存在,如果存在,则执行删除操作,不存在则打印删除失败程序继续运行。由于删除,查找,修改这些接口都要用到查找联系人这个函数,因此这里将这个函数单独抽离出来,实现如下:

// 查找名字
// 删除,修改,查找都要用到此功能,因此单独抽离出来实现一个函数
int Find_Name(Con* pc, char* name)
{
	assert(name);

	for (int i = 0; i < pc->size; i++)
	{
		if (!strcmp(name, pc->data[i].name))
			return i;  // 返回找到的名字的下标
	}

	// 没有找到则返回-1
	return -1;
}

那么整个删除联系人的接口的实现就很清楚了:

// 删除联系人
void ConDel(Con* pc)
{
	assert(pc && pc->size > 0);

	char name[NAME_MAX];
	printf("请输入要删除的联系人信息的名字> ");
	scanf("%s", name);

	int pos = Find_Name(pc, name);
	if (pos != -1)
	{
		for (int i = pos; i < pc->size - 1; i++)
		{
			pc->data[i] = pc->data[i + 1];
		}

		pc->size--;
		printf("删除成功>\n");
	}
	else
	{
		printf("要删除的联系人不存在>\n");
	}
}

查找联系人

  • 查找联系人需要输入你要查找的联系人的名字,前面已经将查找的函数写了,这里只需要通过查找函数的返回值来判断是否找到即可,找到了就将其打印,没有则打印没找到。

函数接口实现:

// 查找联系人
void ConFind(Con* pc)
{
	assert(pc && pc->size > 0);

	char name[NAME_MAX];
	printf("请输入想要查找联系人的名字> ");
	scanf("%s", name);

	int pos = Find_Name(pc, name);

	if (pos != -1)
	{
		printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[pos].name,
			pc->data[pos].age,
			pc->data[pos].sex,
			pc->data[pos].adds,
			pc->data[pos].tele);
	}
	else
	{
		printf("没有找到此联系人>\n");
	}
}

修改联系人信息

  • 有了前面的铺垫,要修改,那还不简单,直接输入要修改的联系人的名字,然后重新输入一遍到这个位置,就ok啦。直接上代码,相信大家一看就懂。

函数接口实现:

// 修改联系人的信息
void ConModify(Con* pc)
{
	assert(pc && pc->size > 0);

	char name[NAME_MAX];
	printf("请输入要修改的联系人的信息的名字> ");
	scanf("%s", name);

	// 查找要修改的联系人所在通讯录里的位置
	int pos = Find_Name(pc, name);

	if (pos != -1)
	{
		printf("请修改>\n");

		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].adds);
		printf("请输入联系人的电话:");
		scanf("%s", pc->data[pos].tele);

		printf("修改成功>\n");
	}
	else
	{
		printf("要修改信息的联系人不存在>\n");
	}
}

展示通讯录

  • 展示通讯录就不用多说了吧,直接上代码!

    函数接口实现:

// 展示通讯录
void ConShow(Con* pc)
{
	assert(pc);

	if (!pc->size)
	{
		printf("通讯录为空>\n");
		return;
	}
	else
	{
		printf("%-20s\t%-4s\t%-5s\t%-20s\t%-12s\n", "名字", "年龄", "性别", "地址", "电话");
		for (int i = 0; i < pc->size; i++)
		{
			printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[i].name,
				pc->data[i].age,
				pc->data[i].sex,
				pc->data[i].adds,
				pc->data[i].tele);
		}
	}
}

通讯录联系人个数

直接上代码!

函数接口实现:

// 此时通讯录里联系人的个数
int ConSize(Con* pc)
{
	assert(pc);

	return pc->size;
}

排序通讯录

  • 排序通讯录这里稍微麻烦一些,我们可以直接用库函数里的qsort来进行排序,这时我们只需要按自己的需求,写出对应的cmp函数即可。例如按年龄排序,年龄排序又分两种,升序和降序。或者按名字排序,通过比较ASCLL码值来进行排序,也分为升序和降序,不过要注意的是,名字是字符串,需要使用strcmp函数来进行比较。

  • 这里我们自己实现一个排序接口来对通讯录进行排序,底层为冒泡排序模板(哈哈哈哈效率低了)。由于是对一个个结构体进行排序,所以在排序的时候,交换数据需要一个字节一个字节的交换。

下面是自我实现的排序代码:

// 交换的时候一个字节一个字节的换,因为不知道排序的是什么类型的数据
void swap(char* buf1, char* buf2, size_t width)
{
	assert(buf1 && buf2);

	while (width--)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

// 排序, 以冒泡排序为框架
void Bubble_Sort(void* bese, size_t num, size_t width, int (*cmp)(const void*, const void*))
{
	assert(bese);

	for (size_t i = 0; i < num - 1; ++i)
	{
		for (size_t j = 0; j < num - 1 - i; ++j)
		{
			if (cmp((char*)bese + j * width, (char*)bese + (j + 1) * width) > 0)
			{
				swap((char*)bese + j * width, (char*)bese + (j + 1) * width, width);
			}
		}
	}
}
  • 有了这个排序,可以根据自己的需求来写cmp函数,若以名字和年龄排序,那么一共有四种情况,下面是对应四种情况的代码:
// 按名字从小到大排序
int cmp_con_name1(const void* a, const void* b)
{
	return strcmp(((Info*)a)->name, ((Info*)b)->name);
}
// 按名字从大到小排序
int cmp_con_name2(const void* a, const void* b)
{
	return strcmp(((Info*)b)->name, ((Info*)a)->name);
}

// 按年龄从小到大排序
int cmp_con_age1(const void* a, const void* b)
{
	return ((Info*)a)->age - ((Info*)b)->age;
}

// 按年龄从大到小排序
int cmp_con_age2(const void* a, const void* b)
{
	return ((Info*)b)->age - ((Info*)a)->age;
}
  • 排序代码ok后,接下来就是对排序的选择进行一个梳理与排版,以下是该该功能的接口函数的代码实现:
// 排序
void ConSort(Con* pc)
{
	assert(pc);

	printf("请选择何种方式排序:\n");
	printf("1.按名字排序:\n2.按年龄排序:\n");
	int n = 0;
	scanf("%d", &n);
	if (n != 1 && n != 2)
	{
		printf("选择错误,退出排序>\n");
		return;
	}

	if (n == 1)
	{
		int k = 0;
		printf("请选择如何排序:\n");
		printf("1.升序\n2.降序\n");
		scanf("%d", &k);

		if (k == 1)
		{
			Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_name1);
			printf("排序成功>\n");
		}
		else if (k == 2)
		{
			Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_name2);
			printf("排序成功>\n");
		}
		else
		{
			printf("选择错误>\n");
		}

	}
	else if (n == 2)
	{
		int k = 0;
		printf("请选择如何排序:\n");
		printf("1.升序\n2.降序\n");
		scanf("%d", &k);

		if (k == 1)
		{
			Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_age1);
			printf("排序成功>\n");
		}
		else if (k == 2)
		{
			Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_age2);
			printf("排序成功>\n");
		}
		else
		{
			printf("选择错误>\n");
		}
	}
}

这里测试一下:

一开始通讯录列表为:

带你轻松实现通讯录(C语言版)_第3张图片
我们按年龄从小到大排序为:
带你轻松实现通讯录(C语言版)_第4张图片
带你轻松实现通讯录(C语言版)_第5张图片
可以看到,此时的确排序成功!

整体的函数接口实现为:

// 按名字从小到大排序
int cmp_con_name1(const void* a, const void* b)
{
	return strcmp(((Info*)a)->name, ((Info*)b)->name);
}
// 按名字从大到小排序
int cmp_con_name2(const void* a, const void* b)
{
	return strcmp(((Info*)b)->name, ((Info*)a)->name);
}

// 按年龄从小到大排序
int cmp_con_age1(const void* a, const void* b)
{
	return ((Info*)a)->age - ((Info*)b)->age;
}

// 按年龄从大到小排序
int cmp_con_age2(const void* a, const void* b)
{
	return ((Info*)b)->age - ((Info*)a)->age;
}

// 交换的时候一个字节一个字节的换,因为不知道排序的是什么类型的数据
void swap(char* buf1, char* buf2, size_t width)
{
	assert(buf1 && buf2);

	while (width--)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

// 排序, 以冒泡排序为框架
void Bubble_Sort(void* bese, size_t num, size_t width, int (*cmp)(const void*, const void*))
{
	assert(bese);

	for (size_t i = 0; i < num - 1; ++i)
	{
		for (size_t j = 0; j < num - 1 - i; ++j)
		{
			if (cmp((char*)bese + j * width, (char*)bese + (j + 1) * width) > 0)
			{
				swap((char*)bese + j * width, (char*)bese + (j + 1) * width, width);
			}
		}
	}
}

// 排序
void ConSort(Con* pc)
{
	assert(pc);

	printf("请选择何种方式排序:\n");
	printf("1.按名字排序:\n2.按年龄排序:\n");
	int n = 0;
	scanf("%d", &n);
	if (n != 1 && n != 2)
	{
		printf("选择错误,退出排序>\n");
		return;
	}

	if (n == 1)
	{
		int k = 0;
		printf("请选择如何排序:\n");
		printf("1.升序\n2.降序\n");
		scanf("%d", &k);

		if (k == 1)
		{
			Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_name1);
			printf("排序成功>\n");
		}
		else if (k == 2)
		{
			Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_name2);
			printf("排序成功>\n");
		}
		else
		{
			printf("选择错误>\n");
		}

	}
	else if (n == 2)
	{
		int k = 0;
		printf("请选择如何排序:\n");
		printf("1.升序\n2.降序\n");
		scanf("%d", &k);

		if (k == 1)
		{
			Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_age1);
			printf("排序成功>\n");
		}
		else if (k == 2)
		{
			Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_age2);
			printf("排序成功>\n");
		}
		else
		{
			printf("选择错误>\n");
		}
	}
}

文件操作储存通讯录信息

  • 对文件操作不熟悉的同志可以先看下这篇博客:文件操作。

  • 文件操作就是将我们进行一系列操作最终定型的通讯录信息存放在文件当中,且下一次运行程序时,可以将信息从文件拿出来。

  • 一共有两个步骤:存和取 ,以二进制方式存,以二进制方式取。

  • 存的时候可以在退出程序的那最后一步自动存,也可以多设置一个选项随时存。

  • 取的时候在通讯录初始化的时候就自动去文件中寻找信息并取。

分别的函数接口实现为:

存:

// 保存通讯录
void SaveContact(Con* pc)
{
	assert(pc);

	// 将信息以二进制形式保存在Contacts.txt文本文档中
	FILE* pf = fopen("Contact.txt", "wb");
	if (pf == NULL)
	{
		perror("Save Con fail");
	}
	else
	{
		for (int i = 0; i < pc->size; i++)
		{
			fwrite(pc->data + i, sizeof(Info), 1, pf);
		}

		fclose(pf);
		pf = NULL;

		printf("保存通讯录数据成功>\n");
	}
}

取:

// 拿之前保存的通讯录数据
void GetConData(Con* pc)
{
	assert(pc);

	FILE* pf = fopen("Contact.txt", "rb");
	if (pf == NULL)
	{
		perror("Get data fail");
	}
	else
	{
		Info tmp = { 0 };

		int i = 0;
		while (fread(&tmp, sizeof(Info), 1, pf))
		{
			Jud_Exp(pc);        // 判断是否要扩容
			pc->data[i] = tmp;  // 每拿一个联系人的信息就放入通讯录
			pc->size++;         // 每get一个计数一次
			i++;                          
		}

		fclose(pf);  // get完后关闭文件
		pf = NULL;

		printf("获取之前的通讯录数据成功>\n");
	}
}

销毁通讯录

  • 销毁通讯录相当于是将通讯录里面的信息全部清空,当然我们在销毁过后也可以选择是否初始化通讯录,如果不选则退出程序。

  • 注意这里的初始化要单独写一个接口,实际上相当于复制一个,只不过说没有从文件中取数据的那一步(纯纯初始化)。

函数接口代码实现:

// 销毁通讯录
void ConDestory(Con* pc)
{
	assert(pc);

	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->size = 0;

	printf("销毁成功>\n");
}

整体代码

Contacts.h

#define NAME_MAX 20  // 名字的最大长度
#define SEX_MAX 5  // 性别的最大长度
#define ADDS_MAX 20  // 地址的最大长度 
#define TELE_MAX 12  // 电话号码的最大长度
#define NEW_SIZE 4 // 每次扩容增加的容量

#include 
#include 
#include 
#include 

typedef struct PeoInfo
{
	char name[NAME_MAX];   // 名字
	int age;               // 年龄
	char sex[SEX_MAX];     // 性别
	char adds[ADDS_MAX];   // 地址
	char tele[TELE_MAX];   // 电话
}Info;

typedef struct contact
{
	Info* data;    
	int size;      // 存放联系人的个数
	int capacity;  // 存放联系人的空间容量
}Con;

// 初始化
void ConInit(Con* pc);
// 初始化但是不拿之前的数据
void ConInitNoInfo(Con* pc);


// 展示通讯录
void ConShow(Con* pc);

// 添加联系人
void ConAdd(Con* pc);

// 删除联系人
void ConDel(Con* pc);

// 查找联系人
void ConFind(Con* pc);

// 修改联系人的信息
void ConModify(Con* pc);

// 此时通讯录里联系人的个数
int ConSize(Con* pc);

// 销毁通讯录
void ConDestory(Con* pc);

// 排序
void ConSort(Con* pc);

// 保存通讯录
void SaveContact(Con* pc);

// 拿之前保存的通讯录数据
void GetConData(Con* pc);

Contacts.c

#include "Contacts.h"

// 初始化
void ConInit(Con* pc)
{
	// 用calloc开辟空间,空间里的数据初始化为0
	// 先开辟 NEW_SIZE 个 , 后面会使用realloc动态增容
	Info* tmp = (Info*)calloc(NEW_SIZE, sizeof(Info));
	if (tmp == NULL)
	{
		perror("calloc fail");
		exit(-1);
	}

	pc->data = tmp;
	pc->size = 0;
	pc->capacity = NEW_SIZE;

	// 拿之前保存的数据
	GetConData(pc);
}

// 初始化但是不拿之前的数据
void ConInitNoInfo(Con* pc)
{
	// 用calloc开辟空间,空间里的数据初始化为0
	// 先开辟 NEW_SIZE 个 , 后面会使用realloc动态增容
	Info* tmp = (Info*)calloc(NEW_SIZE, sizeof(Info));
	if (tmp == NULL)
	{
		perror("calloc fail");
		exit(-1);
	}

	pc->data = tmp;
	pc->size = 0;
	pc->capacity = NEW_SIZE;
}

// 展示通讯录
void ConShow(Con* pc)
{
	assert(pc);

	if (!pc->size)
	{
		printf("通讯录为空>\n");
		return;
	}
	else
	{
		printf("%-20s\t%-4s\t%-5s\t%-20s\t%-12s\n", "名字", "年龄", "性别", "地址", "电话");
		for (int i = 0; i < pc->size; i++)
		{
			printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[i].name,
				pc->data[i].age,
				pc->data[i].sex,
				pc->data[i].adds,
				pc->data[i].tele);
		}
	}
}

// 判断是否要扩容
void Jud_Exp(Con* pc)
{
	if (pc->capacity == pc->size)
	{
		Info* tmp = realloc(pc->data, sizeof(Info) * (pc->capacity + NEW_SIZE));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}

		pc->data = tmp;
		pc->capacity = pc->capacity + NEW_SIZE;
		printf("增容成功>\n");
	}
}

// 添加联系人
void ConAdd(Con* pc)
{
	assert(pc);

	// 需判断容量够不够用,不够则需要扩容
	Jud_Exp(pc);

	printf("请输入联系人的姓名:");
	scanf("%s", pc->data[pc->size].name);
	printf("请输入联系人的年龄:");
	scanf("%d", &pc->data[pc->size].age);
	printf("请输入联系人的性别:");
	scanf("%s", pc->data[pc->size].sex);
	printf("请输入联系人的地址:");
	scanf("%s", pc->data[pc->size].adds);
	printf("请输入联系人的电话:");
	scanf("%s", pc->data[pc->size].tele);

	pc->size++;
	printf("添加成功>\n");
}

// 查找名字
// 删除,修改,查找都要用到此功能,因此单独抽离出来实现一个函数
int Find_Name(Con* pc, char* name)
{
	assert(name);

	for (int i = 0; i < pc->size; i++)
	{
		if (!strcmp(name, pc->data[i].name))
			return i;  // 返回找到的名字的下标
	}

	// 没有找到则返回-1
	return -1;
}

// 删除联系人
void ConDel(Con* pc)
{
	assert(pc && pc->size > 0);

	char name[NAME_MAX];
	printf("请输入要删除的联系人信息的名字> ");
	scanf("%s", name);

	int pos = Find_Name(pc, name);
	if (pos != -1)
	{
		for (int i = pos; i < pc->size - 1; i++)
		{
			pc->data[i] = pc->data[i + 1];
		}

		pc->size--;
		printf("删除成功>\n");
	}
	else
	{
		printf("要删除的联系人不存在>\n");
	}
}

// 查找联系人
void ConFind(Con* pc)
{
	assert(pc && pc->size > 0);

	char name[NAME_MAX];
	printf("请输入想要查找联系人的名字> ");
	scanf("%s", name);

	int pos = Find_Name(pc, name);

	if (pos != -1)
	{
		printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[pos].name,
			pc->data[pos].age,
			pc->data[pos].sex,
			pc->data[pos].adds,
			pc->data[pos].tele);
	}
	else
	{
		printf("没有找到此联系人>\n");
	}
}

// 修改联系人的信息
void ConModify(Con* pc)
{
	assert(pc && pc->size > 0);

	char name[NAME_MAX];
	printf("请输入要修改的联系人的信息的名字> ");
	scanf("%s", name);

	int pos = Find_Name(pc, name);

	if (pos != -1)
	{
		printf("请修改>\n");

		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].adds);
		printf("请输入联系人的电话:");
		scanf("%s", pc->data[pos].tele);

		printf("修改成功>\n");
	}
	else
	{
		printf("要修改信息的联系人不存在>\n");
	}
}

// 此时通讯录里联系人的个数
int ConSize(Con* pc)
{
	assert(pc);

	return pc->size;
}

// 销毁通讯录
void ConDestory(Con* pc)
{
	assert(pc);

	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->size = 0;

	printf("销毁成功>\n");
}


// 按名字从小到大排序
int cmp_con_name1(const void* a, const void* b)
{
	return strcmp(((Info*)a)->name, ((Info*)b)->name);
}
// 按名字从大到小排序
int cmp_con_name2(const void* a, const void* b)
{
	return strcmp(((Info*)b)->name, ((Info*)a)->name);
}

// 按年龄从小到大排序
int cmp_con_age1(const void* a, const void* b)
{
	return ((Info*)a)->age - ((Info*)b)->age;
}

// 按年龄从大到小排序
int cmp_con_age2(const void* a, const void* b)
{
	return ((Info*)b)->age - ((Info*)a)->age;
}

// 交换的时候一个字节一个字节的换,因为不知道排序的是什么类型的数据
void swap(char* buf1, char* buf2, size_t width)
{
	assert(buf1 && buf2);

	while (width--)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

// 排序, 以冒泡排序为框架
void Bubble_Sort(void* bese, size_t num, size_t width, int (*cmp)(const void*, const void*))
{
	assert(bese);

	for (size_t i = 0; i < num - 1; ++i)
	{
		for (size_t j = 0; j < num - 1 - i; ++j)
		{
			if (cmp((char*)bese + j * width, (char*)bese + (j + 1) * width) > 0)
			{
				swap((char*)bese + j * width, (char*)bese + (j + 1) * width, width);
			}
		}
	}
}

// 排序
void ConSort(Con* pc)
{
	assert(pc);

	printf("请选择何种方式排序:\n");
	printf("1.按名字排序:\n2.按年龄排序:\n");
	int n = 0;
	scanf("%d", &n);
	if (n != 1 && n != 2)
	{
		printf("选择错误,退出排序>\n");
		return;
	}

	if (n == 1)
	{
		int k = 0;
		printf("请选择如何排序:\n");
		printf("1.升序\n2.降序\n");
		scanf("%d", &k);

		if (k == 1)
		{
			Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_name1);
			printf("排序成功>\n");
		}
		else if (k == 2)
		{
			Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_name2);
			printf("排序成功>\n");
		}
		else
		{
			printf("选择错误>\n");
		}

	}
	else if (n == 2)
	{
		int k = 0;
		printf("请选择如何排序:\n");
		printf("1.升序\n2.降序\n");
		scanf("%d", &k);

		if (k == 1)
		{
			Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_age1);
			printf("排序成功>\n");
		}
		else if (k == 2)
		{
			Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_age2);
			printf("排序成功>\n");
		}
		else
		{
			printf("选择错误>\n");
		}
	}
}

// 保存通讯录
void SaveContact(Con* pc)
{
	assert(pc);

	// 将信息以二进制形式保存在Contacts.txt文本文档中
	FILE* pf = fopen("Contact.txt", "wb");
	if (pf == NULL)
	{
		perror("Save Con fail");
	}
	else
	{
		for (int i = 0; i < pc->size; i++)
		{
			fwrite(pc->data + i, sizeof(Info), 1, pf);
		}

		fclose(pf);
		pf = NULL;

		printf("保存通讯录数据成功>\n");
	}
}

// 拿之前保存的通讯录数据
void GetConData(Con* pc)
{
	assert(pc);

	FILE* pf = fopen("Contact.txt", "rb");
	if (pf == NULL)
	{
		perror("Get data fail");
	}
	else
	{
		Info tmp = { 0 };

		int i = 0;
		while (fread(&tmp, sizeof(Info), 1, pf))
		{
			Jud_Exp(pc);        // 判断是否要扩容
			pc->data[i] = tmp;  // 每拿一个联系人的信息就放入通讯录
			pc->size++;         // 每get一个计数一次
			i++;                          
		}

		fclose(pf);  // get完后关闭文件
		pf = NULL;

		printf("获取之前的通讯录数据成功>\n");
	}
}

test.c

#include "Contacts.h"

void menu()
{
	printf("***********************************************\n");
	printf("**********      1.add     2.del      **********\n");
	printf("**********      3.find    4.modify   **********\n");
	printf("**********      5.sort    6.show     **********\n");
	printf("**********      7.clear   8.ConSize  **********\n");
	printf("**********      9.destory 0.exit     **********\n");
	printf("***********************************************\n");
}

enum select
{
	EXIT, // 0 退出
	ADD, // 1 增加
	DEL, // 2 删除
	FIND, // 3 查找
	MODIFY, // 4 修改
	SORT, // 5 排序
	SHOW, // 6 展示通讯录
	CLEAR, // 7 清屏
	CONSIZE, // 8 通讯录联系人个数
	DESTORY // 9 销毁通讯录(要释放)
};
	
int main()
{
	int input = 0;
	Con con;
	ConInit(&con);

	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);

		switch (input)
		{
		case EXIT:
			SaveContact(&con);
			printf("退出程序>\n");
			break;
		case ADD:
			ConAdd(&con);
			break;
		case DEL:
			ConDel(&con);
			break;
		case FIND:
			ConFind(&con);
			break;
		case MODIFY:
			ConModify(&con);
			break;
		case SORT:
			ConSort(&con);
			break;
		case SHOW:
			ConShow(&con);
			break;
		case CLEAR:
			system("cls");
			break;
		case CONSIZE:
			printf("现在通讯录里联系人的个数为:%d\n", ConSize(&con));
			break;
		case DESTORY:
			ConDestory(&con);
			int n = 0;
			printf("是否需要重新初始化通讯录?\n1.YES : 0.NO >>> ");
			scanf("%d", &n);
			if (n)
			{
				ConInitNoInfo(&con);
				printf("重新初始化成功>\n");
			}
			else
			{
				input = 0;
				printf("通讯录进程关闭,退出程序>>>>>> \n");
			}
			break;

		default:
			printf("选择错误,请重新选择>\n");
			break;
		}
	} while (input);

	return 0;
}

写在最后

到了这里,一个简简单单的通讯录就完成了!如果还不能自我实现,那可要好好的练习了!

感谢阅读本小白的博客,错误的地方请严厉指出噢!

你可能感兴趣的:(C语言,数据结构与算法,c语言,通讯录,顺序表结构,完成学校任务)