Bootstrap

用到顺序表的通讯录

前言

        顺序表式数据结构中基础部分,也是我接触C语言学到数据结构中的线性结构第一次接触到的结构。从整体上来看,顺序表就像是一个大数组。能够扩容,能够装自定义变量的数组。和普通在栈内的数组不同,我们需要在堆区开辟 空间。这就用到了之前学到的“malloc”、“realloc”。

        学到顺序表,它的存储数据的功能的体现载体之一就是通讯录。这也是比较简单的部分,用于介绍顺序表正好。那么接下来就让我们看看怎么用顺序表来实现通讯录吧。

一. 通讯录中的内容

        既然说到通讯录,那么通讯录就从通讯录需要包括什么样的内容开始。通讯录需要储存的数据有:1)联系人的姓名。2)联系人的性别。3)联系人的年龄。4)联系人的电话号码。5)联系人的地址。

        当然除了这些之外还能够添加其他的信息,但是我们这里只做简单的举例。对应就需要构建结构体,结构体的结构如下:

typedef struct PersonInfo
{
    char name[NAME_MAX];
    char gender[SEX_MAX];
    int age;
    char tel[TEL_MAX];
    char addr[ADDR_MAX];
}PeoInfo;

        为了方便修改字符串的大小,我们使用了宏定义,具体的宏定义如下:

#define NAME_MAX 100
#define SEX_MAX 4
#define TEL_MAX 11
#define ADDR_MAX 100

        为了储存这些结构体中的内容,可以把这些数据存到内存中的堆区,这样我们就会用到内存函数开辟内存空间。为了方便我们找到空间,所以定义了以下结构体作为前置声明:

typedef struct Contact
{
    PeoInfo* p;
    int size;
    int capcity;
}Contact;

        “PeoInfo* p;”就是方便我们找到这块空间的指针,“size”表示我们存储数据的个数,“capcity”表示开辟空间能够储存数据的最大容量。

        到这里通讯录的前置内容就讲解完毕了。

二. 通讯录中的函数

1. 通讯录的初始化

Contact con;

        创建新的“Contact;“——通讯录之后需要初始化它,我们需要构造一个函数去达到目的。

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

        初始化通讯录,我们需要传入“con”的指针,那么它的类型是“Contact*”。为了防止使用者传递空指针,我们可以增加assert断言。随后开始初始化,首先开拓一个固定大小的空间,如果开拓成功,就让“con”内部的指针指向这块空间。随后将“con”中的大小和容量都初始化。代码如下:

//初始化通讯录
void InitContact(Contact* con)
{
    assert(con);

    con->p = (PeoInfo*)malloc(sizeof(PeoInfo) * INIT_SIZE);
    if(con->p == NULL)
    {
        perror("InitContact malloc fail");
        return;
    }

    con->capcity = INIT_SIZE;
    con->size = 0;
}

        其中初始化容量的大小是由宏定义的,定义如下:

#define INIT_SIZE 4

        如此初始化函数构建完毕。

2. 销毁通讯录

//销毁通讯录数据
void DestroyContact(Contact* con);

        因为通讯录的数据都存储在堆之中,所以我们需要手动销毁它。虽然程序结束后会自动销毁,但是做一下总是好的。

        销毁通讯录就很简单了,因为数据都存在一整块内存中,直接使用“free()”将它释放掉,把指针置为空就好了。最后将容量和数量都清零,接完成了销毁通讯录,结构如下:

//销毁通讯录数据
void DestroyContact(Contact* con)
{
    assert(con);

    free(con->p);
    con->p = NULL;
    con->capcity = con->size = 0;
}

3. 展示通讯录

//展示通讯录数据
void ShowContact(Contact* con);

        先讲展示通讯录的方法是因为有了这个函数之后我们能够更加直观地从运行程序来判断通讯录中函数的正确性。

        首先需要判断传入的指针“con”是否为空,然后检查通讯录中有没有数据,如果没有数据就打印“通讯录为空”并直接用“return”结束函数。反之就展示通讯录的内容,这里用自己喜欢的方式展示就行了,我的方式如下:

//展示通讯录数据
void ShowContact(Contact* con)
{
    assert(con);
    //检查通讯录
    if(con->size == 0)
    {
        printf("通讯录为空\n");
        return;
    }

    //打印通讯录
    int i = 0;
    printf("%-6s %-5s %-5s %-13s %s\n", "姓名", "性别", "年龄", "电话", "地址");
    for(i = 0; i < con->size; i++)
    {
        printf("%-6s %-5s %-5d %-13s %s\n", con->p[i].name, 
                                            con->p[i].gender, 
                                            con->p[i].age, 
                                            con->p[i].tel, 
                                            con->p[i].addr);
    }
}

        如果可以的话也可以在“ ”的地方换成“|”,可以看得更清楚些。

        到这里位置通讯录就完成了三个模块了,接下来就是增删数据了。

