地址和指针
到现在为止,我们只用变量的名字。其实每个变量都放在内存里的某个位置,这个位置有一个编号,叫地址。指针就是专门存地址的变量。指针是 C 里最难的一关,所以这一课多点「单步」,看箭头指向谁。
变量住在哪里
在变量名前面加 &,得到它的地址。显示地址用格式符 %p(前面要写 (void *),这是 %p 要的写法)。
1int score = 90;2printf("score = %d\n", score);3printf("&score = %p\n", (void *)&score);
在一台电脑上跑了一次,显示的是:
score = 90 &score = 0x7ffd5e8a1c4c
地址是一串十六进制数字,每台电脑、每次运行都可能不一样:你自己跑这几行,会看到另一个数。这个数本身不用记。要记住的只有一件事:每个变量都有地址,& 拿到它。
两个变量的值可以一样,地址却各是各的。== 也能比地址:
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。
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:
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:
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 指向 a,所以 *p 就是 a。*p = *p + 5 读出 a(1),加 5,再把 6 放回 a。和写 a = a + 5 一样。
换一个指着
指针也是变量,可以换一个地址存进去。给 p 赋值(不带星号)改的是它指向哪里;给 *p 赋值改的是那里的值。
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 = ... 改的,都是箭头当时指着的那个变量。
没指向东西的指针
指针声明了却没赋值,里面是一个乱的地址。顺着它去读去写,就是在读写内存里随便一个地方:
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:
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 课专门讲。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
通过指针加价
✓不要直接写 price,通过 p 给它加 10。程序要显示 price = 60。
price = 60
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}
换一个指着
✓p 现在指向 a。让它改指向 b,再通过 p 把 b 改成 0。程序要显示 a = 3, b = 0。
a = 3, b = 0
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}
修好它
✓这段程序用了没赋值的指针,引擎会停下来。改一行,让它显示 温度:25。
温度:25
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}
这一课学会了
- 每个变量都在内存里有一个地址;
&x得到x的地址,%p显示地址(每次运行都可能不一样)。 int *p = &x;:p存着x的地址,叫p指向x。*p就是p指向的那个变量,能读也能改。int *p里的星号是在声明指针;*p里的星号是顺着指针找过去。p = &y;改的是指向哪里;*p = 5;改的是那里的值。- 没赋值的指针不能用;还不知道指向谁,就先设成
NULL,用之前检查。