函数一节已说明:C 语言参数按值传递,标准 C 也没有 C++ 式的引用形参。若要在函数内修改调用方的变量,应把该变量的地址传给指针形参,在函数内通过解引用访问原对象。本节习惯上称为“按地址传参”或“通过指针传参”。
按值传递:形参是副本
#include <stdio.h>
void addone(int n) {
n++;
}
int main(void) {
int n = 1;
addone(n);
printf("After: %d\n", n); /* 1 */
return 0;
}形参 n 是实参的副本,函数内自增只影响副本,调用结束后 main 中的 n 不变。
传递地址:通过指针修改调用方变量
#include <stdio.h>
void addone(int *n) {
(*n)++;
}
int main(void) {
int n = 1;
addone(&n);
printf("After: %d\n", n); /* 2 */
return 0;
}与按值传递相比,变化在于:
- 形参类型为
int *n,接收的是整型对象的地址。 - 调用处写
addone(&n),传递n的地址;实参仍按值传递的是“指针的值”。
对指针解引用并自增应写 (*n)++。若写成 *n++,由于运算符优先级,语义变为先移动指针再解引用,与预期不符。
结构体指针与箭头运算符
结构体包含多个成员(详见结构体一章)。传递结构体指针可避免复制整个结构体,并在函数内修改原对象:
typedef struct {
int x;
int y;
} point;
void move(point *p) {
(*p).x++;
(*p).y++;
}对结构体指针解引用再取成员较繁琐,可使用箭头运算符 ->:
void move(point *p) {
p->x++;
p->y++;
}规则:. 用于结构体对象本身;-> 用于结构体指针。
示例:通过指针修改年龄
#include <stdio.h>
typedef struct {
char *name;
int age;
} person;
void birthday(person *p);
int main(void) {
person john;
john.name = "John";
john.age = 27;
printf("%s 今年 %d 岁。\n", john.name, john.age);
birthday(&john);
printf("生日快乐!%s 现在 %d 岁了。\n", john.name, john.age);
return 0;
}
void birthday(person *p) {
p->age++;
}输出:
John 今年 27 岁。
生日快乐!John 现在 28 岁了。通过指针传参,函数可在不复制整个结构体的前提下修改调用方的成员,也是后续链表等数据结构常用的接口形式。
本节说明了用指针形参修改外部变量的写法。下一节介绍在堆上动态分配内存,同样依赖指针保存分配得到的地址。