C语言—实现通讯录(增删查改排序)

目录

前言 

思路 

 开始菜单

联系人与通讯录: 

通讯录框架: 

实现初始化 增删改查 排序 打印:

初始化函数:

增加联系人函数:

显示所有联系人信息:

 查找name函数:

删除指定联系人:

查找指定联系人:

修改指定联系人:

排序指定联系人信息:

动态分配联系人个数:

完整版:


前言 

大家对通讯录都不陌生,比如手机里的通讯录,我们可以存储联系人在通讯录里,也可以对已有联系人进行删除、查找、修改和排序

那么如何通过C语言实现和手机上功能一样的通讯录呢?下面我开始一步步构建我们的通讯录。      (文章最后有完整版存放在Gitee)

思路 

通讯录中要包含联系人的一些必要信息,比如:名字、年龄、性别、电话和住址等,这次我们的通讯录就包含上述五种信息,其他相关信息可以自行添加,根据需要修改代码。 

这次我们的通讯录容量设定存放100个联系人。

通讯录功能:

  1. 实现增加联系人,
  2. 删除指定联系人,
  3. 查找指定联系人,
  4. 修改指定联系人,
  5. 查找指定联系人,
  6. 对已有联系人相关信息排序
  7. 除此之外,我们还需要显示所有联系人的信息(也就是打印输出)。

我们创建两个源文件和一个头文件

  • test.c —测试通讯录 
  • contact.h —函数和类型的声明
  • contact.c —函数的实现 

将头文件contact.c包含到text.ccontact.c中,这样可以在源文件中使用被包含的头文件中定义的函数、变量、宏、头文件等。 

好的,思路已经差不多了,现在开始写代码吧 !!!

 开始菜单

将头文件<stdio.h>插入contact.c中。

首先写一个开始菜单menu函数:

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

联系人与通讯录: 

我们的联系人和通讯录都在contact.h中创建。

创建保存联系人信息的结构体PeoInfo,(名字、年龄、性别、电话和住址)

使用 typedef 将 struct PeoInfo 类型自定义成 PeoInfo 更加直观。

typedef struct PeoInfo
{
	char name[20];
	int age;
	char sex[5];
	char tele[12];
	char addr[30];
}PeoInfo;

在头文件中定义一个值为100的常量MAX。

#define MAX 100

创建存放联系人的通讯录Contact,定义PeoInfo类型的数组data,存放MAX个联系人对应的信息。

sz用于记录当前储存联系人的数量,在之后筛选联系人时会用到。

typedef struct Contact
{
	PeoInfo data[MAX];
	int sz;
}Contact;

也可以将联系人各种信息定义为常量,方便日后修改其所需空间。 

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

通讯录框架: 

我们将通讯录框架封装成一个 test 函数,通过do-while语句实现不同功能。

首先定义input变量,由用户输入的input的值判断进行什么操作。

定义结构体Contact类型变量 con ,在这个通讯录框架中con就是通讯录。

然后初始化通讯录,将通讯录的成员赋值为零,函数内部改变改变外部变量通讯录con,需要传递地址给初始化函数,结构体传参最好传地址,节约空间。初始化函数后续进行讲解。

void test()
{
	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:
            printf("退出通讯录\n");
			break;
		default:
            printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
}

通讯录运行通过do-while实现,内部调用menu函数打印开始菜单,然后提示用户选择功能。

接下来通过swich-case语句判断输入不同的 input 值对应实现不同功能。

我们都知道case后只能加整数,但我们可以创建枚举类型替换这些整数,枚举类型成员的值从0开始递增1,适用于代替case后的整数,使用枚举可以更直观了解函数功能

将enum OPPION在contact.h中声明。 

enum OPPION
{
	EXIT,	//退出
	ADD,	//添加
	DEL,	//删除
	SEARCH, //查找
	MODIFY, //修改
	SHOW,   //打印
	SORT    //排序
};

实现初始化 增删改查 排序 打印:

接下来我们创建switch-case中实现通讯录增删改查排序对应的函数:

将他们声明在头文件中,这些函数我们都写在contact.c中。

//初始化函数声明
void InitContact(Contact* pc);

