C语言 — 静态顺序表实现通讯录

目录

1.通讯录结构体的创建

2.通讯录增删查改接口的实现

3.通讯录排序的实现

4.通讯录整体逻辑及代码


目标:

实现一个通讯录,最大可以保存的联系人数量为100

每个人的信息包括:姓名、性别、年龄、电话、住址

通讯录功能:

  1. 添加联系人信息
  2. 删除指定联系人信息
  3. 查找指定联系人信息
  4. 修改指定联系人信息
  5. 显示所有联系人信息
  6. 清空所有联系人
  7. 以名字排序所有联系人

1.通讯录结构体的创建

根据每个人的信息,我们可以创建如下的结构体用于存储联系人信息:

#define name_size 25
#define sex_size 5
#define tele_size 15
#define addr_size 30

struct PeopleList
{
	char name[name_size];
	char sex[sex_size];
	int age;
	char phone_number[tele_size];
	char address[addr_size];
};

很显然,这样的结构体只能存储一个联系人的信息,因此为了实现通讯录可以存下100个联系人的目标,我们需要一个结构体数组,因此,我们想到了数据结构中的静态顺序表,如下:

#define MaxSize 100

typedef struct Contact
{
	struct PeopleList people [MaxSize];
	int size;
}Contact;

这样将结构体数组与记录联系人个数的 size 放在结构体 Contact 中,这样就完成了通讯录的结构体创建

2.通讯录增删查改接口的实现

2.1添加联系人

首先是增加联系人,很简单,我们只需要传入 Contact 结构体变量的地址,再依次输入姓名、性别、年龄、电话、住址的数据即可,代码如下:

void ContactPush(Contact* con)
{
	assert(con);
	if (con->size == MaxSize)
	{
		printf("通讯录已满,无法添加\n");
		return;
	}
	else
	{
		printf("请输入联系人姓名:");
		scanf("%s", con->people[con->size].name);
		printf("请输入联系人性别:");
		scanf("%s", con->people[con->size].sex);
		printf("请输入联系人年龄:");
		scanf("%d", &con->people[con->size].age);
		printf("请输入联系人电话:");
		scanf("%s", con->people[con->size].phone_number);
		printf("请输入联系人住址:");
		scanf("%s", con->people[con->size].address);
		con->size++;
	}
}

这里我们首先使用assert对传入的结构体指针进行断言,以防止传入空指针导致后续对空指针的解引用等危险操作。

assert 函数:若函数括号中为是(非0),则无事发生,若括号中为否(即0),则结束程序并报告所在行数

C语言 — 静态顺序表实现通讯录_第1张图片

在程序中多使用断言可以让自己更快发现代码中错误的地方,可以减少自己调试找bug的时间

 断言无问题之后,判断通讯录是否已满(与设置好的数组最大容量MaxSize比较),若已满,则告知操作者,若未满,则依次输入联系人信息,输入结束后,使记录联系人个数的 size++ 即可。

2.2按名查询联系人

为什么先写查询的操作呢,因为在后续的删除与修改中,都需要按照需要(姓名、性别、年龄、电话、住址)找到对应的联系人,才能进行删除/修改的操作,因此查找可谓是除了插入操作以外的第一步。这里以姓名查找为例。

首先,输入要查找的姓名(字符串),依次比较(strcmp)通讯录中的name成员,若strcmp返回0则说明字符串完全一致,说明找到了,这时令函数返回对应结构体数组的下标,若未找到,则返回 -1 以防止与数组下标相冲突。代码如下:

int ContactFindByName(const Contact* con, char* find)
{
	//找到了返回对应数组下标
	//没找到返回 -1
	assert(con && find);
	int ret = -1;//记录返回值
	if (con->size == 0)
	{
		printf("通讯录为空\n");
		return ret;
	}
	for (int i = 0; i < con->size; i++)
	{
		if (strcmp(con->people[i].name, find) == 0)
		{
			ret = i;
			return ret;
		}
	}
	return ret;
}

2.3按姓名删除联系人

按名查找完成之后,删除就相对容易很多了,我们只需要通过查找找到要删除的姓名对应的数组下标,将对应下标的数据由后面的数据进行依次覆盖,覆盖后将记录联系人数量的 size-- 即可(若删除元素为最后一个,则不进行覆盖,直接将记录联系人数量的 size-- )。 如下:

