用顺序表实现通讯录:从代码到原理,初学者入门指南

用 C 语言中的顺序表(Sequential List)实现一个简易但功能完整的通讯录管理系统。不仅是一个实用的小项目,更是理解线性数据结构、掌握动态内存管理结构体操作的绝佳实践。

🎯 学习目标:

  • 理解顺序表的基本原理
  • 掌握结构体与动态数组的设计方法
  • 实现通讯录的增、删、查、改、显等核心功能

一、多文件结构设计

和之前一样,为了使代码结构清晰、易于维护,我们采用三个文件来组织代码:

  1. contact_0820.h:头文件,用于声明结构体、宏定义和函数原型
  2. fun_0820.c:源文件,用于实现顺序表的各种操作函数
  3. contact_0820.c:测试文件,用于编写主函数,测试顺序表的各种功能

二、数据结构设计:两个关键结构体

我们将在头文件 contact_0820.h 中定义两个结构体,分别表示单个联系人信息整个通讯录管理器

1. 联系人结构体:Contact

// 定义字段最大长度(便于后期维护)
#define NAME_MAX     50
#define GENDER_MAX   10
#define PHONE_MAX    20

typedef struct {
    char name[NAME_MAX];        // 姓名
    int  age;                   // 年龄
    char gender[GENDER_MAX];    // 性别
    char phonenum[PHONE_MAX];   // 电话号码
} Contact;

💡 小贴士:使用宏定义长度,便于统一修改;避免硬编码。


2. 通讯录管理结构体:Contact_List

这个结构体是“笔记本的封面”,负责管理所有联系人。

#define datatype Contact  // 类型别名,方便后续扩展为其他类型

typedef struct {
    datatype* contacts;   // 动态数组,存储联系人数据
    int size;             // 当前已存储的联系人数量
    int capacity;         // 数组当前最大容量
} Contact_List;

🔍 关键字段说明:

  • contacts:指向堆上分配的内存,保存所有联系人
  • size:表示有效数据个数
  • capacity:表示最大容量,用于判断是否需要扩容