//增加联系人
void AddContact(Contact* pc);

//显示所有联系人的信息
void ShowContact(const Contact* pc);//不需要修改加const

//删除指定联系人
void DelContact(Contact* pc);

//查找指定联系人
void SearchContact(const Contact* pc);

//修改指定联系人
void ModifyContact(Contact* pc);

//排序指定联系人信息
void SortContact(Contact* pc);

初始化函数:

void InitContact(Contact* pc)
{
	assert(pc);
	memset(pc->data, 0, sizeof(pc->data));
	pc->sz = 0;
}

宏assert进行断言, 需包含 <assert.h>在contact.h中,如果传入指针为空,则assert 会引发一个错误,并向标准错误流输出一条消息,通常包括断言失败的信息和文件名/行号等信息,程序会终止执行。

memset函数需要包含头文件<stdlib.h>contact.h中,用于对修改字符类型指定字节的值 ,将存放联系人的数组data的每个元素初始化为零。记录储存联系人个数的 sz 也初始化为零。   

增加联系人函数:

void AddContact(Contact* pc)
{
	assert(pc);
	if (pc->sz == MAX) {
		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);
	pc->sz++;
	printf("成功添加联系人\n");
}

判断当前联系人是否已满,已满则执行return;中止增加联系人。

显示所有联系人信息:

void ShowContact(const Contact* pc)
{
	assert(pc);
	//打印列标题
	printf("%-20s\t%-4s\t%-5s\t%-12s\t%-30s\t\n", "名字", "年龄", "性别", "电话", "地址");
	for (int i = 0; i < pc->sz; i++)
	{
		printf("%-20s\t%-4d\t%-5s\t%-12s\t%-30s\t\n",
			pc->data[i].name,
			pc->data[i].age,
			pc->data[i].sex,
			pc->data[i].tele,
			pc->data[i].addr);
	}
}

 打印一行标题方便查看。

 查找name函数:

int FindByName(const Contact* pc, char name[])
{
	int i;
	for (i = 0; i < pc->sz; i++)
	{
	//比较char类型字符串name需要strcmp
		if (strcmp(pc->data[i].name, name) == 0) {
			return i;
		}
	}
	return -1;
}

该函数用来支持函数内部的查找功能,返回值为联系人在数组的下标,可以不需要让别人知道,
所以可以不在头文件里声明,在这个文件内函数能调用即可,加上static也可以。

比较字符串只能用strcmp函数进行比较,需要包含<string.h>在contact.h中。

这个函数将服务于后续各种需要指定联系人的函数。

删除指定联系人:

void DelContact(Contact* pc)
{
	assert(pc);
	char name[MAX_NAME] = { 0 };
	printf("请输入要删除的人的名字:>");
	scanf("%s", name);

	int del = FindByName(pc, name);
	if (del == -1) {
		printf("要删除的人不存在\n");
		return;
	}

	//删除坐标为del的联系人
	int i;
	for (i = del; i < pc->sz - 1; i++) {
		pc->data[i] = pc->data[i + 1];
    }
	pc->sz--;
	printf("成功删除联系人\n");
}

创建一个name数组,大小为自定义常量MAX_NAME,大小为20,name数组用于存放要删除的联系人,然后定义del变量接受FindByName的返回值。

该函数每次只删除一个name,比如:传入的结构体指针包含二十个name,即sz=20。                删除一个则该name后续的元素都要往前移动一位,最后一位元素的下标为19,需要移动sz-1次,则 i 要小于 sz-1,保证下标为del到最后一个元素下标为sz向前移动一位。

删除之后 sz 需要减一。

查找指定联系人:

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");
	else
	{
		printf("%-20s\t%-4s\t%-5s\t%-12s\t%-30s\t\n", 
        "名字", "年龄", "性别", "电话", "地址");

		printf("%-20s\t%-4d\t%-5s\t%-12s\t%-30s\t\n",
				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] = { 0 };
	printf("请输入要修改人的名字\n");
	scanf("%s", name);
	int pos = FindByName(pc, name);
	if (pos == -1)
		printf("要修改的人不存在\n");
	else {
		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");
	}
}

 如果要修改的联系人存在,则通过FindByName函数获得到该联系人在data中的下标,然后通过pos进行修改相关信息。

