C 语言函数:从 0 到 链表封装 --> 一次真正理解“数据 + 行为”的过程
·
很多人学 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 的工程代码。
更多推荐


所有评论(0)