从结构到系统:C语言通讯录的单链表底层实现与业务封装

# 从结构到系统:C语言通讯录的单链表底层实现与业务封装


通讯录是C语言学习者从“语法练习”迈向“工程实践”的经典桥梁。多数教材停留于静态数组版本,插入删除需移动大量元素,容量固定且无法动态扩展。**以单链表为内核重构通讯录**,不仅实现动态内存管理,更将结点操作收敛为清晰的业务接口。本文完整呈现从链表结点定义、增删改查到文件持久化的全过程。


---


## 一、分层架构:结点与通讯录的职责分离


工程化通讯录不应将链表操作散落在`main`函数各处。**定义两层结构**:`ContactNode`负责存储数据与指针,`ContactBook`持有头指针与大小。


```c

// 通讯录结点(内部使用)

typedef struct ContactNode {

    char name[32];

    char phone[16];

    struct ContactNode* next;

} ContactNode;


// 通讯录容器(对外暴露)

typedef struct {

    ContactNode* head;   // 头指针

    size_t size;        // 当前记录数

} ContactBook;

```


**职责边界**:`ContactNode`仅维护链表关系,`ContactBook`管理生命周期与业务逻辑。调用者操作`ContactBook`实例,无需感知`next`指针的存在。


---


## 二、初始化与销毁:资源管理的对称性


**初始化**:头指针置空,计数器归零。


```c

void ContactBook_Init(ContactBook* book) {

    book->head = NULL;

    book->size = 0;

}

```


**销毁**:遍历链表释放所有结点,防止内存泄漏。


```c

void ContactBook_Destroy(ContactBook* book) {

    ContactNode* cur = book->head;

    while (cur) {

        ContactNode* next = cur->next;

        free(cur);

        cur = next;

    }

    book->head = NULL;

    book->size = 0;

}

```


**对称性原则**:`Init`与`Destroy`成对出现。凡`malloc`必有`free`,凡打开资源必有关闭。


---


## 三、增删改查:链表操作的业务化封装


**添加联系人**:头插法实现简单,无需遍历尾部。


```c

int ContactBook_Add(ContactBook* book, const char* name, const char* phone) {

    ContactNode* node = (ContactNode*)malloc(sizeof(ContactNode));

    if (!node) return -1;  // 内存分配失败

    <"q5.a8k1.org.cn"><"t1.a8k1.org.cn"><"i8.a8k1.org.cn">

    strncpy(node->name, name, sizeof(node->name) - 1);

    strncpy(node->phone, phone, sizeof(node->phone) - 1);

    node->name[sizeof(node->name) - 1] = '\0';

    node->phone[sizeof(node->phone) - 1] = '\0';

    

    node->next = book->head;

    book->head = node;

    book->size++;

    return 0;

}

```


**按姓名删除**:二级指针统一处理头结点与中间结点。


```c

int ContactBook_RemoveByName(ContactBook* book, const char* name) {

    ContactNode** curr = &book->head;

    while (*curr) {

        if (strcmp((*curr)->name, name) == 0) {

            ContactNode* to_delete = *curr;

            *curr = (*curr)->next;  // 前驱直接连接后继

            free(to_delete);

            book->size--;

            return 1;  // 删除成功

        }

        curr = &(*curr)->next;

    }

    return 0;  // 未找到

}

```


**二级指针的语义**:`curr`指向**当前结点指针的地址**。修改`*curr`即修改了前驱结点的`next`字段或头指针自身。**无需区分删除首结点与其他结点**。


**按姓名查找**:返回结点指针,或更安全地返回拷贝。


```c

const ContactNode* ContactBook_FindByName(const ContactBook* book, const char* name) {

    const ContactNode* cur = book->head;

    while (cur) {

        if (strcmp(cur->name, name) == 0) {

            return cur;

        }

        cur = cur->next;

    }

    return NULL;

}

```


**修改联系人**:删除+添加,或直接更新结点字段。


---


## 四、遍历与展示:回调函数实现解耦


将遍历逻辑与输出格式分离,**接受函数指针**作为访问器。


