小白学习顺序表 之 通讯录实现

#前言

在日常编程学习中,实现一个联系人管理系统。

它不仅能让我们熟悉结构体、动态内存分配、文件操作等知识点,还能帮助我们更好地理解程序的模块化设计。

一、整体架构

这个联系人管理系统主要由以下几个文件组成:

contactTest.cpp:主函数所在文件,负责程序的入口和菜单的显示,以及用户操作的分发。

SeqList.h:定义了顺序表的结构和相关操作的函数声明。

contact.h:定义了联系人的信息结构和联系人管理的相关函数声明。

SeqList.cpp:实现了顺序表的各种操作,如初始化、插入、删除等。

contact.cpp:实现了联系人的添加、删除、修改、查找等具体功能,以及联系人信息的文件读写操作。

二、核心数据结构

1. 联系人信息结构体(Info

复制代码
typedef struct SLcontact {
    char name[NAME_MAX];
    int age;
    char gender[SEX_MAX];
    char phone[TEL_MAX];
    char addr[ADDR_MAX];
} Info;

这个结构体用于存储联系人的基本信息,包括姓名、年龄、性别、电话和地址。

2. 顺序表结构体(SL

复制代码
typedef struct SeqList
{
    SLDataType* a;
    int size;      // 元素个数
    int capacity;  // 容量
} SL;

顺序表是一种线性数据结构,这里用它来存储联系人信息。

a 是一个指向 SLDataType 类型的指针,用于动态分配内存;

size 表示当前顺序表中元素的个数;capacity 表示顺序表的容量。

三、主要操作及原理

1. 顺序表初始化(SLInit

复制代码
void SLInit(SL* ps) {
    ps->a = NULL;
    ps->capacity = ps->size = 0;
}

这个函数将顺序表的指针 a 置为 NULL,并将容量和元素个数都初始化为 0。

2. 顺序表扩容(SLdepend

复制代码
void SLdepend(SL* ps) {
    if (ps->capacity == ps->size) {
        int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
        SLDataType* ret = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
        if (ret == NULL) {
            perror("realloc");
            exit(1);
        }
        ps->a = ret;
        ps->capacity = newcapacity;
    }
}

当顺序表的容量和元素个数相等时,需要进行扩容。

这里采用的是每次扩容为原来的 2 倍。

使用 realloc 函数重新分配内存,如果分配失败,会输出错误信息并退出程序。

3. 顺序表尾部插入(SLPushBack

复制代码
void SLPushBack(SL* ps, SLDataType x) {
    assert(ps);
    SLdepend(ps);
    ps->a[ps->size++] = x;
}

在顺序表的尾部插入一个元素。首先使用 assert 函数确保指针 ps 不为 NULL,然后调用 SLdepend 函数检查是否需要扩容,最后将元素插入到顺序表的尾部,并将元素个数加 1。

4. 顺序表头部插入(SLPushFront

复制代码
void SLPushFront(SL* ps, SLDataType x) {
    assert(ps);
    SLdepend(ps);
    for (int i = ps->size; i > 0; i--) {
        ps->a[i] = ps->a[i - 1];
    }
    ps->a[0] = x;
    ps->size++;
}

在顺序表的头部插入一个元素。同样先确保指针不为 NULL 并检查是否需要扩容,然后将顺序表中的所有元素依次向后移动一位,最后将元素插入到头部,并将元素个数加 1。

例如,原始顺序表:[A, B, C]
步骤 1:i=3,a [3] = a [2] → [A, B, C, C]
步骤 2:i=2,a [2] = a [1] → [A, B, B, C]
步骤 3:i=1,a [1] = a [0] → [A, A, B, C]
插入元素 X 后:[X, A, B, C]

5. 顺序表尾部删除(SLPopBack

复制代码
void SLPopBack(SL* ps) {
    assert(ps);
    assert(ps->size > 0);
    ps->size--;  // 逻辑删除,只减少元素个数
}

删除顺序表的尾部元素。

使用 assert 函数确保指针不为 NULL 且顺序表中至少有一个元素,然后将元素个数减 1。

6. 顺序表头部删除(SLPopFront

复制代码
void SLPopFront(SL* ps) {
    assert(ps);
    assert(ps->size > 0);

    // 将后面的元素依次向前移动一位
    for (int i = 0; i < ps->size - 1; i++) {
        ps->a[i] = ps->a[i + 1];
    }

    ps->size--;  // 减少元素个数
}

删除顺序表的头部元素。

先确保指针不为 NULL 且顺序表中至少有一个元素,然后将顺序表中的所有元素依次向前移动一位,最后将元素个数减 1。

例如,原始顺序表:[A, B, C]
步骤 1:i=0,a [0] = a [1] → [B, B, C]
步骤 2:i=1,a [1] = a [2] → [B, C, C]
删除后:[B, C]

7. 联系人添加(Addcontact

复制代码
void Addcontact(Cont* pcon) {
    Info info;

    printf("请输入联系人的姓名:");
    scanf_s("%s", info.name, (unsigned)_countof(info.name));
    printf("请输入联系人的年龄:");
    scanf_s("%d", &info.age);
    printf("请输入联系人的性别:");
    scanf_s("%s", info.gender, (unsigned)_countof(info.gender));
    printf("请输入联系人的电话:");
    scanf_s("%s", info.phone, (unsigned)_countof(info.phone));
    printf("请输入联系人的地址:");
    scanf_s("%s", info.addr, (unsigned)_countof(info.addr));

    SLPushBack(pcon, info);
}

该函数用于添加联系人信息。

首先创建一个 Info 类型的变量 info,然后通过 scanf_s 函数从用户输入中获取联系人的姓名、年龄、性别、电话和地址,

最后调用 SLPushBack 函数将联系人信息插入到顺序表的尾部。

8. 联系人删除(Delcontact

复制代码
void Delcontact(Cont* pcon) {
    printf("请输入要删除的联系人的姓名:\n");
    char name[NAME_MAX];
    scanf_s("%s", name, (unsigned)_countof(name)); 

    int findIndex = FindByName(pcon, name);
    if (findIndex < 0) {
        printf("要删除的联系人不存在!\n");
        return;  
    }

    SLErase(pcon, findIndex);
    printf("联系人删除成功!\n");
}

该函数用于删除指定姓名的联系人。

首先获取用户输入的联系人姓名,然后调用 FindByName 函数查找该联系人在顺序表中的位置,

如果找不到则输出提示信息并返回;

如果找到,则调用 SLErase 函数删除该联系人,并输出删除成功的提示信息。

9. 联系人修改(Modifycontact

复制代码
void Modifycontact(Cont* pcon) {
    char name[NAME_MAX];
    printf("请输入要修改的联系人的姓名:\n");
    scanf_s("%s", name);

    int findIndex = FindByName(pcon, name);
    if (findIndex < 0) {
        printf("要修改的联系人不存在!\n");
        return;
    }
    printf("请输入新的姓名:\n");
    scanf_s("%s", pcon->a[findIndex].name);
    printf("请输入新的年龄:\n");
    scanf_s("%d", &pcon->a[findIndex].age);
    printf("请输入新的性别:\n");
    scanf_s("%s", pcon->a[findIndex].gender);
    printf("请输入新的电话:\n");
    scanf_s("%s", pcon->a[findIndex].phone);
    printf("请输入新的地址:\n");
    scanf_s("%s", pcon->a[findIndex].addr);

    printf("联系人修改成功!\n");
}

该函数用于修改指定姓名的联系人信息。

首先获取用户输入的联系人姓名,然后调用 FindByName 函数查找该联系人在顺序表中的位置,

如果找不到则输出提示信息并返回;

如果找到,则通过 scanf_s 函数获取用户输入的新信息,并更新该联系人的信息,最后输出修改成功的提示信息。

10. 联系人查找(Findcontact

复制代码
void Findcontact(Cont* pcon) {
    char name[NAME_MAX];
    printf("请输入要查找的用户的姓名:\n");
    scanf_s("%s", name);

    int findIndex = FindByName(pcon, name);
    if (findIndex < 0) {
        printf("该联系人不存在!\n");
        return;
    }
    // 找到了,打印一下找到的联系人的信息
    printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "地址");
    printf("%s %s %d %s %s\n",
        pcon->a[findIndex].name,
        pcon->a[findIndex].gender,
        pcon->a[findIndex].age,
        pcon->a[findIndex].phone,
        pcon->a[findIndex].addr
    );
}

该函数用于查找指定姓名的联系人信息。

首先获取用户输入的联系人姓名,然后调用 FindByName 函数查找该联系人在顺序表中的位置,

如果找不到则输出提示信息并返回;如果找到,则输出该联系人的信息。

11. 联系人信息保存到文件(SaveContactToFile

复制代码
void SaveContactToFile(Cont* pcon, const char* filename) {
    FILE* file = fopen(filename, "wb");  // 以二进制写模式打开文件
    if (file == NULL) {
        perror("无法打开文件");
        return;
    }

    // 写入联系人数量
    fwrite(&pcon->size, sizeof(int), 1, file);

    // 写入每个联系人
    for (int i = 0; i < pcon->size; i++) {
        fwrite(&pcon->a[i], sizeof(Info), 1, file);
    }

    fclose(file);
}

该函数用于将联系人信息保存到文件中。

首先以二进制写模式打开文件,如果打开失败则输出错误信息并返回;

然后写入联系人的数量,再依次写入每个联系人的信息;最后关闭文件。

12. 从文件中加载联系人信息(LoadContactFromFile

复制代码
void LoadContactFromFile(Cont* pcon, const char* filename) {
    FILE* file = fopen(filename, "rb");  // 以二进制读模式打开文件
    if (file == NULL) {
        printf("文件不存在,初始化通讯录\n");
        return;
    }

    // 销毁原有数据
    SLDestroy(pcon);
    SLInit(pcon);

    // 读取联系人数量
    int count;
    fread(&count, sizeof(int), 1, file);

    // 读取每个联系人
    for (int i = 0; i < count; i++) {
        Info info;
        fread(&info, sizeof(Info), 1, file);
        SLPushBack(pcon, info);
    }

    fclose(file);
}

该函数用于从文件中加载联系人信息。

首先以二进制读模式打开文件,如果文件不存在则输出提示信息并返回;

然后销毁原有数据并重新初始化顺序表,接着读取联系人的数量,再依次读取每个联系人的信息并插入到顺序表中;最后关闭文件。

四、注意事项

动态内存管理 :在使用 realloc 函数进行内存扩容时,要注意检查内存分配是否成功,避免出现内存泄漏。

文件操作 :在进行文件读写操作时,要确保文件能够正常打开和关闭,避免数据丢失。

输入验证 :在获取用户输入时,要考虑输入的合法性,避免出现程序崩溃或数据错误。

函数调用 :在调用函数时,要确保传递的参数类型和数量正确,避免出现编译错误或运行时错误。

五、总结知识点

结构体 :使用结构体来组织和存储联系人的信息,方便对联系人进行管理。

顺序表 :顺序表是一种线性数据结构,通过动态内存分配实现了可变长度的存储。

动态内存管理 :使用 malloc、realloc 和 free 函数进行动态内存的分配和释放。

文件操作 :使用 fopen、fread、fwrite 和 fclose 函数进行文件的读写操作。

模块化设计 :将不同的功能封装成不同的函数,提高了代码的可读性和可维护性。

相关推荐
丶Darling.7 分钟前
Day126 | 灵神 | 二叉树 | 层数最深的叶子结点的和
数据结构·c++·算法·二叉树·深度优先
清风徐来QCQ12 分钟前
python语法学习
学习
miaoyumeng_wn27 分钟前
5月21日学习笔记
笔记·学习·oracle
岂是尔等觊觎1 小时前
PCB设计教程【入门篇】——电路分析基础-基本元件(二极管三极管场效应管)
经验分享·笔记·嵌入式硬件·学习·pcb工艺
梁下轻语的秋缘1 小时前
每日c/c++题 备战蓝桥杯(洛谷P4715 【深基16.例1】淘汰赛 题解)
c语言·c++·蓝桥杯
梁下轻语的秋缘1 小时前
每日c/c++题 备战蓝桥杯(洛谷P1873 EKO砍树问题详解)
c语言·c++·蓝桥杯
拾忆-eleven1 小时前
NLP学习路线图(二): 概率论与统计学(贝叶斯定理、概率分布等)
学习·自然语言处理·概率论
清晨朝暮2 小时前
【Linux 学习计划】-- git 在Linux远端服务器上的部署与简单使用
学习
奕天者2 小时前
计算机网络学习(三)——HTTP
学习·计算机网络·http
蓝心湄3 小时前
C语言-枚举
c语言·开发语言·算法