void ContactDelete_name(Contact* con, char* name)
{
	assert(con && name);
	int ret = ContactFindByName(con, name);
	if (ret != -1)
	{
		if (ret == con->size - 1)
		{
			con->size--;
			return ;

		}
		else
		{
			for (int i = ret; i < con->size - 1; i++)
			{
				//con->people[i].age = con->people[i + 1].age;
				//*con->people[i].address = *con->people[i + 1].address;
				//*con->people[i].name = *con->people[i + 1].name;
				//*con->people[i].sex = *con->people[i + 1].sex;
				//*con->people[i].phone_number = *con->people[i + 1].phone_number;
				con->people[i] = con->people[i + 1];
			}
		}

		con->size--;
	}
	else
	{
		printf("查无此人,无法删除\n");
	}
}

2.4按名称修改联系人信息

与删除操作类似,通过查找找到对应联系人下标,通过下标找到找到对应联系人,随后修改联系人各个数据即可。

void ContactRevise_name(Contact* con, char* name)
{
	assert(con && name);
	int ret = ContactFindByName(con, name);
	if (ret != -1)
	{
		printf("请输入修改后的联系人姓名:");
		scanf("%s", con->people[ret].name);
		printf("请输入修改后的联系人性别:");
		scanf("%s", con->people[ret].sex);
		printf("请输入修改后的联系人年龄:");
		scanf("%d", &con->people[ret].age);
		printf("请输入修改后的联系人电话:");
		scanf("%s", con->people[ret].phone_number);
		printf("请输入修改后的联系人住址:");
		scanf("%s", con->people[ret].address);
	}
	else
	{
		printf("查无此人,无法修改\n");
	}
}

2.5显示整个通讯录

整了增删查改四大操作了,可以用一次打印一次更直观的看一看效果吧

void ContactPrint(const Contact* con)
{
	assert(con);
	printf("%-20s", "姓名");
	printf("%-7s", "性别");
	printf("%-6s", "年龄");
	printf("%-20s", "手机号码");
	printf("%-25s", "现居地址");
	printf("\n");
	if (con->size == 0)
	{
		printf("(null)\n");
	}
	else
	{
		for (int i = 0; i < con->size; i++)
		{
			printf("%-20s", con->people[i].name);
			printf("%-7s", con->people[i].sex);
			printf("%-6d", con->people[i].age);
			printf("%-20s", con->people[i].phone_number);
			printf("%-25s", con->people[i].address);
			printf("\n");
		}
	}
	printf("\n");
}

当然,我们也可以选择只打印需要的一列,比如查找之后,打印一遍所查找联系人的数据

void ContactPrint_n(const Contact* con,int n)
{
	assert(con);
	printf("%-20s","姓名");
	printf("%-7s", "性别");
	printf("%-6s", "年龄");
	printf("%-20s", "手机号码");
	printf("%-25s", "现居地址");
	printf("\n");
	if (n < con->size && n >= 0)
	{
		printf("%-20s", con->people[n].name);
		printf("%-7s", con->people[n].sex);
		printf("%-6d", con->people[n].age);
		printf("%-20s", con->people[n].phone_number);
		printf("%-25s", con->people[n].address);
		printf("\n");
		printf("\n");
	}
	else
	{
		return;
	}
}

打印时通过在%s 之间加 -20(打印20个字符,左对齐,若打印的字符不足20,则由空格补充)等用于统一打印长度,可以达到即使打印的字符长度不一也可以对齐。

打印效果如下所示:

 3.通讯录排序(按名字)

排序这里我们先采用逻辑较为简单的冒泡排序思想,这里选择按升序排列,通过strcmp比较两个相邻的联系人姓名,若返回值大于0则说明的前面的姓名字符串大于后一个,则交换两者,则通过两个for循环嵌套即可完成全部通讯录联系人的排序:

void ContactSort_name(Contact* con)
{
	assert(con);
	for (int i = 0; i < con->size - 1; i++)
	{
		struct PeopleList exchange;
		for (int j = 0; j < con->size - 1 - i; j++)
		{
			if (strcmp(con->people[j].name, con->people[j + 1].name) > 0)
			{
				exchange = con->people[j];
				con->people[j] = con->people[j + 1];
				con->people[j + 1] = exchange;
			}
		}
	}
}

