接上集,我们继续指针
2. 指针与数组
上一节我们学习了指针的基本使用。
我们知道:
int a = 10;
int *p = &a;p 保存的是 a 的地址,而 *p
可以通过这个地址访问 a 中的数据。
数组中的元素也是存放在连续的内存空间中的,因此指针也可以用来访问和遍历数组。
数组名与地址
先看一个数组:
int a[5] = {10, 20, 30, 40, 50};数组中的元素会连续存放:
┌──────┬──────┬──────┬──────┬──────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │
└──────┴──────┴──────┴──────┴──────┘
↑ ↑ ↑ ↑ ↑
a[0] a[1] a[2] a[3] a[4]
在大多数表达式中,数组名 a
会转换成指向第一个元素的指针。
也就是说:
a可以理解为:
&a[0]因此:
a
↓
第一个元素的地址
↓
&a[0]
例如:
printf("%p\n", (void *)a);
printf("%p\n", (void *)&a[0]);这两个地址的数值通常相同。
int *p = a
既然 a
可以表示第一个元素的地址,那么就可以让一个指针保存这个地址:
int a[5] = {10, 20, 30, 40, 50};
int *p = a;也可以理解成:
int *p = &a[0];此时:
p
↓
a[0]
↓
10
所以:
printf("%d\n", *p);会输出:
10
因为 p 保存的是 a[0] 的地址,而
*p 会访问这个地址中的数据。
*p 访问第一个元素
例如:
#include <stdio.h>
int main() {
int a[5] = {10, 20, 30, 40, 50};
int *p = a;
printf("%d\n", *p);
return 0;
}输出:
10
这里:
*p实际上访问的就是:
a[0]因此:
*p == a[0]可以理解为:
p
↓
a[0] 的地址
↓
*
↓
a[0] 的数据
↓
10
p++ 访问下一个元素
前面我们学习过:
p++;指针并不是简单地让地址增加 1。
指针会根据自己的类型,移动对应类型的大小。
例如:
int a[5] = {10, 20, 30, 40, 50};
int *p = a;假设:
sizeof(int) = 4
那么:
p
↓
a[0]
执行:
p++;之后:
p
↓
a[1]
再执行:
p++;之后:
p
↓
a[2]
因此:
printf("%d\n", *p);就可以依次访问:
10
20
30
40
50
例如:
#include <stdio.h>
int main() {
int a[5] = {10, 20, 30, 40, 50};
int *p = a;
printf("%d\n", *p);
p++;
printf("%d\n", *p);
p++;
printf("%d\n", *p);
p++;
printf("%d\n", *p);
p++;
printf("%d\n", *p);
return 0;
}输出:
10
20
30
40
50
因此:
p
↓
a[0]
p++
↓
a[1]
p++
↓
a[2]
p++
↓
a[3]
p++
↓
a[4]
这就是使用指针遍历数组的基础。
*(p + i)
除了使用:
p++;逐个移动指针之外,还可以直接让指针向后移动 i
个元素:
*(p + i)例如:
int a[5] = {10, 20, 30, 40, 50};
int *p = a;
printf("%d\n", *(p + 0));
printf("%d\n", *(p + 1));
printf("%d\n", *(p + 2));
printf("%d\n", *(p + 3));
printf("%d\n", *(p + 4));输出:
10
20
30
40
50
可以理解为:
p
↓
a[0]
p + 1
↓
a[1]
p + 2
↓
a[2]
p + 3
↓
a[3]
p + 4
↓
a[4]
注意:
p + i得到的是地址。
而:
*(p + i)才是通过这个地址访问对应的数据。
所以:
p + i
↓
第 i 个元素的地址
*(p + i)
↓
第 i 个元素的数据
a[i] 和 *(a + i)
这是 C 语言中非常重要的一组关系:
a[i] == *(a + i)例如:
int a[5] = {10, 20, 30, 40, 50};
printf("%d\n", a[2]);
printf("%d\n", *(a + 2));两次都会输出:
30
为什么?
因为:
a可以看成指向第一个元素的地址。
所以:
a + 2就是向后移动两个元素:
a
↓
a[0]
a + 1
↓
a[1]
a + 2
↓
a[2]
此时:
a + 2得到的是 a[2] 的地址。
再使用:
*进行解引用:
*(a + 2)就可以得到 a[2] 中的数据。
因此:
a[i]
↓
直接使用数组下标访问
*(a + i)
↓
a
↓
向后移动 i 个元素
↓
得到第 i 个元素的地址
↓
*
↓
得到第 i 个元素的数据
所以:
a[i] == *(a + i)为什么 a + i
不是简单地加 i 个字节?
例如:
int a[5] = {10, 20, 30, 40, 50};假设:
sizeof(int) = 4
假设 a 的起始地址为:
1000
那么:
a + 0 → 1000
a + 1 → 1004
a + 2 → 1008
a + 3 → 1012
a + 4 → 1016
所以:
a + i实际上是移动 i 个 int 元素,而不是移动
i 个字节。
可以理解为:
a + i
= 起始地址 + i × sizeof(int)
例如:
a + 2
= 1000 + 2 × 4
= 1008
1008 就是 a[2] 的地址。
用指针遍历数组
现在我们已经知道:
int *p = a;以及:
*p和:
p++因此可以使用指针遍历整个数组。
例如:
#include <stdio.h>
int main() {
int a[5] = {10, 20, 30, 40, 50};
int *p = a;
for (int i = 0; i < 5; i++) {
printf("%d\n", *p);
p++;
}
return 0;
}输出:
10
20
30
40
50
这里:
*p负责访问当前元素。
而:
p++;负责移动到下一个元素。
整个过程就是:
p
↓
a[0]
↓
*p → 10
p++
↓
a[1]
↓
*p → 20
p++
↓
a[2]
↓
*p → 30
p++
↓
a[3]
↓
*p → 40
p++
↓
a[4]
↓
*p → 50
总结
这一节最重要的是理解下面几组关系:
a可以理解为指向数组第一个元素的地址。
int *p = a;让指针 p 指向数组第一个元素。
*p访问当前指针所指向的元素。
p++移动到下一个数组元素。
p + i得到向后移动 i 个元素后的地址。
*(p + i)通过这个地址访问第 i 个元素。
最重要的一条:
a[i] == *(a + i)因此:
数组下标访问:
a[i]
↓
第 i 个元素
指针访问:
*(a + i)
↓
移动到第 i 个元素
↓
通过地址访问数据
这样,数组和指针就连接起来了。
Comments NOTHING