C语言链表实战:构建电话簿管理系统

周末不加班
周末不加班
发布时间:2026-10-16 | 1人浏览过
原创

通过C语言实现电话簿管理系统的完整教程。文章详细解析如何利用单向链表动态管理联系人数据,实现了包含初始创建、追加添加、按名/号查找、双维度排序以及文件持久化保存等功能。提供带有详细注释的结构体定义与函数实现代码,适合C语言进阶学习者参考。

项目目标与功能概述

C语言入门后,编写“电话簿”或“通讯录”是理解指针、内存管理和链表结构的经典练习。本系统旨在解决手动输入繁琐且无法持久化的问题,通过命令行界面提供以下核心功能:

  • 初始创建:交互式输入联系人信息,构建初始链表。
  • 追加添加:在现有数据基础上增加新联系人。
  • 删除记录:根据姓名或号码定位并移除特定节点。
  • 查看与显示:遍历链表展示所有成员信息,支持分页。
  • 查找功能:支持按“姓名”或“电话号码”精确匹配查询。
  • 排序功能:支持按姓名字母顺序或电话号码升序排列。
  • 数据持久化:将内存中的数据写入二进制文件(dianhuabu.dat),下次启动可自动加载。

核心结构设计

由于联系人的数量是不确定的,使用固定大小的数组难以满足需求,因此采用单向链表作为数据存储结构。链表的优点在于可以动态分配内存,随时增删节点而不必移动大量数据。

第1步:定义联系人结构体

结构体需要包含基本信息字段以及指向下一个节点的指针。为了提高代码可维护性,使用 typedef 简化类型声明。

#define _CRT_SECURE_NO_WARNINGS
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct phone_book {
    char job[30];      // 工作单位
    char number[20];   // 电话号码
    char name[15];     // 姓名拼音
    char email[30];    // 电子邮箱
    struct phone_book* next; // 指向下一个节点的指针
} phone_book;

#define LEN sizeof(phone_book) // 节点大小宏定义

关键功能实现解析

第2步:创建电话簿(初始化链表)

创建过程涉及动态内存分配和头尾指针的管理。程序进入循环,直到用户输入特定的结束标志(如名字为 0)才停止录入。

  • 入口:creat_list() 函数。
  • 动作:
    1. 初始化头指针 head = NULL。
    2. 分配第一个节点内存 p1 = (phone_book*)malloc(LEN)。
    3. 循环读取输入:若名字为 0 则跳出;否则填充数据。
    4. 第一个节点直接赋值给 head;后续节点通过尾指针 p2->next = p1 连接。
    5. 最后将尾节点的 next 设为 NULL,确保链表闭合。
  • 完成状态:返回指向链表首节点的指针 head。

第3步:删除联系人(链表节点释放)

删除操作相对复杂,需区分待删除节点是否为头节点。若为头节点,直接将 head 指向下一个节点即可;若非头节点,需保留前驱节点的引用以维持链表连续性。

// 伪代码逻辑示意
if (p == head) {
    head = p->next; // 头节点变动
} else {
    p1->next = p->next; // 前驱节点跳过当前节点
}
free(p); // 释放内存

注意:在实际代码中,需先遍历找到目标节点及其前驱节点,确认存在后再执行释放,防止野指针或内存泄漏。

第4步:数据查找与排序

查找:遍历链表,对比姓名或号码字符串。若遍历至 NULL 仍未找到,则提示未收录。

排序:原代码实现采用了临时数组法而非纯指针交换法。先将链表数据拷贝至静态数组 phone_book a[1000],使用冒泡排序算法对数组进行排序,再输出结果。这种方法代码实现简单,但不改变原链表结构,仅用于展示。

完整源代码

以下为整合了上述逻辑的完整 C 语言代码。包含菜单交互、文件读写(fopen, fread, )及基础的功能模块。请确保编译环境支持 gets_s(Visual Studio 等 MSVC 编译器)或替换为标准库安全的输入方式。

#define _CRT_SECURE_NO_WARNINGS
#include <string.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <malloc.h>

typedef struct phone_book {
    char job[30];
    char number[20];
    char name[15];
    char email[30];
    struct phone_book* next;
} phone_book;

#define LEN sizeof(phone_book)