交换数据时,我选择建立一个与联系人类型相同的结构体变量用作中转,这里注意 i 的取值不能等于size - 1,交换第size - 1时,会因为与con->people[size] 比较甚至交换造成内存越界访问。

4.通讯录的实现

完成了所有的目标功能函数,现在开始完善整个通讯录吧,首先使用 Contact结构体创建一个结构体变量,刚创建好的结构体变量需要初始化,即将 size置为 0 ,并使用 memset 将结构体数组内的数据清零:

void ContactInit(Contact* con)
{
	assert(con);
	con->size = 0;
	//重置存放联系人信息的内存数据为0
	memset(con->people, 0, sizeof(con->people));
}

初始化函数既可以用作开始的初始化,也可以用作最后借用完成后的销毁操作。

接下来通过do ... while 函数与 switch 语句即可实现整个通讯录的循环操作

int main()
{
	int input = -1;
	Contact con;
	ContactInit(&con);

	do
	{
		char name[100] = { 0 };//保存并传递按姓名查找时的数据
		int ret = -1;//用来接收查找接口的返回值
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			printf("正在退出通讯录>>>>>");
			break;
		case 1:
			ContactPush(&con);
			printf("添加联系人后的通讯录\n");
			ContactPrint(&con);
			break;
		case 2:
			printf("请输入要删除的联系人姓名:");
			scanf("%s", &name);
			ContactDelete_name(&con, name);
			printf("删除后的通讯录\n");
			ContactPrint(&con);
			break;
		case 3:
			printf("请输入要查找的联系人姓名:");
			scanf("%s", &name);
			ret = ContactFindByName(&con, name);
			if (ret != -1)
			{
				printf("找到了,此联系人的信息如下所示\n");
				ContactPrint_n(&con, ret);
				ret = -1;
			}
			else
			{
				printf("未找到\n");
			}
			break;
		case 4:
			printf("请输入需要修改的联系人姓名:");
			scanf("%s", name);
			ContactRevise_name(&con, name);
			printf("修改联系人后的通讯录\n");
			ContactPrint(&con);
			break;
		case 5:
			ContactPrint(&con);
			break;
		case 6:
			ContactDestroy(&con);
			printf("清空联系人后的通讯录\n");
			ContactPrint(&con);
			break;
		case 7:
			ContactSort_name(&con);
			printf("按姓名排序后的通讯录\n");
			ContactPrint(&con);
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}

	} while (input);
	return 0;
}

一下为整个程序的代码:

Contact.h

#pragma once

#include
#include
#include
#include

#define MaxSize 100
#define name_size 25
#define sex_size 5
#define tele_size 15
#define addr_size 30

struct PeopleList
{
	char name[name_size];
	char sex[sex_size];
	int age;
	char phone_number[tele_size];
	char address[addr_size];
};


typedef struct Contact
{
	struct PeopleList people [MaxSize];
	int size;
}Contact;

//通讯录:初始化
void ContactInit(Contact* con);

//通讯录:添加联系人
void ContactPush(Contact* con);

//通讯录:展示
void ContactPrint(const Contact* con);
//通讯录:选择展示
void ContactPrint_n(const Contact* con,int n);

//通讯录:按名字查找
int ContactFindByName(const Contact* con, char* find);

//通讯录:按名称修改对应联系人信息
void ContactRevise_name(Contact* con, char* name);

//通讯录:按名称删除对应联系人信息
void ContactDelete_name(Contact* con, char* name);

//通讯录:按名称排序联系人信息
void ContactSort_name(Contact* con);

//清空通讯录信息
void ContactDestroy(Contact* con);

Contact.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"Contact.h"

void ContactInit(Contact* con)
{
	assert(con);
	con->size = 0;
	//重置存放联系人信息的内存数据为0
	memset(con->people, 0, sizeof(con->people));
}

