C语言通讯录实战:从静态结构体到动态内存与文件持久化

Cora
Cora
发布时间:2026-10-15 | 4人浏览过
原创

通过结构体定义人员信息,利用顺序表逻辑实现通讯录的增删查改功能。文章逐步演示如何将静态存储升级为动态内存分配以突破人数上限,并结合文件二进制读写技术实现数据持久化,提供完整的C语言底层开发思路与代码实现。

一、基础版本:静态结构与菜单逻辑

实现通讯录的核心在于数据结构的设计与控制流程的划分。我们需要定义存储单条信息的结构体,以及包含所有信息和数量统计的通讯录容器,配合菜单驱动用户交互。

第1步:定义人员信息与通讯录结构

使用 struct 定义单个联系人的详细信息,包括姓名、年龄、性别、电话和地址。同时创建一个容器结构体,内部包含一个固定大小的结构体数组用于存储成员,以及一个整型变量记录当前人数。

typedef struct peoInfo 
{
     char name[20]; //姓名
     int age;       //年龄
     char sex[10];  //性别
     char tele[12]; //手机号
     char addr[30]; //地址
 }peoInfo;

// 静态版本:固定大小数组
typedef struct Contact
{
    peoInfo data[100]; //人员名单 结构体数组
    int count;         //人员数量
}Contact;

第2步:初始化通讯录

在程序启动或调用前,需将通讯录状态重置。使用 assert 确保指针有效,将计数归零,并清空数组内存以避免脏数据。

void InitContact(Contact* con)
{
    assert(con);
    con->count = 0; //初始人数为0
    memset(con->data, 0, sizeof(con->data)); 
}

第3步:设计交互式菜单

使用枚举类型定义操作选项,并在主循环中打印菜单供用户选择。常见的功能包括添加(add)、删除(del)、查找(serch)、修改(modify)、展示(show)、排序(sort)及退出(exit)。

enum a
{
    ext, add, del, serch, modify, show, sort
};

void menu(void)
{
    printf("===============================\n");
    printf("====1.add  2.del  3.serch=====\n");
    printf("=====4.modify   5.show======\n");
    printf("=====6.sort     0.exit======\n");
    printf("===============================\n");
}

第4步:主函数控制流

创建通讯录实例,进入死循环等待用户输入。根据输入值切换不同的功能分支,调用对应的处理函数。

int main()
{
    int choice; 
    Contact con; 
    InitContact(&con); // 确保先初始化

    do
    {
        menu();
        printf("请选择功能\n");
        scanf("%d", &choice);
        switch(choice)
        {
            case add: AddContact(&con); break;
            case del: DelContanct(&con); break;
            case serch: SerContact(&con); break;
            case modify: ModContact(&con); break;
            case show: ShowContact(&con); break;
            case sort: SorContact(&con); break;
            case ext: printf("程序结束\n"); break;
            default: printf("输入有误请重新输入\n");
        }
    } while (choice); // 选择0则退出循环
    return 0;
}

二、核心功能实现细节

第5步:实现添加联系人

增加人员前,必须检查当前人数是否已达数组上限 Max(即100)。若未满,依次录入各字段信息,并将计数器加1。

void AddContact(Contact* con)
{
    assert(con);
    if (con->count == 100) // 假设Max定义为100
    {
        printf("存放已满\n");
        return;
    }
    // ... 此处省略scanf输入部分,实际编码需严格检查缓冲区溢出 ...
    // scanf("%s", con->data[con->count].name); 
    // ... 其他字段输入 ...
    con->count++;
    printf("增加成功\n");
}

第6步:实现查找与删除

查找通常按姓名进行。遍历数组,使用 strcmp 比对姓名。找到返回下标,未找到返回-1。删除时,利用找到的下标,将该位置之后的所有元素向前移动一位覆盖,最后计数器减1。

// 查找辅助函数
static int FindByName(Contact* con, char name[])
{
    assert(con);
    for(int i=0; icount; i++)
    {
        if (0 == strcmp(con->data[i].name, name))
            return i;
    }
    return -1;
}

// 删除函数
void DelContanct(Contact* con)
{
    char name[20] = {0};
    printf("请输入你想删除的名字\n");
    // scanf "%s" ...
    int pos = FindByName(con, name);
    if (pos == -1)
    {
        printf("没有你要删除的人\n");
    }
    else
    {
        for(int i=pos; icount-1; i++)
        {
            con->data[i] = con->data[i+1]; // 后续元素前移
        }
        con->count--; 
        printf("删除成功\n");
    }
}