4. 添加联系人到通讯录

//添加通讯录数据
void AddContact(Contact* con);

        添加通讯录中的联系人,我们就需要给“con”中指针指向的空间赋值,获取联系人的信息。不过在此之前我们需要判断开辟的空间是否存满了数据如果存满了我们就需要用“realloc”开辟空间。获取完联系人信息之后不要忘记了给通讯录中内容增加了需要记录。函数代码如下:

//添加通讯录数据
void AddContact(Contact* con)
{
    assert(con);
    if(con->capcity == con->size)
    {
        AddCup(con);
    }

    //获取用户输入的信息
    printf("请输入要添加的人的姓名\n");
    scanf("%s", con->p[con->size].name);
    printf("请输入要添加的人的性别\n");
    scanf("%s", con->p[con->size].gender);
    printf("请输入要添加的人的年龄\n");
    scanf("%d", &(con->p[con->size].age));
    printf("请输入要添加的人的电话\n");
    scanf("%s", con->p[con->size].tel);
    printf("请输入要添加的人的地址\n");
    scanf("%s", con->p[con->size].addr);
    printf("增加成功\n");
    con->size++;
}

        其中函数“AddCup()”,是增加容量的函数,代码如下:

void AddCup(Contact* con)
{
    assert(con);

    PeoInfo* newnode = (PeoInfo*)realloc(con->p, sizeof(PeoInfo) * 2 * con->capcity);
    if(newnode == NULL)
    {
        perror("AddCup realloc fail");
        return;
    }

    con->p = newnode;
    con->capcity *= 2;
}

        首先用“realloc”将容量扩大为原来的两倍,如果开辟失败打印"AddCup realloc fail"的错误信息,然后结束函数,反之就将“newnode”赋给“con”中的“p“。随后将”capcity“也同样置为原来的两倍。

5. 从通讯录中删除联系人

//删除通讯录数据
void DelContact(Contact* con);

        从通讯录中删除联系人,我们需要先判断通讯录是否为空,为空将无法删除。随后输入姓名,然后遍历整个数据表找到对应通讯录中的数据然后删除,删除的方式是将空间后面的数据向前挪动一位,达到删除的效果,最后将“size”减一。函数内容如下:

//删除通讯录数据
void DelContact(Contact* con)
{
    assert(con);
    //检查通讯录
    if(con->size == 0)
    {
        printf("通讯录为空, 无法删除\n");
        return;
    }

    char name[NAME_MAX];
    printf("请输入需要删除联系人的姓名\n");
    scanf("%s", name);

    int i = 0;
    for(i = 0; i < con->size; i++)
    {
        if(0 == strcmp(name, con->p[i].name))
        {
            break;
        }
    }

    if(i == con->size)
    {
        printf("未找到该联系人,程序退出\n");
        return;
    }

    for(; i < con->size - 1; i++)
    {
        con->p[i] = con->p[i + 1];
    }
    printf("删除联系人成功\n");
    con->size--;
}

6. 修改通讯录中联系人的数据

//修改通讯录数据
void ModifyContact(Contact* con);

        修改数据和删除数据内容差不多,也需要先判断通讯录是否为空。如果不为空就遍历通讯录找到对应姓名的联系人,然后修改此处数据即可。如果遍历之后没找到就打印"未找到该联系人,程序退出\n"。函数的结构如下:

//修改通讯录数据
void ModifyContact(Contact* con)
{
    assert(con);
    //检查通讯录
    if(con->size == 0)
    {
        printf("通讯录为空\n");
        return;
    }

    char name[NAME_MAX];
    printf("请输入需要修改的联系人的姓名\n");
    scanf("%s", name);

    int i = 0;
    for(i = 0; i < con->size; i++)
    {
        if(0 == strcmp(name, con->p[i].name))
        {
            printf("找到该联系人了,下标为:%d\n", i);
            //获取用户输入的信息
            printf("请输入要修改的人的姓名\n");
            scanf("%s", con->p[i].name);
            printf("请输入要修改的人的性别\n");
            scanf("%s", con->p[i].gender);
            printf("请输入要修改的人的年龄\n");
            scanf("%d", &(con->p[i].age));
            printf("请输入要修改的人的电话\n");
            scanf("%s", con->p[i].tel);
            printf("请输入要修改的人的地址\n");
            scanf("%s", con->p[i].addr);
            printf("修改成功\n");
            return;
        }
    }

    printf("未找到该联系人,程序退出\n");
}