void ContactPush(Contact* con)
{
	assert(con);
	if (con->size == MaxSize)
	{
		printf("通讯录已满,无法添加\n");
		return;
	}
	else
	{
		printf("请输入联系人姓名:");
		scanf("%s", con->people[con->size].name);
		printf("请输入联系人性别:");
		scanf("%s", con->people[con->size].sex);
		printf("请输入联系人年龄:");
		scanf("%d", &con->people[con->size].age);
		printf("请输入联系人电话:");
		scanf("%s", con->people[con->size].phone_number);
		printf("请输入联系人住址:");
		scanf("%s", con->people[con->size].address);
		con->size++;
	}
}

void ContactPrint(const Contact* con)
{
	assert(con);
	printf("%-20s", "姓名");
	printf("%-7s", "性别");
	printf("%-6s", "年龄");
	printf("%-20s", "手机号码");
	printf("%-25s", "现居地址");
	printf("\n");
	if (con->size == 0)
	{
		printf("(null)\n");
	}
	else
	{
		for (int i = 0; i < con->size; i++)
		{
			printf("%-20s", con->people[i].name);
			printf("%-7s", con->people[i].sex);
			printf("%-6d", con->people[i].age);
			printf("%-20s", con->people[i].phone_number);
			printf("%-25s", con->people[i].address);
			printf("\n");
		}
	}
	printf("\n");
}

void ContactPrint_n(const Contact* con,int n)
{
	assert(con);
	printf("%-20s","姓名");
	printf("%-7s", "性别");
	printf("%-6s", "年龄");
	printf("%-20s", "手机号码");
	printf("%-25s", "现居地址");
	printf("\n");
	if (n < con->size && n >= 0)
	{
		printf("%-20s", con->people[n].name);
		printf("%-7s", con->people[n].sex);
		printf("%-6d", con->people[n].age);
		printf("%-20s", con->people[n].phone_number);
		printf("%-25s", con->people[n].address);
		printf("\n");
		printf("\n");

	}
	else
	{
		return;
	}
}


int ContactFindByName(const Contact* con, char* find)
{
	//找到了返回对应数组下标
	//没找到返回 -1
	assert(con && find);
	int ret = -1;
	if (con->size == 0)
	{
		printf("通讯录为空\n");
		return ret;
	}
	for (int i = 0; i < con->size; i++)
	{
		if (strcmp(con->people[i].name, find) == 0)
		{
			ret = i;
			return ret;
		}
	}
	return ret;
}

void ContactRevise_name(Contact* con, char* name)
{
	assert(con && name);
	int ret = ContactFindByName(con, name);
	if (ret != -1)
	{
		printf("请输入修改后的联系人姓名:");
		scanf("%s", con->people[ret].name);
		printf("请输入修改后的联系人性别:");
		scanf("%s", con->people[ret].sex);
		printf("请输入修改后的联系人年龄:");
		scanf("%d", &con->people[ret].age);
		printf("请输入修改后的联系人电话:");
		scanf("%s", con->people[ret].phone_number);
		printf("请输入修改后的联系人住址:");
		scanf("%s", con->people[ret].address);
	}
	else
	{
		printf("查无此人,无法修改\n");
	}
}

void ContactDelete_name(Contact* con, char* name)
{
	assert(con && name);
	int ret = ContactFindByName(con, name);
	if (ret != -1)
	{
		if (ret == con->size - 1)
		{
			con->size--;
			return ;

		}
		else
		{
			for (int i = ret; i < con->size - 1; i++)
			{
				//con->people[i].age = con->people[i + 1].age;
				//*con->people[i].address = *con->people[i + 1].address;
				//*con->people[i].name = *con->people[i + 1].name;
				//*con->people[i].sex = *con->people[i + 1].sex;
				//*con->people[i].phone_number = *con->people[i + 1].phone_number;
				con->people[i] = con->people[i + 1];
			}
		}

		con->size--;
	}
	else
	{
		printf("查无此人,无法删除\n");
	}
}

void ContactSort_name(Contact* con)
{
	assert(con);
	for (int i = 0; i < con->size - 1; i++)
	{
		struct PeopleList exchange;
		for (int j = 0; j < con->size - 1 - i; j++)
		{
			if (strcmp(con->people[j].name, con->people[j + 1].name) > 0)
			{
				exchange = con->people[j];
				con->people[j] = con->people[j + 1];
				con->people[j + 1] = exchange;
			}
		}
	}
}



