很多人学 C 语言时,都会在「函数、指针、结构体、链表」之间来回卡壳。
真正的难点并不是语法,而是不知道如何用函数去“组织数据的行为”。

本文将从 C 函数最基础用法 出发,逐步引入 指针、结构体、动态内存,最终用 函数完整封装一个单链表,让你真正理解:

👉 C 语言的工程本质:结构体 = 数据,函数 = 行为

一、为什么「函数」是 C 语言的核心?

在 C 语言中:

  • 结构体:只负责描述数据

  • 函数:负责对数据进行操作

如果你只会写结构体,而不会用函数去操作它,那么你写的代码永远只能停留在「演示级」。

一个反例:没有函数的 C 代码

struct Student {
    int id;
    int score;
};

int main() {
    struct Student s;
    s.id = 1;
    s.score = 100;

    printf("%d\n", s.score);
}

问题是:

  • 所有逻辑堆在 main
  • 完全无法复用
  • 一旦复杂就失控

👉 解决方案:用函数封装行为

二、C 语言函数的最基础形态

1️⃣ 函数的定义与调用

int add(int a, int b) {
    return a + b;
}

int main() {
    int result = add(3, 5);
    printf("%d\n", result);
    return 0;
}

函数由三部分组成:

  • 返回值类型
  • 参数列表
  • 函数体

2️⃣ 函数的本质:一段可复用的逻辑

你可以把函数理解为:

“给一组输入,产生一个结果的逻辑单元”

但在 C 语言里,这还远远不够。

三、为什么 C 语言函数必须配合「指针」?

1️⃣ 值传递的局限

void change(int x) {
    x = 10;
}

int main() {
    int a = 5;
    change(a);
    printf("%d\n", a); // 仍然是 5
}

原因:

  • 函数拿到的是 变量的拷贝
  • 外部变量不会被修改

2️⃣ 使用指针修改外部变量(关键)

void change(int* p) {
    *p = 10;
}

int main() {
    int a = 5;
    change(&a);
    printf("%d\n", a); // 10
}

👉 C 的函数想要“产生副作用”,必须使用指针

四、函数 + 结构体:工程代码的起点

1️⃣ 定义一个结构体

typedef struct {
    int id;
    int score;
} Student;

2️⃣ 通过函数操作结构体

void print_student(Student* s) {
    printf("id=%d, score=%d\n", s->id, s->score);
}

注意:

  • 永远传结构体指针,而不是结构体本身
  • 避免拷贝,提高效率

五、从结构体走向链表

1️⃣ 链表节点的结构体定义

typedef struct Node {
    int data;
    struct Node* next;
} Node;

链表的本质:

结构体中,包含指向同类型结构体的指针

六、用函数一步步封装一个链表

1️⃣ 创建节点(返回指针)

Node* create_node(int value) {
    Node* node = (Node*)malloc(sizeof(Node));
    if (node == NULL) {
        return NULL;
    }
    node->data = value;
    node->next = NULL;
    return node;
}

这里你第一次看到:

  • 函数返回指针
  • 动态内存分配

2️⃣ 尾插节点(修改头指针)

void append(Node** head, int value) {
    Node* newNode = create_node(value);
    if (*head == NULL) {
        *head = newNode;
        return;
    }

    Node* cur = *head;
    while (cur->next != NULL) {
        cur = cur->next;
    }
    cur->next = newNode;
}

⚠️ 重点:

  • 为什么是 Node** head
  • 因为函数内部要修改 head 本身

 Node** head 是二级指针  

tips:为什么 C 一定要用二级指针?一次彻底讲清

3️⃣ 遍历链表

void print_list(Node* head) {
    Node* cur = head;
    while (cur != NULL) {
        printf("%d -> ", cur->data);
        cur = cur->next;
    }
    printf("NULL\n");
}

4️⃣ 删除节点

void remove_node(Node** head, int value) {
    Node* cur = *head;
    Node* prev = NULL;

    while (cur != NULL) {
        if (cur->data == value) {
            if (prev == NULL) {
                *head = cur->next;
            } else {
                prev->next = cur->next;
            }
            free(cur);
            return;
        }
        prev = cur;
        cur = cur->next;
    }
}

5️⃣ 释放整个链表(非常重要)

void destroy_list(Node* head) {
    Node* cur = head;
    while (cur != NULL) {
        Node* next = cur->next;
        free(cur);
        cur = next;
    }
}

七、完整测试示例

int main() {
    Node* head = NULL;

    append(&head, 1);
    append(&head, 2);
    append(&head, 3);

    print_list(head);

    remove_node(&head, 2);
    print_list(head);

    destroy_list(head);
    return 0;
}

输出:

1 -> 2 -> 3 -> NULL
1 -> 3 -> NULL

八、从链表到 MessageQueue 的思想迁移

你会发现:

  • 链表 = 存数据

  • 函数 = 操作数据

  • 头指针 = 队列入口

这和 Android 的 MessageQueue / Looper 在 思想层面是完全一致的。

九、总结一句话(非常重要)

C 语言不是“语法语言”,而是“内存 + 行为设计语言”

当你学会用 函数封装结构体行为,你才真正开始写 C 的工程代码。

更多推荐