第7步:展示与排序

展示功能遍历数组打印各字段,注意格式化对齐。排序可使用标准库函数 qsort,传入比较函数指针,实现对姓名字符串的字典序排序。

int cmp_stu_by_name(const void* e1, const void* e2)
{
    return strcmp(((peoInfo*)e1)->name, ((peoInfo*)e2)->name);
}

void SorContact(Contact* con)
{
    qsort(con->data, con->count, sizeof(peoInfo), cmp_stu_by_name);
}

三、进阶优化:动态内存管理

静态数组限制了人数上限。进阶版通过指针代替数组,使用 calloc 初始分配,realloc 在满员时扩容,实现无限容纳。

第8步:修改数据结构与初始化

将 data 字段改为指针 peoInfo *data,并新增容量字段 capicity。初始化时分配少量空间(如3个单位)。

typedef struct Contact
{
    peoInfo *data;      // 指针替代数组
    int count;          // 当前人数
    int capicity;       // 当前总容量
}Contact;

void InitContact(Contact* con)
{
    assert(con);
    con->count = 0;
    // 初始分配3个空间,calloc自动清零
    con->data = (peoInfo*)calloc(3, sizeof(peoInfo)); 
    if(con->data == NULL)
    {
        perror("Init Contact");
        return;
    }
    con->capicity = 3;
}

第9步:动态扩容策略

在添加联系人时,先判断 count 是否等于 capicity。若相等,调用 realloc 扩大两倍容量。注意检查 realloc 返回值,成功再赋值,失败保留原指针防泄露。

void AddContact(Contact* con)
{
    assert(con);
    // 检查是否需要扩容
    if(con->count == con->capicity)
    {
        peoInfo* ptr = (peoInfo*)realloc(con->data, (con->capicity * 2) * sizeof(peoInfo));
        if(ptr != NULL)
        {
            con->data = ptr;
            con->capicity *= 2;
        }
        else
        {
            perror("Reallocation Failed");
            return;
        }
    }
    // ... 录入信息 ...
    con->count++;
}

四、数据持久化:文件读写

程序退出后动态内存释放,数据会丢失。通过将数据写入文件(Save)和从文件加载数据(Load),可实现通讯录信息的长期保存。

第10步:保存数据到文件

使用 fopen 以二进制写模式 (wb) 打开文件。遍历通讯录,使用 fwrite 每次写入一个人员结构体。操作完成后务必 fclose。

void SaveContact(Contact* con)
{
    FILE* pfWrite = fopen("Contact.dat", "wb"); 
    if(pfWrite == NULL)
    {
        perror("SaveContact");
        return;
    }
    for(int i=0; icount; i++)
    {
        fwrite(&con->data[i], sizeof(peoInfo), 1, pfWrite);
    }
    fclose(pfWrite);
    pfWrite = NULL;
}

第11步:从文件加载数据

以二进制读模式 (rb) 打开文件。读取时需注意:文件中的数据可能超过当前分配的内存容量。因此每读取一条记录,都要检查是否需要扩容,然后再存入 data 数组并增加计数。建议使用临时变量接收读取结果。

void LoadContact(Contact* con)
{
    FILE* pfRead = fopen("Contact.dat", "rb");
    if(pfRead == NULL)
    {
        perror("LoadContact");
        return;
    }
    
    peoInfo tmp = {0};
    // 逐个读取直到文件末尾
    while(fread(&tmp, sizeof(peoInfo), 1, pfRead) == 1)
    {
        // 加载时也可能需要扩容
        if(con->count == con->capicity)
        {
             peoInfo* ptr = (peoInfo*)realloc(con->data, (con->capicity * 2) * sizeof(peoInfo));
             if(ptr != NULL)
             {
                 con->data = ptr;
                 con->capicity *= 2;
             }
        }
        // 存入通讯录
        con->data[con->count] = tmp;
        con->count++;
    }
    fclose(pfRead);
}

以上就是C语言通讯录从基础静态版本到动态内存管理及文件持久化的详细内容。通过掌握结构体应用、动态内存操作及文件IO,能够构建出健壮且实用的数据存储系统。更多关于C语言底层开发的技巧与案例请关注本站其它相关文章!

相关标签:

本站声明

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

Python 相关产品和应用

全部下载

专题列表