void ContactDestroy(Contact* con)
{
	assert(con);
	if (con->size > 0)
	{
		memset(con->people, 0, sizeof(con->people[0]) * con->size);
		con->size = 0;
	}
	else
		return;
}

test.c

(由于写好的功能函数需要测试其有没有问题,因此先用了 test1()测试了这些函数)

#define _CRT_SECURE_NO_WARNINGS 1
#include"Contact.h"

void ContactAdd(Contact* con)
{

}

void test1()
{
	Contact con;
	ContactInit(&con);
	int ret = ContactFindByName(&con, "111");
	if (ret != -1)
	{
		printf("找到了,此联系人的信息如下所示\n");
		ContactPrint_n(&con, ret);
		ret = -1;
	}
	else
	{
		printf("未找到\n");
	}
	ContactPush(&con);
	ContactPush(&con);
	ContactPush(&con);
	ContactPush(&con);
	ret = ContactFindByName(&con, "1111");
	if (ret != -1)
	{
		printf("找到了,此联系人的信息如下所示\n");
		ContactPrint_n(&con, ret);
		ret = -1;
	}
	else
	{
		printf("未找到\n");
	}
	ContactRevise_name(&con, "111");

	ContactPrint(&con);
	ContactSort_name(&con);
	ContactPrint(&con);
	ContactDestroy(&con);
	ContactPrint(&con);
}

void menu()
{
	printf("************************************\n");
	printf("******** 1.添加联系人信息   ********\n");
	printf("******** 2.删除指定联系人   ********\n");
	printf("******** 3.查找指定联系人   ********\n");
	printf("******** 4.修改联系人信息   ********\n");
	printf("******** 5.显示所有联系人   ********\n");
	printf("******** 6.清空所有联系人   ********\n");
	printf("******** 7.按姓名排序联系人 ********\n");
	printf("******** 0.退出通讯录       ********\n");
	printf("************************************\n");
}

int main()
{
	//test1();//测试接口用函数
	int input = -1;
	Contact con;
	ContactInit(&con);

	do
	{
		char name[100] = { 0 };//保存并传递按姓名查找时的数据
		int ret = -1;//用来接收查找接口的返回值
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			printf("正在退出通讯录>>>>>");
			break;
		case 1:
			ContactPush(&con);
			printf("添加联系人后的通讯录\n");
			ContactPrint(&con);
			break;
		case 2:
			printf("请输入要删除的联系人姓名:");
			scanf("%s", &name);
			ContactDelete_name(&con, name);
			printf("删除后的通讯录\n");
			ContactPrint(&con);
			break;
		case 3:
			printf("请输入要查找的联系人姓名:");
			scanf("%s", &name);
			ret = ContactFindByName(&con, name);
			if (ret != -1)
			{
				printf("找到了,此联系人的信息如下所示\n");
				ContactPrint_n(&con, ret);
				ret = -1;
			}
			else
			{
				printf("未找到\n");
			}
			break;
		case 4:
			printf("请输入需要修改的联系人姓名:");
			scanf("%s", name);
			ContactRevise_name(&con, name);
			printf("修改联系人后的通讯录\n");
			ContactPrint(&con);
			break;
		case 5:
			ContactPrint(&con);
			break;
		case 6:
			ContactDestroy(&con);
			printf("清空联系人后的通讯录\n");
			ContactPrint(&con);
			break;
		case 7:
			ContactSort_name(&con);
			printf("按姓名排序后的通讯录\n");
			ContactPrint(&con);
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}


	} while (input);
	return 0;
}

本次通讯录实现还有很多缺点,比如一开始就创造了很多空间(100个 Struct PeopleList 类型的数组),空间开销极大,并且存在大量空间浪费(基本用不到这么多),但是空间少了的话又无法扩容。

以上缺点都是静态顺序表不可避免的缺点,那有没有其他的方法可以避免这些缺点呢?

动态顺序表就可以解决上述面对空间给的应该大还是小的窘境。

动态顺序表顾名思义,可以通过动态开辟内存来改变顺序表的大小,意味着可以扩容,解决了静态顺序表的痛点。

我下次对此通讯录进行优化,采用动态顺序表进行通讯录的实现。

你可能感兴趣的:(c语言,c++,数据结构)