函数篇说过:C 参数默认按值传递,传进去的是拷贝,函数里改不到外面的变量。那怎么让函数真的改到调用方的变量?答案是:把那个变量的地址传进去,按引用(by reference)传参。
按值传:改不动
#include <stdio.h>
void addone(int n) {
n++; // n 是函数的局部拷贝,加一不影响外面的变量
}
int main() {
int n = 1;
addone(n);
printf("After: %d\n", n); // 还是 1
return 0;
}n 作为参数进来时被复制了一份,函数里改的是副本,函数一结束副本就没了,外面的 n 纹丝不动。
按引用传:给地址,就能改
#include <stdio.h>
void addone(int *n) {
(*n)++; // n 是指针,*n 是它指向的变量,这回是真的改了
}
int main() {
int n = 1;
addone(&n); // 传地址,不传值
printf("After: %d\n", n); // 2
return 0;
}两处关键差异:
- 函数参数类型从
int n变成int *n,接收的是指针。 - 调用时从
addone(n)变成addone(&n),传的是 n 的地址。
这样函数知道了 n 的内存位置,(*n)++ 直接对那块内存加一,改的就是 main 里那个 n 本人。注意解引用要加括号:(*n)++,写成 *n++ 会变成移动指针再解引用(下一节指针算术),语义完全错。
传结构体指针 + 箭头运算符 ->
结构体是一团多个变量(下一章讲),想写个函数让点的 x、y 都平移一点。传指针只要一个参数就够:
typedef struct {
int x;
int y;
} point;
void move(point *p) {
(*p).x++;
(*p).y++;
}(*p).x 每次写都别扭,而解引用结构体再取成员太常用了,C 干脆给了个简写,箭头运算符 ->:
void move(point *p) {
p->x++;
p->y++;
}记忆法:普通的点 . 用在结构体本身,箭头 -> 用在结构体指针上。
完整例子:给 person 过生日
#include <stdio.h>
typedef struct {
char *name;
int age;
} person;
/* 声明 */
void birthday(person *p);
int main() {
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;
}
/* 定义:通过指针给年龄 +1 */
void birthday(person *p) {
p->age++;
}输出:
John 今年 27 岁。
生日快乐!John 现在 28 岁了。指针在函数界的最大价值,就是让函数握有改外面变量的钥匙,还避免了把一大坨结构体整个拷贝进栈的浪费。