C 传地址改实参
更新时间:2026-08-26。本文是
languages/c/主题入门层第 39 篇。值传递与传地址讲过"传地址能改外面",这一篇进阶:想改的对象本身是指针时,要传指针的指针。这是从"会用指针"到"会设计指针"的分水岭。
本文要回答的问题
- 指针参数到底能改什么、不能改什么?
- 为什么链表插入要用
Node **? - 传
int *和传int **的边界在哪?
一、指针参数:能改"指向的东西",改不了"指针本身"
c
void change_value(int *p) {
*p = 100; // 能:改 p 指向的 int
p = NULL; // 不能影响外面:p 是副本!
}
int main(void) {
int x = 5;
int *ptr = &x;
change_value(ptr);
printf("%d\n", x); // 100:值改了
printf("%p\n", ptr); // 还是原地址:ptr 没变
return 0;
}p 是 ptr 的副本。函数内 *p = 100 通过副本里的地址改了 x;但 p = NULL 只改了副本,外面的 ptr 纹丝不动。结论:指针参数能改"所指之物",改不了"指针变量本身"。
二、想改指针本身:传指针的指针
c
void reset_pointer(int **pp) { // 二级指针:pp 存着"ptr 的地址"
*pp = NULL; // 改 *pp,即外面的 ptr
}
int main(void) {
int x = 5;
int *ptr = &x;
reset_pointer(&ptr); // 传 ptr 的地址
printf("%p\n", ptr); // (nil):ptr 真的变成 NULL 了!
return 0;
}| 想改什么 | 传什么 | 形参 |
|---|---|---|
| 改 int | &x | int * |
| 改 int* | &ptr | int ** |
| 改 int** | &pp | int *** |
模式:想通过函数修改某对象,就传它的地址。对象是 int → 传 int*;对象是 int* → 传 int**。一层套一层,规则不变。
三、实战:链表插入为什么用 Node **
链表插入头节点,要在调用处更新头指针:
c
typedef struct Node {
int data;
struct Node *next;
} Node;
// 传 Node**:能改调用者的 head
void insert_head(Node **head, int val) {
Node *new_node = malloc(sizeof(Node));
new_node->data = val;
new_node->next = *head; // 新节点指向旧头
*head = new_node; // 改外面的 head!
}
int main(void) {
Node *head = NULL;
insert_head(&head, 10); // 必须传 &head
insert_head(&head, 20);
// head 现在是 20 → 10 → NULL
return 0;
}如果写成 void insert_head(Node *head, int val)(一级指针),head = new_node 只改了副本,调用者还是 NULL——链表就丢了。凡是要"改头指针/根指针"的,一律二级指针。
四、多返回值模式:指针参数的经典用途
c
// 一个函数产出多个结果,用指针参数"带出去"
int div_mod(int a, int b, int *quot, int *rem) {
if (b == 0) return -1; // 错误信号走返回值
*quot = a / b;
*rem = a % b;
return 0; // 成功
}
int main(void) {
int q, r;
if (div_mod(7, 2, &q, &r) == 0) {
printf("商 %d 余 %d\n", q, r);
}
return 0;
}分工惯例:返回值表达"成败/状态",指针参数带出"结果数据"。C 没有多返回值,这是标准解法。
五、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
该传 ** 传成 * | 外层指针没变 | 检查"要改的是不是指针本身" |
忘了 & | 类型不匹配 | &ptr 别省 |
| 函数内改形参以为改了实参 | 外部没变 | 用指针且解引用 |
| 传 NULL 进函数没判空 | 段错误 | 函数入口判空 |
| 多返回值只拿一个 | 逻辑缺一块 | 参数全部带出 |
六、与本站主线衔接
一句话总结
指针参数能改"所指之物"、改不了"指针本身"——想改指针就传指针的地址(**);链表插头、改根指针、多返回值全用这个模式;判断标准只有一句:你要改的那个对象,它自己是不是指针?