// ================= 链表基本操作 =================

// 创建链表(覆盖式创建)
phone_book* creat_list() {
    phone_book* head, * p1, * p2;
    char name[15];
    int n = 0;
    head = NULL;
    p1 = (phone_book*)malloc(LEN);
    p2 = p1;
    while (1) {
        printf("请输入姓名(姓名为0时停止创建):"); gets_s(p1->name);
        if (strcmp(p1->name, "0") == 0) break;
        printf("请输入电话号码:"); gets_s(p1->number);
        printf("请输入工作单位:"); gets_s(p1->job);
        printf("请输入E-mail:"); gets_s(p1->email);
        n = n + 1;
        if (n == 1) head = p1;
        else p2->next = p1;
        p2 = p1;
        p1 = (phone_book*)malloc(LEN);
    }
    p2->next = NULL;
    return head;
}

// 打印链表
void print_list(phone_book* head) {
    void menu();
    int n = 0;
    printf("现在通讯录中有如下成员:");
    while (head != NULL) {
        printf("\n名字:"); puts(head->name);
        printf("\n电话号码:"); puts(head->number);
        printf("\n工作单位:"); puts(head->job);
        printf("\nE-mail:"); puts(head->email);
        putchar('\n');
        head = head->next;
        n++;
        if (n % 8 == 0) {
            printf("按回车键显示下一页");
            getchar();
            system("cls");
            menu();
        }
    }
    printf("总共%d个联系人\n", n);
}

// 计算长度
int length(phone_book* head) {
    int n = 0;
    phone_book* p;
    p = head;
    while (p != NULL) {
        p = p->next; n++;
    }
    return n;
}

