请记住,在大多数表达式中,数组名可以很容易地变成指向第一个元素的指针(阅读一些数组名称不会衰变为指向第一个元素的指针的例外情况? 由@H2CO3 很好地回答)。
为了更好地理解,请考虑我的图示:
首先,假设a
存储在内存中如下所示。
a
+
| 0 | 1 | 2 | 3 | 4 |
+
▲ ▲ ▲ ▲ ▲
| | | | |
a a+1 a+2 a+3 a+3
声明 static int *p[] = {a, a+1, a+2, a+3, a+4};
创建了一个新的指向整数的指针数组,其具有以下值:
p[0] == a
p[1] == a + 1
p[2] == a + 2
p[3] == a + 3
p[4] == a + 4
现在,
p
也可以被认为是被存储在内存中的像下面这样的东西:
p
+
| a |a +1| a+2| a+3| a+4 |
+
▲ ▲ ▲ ▲ ▲
| | | | |
p p+1 p+2 p+3 p+4
在赋值
ptr = p;
之后,情况会变成这样:
p a
+
| a |a +1| a+2| a+3| a+4 | | 0 | 1 | 2 | 3 | 4 |
+
▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲
| | | | | | | | | |
p p+1 p+2 p+3 p+4 a a+1 a+2 a+3 a+3
ptr
Notice: ptr points to first location in pointer array p[]
表达式:**ptr++;
现在我们考虑第一个 printf 语句之前的表达式 **ptr++;
。
ptr
等于指向指针数组第一个元素的地址 p
。因此,ptr
指向数组中的第一个元素p[0]
(或者我们可以说ptr
== &p[0]
)。
*ptr
表示p[0]
,因为p[0]
是a
,所以*ptr
是a
(因此*ptr
== a
)。
由于*ptr
是a
,那么**ptr
是*a
== *(a + 0)
== a[0]
,即0
。
注意,在表达式**ptr++;
中,我们没有将其值分配给任何lhs变量。
因此,**ptr++;
的效果与ptr++;
相同== ptr = ptr + 1
= p + 1
这样,在此表达式之后,ptr
指向p[1]
(或者我们可以说ptr
== &p[1]
)。
打印-1:
第一个 printf 之前的事情变成了:
p a
+
| a | a+1| a+2| a+3| a+4 | | 0 | 1 | 2 | 3 | 4 |
+
▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲
| | | | | | | | | |
p p+1 p+2 p+3 p+4 a a+1 a+2 a+3 a+3
ptr
Notice: ptr is equals to p + 1 that means it points to p[1]
现在我们可以理解第一个printf:
ptr - p
输出 1
,因为:
ptr = p + 1
,所以 ptr - p
== p + 1 - p
== 1
*ptr - a
输出 1
,因为:
ptr = p + 1
,所以 *ptr
== *(p + 1)
== p[1]
== a + 1
这意味着:*ptr - a
= a + 1 - a
== 1
**ptr
输出 1
,因为:
*ptr
== a + 1
来自于第2点
所以 **ptr
== *(a + 1)
== a[1]
== 1
表达式: *++*ptr;
在第一个printf之后,我们有一个表达式*++*ptr;
。
如上面的第2点所述,*ptr
== p [1]
。
因此,++*ptr
(即++p [1]
)将增加p [1]
到a + 2
。
再次理解,在表达式*++*ptr;
中,我们不将其值分配给任何lhs变量,因此*++*ptr;
的效果只是++*ptr;
。
现在,在第二个printf之前,情况变为:
p a
+
| a |a+2 | a+2| a+3| a+4 | | 0 | 1 | 2 | 3 | 4 |
+
▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲
| | | | | | | | | |
p p+1 p+2 p+3 p+4 a a+1 a+2 a+3 a+3
ptr
Notice: p[1] became a + 2
打印-2:
现在我们可以理解 第二个 printf:
ptr - p
的输出为 1
,因为:
ptr = p + 1
,所以 ptr - p
== p + 1 - p
== 1
*ptr - a
的输出为 2
,因为:
ptr = p + 1
,所以 *ptr
== *(p + 1)
== p[1]
== a + 2
这意味着: *ptr - a
== a + 2 - a
== 2
**ptr
的输出为 2
,因为:
*ptr
== a + 2
来自于点-2
所以 **ptr
== *(a + 2)
== a[2]
== 2
表达式:++**ptr;
现在在第三个printf之前有一个表达式
++**ptr;
。
正如我们从上面的第3点所知,
**ptr
==
a[2]
。
因此
++**ptr
==
++a[2]
将会将
a[2]
增加到
3
。
因此,在第三个printf之前的事情变成:
p a
+
| a | a+2| a+2| a+3| a+4 | | 0 | 1 | 3 | 3 | 4 |
+
▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲
| | | | | | | | | |
p p+1 p+2 p+3 p+4 a a+1 a+2 a+3 a+3
ptr
Notice: a[2] = 3
打印-3:
现在我们可以理解第三个printf:
ptr - p
输出 1
,因为:
ptr = p + 1
所以 ptr - p
== p + 1 - p
== 1
*ptr - a
输出 2
,因为:
ptr = p + 1
所以 *ptr
== *(p + 1)
== p[1]
== a + 2
这意味着:*ptr - a
= a + 2 - a
== 2
**ptr
输出 3
,因为:
*ptr
== a + 2
来自于第二个点
所以 **ptr
== *(a + 2)
== a[2]
== 3
编辑 注意:两个指针的差异具有类型 ptrdiff_t
,因此正确的转换说明符是 %td
,而不是 %d
。
另外一点:
我希望添加这一点,因为我相信对于新学习者会很有帮助
假设我们有以下两行代码,在 return 0;
前有一个额外的第四个 printf。
**++ptr; // additional
printf("%d %d %d\n", ptr-p, *ptr-a, **ptr); // fourth printf
可以在Codepade上检查这个工作代码,这一行输出2 2 3
。
表达式:**++ptr;
因为ptr
等于p + 1
,所以在增量++
操作后,ptr
变成p + 2
(或者我们可以说ptr
== &p[2]
)。
之后双重引用操作**
==> **(p + 2)
== *p[2]
== *(a + 2)
== a[2]
== 3
。
现在,由于在这个语句中没有任何赋值操作,因此表达式**++ptr;
的效果就是++ptr;
。
因此,在表达式**++ptr;
之后,以下内容将如下图所示:
p a
+
| a | a+2| a+2| a+3| a+4 | | 0 | 1 | 3 | 3 | 4 |
+
▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲
| | | | | | | | | |
p p+1 p+2 p+3 p+4 a a+1 a+2 a+3 a+3
ptr
Notice: ptr is equals to p + 2 that means it points to p[2]
打印-4:
考虑到之前提到的 Forth printf,我加入了以下问题:
ptr - p
的输出结果为 2
,因为:
ptr = p + 2
,所以 ptr - p
== p + 2 - p
== 2
*ptr - a
的输出结果为 2
,因为:
ptr = p + 2
,所以 *ptr
== *(p + 2)
== p[2]
== a + 2
这意味着:*ptr - a
= a + 2 - a
== 2
**ptr
的输出结果为 3
,因为:
*ptr
从上面的 point-2 中得知为 a + 2
所以 **ptr
== *(a + 2)
== a[2]
== 3
*
和++
的优先级规则。 - Barmar*++*ptr
中,我的理解是指对于*ptr
所指向的值进行前缀自增,然后对该结果指针进行解引用以产生表达式值。但这是极其糟糕的代码,实际编码时应该避免使用。由于C语言指针的特性,这段代码可能会让人感到困惑和混乱。 - Hot Licks%d
。%td
是用于ptrdiff_t
的正确格式字符串。 - Carl Norum