排序指定联系人信息:

int CompareByName(const void* a, const void* b) {
	return strcmp(((PeoInfo*)a)->name, ((PeoInfo*)b)->name);
}

int CompareByAge(const void* a, const void* b) {
	return ((PeoInfo*)a)->age - ((PeoInfo*)b)->age;
}

int CompareByAddr(const void* a, const void* b) {
	return strcmp(((PeoInfo*)a)->addr, ((PeoInfo*)b)->addr);
}

void SortContact(Contact* pc)
{
	assert(pc);
	char del[10];
	printf("请输入想要排序的项目\n");
	scanf("%s", del);

	if (strcmp(del, "name") == 0)
	{
		qsort(pc->data, pc->sz, sizeof(PeoInfo), CompareByName);
	}
	else if (strcmp(del, "age") == 0)
	{
		qsort(pc->data, pc->sz, sizeof(PeoInfo), CompareByAge);
	}
	else if (strcmp(del, "addr") == 0)
	{
		qsort(pc->data, pc->sz, sizeof(PeoInfo), CompareByAddr);
	}
	else
	{
		printf("无效的排序选项\n");
		return;
	}

	// 输出排序后的结果
	printf("%-20s\t%-4s\t%-5s\t%-12s\t%-30s\t\n", "名字", "年龄", "性别", "电话", "地址");
	for (int i = 0; i < pc->sz; i++)
	{
		printf("%-20s\t%-4d\t%-5s\t%-12s\t%-30s\t\n",
			pc->data[i].name,
			pc->data[i].age,
			pc->data[i].sex,
			pc->data[i].tele,
			pc->data[i].addr);
	}
}

运用qsort函数排序指定联系人的信息,在联系人信息中我只写了姓名、年龄和地址这些能进行排序对应的函数。

如果你忘记qsort函数了,希望这篇文章的qsort函数部分对你有帮助详解C语言—进阶指针(二)

动态分配联系人个数:

这篇《动态分配内存》讲解了相关知识,如果你忘记了,可以复习一下。

可以当以上正常版本的通讯录学懂之后,回过头来学习动态版本的通讯录。

在头文件contact.h中增加两个自定义常量,

初始化存储联系人个数的数组data的值为 DEFAULT_SZ也就是 3。

后续通过增容函数,使其每次是存放联系人个数的上限增加两个联系人。

#define DEFAULT_SZ 3 //默认值3
#define INC_SZ 2  //每次增加两个联系人

对结构体Contact进行调整: 

typedef struct Contact
{
	PeoInfo* data;
	int sz;//当前存放的有效元素的个数
	int capacity;//通讯录当前最大容量
}Contact;

 修改初始化函数:

void InitContact(Contact* pc)
{
	assert(pc);
	pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
	if (pc->data == NULL) {
		perror("InitContact");
		return;
	}
	pc->sz = 0;
	pc->capacity = DEFAULT_SZ;
}

增加增容函数 :

int CheckCapacity(Contact* pc)
{
	if (pc->sz == pc->capacity)//判断是否需要增容
	{
		PeoInfo* ptr=(PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
		if (ptr == NULL) {
			perror("CheckCapacity");
			return 0;
		}
		else {
			pc->data = ptr;
			pc->capacity += INC_SZ;
			printf("成功增容\n");
			return 1;
		}
	}
	return 1;
}

如果增容失败则停止增加联系人:

void AddContact(Contact* pc)
{
	assert(pc);
	if (CheckCapacity(pc) == 0)//增容失败,停止添加联系人
		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);
	pc->sz++;
	printf("成功添加联系人\n");
}

 对switch-case中EXIT添加对动态分配内存进行释放的DestroyContact函数。

		case EXIT:
			DestroyContact(&con);
			printf("退出通讯录\n");
			break;
void DestroyContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->sz = 0;
}

完整版:

完整版我放在Gitee上了,

请自行查阅: 正常版本   and   动态版本

猜你喜欢

转载自blog.csdn.net/m0_73800602/article/details/133216686