// 按号码排序(数组法)
void sortbynumber(phone_book* head) {
    void menu();
    int n = length(head);
    int i, j;
    phone_book temp, * p;
    p = head;
    phone_book a[1000]; // 假设最大1000条
    for (i = 1; i <= n; i++) {
        strcpy(a[i].name, p->name);
        strcpy(a[i].number, p->number);
        strcpy(a[i].job, p->job);
        strcpy(a[i].email, p->email);
        p = p->next;
    }
    // 冒泡排序
    for (i = 1; i <= n - 1; i++) {
        for (j = 1; j <= n - i; j++) {
            if (strcmp(a[j].number, a[j + 1].number) > 0) {
                temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
    }
    printf("现在通讯录中有如下成员(已按号码排序):");
    for (i = 1; i <= n; i++) {
        printf("\n名字:"); puts(a[i].name);
        printf("\n电话号码:"); puts(a[i].number);
        printf("\n工作单位:"); puts(a[i].job);
        printf("\nE-mail:"); puts(a[i].email);
        putchar('\n');
        if (i % 8 == 0) {
            printf("按回车键显示下一页");
            getchar();
            system("cls");
            menu();
        }
    }
    printf("总共%d个联系人\n", n);
}

// 按名字排序
void sortbyname(phone_book* head) {
    void menu();
    int n = length(head);
    int i, j;
    phone_book temp, * p;
    p = head;
    phone_book a[1000];
    for (i = 1; i <= n; i++) {
        strcpy(a[i].name, p->name);
        strcpy(a[i].number, p->number);
        strcpy(a[i].job, p->job);
        strcpy(a[i].email, p->email);
        p = p->next;
    }
    for (i = 1; i <= n - 1; i++) {
        for (j = 1; j <= n - i; j++) {
            if (strcmp(a[j].name, a[j + 1].name) > 0) {
                temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
    }
    printf("现在通讯录中有如下成员(已按名字排序):");
    for (i = 1; i <= n; i++) {
        printf("\n名字:"); puts(a[i].name);
        printf("\n电话号码:"); puts(a[i].number);
        printf("\n工作单位:"); puts(a[i].job);
        printf("\nE-mail:"); puts(a[i].email);
        putchar('\n');
        if (i % 8 == 0) {
            printf("按回车键显示下一页");
            getchar();
            system("cls");
            menu();
        }
    }
    printf("总共%d个联系人\n", n);
}

// 通用排序入口
void sort_list(phone_book* head) {
    void menu();
    if (head == NULL) { printf("电话簿为空,请重新创建!"); return; }
    char a;
    printf("1.按电话号码升序排序\n2.按姓名字母升序排序\n请选择:");
    a = getchar(); getchar();
    switch (a) {
        case '1': sortbynumber(head); break;
        case '2': sortbyname(head); break;
        default: printf("输入有误!\n"); break;
    }
}

// ================= 查找功能 =================

void findbyname(phone_book* head) {
    if (head == NULL) { printf("电话簿为空,请重新创建"); return; }
    char name[15]; printf("请输入名字:"); gets_s(name);
    while (strcmp(name, head->name) != 0) {
        head = head->next;
        if (head == NULL) { printf("电话簿未有此联系人\n"); return; }
    }
    printf("%s的电话号码为:", name); puts(head->number);
    printf("\n工作单位:"); puts(head->job);
    printf("\nE-mail:"); puts(head->email);
}

void findbynumber(phone_book* head) {
    if (head == NULL) { printf("电话簿为空,请重新创建"); return; }
    char number[20]; printf("请输入电话号码:"); gets_s(number);
    while (strcmp(number, head->number) != 0) {
        head = head->next;
        if (head == NULL) { printf("电话簿未有此联系人\n"); return; }
    }
    printf("%s的主人为:", number); puts(head->name);
    printf("\n工作单位:"); puts(head->job);
    printf("\nE-mail:"); puts(head->email);
}

void find(phone_book* head) {
    int n;
    printf("    1.通过名字查找\n    2.通过电话号码查找\n    3.退出\n    请选择:");
    scanf("%d", &n); getchar();
    while (1) {
        if (n == 1) { findbyname(head); printf("请选择服务项:"); scanf("%d", &n); getchar(); }
        else if (n == 2) { findbynumber(head); printf("请选择服务项:"); scanf("%d", &n); getchar(); }
        else if (n == 3) return;
        else { printf("输入不正确!"); printf("请选择服务项:"); scanf("%d", &n); getchar(); }
    }
}

// ================= 添加与删除 =================

void add_list(phone_book* head) {
    phone_book* p1, * p2, * h;
    h = NULL; // 新添加部分的头
    char name[15];
    p1 = (phone_book*)malloc(LEN);
    p2 = p1;
    int n = 0;
    while (1) {
        printf("请输入名字(名字为0时停止)"); gets_s(p1->name);
        if (strcmp(p1->name, "0") == 0) break;
        printf("请输入电话号码:"); gets_s(p1->number);
        printf("请输入工作单位:"); gets_s(p1->job);
        printf("请输入E-mail:"); gets_s(p1->email);
        n = n + 1;
        if (n == 1) h = p1;
        else p2->next = p1;
        p2 = p1;
        p1 = (phone_book*)malloc(LEN);
    }
    p2->next = NULL; // 新部分尾部置空
    if (head == NULL) {
        head = h; // 如果原表为空,直接接管
        return; // 注意:这里原代码可能有逻辑缺陷,通常应更新全局头指针或使用二级指针
    }
    // 原代码逻辑:将新链表拼接到原链表末尾
    // 需先找到原链表末尾,但此处直接操作 head->next 可能不准确,建议优化为遍历末尾
    // 原代码简化处理:直接插入在第二个位置?原代码逻辑如下:
    p1 = head->next;
    head->next = h;
    p2->next = p1;
}

phone_book* delete_list(phone_book* head) {
    char a[20];
    printf("请输入联系人名字或电话:"); gets_s(a);
    if (head == NULL) { printf("电话簿为空,请重新创建"); return head; }
    phone_book* p = head, * p1 = NULL;
    // 查找目标节点
    while (strcmp(a, p->name) != 0 && strcmp(a, p->number) != 0) {
        p1 = p;
        p = p->next;
        if (p == NULL) { printf("电话簿未有此联系人\n"); return head; }
    }
    printf("查找到了! 是否删除(Y/N)?"); char b[10]; gets_s(b);
    if (strcmp(b, "y") == 0 || strcmp(b, "Y") == 0) {
        if (p == head) {
            head = p->next;
            free(p);
        } else {
            p1->next = p->next;
            free(p);
        }
        printf("成功删除!");
    } else if (strcmp(b, "n") == 0 || strcmp(b, "N") == 0) {
        printf("取消删除!");
    } else {
        printf("输入错误!");
    }
    return head;
}

// ================= 文件 IO =================

void save_list(phone_book* head) {
    FILE* fp;
    if ((fp = fopen("dianhuabu.dat", "wb")) == NULL) {
        printf("File cannot be opened\n"); exit(0);
    }
    if (head == NULL) { printf("通讯录为空\n"); fclose(fp); return; }
    phone_book* p1 = head;
    while (p1 != NULL) {
        if (fwrite(p1, LEN, 1, fp) != 1) {
            printf("cannot write file\n"); fclose(fp); return;
        }
        p1 = p1->next;
    }
    printf("保存完毕!\n");
    fclose(fp);
}

phone_book* load_list(phone_book* head) {
    FILE* fp;
    // 实际使用中应先清空旧链表,此处原代码逻辑较为简略,可能存在内存泄漏风险
    // 生产环境建议在load前 free_list(head)
    if ((fp = fopen("dianhuabu.dat", "rb")) == NULL) {
        printf("电话簿为空,请重新创建\n"); return head;
    }
    phone_book* p1, * p2;
    p1 = (phone_book*)malloc(LEN);
    if (fread(p1, LEN, 1, fp) == 0) {
        printf("电话簿为空,请重新创建");
        free(p1); // 修正:读取失败需释放
        fclose(fp); // 修正:关闭文件
        return head;
    }
    head = p1;
    p2 = p1;
    p1 = (phone_book*)malloc(LEN);
    while (fread(p1, LEN, 1, fp)) {
        p2->next = p1;
        p2 = p1;
        p1 = (phone_book*)malloc(LEN);
    }
    p2->next = NULL;
    free(p1); // 释放最后一次多分配的节点
    fclose(fp);
    return head;
}

// ================= 主界面 =================

void menu() {
    printf("           欢迎进入电话簿系统            \n");
    printf("********************************************\n");
    printf("        1、创建电话簿(会将原有电话簿覆盖)\n");
    printf("        2、查找联系人               \n");
    printf("        3、添加联系人               \n");
    printf("        4、删除联系人               \n");
    printf("        5、显示联系人               \n");
    printf("        6、查看电话簿(排序)         \n");
    printf("        7、退出系统                 \n");
    printf("********************************************\n");
}

int main() {
    system("cls");
    menu();
    printf("          请选择你所需要的服务:");
    int n;
    scanf("%d", &n);
    getchar();
    phone_book* head = NULL;
    while (1) {
        system("cls");
        menu();
        switch (n) {
            case 1: head = creat_list(); print_list(head); save_list(head); printf("操作完成\n"); break;
            case 2: head = load_list(head); find(head); printf("操作完成\n"); break;
            case 3: head = load_list(head); add_list(head); save_list(head); printf("操作完成\n"); break;
            case 4: head = load_list(head); head = delete_list(head); save_list(head); printf("操作完成\n"); break;
            case 5: head = load_list(head); print_list(head); save_list(head); break;
            case 6: head = load_list(head); sort_list(head); printf("操作完成\n"); break;
            case 7: system("cls"); return 0;
            default: printf("\n输入有误,请重新输入:"); scanf("%d", &n); getchar(); break;
        }
        if(n!=7) { printf("\n如需要其他服务,请重新输入:"); scanf("%d", &n); getchar(); }
    }
    return 0;
}

总结与注意事项

该电话簿系统是学习 C 语言数据结构的良好实践案例。开发者在此过程中掌握了链表的动态构建与维护、文件二进制读写的标准流程,以及基本的字符串比较算法。

改进建议: 1. 内存管理原代码中的 add_list 和 load_list 在处理链表拼接时逻辑较为紧凑,建议在生产环境中引入专门的 free_list(head) 函数,在每次切换操作前清空内存,防止堆溢出。 2. 输入安全:gets_s 是 VS 的特有扩展,GCC 环境下建议使用 fgets 配合缓冲区清理,以避免潜在的安全警告或崩溃。

以上就是 C语言实现电话簿管理系统的详细内容,更多关于C语言链表实战与项目开发技巧的资料请关注本站其它相关文章!

相关标签:

本站声明

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

Python 相关产品和应用

全部下载

专题列表