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

news2024/10/6 8:21:56

文章目录

  • 前言
  • 通讯录初始化
  • 通讯录运行的基本框架和菜单
  • 增添联系人
  • 删除联系人
  • 查找联系人
  • 修改联系人信息
  • 展示通讯录
  • 通讯录联系人个数
  • 排序通讯录
  • 文件操作储存通讯录信息
  • 销毁通讯录
  • 整体代码
    • 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 <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>

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");
}

在这里插入图片描述

  • 然后在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");
		}
	}
}

这里测试一下:

一开始通讯录列表为:

在这里插入图片描述
我们按年龄从小到大排序为:
在这里插入图片描述
在这里插入图片描述
可以看到,此时的确排序成功!

整体的函数接口实现为:

// 按名字从小到大排序
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 <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>

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;
}

写在最后

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/366386.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Web网页测试全流程解析论Web自动化测试

1、功能测试 web网页测试中的功能测试&#xff0c;主要测试网页中的所有链接、数据库连接、用于在网页中提交或获取用户信息的表单、Cookie 测试等。 &#xff08;1&#xff09;查看所有链接&#xff1a; 测试从所有页面到被测特定域的传出链接。 测试所有内部链接。 测试链…

开学准备哪些电容笔?ipad触控笔推荐平价

在现代&#xff0c;数码产品的发展受到高技术的驱动。不管是在工作上&#xff0c;还是在学习上&#xff0c;大的显示屏可以使图像更加清晰。Ipad将成为我们日常生活中不可或缺的一部分&#xff0c;无论现在或将来。如果ipad配上一款方便操作的电容笔&#xff0c;将极大地提高我…

Unity性能优化:如何优化Drawcall

前言 降低游戏的Drawcall&#xff0c;是渲染优化很重要的手段&#xff0c;接下来从以下4个方面来分析如何降低DrawCall: 对惹&#xff0c;这里有一个游戏开发交流小组&#xff0c;希望大家可以点击进来一起交流一下开发经验呀 降低Drawcall的意义是什么?如何查看游戏的Drawca…

C++继承、构造函数和析构函数

构造函数 与 析构函数 构造函数代表一个对象的生成&#xff0c;主要作用是初始化类的成员变量&#xff0c;可以被重载 如果没有显式构造函数&#xff0c;则实例化对象时&#xff0c;系统会自动生成一个无参的构造函数 构造函数的名称与类名相同 析构函数代表的是一个对象的销…

初识Python——“Python”

各位CSDN的uu们你们好呀&#xff0c;今天进入到了我们的新专栏噢&#xff0c;Python是小雅兰的专业课&#xff0c;那么现在&#xff0c;就让我们进入Python的世界吧 计算机基础概念 什么是计算机&#xff1f; 什么是编程&#xff1f; 编程语言有哪些&#xff1f; Python背景知…

MySQL的安装(详解)

文章目录前言一、yum方式安装1、下载并安装MySQL2、 启动MySQL数据库3、查看MySQL初始密码4、登录数据库5、修改MySQL默认密码6、授予root用户远程管理权限7、输入exit退出数据库二、rpm安装方式1、检查2、卸载mariadb3、安装4、启动5、密码总结前言 本教程为Linux下安装mysql的…

若依配置教程(九)若依前后端分离版部署到服务器Nginx(Windows版)

搭建若依环境 要部署到服务器上&#xff0c;首先要在本地运行若依系统 文章目录搭建若依环境后端部署1.在application.yml中修改后台端口&#xff0c;这里默认是8080。2.在application-druid.yml中修改正式环境数据库。3.后端打包部署前端部署下载安装NginxNginx代理配置启动N…

UnityEditor编辑器扩展代码实现Project搜索的实现功能和切换Component等

反射实现切换Gameobjecect-Comp之前介绍过Kinematic Character Controller这个插件这个插件很容易和另外一个插件混淆&#xff0c;两个作者头像比较相像&#xff0c;而且这个插件的作者不太喜欢露脸&#xff08;他现在做Dot-CharacterControl去了&#xff09;&#xff0c;几乎网…

人人能读懂redux原理剖析

一、Redux是什么&#xff1f; 众所周知&#xff0c;Redux最早运用于React框架中&#xff0c;是一个全局状态管理器。Redux解决了在开发过程中数据无限层层传递而引发的一系列问题&#xff0c;因此我们有必要来了解一下Redux到底是如何实现的&#xff1f; 二、Redux的核心思想…

