Kodewick
第 15 课,共 30 课目录

地址和指针

第 15 课,共 30 课 · 第 3 部分 · 指针 · 约 15 分钟

到现在为止,我们只用变量的名字。其实每个变量都放在内存里的某个位置,这个位置有一个编号,叫地址。指针就是专门存地址的变量。指针是 C 里最难的一关,所以这一课多点「单步」,看箭头指向谁。

变量住在哪里

在变量名前面加 &,得到它的地址。显示地址用格式符 %p(前面要写 (void *),这是 %p 要的写法)。

1int score = 90;2printf("score  = %d\n", score);3printf("&score = %p\n", (void *)&score);

在一台电脑上跑了一次,显示的是:

score  = 90
&score = 0x7ffd5e8a1c4c

地址是一串十六进制数字,每台电脑、每次运行都可能不一样:你自己跑这几行,会看到另一个数。这个数本身不用记。要记住的只有一件事:每个变量都有地址,& 拿到它。

两个变量的值可以一样,地址却各是各的。== 也能比地址:

main.c
1#include <stdio.h>2 3int main(void) {4    int a = 5;5    int b = 5;6    printf("a == b:%d\n", a == b);7    printf("&a == &b:%d\n", &a == &b);8    return 0;9}
输出
a == b:1
&a == &b:0

1 是真,0 是假(第 5 课)。a 和 b 相等,可它们住在两个不同的地方。

现在可以看懂第 3 课的 scanf("%d", &age) 了:&age 是 age 的地址。scanf 要知道 age 在内存里的哪个位置,才能把读到的数放进去。

指针:存地址的变量

int *p 读作「p 是一个指向 int 的指针」。把 &score 存进去,p 就指向 score。在指针前面加 *,意思是「顺着地址找到那个变量」,所以 *p = 100 改的是 score。

main.c
1#include <stdio.h>2 3int main(void) {4    int score = 90;5    int *p = &score;6    *p = 100;7    printf("%d\n", score);8    return 0;9}
输出
100

点单步:p 画成一条箭头,指着 score。走过第 6 行,箭头那头的 score 变成了 100,而第 6 行里根本没写 score。

指针的类型说明它指向什么:int * 指向 int,double * 指向 double,char * 指向 char:

main.c
1#include <stdio.h>2 3int main(void) {4    double price = 4.50;5    char grade = 'B';6    double *pp = &price;7    char *pg = &grade;8    *pp = *pp * 2;9    *pg = 'A';10    printf("%.2f %c\n", price, grade);11    return 0;12}
输出
9.00 A

两个 * 不是一回事

* 在两个地方出现,意思不一样:

写法在哪里意思
int *p声明的时候p 是指向 int 的指针
*p用的时候p 指向的那个变量
&x用的时候x 的地址

所以 int *q = &n; 是声明 q,把 n 的地址存进 q,并没有往 *q 里放东西。之后 *q 读到的就是 n:

main.c
1#include <stdio.h>2 3int main(void) {4    int n = 7;5    int *q = &n;6    printf("n = %d, *q = %d\n", n, *q);7    n = 8;8    printf("n = %d, *q = %d\n", n, *q);9    return 0;10}
输出
n = 7, *q = 7
n = 8, *q = 8

q 里存的不是 7 的副本,而是 n 在哪里。所以 n 一变,*q 读到的也跟着变。

先猜一猜

这段程序显示什么?

1#include <stdio.h>2 3int main(void) {4    int a = 1;5    int *p = &a;6    *p = *p + 5;7    printf("%d\n", a);8    return 0;9}

换一个指着

指针也是变量,可以换一个地址存进去。给 p 赋值(不带星号)改的是它指向哪里;给 *p 赋值改的是那里的值。

main.c
1#include <stdio.h>2 3int main(void) {4    int apples = 3;5    int pears = 5;6    int *p = &apples;7    *p = 4;8    p = &pears;9    *p = 6;10    printf("苹果 %d 个,梨 %d 个\n", apples, pears);11    return 0;12}
输出
苹果 4 个,梨 6 个

点单步看箭头:一开始指着 apples,走到第 8 行,箭头转过去指着 pears。每一句 *p = ... 改的,都是箭头当时指着的那个变量。

没指向东西的指针

指针声明了却没赋值,里面是一个乱的地址。顺着它去读去写,就是在读写内存里随便一个地方:

main.c
1#include <stdio.h>2 3int main(void) {4    int *p;5    *p = 5;6    printf("%d\n", *p);7    return 0;8}
输出
use of uninitialized value

发生了什么: 停在第 5 行:「p」还没有赋值就被用了。真 C 里这会是一个乱数(每次运行都可能不一样)。

在真的电脑上,这种程序有时崩溃、有时看起来没事,是最难查的错之一。我们的引擎会在出事的那一行停下来说清楚。点「看看是怎么出错的」看一看。

还不知道指针该指向谁的时候,先把它设成 NULL:这是一个特别的值,意思是「什么也不指」。用之前检查 p != NULL:

main.c
1#include <stdio.h>2 3int main(void) {4    int stock = 12;5    int *p = NULL;6    if (p != NULL) {7        printf("之前:%d\n", *p);8    }9    p = &stock;10    if (p != NULL) {11        printf("之后:%d\n", *p);12    }13    return 0;14}
输出
之后:12

小技巧: 指针一声明就给它一个值:要么是某个变量的地址,要么是 NULL。指针还会怎么出事,第 18 课专门讲。

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。

练习 1

通过指针加价

不要直接写 price,通过 p 给它加 10。程序要显示 price = 60。

要显示
price = 60
exercise1.c
1#include <stdio.h>2 3int main(void) {4    int price = 50;5    int *p = &price;6    // 通过 p 给 price 加 107 8    printf("price = %d\n", price);9    return 0;10}
输出
点「运行」或按 Ctrl+Enter
练习 2

换一个指着

p 现在指向 a。让它改指向 b,再通过 p 把 b 改成 0。程序要显示 a = 3, b = 0。

要显示
a = 3, b = 0
exercise2.c
1#include <stdio.h>2 3int main(void) {4    int a = 3;5    int b = 8;6    int *p = &a;7 8    printf("a = %d, b = %d\n", a, b);9    return 0;10}
输出
点「运行」或按 Ctrl+Enter
练习 3

修好它

这段程序用了没赋值的指针,引擎会停下来。改一行,让它显示 温度:25。

要显示
温度:25
exercise3.c
1#include <stdio.h>2 3int main(void) {4    int temp = 21;5    int *p;6    *p = 25;7    printf("温度:%d\n", temp);8    return 0;9}
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • 每个变量都在内存里有一个地址;&x 得到 x 的地址,%p 显示地址(每次运行都可能不一样)。
  • int *p = &x;:p 存着 x 的地址,叫 p 指向 x。
  • *p 就是 p 指向的那个变量,能读也能改。
  • int *p 里的星号是在声明指针;*p 里的星号是顺着指针找过去。
  • p = &y; 改的是指向哪里;*p = 5; 改的是那里的值。
  • 没赋值的指针不能用;还不知道指向谁,就先设成 NULL,用之前检查。