7. 在通讯录中查找联系人是否存在

//查找通讯录数据
void FindContact(Contact* con);

        这里就比较简单了,前面也讲了遍历通讯录,这里就直接给代码了:

//查找通讯录数据
void FindContact(Contact* con)
{
    assert(con);
    //检查通讯录
    if(con->size == 0)
    {
        printf("通讯录为空\n");
        return;
    }

    char name[NAME_MAX];
    printf("请输入需要查找的联系人的姓名\n");
    scanf("%s", name);

    int i = 0;
    for(i = 0; i < con->size; i++)
    {
        if(0 == strcmp(name, con->p[i].name))
        {
            printf("找到该联系人了,下标为:%d\n", i);
            printf("%-6s %-5s %-5s %-13s %s\n", "姓名", "性别", "年龄", "电话", "地址");
            printf("%-6s %-5s %-5d %-13s %s\n", con->p[i].name, 
                                                con->p[i].gender, 
                                                con->p[i].age, 
                                                con->p[i].tel, 
                                                con->p[i].addr);
            return;
        }
    }

    printf("未找到该联系人,程序退出\n");
}

        这里找到了就打印一下联系人数据。

        其实可以把“i”作为一个返回值,然后这个搜索函数就可以用到删除联系人和修改联系人信息的函数中。

三. 制作通讯录的操作界面

1. 枚举菜单

        之前学过的枚举就可以用在这里,作为操作菜单正好合适。

typedef enum Select
{
    exit_,
    addcontact,
    delcontact,
    showcontact,
    findcontact,
    modifycontact
}Select;

        和枚举相关的菜单如下:

void menu()
{
    printf("*******************************************\n");
    printf("*****  0.exit_        1.addcontact    *****\n");
    printf("*****  2.delcontact   3.showcontact   *****\n");
    printf("*****  4.findcontact  5.modifycontact *****\n");
    printf("*******************************************\n");
    printf("请输入您需要的功能:");
}

2. 通讯录系统主体

        首先创建结构体“Contact”,将它初始化。随后进入选择界面通过输入数据来选择功能,这里枚举所对应得到数字正好就是枚举中的顺序。将枚举写到”switch“选择语句中也能更好的判断功能。循环前创建枚举变量”select“并置为0。进入”do while“循环,因为这个工能至少需要进入一次。然后选择对应功能,如果不在功能内就打印"输入错误请重新输入\n"。如果”select“得到的的值为0就结束循环,销毁通讯录。整体代码如下:

void TestContact()
{
    Contact con;
    InitContact(&con);
    Select select = exit_;
    do
    {
        menu();
        scanf("%d", &select);
        switch(select)
        {
            case exit_:
                printf("退出通讯录\n");
                break;
            case addcontact:
                AddContact(&con);
                break;
            case delcontact:
                DelContact(&con);
                break;
            case showcontact:
                ShowContact(&con);
                break;
            case findcontact:
                FindContact(&con);
                break;
            case modifycontact:
                ModifyContact(&con);
                break;
            default:
                printf("输入错误请重新输入\n");
                break;
        }
    } while (select);
    DestroyContact(&con);
}

四. 检测代码的运行

        首先建立“main”函数,并包括各种头文件:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

int main()
{
    TestContact();
    return 0;
}

        随后运行查看结果。

1. 增加联系人

        选择1,然后输入数据,这里为了方便全部输入11或者22,结果如下:

        从结果来看没有问题。

2. 打印通讯录

        这里先打印通讯录好了,方便检测上面输入是否正确。

        这里可以看到输出无误,只是展示的位置与实际结果有些偏差。

3. 搜索联系人

        这里直接用图片说明:

        找到联系人,和没找到的结果都是对的,函数无误。

4. 修改联系人

       找到11修改为55。

        再次查找11:

        可以看出没有问题,当然读者也可以在过程中增加打印通讯录,来观察是否修改成功,例如。

5. 删除联系人

        删除联系人22,并打印通讯录:

0. 结束通讯录

        输入0结束通讯录。

        可以看到以上代码运行的时候有错误的符号,那是因为我的文本内容编译器不认识,我使用的是“vscode”,它的右下角可以调整,文本内容。这里在用C++的时候忘记调回去了,所以会出现错误码。

        这里修改回来之后重新粘贴文本就好了。

作者结语

        通讯录在C语言中也是比较简单的一环,通过5个模块就完成了工作的分配,其实还能够增加排序模块,或者其他模块,这里不在举例,请读者自行探索。

        作为作者,我的话是比较少的那种,还希望读者多多见谅。先一片博客会讲讲链表的内容,敬请期待。

;