代码如下:
char a[][5] = { "abc", "ijk", "defg", "opq" };
char *p = &a[1][1];
printf("%d,%s,%s", a[2] - a[0], &p[1], &p[5]);
// the output is: 10 k "efg"
// what I think is: 3 k 'e'
我相信a[2]是'd',a[0]是'a',所以我得到3。但是a[2]和a[0]是字符串类型,所以他们如何做减号手术?最后,为什么 &p[5] 是“efg”?我对我的结果感到困惑。
首先,想想
a
和p
是什么。这是它们的直观表示:
a
┌────────────────────────────────┬─────────────────────────────────┬─────────────────────────────────┬────────────────────────────────┐
│┌─────┬─────┬─────┬─────┬─────┐ │ ┌─────┬─────┬─────┬─────┬─────┐ │ ┌─────┬─────┬─────┬─────┬─────┐ │ ┌─────┬─────┬─────┬─────┬─────┐│
││ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ││
││ 'a' │ 'b' │ 'c' │ '\0'│ │ │ │ 'i' │ 'j' │ 'k' │ '\0'│ │ │ │ 'd' │ 'e' │ 'f' │ 'g' │ '\0'│ │ │ 'a' │ 'b' │ 'c' │ '\0'│ ││
││ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ││
│└─────┴─────┴─────┴─────┴─────┘ │ └─────┴──▲──┴─────┴─────┴─────┘ │ └─────┴─────┴─────┴─────┴─────┘ │ └─────┴─────┴─────┴─────┴─────┘│
└────────────────────────────────┴──────────┼──────────────────────┴─────────────────────────────────┴────────────────────────────────┘
│
p │
┌────────────┐ │
│ │ │
│ ──────┼──────────────────────────────┘
│ │
└────────────┘
a
是一个由 4 个 5 个 char
s 组成的数组,p
是指向 a
中第二个数组的第二个元素的指针。
从那里,你可以弄清楚你所有的参数
printf
意味着什么:
a[2] - a[0]
:由于a
是数组的数组,所以a[2]
和a[0]
一定是数组。现在,您不能减去数组,但 C++ 中的数组可以隐式转换为指向其第一个元素的指针。这意味着 a[2] - a[0]
的意思是“指向 a
中第三个数组的第一个元素的指针与指向 a
中第一个数组的第一个元素的指针之间的差异” 正如您在上图中看到的那样,这两个元素之间有 10 个 char
空格,所以你得到 10.&p[1]
:从上图可以看出,p
是指向'j'
中的"ijk"
的指针。这意味着 p[1]
是 'k'
中的 "ijk"
,而 &p[1]
是指向该 'k'
的指针。由于 "%s"
格式说明符告诉 printf
将指针视为指向空终止字符串的第一个元素的指针,它将打印指向的 'k'
和任何后续字符,直到遇到 '\0'
字符.这恰好是下一个字符,所以它只打印“k”。&p[5]
:同上,由于p
指向'j'
中的第二个数组a
,所以p[5]
就是其后5个位置的字符('e'
中的"defg"
)。同样,由于 "%s%"
格式说明符告诉 printf 打印以该参数指向的字符开头的字符,因此它将从 'e'
开始并打印 'f'
和 'g'
,然后再找到 '\0'
字符和停止。因此输出是“efg”。我将在下面尝试解释这些语句中的每一个最终是如何被编译器解释的。
char a[][5] = { "abc", "ijk", "defg", "opq" };
对于编译器,这将解析为一个指针列表,其中每个指针指向一个字符数组(也称为字符串文字)。
char *p = &a[1][1];
首先,
a[1]
解析为指向字符串"ijk"
的指针。因此,a[1][1]
或 (a[1])[1]
正在索引字符串 "ijk"
以获取字符 'j'
。然后,代码使用 char* p
运算符将这个“j”字符的地址存储到 &
中,以获取其在内存中的地址。
printf("%d,%s,%s", a[2] - a[0], &p[1], &p[5]);
让我们逐个讨论:
printf("%d", a[2] - a[0]);
如前所述,a是一个字符指针数组。因此,
a[2]
返回一个指针,a[0]
返回一个指针。由于指针基本上只是表示地址的整数,因此减去两个指针会得到另一个整数。在这种情况下,您会得到 10
,因为这恰好是这两个地址的差异。
printf("%s", &p[1]);
如前所述,
p
等于字符串"ijk"
的字母'j'的地址。因此,p[1]
将解析为 *(p + 1)
并且现在 等于 字母 'k'
。然后,应用 &
恢复取消引用并最终将地址返回到字母 'k'
。最后,%s
格式指定将打印整个 null terminated 字符串。因为所有字符串文字都是空终止的并且'k'
是字符串文字"ijk"
的最后一个字母,所以printf
打印的最终字符串只是"k"
.
printf("%s", &p[5]);
同样,
p
等于字符串'j'
的字母"ijk"
的地址。因此,p[5]
将解析为*(p + 5)
。在这种情况下,这是未定义的行为,因为我们试图取消引用超出字符串"ijk"
长度的地址。然而,程序并没有崩溃,因为我们恰好登陆了字符串“defg”中字母'e'
的地址。因此,%s
解析为 "efg"
.