08-C语言指针(续1)

plana_lab 发布于 20 天前 72 次阅读


接上集,我们继续指针


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 个元素
   ↓
通过地址访问数据

这样,数组和指针就连接起来了。

此作者没有提供个人介绍。
最后更新于 2026-09-17