三、核心功能实现(contact_0820.c

接下来我们逐步实现通讯录的各项功能,遵循“初始化 → 增删查改 → 销毁”的生命周期。


1. 初始化通讯录:init_contact_list

首次使用前必须初始化结构体,避免野指针。

void init_contact_list(Contact_List* list) {
    list->contacts = NULL;   // 初始无内存
    list->size = 0;          // 当前人数为0
    list->capacity = 0;      // 容量为0
}

✅ 初始化后即可调用 check_space 自动分配空间。


2. 自动扩容机制:check_space

当容量不足时,自动扩展数组大小,保证插入顺利进行。

void check_space(Contact_List* list) {
    // 若当前数量 >= 容量,则需要扩容
    if (list->size >= list->capacity) {
        // 首次分配100个空间,之后每次翻倍
        int new_capacity = (list->capacity == 0) ? 100 : list->capacity * 2;
        
        datatype* new_contacts = (datatype*)realloc(list->contacts, 
                                                   new_capacity * sizeof(datatype));
        if (new_contacts == NULL) {
            fprintf(stderr, "内存分配失败!程序终止。\n");
            exit(EXIT_FAILURE);
        }

        list->contacts = new_contacts;
        list->capacity = new_capacity;
    }
}

⚠️ 注意:realloc 可能失败,必须检查返回值!


3. 添加联系人:insert_contact

支持在指定位置插入新联系人(如插队、追加等)。

int insert_contact(Contact_List *list, int location, const char *name, int age, const char *gender, const char *phonenum)
{
    check_space(list);
    if (location < 0 || location > list->size)
    {
        fprintf(stderr, "Invalid location\n");
        return -1;
    }
    for (int i = list->size; i > location; i--)
    {
        list->contacts[i] = list->contacts[i - 1];
    }
    list->size++;
    strcpy_s(list->contacts[location].name, name_max,name);
    list->contacts[location].age = age;
    strcpy_s(list->contacts[location].gender,gender_max,gender);
    strcpy_s(list->contacts[location].phonenum, phone_max,phonenum);
    return 0;
}

🔄 插入时间复杂度:O(n),因需移动元素。


4. 显示所有联系人:print_contact_list

遍历并打印所有联系人信息。

void print_contact_list(const Contact_List* list) {
    if (list->size == 0) {
        printf("通讯录为空,暂无联系人。\n");
        return;
    }

    printf("当前共有 %d 位联系人:\n\n", list->size);
    for (int i = 0; i < list->size; i++) {
        printf("  序号: %d:\n", i + 1);
        printf("  姓名: %s\n", list->contacts[i].name);
        printf("  年龄: %d\n", list->contacts[i].age);
        printf("  性别: %s\n", list->contacts[i].gender);
        printf("  电话: %s\n", list->contacts[i].phonenum);
        printf("------------------------\n");
    }
}

✅ 加入提示语和格式美化,提升用户体验。


5. 删除联系人:delete_contact

删除指定位置的联系人:前移后续元素填补空位。

int delete_contact(Contact_List* list, int loc) {
    if (loc < 0 || loc >= list->size) {
        fprintf(stderr, "错误:无法删除位置 %d 的联系人(范围 [0, %d))\n", loc, list->size);
        return -1;
    }

    // 从删除位置开始,前面覆盖后面
    for (int i = loc; i < list->size - 1; i++) {
        list->contacts[i] = list->contacts[i + 1];
    }

    list->size--;  // 数量减1
    return 0;
}

🔄 删除时间复杂度同样是 O(n)。


6. 查找联系人:find_contact

按姓名精确查找,返回索引位置。

int find_contact(const Contact_List* list, const char* name) {
    for (int i = 0; i < list->size; i++) {
        if (strcmp(list->contacts[i].name, name) == 0) {
            printf("找到了\n");
            printf("  位置: 第 %d 位\n", i + 1);
            printf(". 姓名: %s\n", list->contacts[i].name);
            printf("  年龄: %d\n", list->contacts[i].age);
            printf("  性别: %s\n", list->contacts[i].gender);
            printf("  电话: %s\n", list->contacts[i].phonenum);
            return i;  // 返回索引
        }
    }
    printf("未找到名为 '%s' 的联系人。\n", name);
    return -1;
}

🔎 可扩展为模糊搜索或支持多个字段查询。


7. 销毁通讯录:destroy_contact_list

void destroy_contact_list(Contact_List* list) {
    if (list->contacts != NULL) {
        free(list->contacts);
        list->contacts = NULL;
    }
    list->size = 0;
    list->capacity = 0;
    printf("🗑️ 通讯录已销毁,内存释放完成。\n");
}

✅ 必须调用!尤其在长期运行程序中尤为重要。


四、测试驱动:验证功能完整性(test_0820.c

最后,我们编写主函数来测试所有功能。

#include "contact_0820.h"

int main()
{
    Contact_List contact_list;

    init_contact_list(&contact_list);

    insert_contact(&contact_list, 0, "zhangsan", 3, "male", "111-2222-3333"); 
    insert_contact(&contact_list, 0, "lisi", 5, "male", "111-5555-3333");
    insert_contact(&contact_list, contact_list.size, "wangxiaoer", 7, "female", "111-6666-3333");
    insert_contact(&contact_list, contact_list.size, "cuihua", 9, "female", "111-2222-8888");
    insert_contact(&contact_list, contact_list.size-1, "woshidashabi", 11, "male", "123-2222-8888");

    print_contact_list(&contact_list);

    printf("+++++++++++++++++++++++\n");

    delete_contact(&contact_list, 0);
    print_contact_list(&contact_list);

    printf("+++++++++++++++++++++++\n");

    delete_contact(&contact_list, contact_list.size-1);
    print_contact_list(&contact_list);

    find_contact(&contact_list, "woshidashabi");
    printf("+++++++++++++++++++++++\n");
    find_contact(&contact_list, "cuihua");

    destroy_contact_list(&contact_list);
    return 0;
}

✅ 运行结果

当前共有 5 位联系人:

  序号: 1:
  姓名: lisi
  年龄: 5
  性别: male
  电话: 111-5555-3333
------------------------
  序号: 2:
  姓名: zhangsan
  年龄: 3
  性别: male
  电话: 111-2222-3333
------------------------
  序号: 3:
  姓名: wangxiaoer
  年龄: 7
  性别: female
  电话: 111-6666-3333
------------------------
  序号: 4:
  姓名: woshidashabi
  年龄: 11
  性别: male
  电话: 123-2222-8888
------------------------
  序号: 5:
  姓名: cuihua
  年龄: 9
  性别: female
  电话: 111-2222-8888
------------------------
+++++++++++++++++++++++
当前共有 4 位联系人:

  序号: 1:
  姓名: zhangsan
  年龄: 3
  性别: male
  电话: 111-2222-3333
------------------------
  序号: 2:
  姓名: wangxiaoer
  年龄: 7
  性别: female
  电话: 111-6666-3333
------------------------
  序号: 3:
  姓名: woshidashabi
  年龄: 11
  性别: male
  电话: 123-2222-8888
------------------------
  序号: 4:
  姓名: cuihua
  年龄: 9
  性别: female
  电话: 111-2222-8888
------------------------
+++++++++++++++++++++++
当前共有 3 位联系人:

  序号: 1:
  姓名: zhangsan
  年龄: 3
  性别: male
  电话: 111-2222-3333
------------------------
  序号: 2:
  姓名: wangxiaoer
  年龄: 7
  性别: female
  电话: 111-6666-3333
------------------------
  序号: 3:
  姓名: woshidashabi
  年龄: 11
  性别: male
  电话: 123-2222-8888
------------------------
找到了
  位置: 第 3 位
. 姓名: woshidashabi
  年龄: 11
  性别: male
  电话: 123-2222-8888
+++++++++++++++++++++++
未找到名为 'cuihua' 的联系人。

五、总结与思考

通过本次实践,我们完成了以下目标:

✅ 核心收获:

  • 掌握了顺序表作为线性结构的基本实现方式
  • 学会了使用 realloc 进行动态扩容
  • 理解了插入/删除操作中元素移动的逻辑
  • 实践了 C 语言中结构体 + 指针 + 内存管理的综合应用

❗ 局限性分析:

  • 插入/删除效率较低(O(n))
  • 频繁扩容可能带来性能开销
  • 不支持重复姓名(当前查找只返回第一个)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值