函数篇说过: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 纹丝不动。

按引用传:给地址,就能改

C 语言按引用传参(指针)示例运行
#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 岁了。

指针在函数界的最大价值,就是让函数握有改外面变量的钥匙,还避免了把一大坨结构体整个拷贝进栈的浪费。