C语言实现可排序通讯录:柔性数组与动态扩容详解

季宁
季宁
发布时间:2026-10-16 | 6人浏览过
原创

本文使用C语言构建一个包含增删改查、清空及排序功能的通讯录系统。通过结构体与柔性数组技术实现数据的连续存储,结合动态内存管理(malloc和realloc)解决容量限制问题,并利用标准库函数qsort实现快速排序,提供完整的头文件、源文件及主程序代码。

设计思路与目标

本实例旨在实现一个支持多种操作的命令行通讯录。核心功能包括:

  1. 显示菜单界面
  2. 添加联系人(需检测重复及内存不足时的自动扩容)
  3. 删除联系人
  4. 查找联系人
  5. 修改联系人信息
  6. 显示所有联系人列表
  7. 清空当前通讯录
  8. 按姓名对联系人进行排序

数据结构与头文件定义

在开始编写逻辑前,需要定义联系人的基本属性以及通讯录容器的结构。这里使用person_t存储单个联系人信息,使用contact_t作为容器结构体。

关键设计点:

  • 使用宏定义固定字段长度,如姓名最大32字节,电话16字节等。
  • 通讯录结构体尾部使用person_t friends[0],这是C99标准中的柔性数组(Flexible Array Member),允许在分配内存时只指定结构体头部大小,后续紧接存放数组元素,避免指针间接访问,提高缓存命中率。
  • 引入cap表示总容量,size表示当前已用数量,用于判断是否满员。
#ifndef _CONTACT_H_
#define _CONTACT_H_

// 定义各字段最大长度
#define NAME_SIZE 32
#define SEX_SIZE 8
#define TELPHONE_SIZE 16
#define ADDRESS_SIZE 128

#include <stdio.h>
#include <Windows.h>
#include <assert.h>
#pragma warning(disable:4996)

// 初始容量设定
#define INIT_NUM 1000 // 初始分配空间
#define INC_SIZE 5    // 每次扩容增加的数量

// 个人联系人结构体
typedef struct person {
    char name[NAME_SIZE];
    char sex[SEX_SIZE];
    int age;
    char telphone[TELPHONE_SIZE];
    char address[ADDRESS_SIZE];
} person_t;

// 通讯录容器结构体(使用柔性数组)
typedef struct contact {
    FILE *save;
    int cap;    // 总容量
    int size;   // 当前有效人数
    person_t friends[0]; // 柔性数组成员
} contact_t;

// 函数声明
void InitContact(contact_t **ct);
void AddFriend(contact_t **ct);
void ShowContact(contact_t *ct);
void ModFriend(contact_t *ct);
void SearchFriend(contact_t *ct);
void ClearContact(contact_t *ct);
void DelFriend(contact_t *ct);
void SortContact(contact_t *ct);
#endif

初始化与动态扩容机制

通讯录创建时需分配内存,当空间不足时需进行扩容。由于使用了柔性数组,不能直接对数组指针调用realloc,而是对整个结构体指针进行重分配。

第1步:初始化通讯录

目的:分配初始内存并重置计数器。
入口:InitContact函数
动作:使用malloc分配sizeof(contact_t) + INIT_NUM * sizeof(person_t)大小的内存。若分配失败则报错退出;成功则设置size=0,cap=INIT_NUM。

void InitContact(contact_t **ct) {
    *ct = (contact_t *)malloc(sizeof(contact_t) + INIT_NUM * sizeof(person_t));
    if (*ct == NULL) {
        perror("malloc");
        exit(1);
    }
    (*ct)->size = 0;
    (*ct)->cap = INIT_NUM;
}

第2步:实现自动扩容

目的:当通讯录已满且用户尝试添加新人时,扩大存储空间。
动作:使用realloc调整内存块大小,新大小为sizeof(contact_t) + ((*)->cap + INC_SIZE) * sizeof(person_t)。若realloc返回空指针,说明内存不足,返回0;否则更新指针并增加cap值。

static int Inc(contact_t **ct) {
    assert(ct);
    contact_t *ct_temp = (contact_t *)realloc(*ct, sizeof(contact_t) + ((*ct)->cap + INC_SIZE) * sizeof(person_t));
    if (ct_temp == NULL) {
        return 0;
    }
    *ct = ct_temp;
    (*ct)->cap += INC_SIZE;
    printf("自动扩容成功\n");
    return 1;
}

第3步:辅助检查函数

判断是否已满:IsFull,比较size与cap。
判断是否为空:IsEmpty,检查size是否为0。

static int IsFull(contact_t *ct) { return ct->cap == ct->size; }
static int IsEmpty(contact_t *ct) { return ct->size == 0; }

核心业务逻辑实现

第4步:添加联系人

目的:向通讯录中插入新记录,并处理去重和扩容。
流程:先输入信息构造临时变量p,检查是否存在同名联系人(防止重复)。若未满或扩容成功,则将数据复制到通讯录中,并增加size。

void AddFriend(contact_t **ct) {
    assert(ct);
    // 若未满 或 扩容成功,才执行插入
    if (!IsFull(*ct) || Inc(ct)) {
        person_t p;
        printf("请输入新增用户的姓名: "); scanf("%s", p.name);
        printf("请输入新增用户的性别: "); scanf("%s", p.sex);
        printf("请输入新增用户的年纪: "); scanf("%d", &(p.age));
        printf("请输入新增用户的电话: "); scanf("%s", p.telphone);
        printf("请输入新增用户的地址: "); scanf("%s", p.address);

        // 查重
        if (IsExist(*ct, &p)) {
            printf("%s 已经存在,请不要重复插入\n", p.name);
            return;
        }
        memcpy((*ct)->friends + (*ct)->size, &p, sizeof(p));
        (*ct)->size += 1;
        printf("新增用户%s成功\n", p.name);
    } else {
        printf("扩容失败\n");
    }
}

