目录
1.通讯录结构体的创建
2.通讯录增删查改接口的实现
3.通讯录排序的实现
4.通讯录整体逻辑及代码
目标:
实现一个通讯录,最大可以保存的联系人数量为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];
};
很显然,这样的结构体只能存储一个联系人的信息,因此为了实现通讯录可以存下100个联系人的目标,我们需要一个结构体数组,因此,我们想到了数据结构中的静态顺序表,如下:
#define MaxSize 100
typedef struct Contact
{
struct PeopleList people [MaxSize];
int size;
}Contact;
这样将结构体数组与记录联系人个数的 size 放在结构体 Contact 中,这样就完成了通讯录的结构体创建
首先是增加联系人,很简单,我们只需要传入 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),则结束程序并报告所在行数
在程序中多使用断言可以让自己更快发现代码中错误的地方,可以减少自己调试找bug的时间
断言无问题之后,判断通讯录是否已满(与设置好的数组最大容量MaxSize比较),若已满,则告知操作者,若未满,则依次输入联系人信息,输入结束后,使记录联系人个数的 size++ 即可。
为什么先写查询的操作呢,因为在后续的删除与修改中,都需要按照需要(姓名、性别、年龄、电话、住址)找到对应的联系人,才能进行删除/修改的操作,因此查找可谓是除了插入操作以外的第一步。这里以姓名查找为例。
首先,输入要查找的姓名(字符串),依次比较(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;
}
按名查找完成之后,删除就相对容易很多了,我们只需要通过查找找到要删除的姓名对应的数组下标,将对应下标的数据由后面的数据进行依次覆盖,覆盖后将记录联系人数量的 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");
}
}
与删除操作类似,通过查找找到对应联系人下标,通过下标找到找到对应联系人,随后修改联系人各个数据即可。
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,则由空格补充)等用于统一打印长度,可以达到即使打印的字符长度不一也可以对齐。
打印效果如下所示:
排序这里我们先采用逻辑较为简单的冒泡排序思想,这里选择按升序排列,通过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] 比较甚至交换造成内存越界访问。
完成了所有的目标功能函数,现在开始完善整个通讯录吧,首先使用 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 类型的数组),空间开销极大,并且存在大量空间浪费(基本用不到这么多),但是空间少了的话又无法扩容。
以上缺点都是静态顺序表不可避免的缺点,那有没有其他的方法可以避免这些缺点呢?
动态顺序表就可以解决上述面对空间给的应该大还是小的窘境。
动态顺序表顾名思义,可以通过动态开辟内存来改变顺序表的大小,意味着可以扩容,解决了静态顺序表的痛点。
我下次对此通讯录进行优化,采用动态顺序表进行通讯录的实现。