计算机网络之IP协议(详解

网络层主管地址管理与路由选择。而IP协议就是网络层中一个非常重要的协议。它的作用就是在复杂的网络环境中确定一个合适的路径。IP协议头格式4位版本号(version) 指定IP协议的版本&#xff0c;目前只有两个版本&#xff1a;IP v4和IP v6.对于IP v4来说&#xff0c;这个值就是4…

边缘云是什么?

涂鸦边缘云服务 旨在解决物联网边缘位置的连接需求和提高设备自主管理能力。并与涂鸦 IoT 云服务和 IoT 终端形成云边端三位一体的端到端产品架构。使用涂鸦边缘云&#xff0c;能极大降低设备响应延时、降低网络带宽压力、提高算力分发能力&#xff0c;并构建以下技术优势&…

IDEA 30 个好用天花板技巧,敲代码直接接爽到飞。

IDEA 作为Java开发工具的后起之秀&#xff0c;几乎以碾压之势把其他对手甩在了身后&#xff0c;主要原因还是归功于&#xff1a;好用&#xff1b;虽然有点重&#xff0c;但依旧瑕不掩瑜&#xff0c;内置了非常多的功能&#xff0c;大大提高了日常的开发效率&#xff0c;下面汇总…

LAMP架构与搭建论坛

目录 1、LAMP架构简述 2、各组件作用 3、构建LAMP平台 1.编译安装Apache httpd服务 2.编译安装mysql 3.编译安装php 4.搭建一个论坛 1、LAMP架构简述 LAMP架构是目前成熟的企业网站应用模式之一&#xff0c;指的是协同工作的一整台系统和相关软件&#xff0c;能够提供动…

Spring Boot整合Thymeleaf和FreeMarker模板

虽然目前市场上多数的开发模式采用前后端分离的技术&#xff0c;视图层的技术在小一些的项目中还是非常有用的&#xff0c;所以一直也占有一席之地&#xff0c;如spring官方的spring.io等网站就是使用视图层技术实现的。 目前Spring Boot支持的较好的两个视图层模板引擎是Thyme…

【git】git版本控制

目录 1.在合适的位置打开bush,创建仓库 2.检查&#xff1a;跳转到当前文件夹&#xff0c;显示当前文件夹的相对路径 3.初始化 4.创建一个文本文件readme.txt 5.手动向readme文件中添加一些内容 6.把文件添加到暂存区 7.把文件提交到git仓库 8.手动修改readme.txt文件 9.查看当前…

前端监控之用户行为监控实践2(数据统计mongodb)

一、技术栈介绍 我们当前的项目&#xff0c;后端是node 搭建&#xff0c;数据库是非关系型数据库 mongodb。 二、数据情况介绍 日志存储存储格式如下&#xff1a; 主要包括&#xff1a; key意义type当前访问类型actionTime访问时间content访问内容erp、fullname、orgname、…

【Spring MVC】这一篇,带你从入门到进阶

目录 1、什么是MVC&#xff1f; 2、什么是 Spring MVC 3、如何学好 Spring MVC&#xff1f; 3.1、如何创建 Spring MVC 项目 3.1.1、使用Spring Initializr创建&#xff08;推荐&#xff09; 3.2、将 Spring 程序与用户&#xff08;浏览器&#xff09;联通 3.3、基础注解…

6.5 拓展:如何实现 Web API 版本控制,同时兼容无版本控制的原始接口?

第6章 构建 RESTful 服务 6.1 RESTful 简介 6.2 构建 RESTful 应用接口 6.3 使用 Swagger 生成 Web API 文档 6.4 实战&#xff1a;实现 Web API 版本控制 6.5 拓展&#xff1a;如何实现 Web API 版本控制&#xff0c;同时兼容无版本控制的原始接口&#xff1f; 6.5 拓展&#…

干旱预测方法总结及基于人工神经网络的干旱预测案例分析(MATLAB全代码)

本案例采用SPEI干旱指数&#xff0c;构建ANN和BP神经网络预测模型&#xff0c;并开展1~3个月预见期的干旱预测&#xff0c;对比分析干旱预测模型的适用性&#xff0c;为流域干旱预警和管理提供技术依据。 干旱预测 1 干旱预测方法 1.1 统计学干旱预测 根据历史降水或气温等…

【python】用plotly绘制正二十面体

文章目录顶点棱实现正二十面体plotly 的 Python 软件包是一个开源的代码库&#xff0c;它基于 plot.js&#xff0c;而后者基于 d3.js。我们实际使用的则是一个对 plotly 进行封装的库&#xff0c;名叫 cufflinks&#xff0c;能让你更方便地使用 plotly 和 Pandas 数据表协同工作…