```c

void ContactBook_Foreach(const ContactBook* book, void (*visit)(const ContactNode*)) {

    const ContactNode* cur = book->head;

    while (cur) {

        visit(cur);

        cur = cur->next;

    }

}

<"o0.a8k1.org.cn"><"l4.a8k1.org.cn"><"c6.a8k1.org.cn">

void print_contact(const ContactNode* node) {

    printf("%-16s %-15s\n", node->name, node->phone);

}

```


**调用**:`ContactBook_Foreach(&book, print_contact);`。此模式使通讯录核心代码无需关心输出格式——终端表格、JSON、HTML均可由外部传入。


---


## 五、文件持久化:二进制存储的简洁实现


通讯录数据需跨会话保存。**二进制块写入**比文本解析更直接。


**保存**:遍历链表,逐个结点写入文件。


```c

int ContactBook_Save(const ContactBook* book, const char* filename) {

    FILE* fp = fopen(filename, "wb");

    if (!fp) return -1;

    

    const ContactNode* cur = book->head;

    while (cur) {

        size_t written = fwrite(cur, sizeof(ContactNode), 1, fp);

        if (written != 1) {

            fclose(fp);

            return -2;

        }

        cur = cur->next;

    }

    fclose(fp);

    return 0;

}

```


**加载**:循环读取结点,头插法重建链表。


```c

int ContactBook_Load(ContactBook* book, const char* filename) {

    FILE* fp = fopen(filename, "rb");

    if (!fp) return -1;

    

    ContactBook_Destroy(book);  // 清空当前通讯录

    

    ContactNode node_buf;

    while (fread(&node_buf, sizeof(ContactNode), 1, fp) == 1) {

        ContactNode* node = (ContactNode*)malloc(sizeof(ContactNode));

        memcpy(node, &node_buf, sizeof(ContactNode));

        node->next = book->head;  // 头插

        book->head = node;

        book->size++;

    }

    fclose(fp);

    return 0;

}

```


**注意**:直接存储指针字段`next`无意义,加载时需重建链表关系。此处头插法天然形成与原链表相反的顺序——若需保持顺序,可尾插或存储时附加索引。


---


## 六、业务循环:用户界面与内核的分离


`main`函数承担**交互逻辑**,通讯录内核仅提供数据操作接口。


```c

int main(void) {

    ContactBook book;

    ContactBook_Init(&book);

    ContactBook_Load(&book, "contacts.dat");

    

    char cmd[16];

    while (1) {

        printf("> ");

        scanf("%s", cmd);

        if (strcmp(cmd, "add") == 0) {

            char name[32], phone[16];

            scanf("%s %s", name, phone);

            ContactBook_Add(&book, name, phone);

        } else if (strcmp(cmd, "list") == 0) {

            ContactBook_Foreach(&book, print_contact);

        } else if (strcmp(cmd, "del") == 0) {

            char name[32];

            scanf("%s", name);

            ContactBook_RemoveByName(&book, name);

        } else if (strcmp(cmd, "quit") == 0) {

            break;

        }

    }

    

    ContactBook_Save(&book, "contacts.dat");

    ContactBook_Destroy(&book);

    return 0;

}

```


**分层收益**:通讯录内核不涉及`scanf`/`printf`,可独立测试;更换为GUI界面时,内核零修改。


---


## 七、工程化要点总结


| 维度       | 通讯录demo的常见问题              | 工程化改进                     |

|------------|-----------------------------------|-------------------------------|

| 数据存储   | 静态数组,容量固定               | 单链表动态分配               |

| 接口设计   | 操作散落main,难以复用          | 封装ContactBook容器          |

| 边界处理   | 删除首结点需特殊判断            | 二级指针统一处理            |

| 资源管理   | 退出未释放内存                  | Destroy对称释放             |

| 持久化     | CSV/JSON手工解析               | 二进制块读写               |

| 可扩展性   | 输出格式硬编码                 | 回调函数注入               |


---


通讯录虽小,五脏俱全。它完整覆盖了**动态内存管理、指针操作、文件I/O、分层设计**四大核心能力。从“单链表实现”到“业务功能封装”,跨越的是对数据结构的理解从“结点操作”升维至“容器抽象”。当调用者仅需`ContactBook_Add`而不必手动维护`next`指针,通讯录便从“链表练习题”蜕变为“可交付的软件组件”。


请使用浏览器的分享功能分享到微信等