第5步:查找联系人

目的:根据姓名检索联系人并打印详情。
动作:调用内部辅助函数SearchCore遍历数组,若找到返回索引,否则返回-1。主函数根据索引打印格式化表格或提示不存在。

// 核心查找逻辑
static int SearchCore(contact_t *ct, const char *name) {
    assert(ct);
    for (int i = 0; i < ct->size; i++) {
        if (strcmp(name, ct->friends[i].name) == 0) {
            return i;
        }
    }
    return -1;
}

void SearchFriend(contact_t *ct) {
    assert(ct);
    char name[NAME_SIZE];
    printf("请输入你要查找的人的姓名: ");
    scanf("%s", name);
    int i = SearchCore(ct, name);
    printf("|%-10s|%-10s|%-10s|%-10s|%10s|\n", "姓名", "性别", "年纪", "电话", "地址");
    if (i >= 0) {
        person_t *p = ct->friends + i;
        printf("| %-10s | %-10s | %-10d | %-10s | %10s | \n", p->name, p->sex, p->age, p->telphone, p->address);
    } else {
        printf("你要查找的人%s不存在\n", name);
    }
}

第6步:修改联系人

目的:定位到特定联系人后覆盖其字段。
注意:需先获取目标索引,直接修改指针指向的结构体成员即可,无需再次复制。

void ModFriend(contact_t *ct) {
    assert(ct);
    char name[NAME_SIZE];
    printf("请输入你要修改的人的姓名: ");
    scanf("%s", name);
    int i = SearchCore(ct, name);
    if (i >= 0) {
        person_t *p = ct->friends + i;
        printf("请输入修改后的姓名: "); scanf("%s", p->name);
        printf("请输入修改后的性别: "); scanf("%s", p->sex);
        printf("请输入修改后的年纪: "); scanf("%d", &(p->age));
        printf("请输入修改后的电话: "); scanf("%s", p->telphone);
        printf("请输入修改后的地址: "); scanf("%s", p->address);
        printf("修改用户%s成功\n", p->name);
    } else {
        printf("你要修改的联系人%s不存在\n", name);
    }
}

第7步:删除与清空

删除策略:为简化操作,不保持原有顺序,直接将最后一个元素的数据覆盖到待删除元素的位置,然后将size减1。这是一种高效的删除方式。
清空策略:只需将size设为0,物理内存保持不变,下次添加可直接复用。

void DelFriend(contact_t *ct) {
    assert(ct);
    char name[NAME_SIZE];
    printf("请输入你要删除的人的姓名: ");
    scanf("%s", name);
    int i = SearchCore(ct, name);
    if (i >= 0) {
        ct->friends[i] = ct->friends[ct->size - 1];
        ct->size -= 1;
        printf("删除成功\n");
    } else {
        printf("你要删除的人%s不存在\n", name);
    }
}

void ClearContact(contact_t *ct) {
    assert(ct);
    ct->size = 0;
}

排序功能实现

C语言标准库提供了qsort函数用于快速排序。我们需要提供一个比较函数,该函数接收两个const void *类型的参数。

static int CmpPerson(const void *p1, const void *p2) {
    assert(p1 && p2);
    person_t *_p1 = (person_t *)p1;
    person_t *_p2 = (person_t *)p2;
    // 按姓名字符串字典序排序
    return strcmp(_p1->name, _p2->name);
}

void SortContact(contact_t *ct) {
    assert(ct);
    if (!IsEmpty(ct)) {
        qsort(ct->friends, ct->size, sizeof(person_t), CmpPerson);
    }
}

显示通讯录

遍历size个元素,按表头格式输出每一项的信息,便于用户查看当前状态。

void ShowContact(contact_t *ct) {
    assert(ct);
    printf("|cap:%d|size:%d|\n", ct->cap, ct->size);
    printf("|%-10s|%-10s|%-10s|%-10s|%10s|\n", "姓名", "性别", "年纪", "电话", "地址");
    for (int i = 0; i < ct->size; i++) {
        person_t *p = ct->friends + i;
        printf("|%-10s|%-10s|%-10d|%-10s|%10s|\n", p->name, p->sex, p->age, p->telphone, p->address);
    }
}

主程序交互流程

主函数中构建无限循环,调用菜单函数展示选项,并根据用户输入的整数分支调用对应的业务函数。

#include "contact.h"

void ShowMenu() {
    printf("#######################\n");
    printf("# 1.Add 2.Del 3.Search #\n");
    printf("# 4.Mod 5.Show 6.Clear #\n");
    printf("# 7.Sort      0.Exit #\n");
    printf("#######################\n");
    printf("please select: ");
}

int main() {
    contact_t *ct = NULL;
    InitContact(&ct);
    int quit = 0;
    while (!quit) {
        int select = 0;
        ShowMenu();
        scanf("%d", &select);
        switch (select) {
            case 1: AddFriend(&ct); break;
            case 2: DelFriend(ct); break;
            case 3: SearchFriend(ct); break;
            case 4: ModFriend(ct); break;
            case 5: ShowContact(ct); break;
            case 6: ClearContact(ct); break;
            case 7: SortContact(ct); break;
            case 0: return 0;
            default: break;
        }
    }
    system("pause");
    return 0;
}

以上就是C语言实现可排序通讯录的详细内容,更多关于C语言数据结构开发的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表