函数一节已说明: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 不变。

传递地址:通过指针修改调用方变量

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

通过指针传参,函数可在不复制整个结构体的前提下修改调用方的成员,也是后续链表等数据结构常用的接口形式。

本节说明了用指针形参修改外部变量的写法。下一节介绍在堆上动态分配内存,同样依赖指针保存分配得到的地址。