一、前言
通讯录是我们日常生活中不可或缺的一种功能,在手机联系人界面、QQ群、抖音都有着极其重要的作用,但是通讯录到底是怎么实现的呢?它的底层结构是怎样运行的?都是需要我们去学习和思考的,接下来我们一起用C语言来了解和学习通讯录的实现。
二、通讯录的结构
如图中的一个QQ通讯录,我们可以清楚的看到上面的一个个联系人,这些联系人被分成不同的群组,每个群组中又包含着联系人的信息,那么这些数据是如何存储在其中的呢?
在数据结构中,有顺序表、链表、树、图等结构,但是在通讯录的实现中要用到那些数据结构,则需要根据通讯录的功能及结构来确定。
对于通讯录来说,我们在日常使用通讯录时,往往会随机的向通讯录中增、删、查、改一些数据,伴随着这些操作,通讯录中的数据也会随之改变,但是我们在使用通讯录的同时,并不会经常删除或者增加大量的联系人,而对联系人的查找和改动会多一些。
如此一来,对比如上对通讯录的总结,那么顺序表结构就比较适合了,顺序表的特点为:能够随机访问数据、存储密度大、尾节点的插入删除方便但是对于中间节点的插入和删除较为繁琐,所以这些特点会比较符合通讯录的特征,由于考虑到通讯录会随着时间的推移而渐渐增加联系人,所以我们采用动态顺序表,以达到避免顺序表饱和的情况。
所以本章将采用顺序表的结构实现通讯录。
三、底层代码、通讯录代码、主程序
对于通讯录的实现,本文将采用VS来实现,同时对于底层代码,将采用命名为“sqlist.h”、“sqlist.c”的头文件和源文件,对于通讯录代码,将采用命名为“contact.h”、“contact.c”的头文件和源文件,主程序使用“program.c”的源文件。
3.1底层代码
3.1.1建立头文件和源文件
3.1.2声明
为了写出来的代码具有一般性,我们一般事先声明所要用到的类型,代码如下:
#define N 4
typedef contact sq;
typedef struct SQLIST
{
int size;
int capacity;
sq* arr;
}SQ;
3.1.3基本操作代码
在顺序表的实现中,通常为顺序表的初始化、插入(头插、尾插、任意位置插入)、删除(头删、尾删、任意位置删除)、查找数据、改变数据、展示通讯录中的数据。
在头文件中定义如下:
//初始化
void SqlistInit(SQ* pf);
//尾插
void SqlistPushBck(SQ* pf,sq n);
//尾删
void SqlistDeleteBack(SQ* pf);
//查找数据
void SqlistFindData(SQ* pf, char* str);//以查找名字为例
//改变数据
void SqlistModifyData(SQ* pf, char* str);//以改变名字为例
//展示
void SqlistShow(SQ* pf);
在源文件中代码实现如下:
1、对于顺序表的初始化,我们首先应该创建空间(空间大小随意,这里默认4个空间大小)
//初始化
void SqlistInit(SQ* pf)
{
(SQ*)pf->s = (SQ*)malloc(sizeof(SQ) * N);
assert(pf);
pf->capacity = N;//将顺序表的空间扩大到和申请空间大小的一致
pf->size = 0;//让顺序表中数据的个数为0
}
2、我们要想往通讯录中插入联系人,必须将联系人的姓名、年龄、性别、电话号码一并传入通讯录中,所以这里要新建一个结构体,结构体中要包含上述联系人的信息,在我们一一输入完成后一并传入通讯录中。但是需要注意的是,我们在向顺序表
我们要想往顺序表中插入数据时,首先要考虑的就是顺序表中的数据是否已经满了,如皋满了则要再次动态的扩大空间大小,如果不满直接插入即可,所以在此之前我们应该建立一个判断顺序表空间的函数,并且顺序表一旦为空就在函数里完成扩容。
//检查顺序表是否为满
void SqlistCheck(SQ* pf)
{
assert(pf);
if (pf->capacity == pf->size)//如果链表满了,就扩容。
{
SQ* tmp = (SQ*)realloc(pf->s, sizeof(SQ) * pf->capacity * 2);
assert(tmp);//判断空间是否追加成功
pf = tmp;
pf->capacity *= 2;
}
}
//尾插
void SqlistPushBck(SQ* pf, sq n)
{
assert(pf);
SqlistCheck(pf);判断链表是否为满
pf->s[pf->size] = n;//将数据插入到顺序表尾部
pf->size++;//顺序表的下标向后移动一位
}
3、对于顺序表的尾删除数据则是要简单许多,只需把pf->size减一即可,但是在删除之前要注意顺序表是否为空,如果为空就要终止删除操作。
//尾删
void SqlistDeleteBack(SQ* pf)
{
assert(pf);
assert(pf->size);//判断顺序表是否为空
pf->size--;
}
4、查找数据,在此我们以查找联系人的姓名为例,因此我们要传入的不止是结构体的指针,还有联系人的姓名。
//查找数据
int SqlistFindData(SQ* pf, char* str)//以查找名字为例
{
assert(pf);
assert(str);
if (pf->size == 0)
{
return -1;//如果顺序表为空,则返回-1,代表查无此人
}
for(int i=0;i<pf->size;i++)
{
if (strcmp(pf->s[i].name, str) == 0)
return i;//找到了返回该数据在顺序表中的下标
}
return -1;//如果遍历整个顺序表没有找到,则返回-1,代表查无此人
}
5、要改变顺序表中数据的信息,首先要找到要改变的数据,再将传入的新数据替换该数据,但是如果顺序表为空或者该数据不存在,则报错。
//改变数据
void SqlistModifyData(SQ* pf, char* member, char* name)//以改变名字为例
{
assert(pf);
if (SqlistFindData(pf, member) == -1)
{
printf("找不到该联系人\n");
return;
}
strcpy(pf->s[SqlistFindData(pf, member)].name, name);
}
6、在完成输入数据后,我们想要知道顺序表中是否已经存储完成,这时我们就应该写一个打印函数,将顺序表中的数据打印出来。
//展示
void SqlistShow(SQ* pf)
{
assert(pf);
assert(pf->size);
printf("姓名 年龄 性别 电话号码\n");
for (int n = 0; n < pf->size; n++)
{
printf("%s %d %s %s ", pf->s[n].name,
pf->s[n].age,
pf->s[n].gander,
pf->s[n].tel);
printf("\n");
}
}
3.2通讯录代码
完成顺序表的底层代码后,我们就可以编写通讯录的程序了,这时我们只需做好前备工作,直接调用我们的底层代码即可。
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
typedef struct SQLIST con;
#define NAME_MAX 20
#define TEL_MAX 20
#define GANDER_MAX 15
typedef struct CONTACT
{
char name[NAME_MAX];
int age;
char gander[GANDER_MAX];
char tel[TEL_MAX];
}contact;
//初始化通讯录
void ContactInit(con* pf);
//插入联系人
void ContactPushBack(con* pf);
//删除通讯录中的联系人
void ContactDelete(con* pf);
//修改通讯录中联系人的信息,以名字为例
void ContactModify(con* pf);
//查找通讯录中联系人的信息
void ContactCheck(con* pf);
//展示通讯录
void ContactShow(con* pf);
//初始化通讯录
void ContactInit(con* pf)
{
SqlistInit(pf);
}
//插入联系人
void ContactPushBack(con* pf)
{
assert(pf);
contact cc;
printf("请输入要插入联系人的姓名:");
scanf("%s", cc.name);
printf("请输入要插入联系人的年龄:");
scanf("%d", &(cc.age));
printf("请输入要插入联系人的性别:");
scanf("%s", cc.gander);
printf("请输入要插入联系人的电话号码:");
scanf("%s", cc.tel);
SqlistPushBck(pf, cc);
}
//删除通讯录中的联系人
void ContactDelete(con* pf)
{
SqlistDeleteBack(pf);
}
//查找通讯录中联系人的信息
void ContactCheck(con* pf)
{
char name[NAME_MAX] = { 0 };
printf("请输入要查找的联系人姓名:");
scanf("%s", name);
if (SqlistFindData(pf, name) == -1)
printf("查无此人\n");
else
printf("找到了,此人在通讯录中的下标为:%d", SqlistFindData(pf, name));
}
//展示通讯录
void ContactShow(con* pf)
{
SqlistShow(pf);
}
//改变数据
void ContactModify(con* pf)
{
char member[20] = { 0 };
char name[20] = { 0 };
printf("请输入要改变联系人的姓名:");
scanf("%s", member);
printf("请输入新的姓名:");
scanf("%s", name);
SqlistModifyData(pf, member, name);
}
3.3主程序
写完底层代码和通讯录代码后,我们就可以进行测试了,在此我们首先输入三个人的信息即:11 11 11 11,22 22 22 22,33 33 33 33。输入完成进行展示通讯录,查看是否输入成功,之后再删除33的数据,最后再把11的姓名改成33,展示通讯录。
代码如下:
void func1()
{
SQ s;
ContactInit(&s);//初始化
ContactPushBack(&s);//插入
ContactPushBack(&s);//插入
ContactPushBack(&s);//插入
ContactShow(&s);//展示
ContactDelete(&s);//删除
ContactShow(&s);//展示
ContactModify(&s);//修改数据
ContactShow(&s);//展示
}
int main()
{
func1();
return 0;
}
四、总结
以上便是通讯录的大体步骤,其中最核心的代码也是顺序表的核心操作,即增删改查,所以只要我们熟悉顺序表的操作,那么对于类似于通讯录问题的实现也就变得简单了,只需做好前备工作直接调用底层代码(顺序表的基本操作)即可。