示例代码中唯一的数组是由字符串字面量产生的数组:
"computer"
在大多数情况下,1 C 中的字符串文字指示编译器在内存中的某处生成(或表现得好像它已经生成)一个数组,该数组的内容用字符填充组成字符串文字。不允许在数组元素上写字——就好像数组的类型为 const char [<em>N</em>] 以对应适当的整数常量 N——但数组本身实际上具有类型 char [<em>N</em>]。 (在大多数现代编译器上,如果可能,该数组可能会放置在物理只读存储中,例如 ROM 或文本空间,尽管许多编译器都有可能影响这一点的指令。正如其他人指出的那样,规则有点与 C++ 不同。整数常量 N 由字符串文字中的字符数确定,并添加了终止 '\0' char。)
一般来说,给定一个类型为“数组N of T”的变量v(对于某些类型T),您只需将&v写成得到它的地址。这是一个“指向 T 的数组 N 的指针”类型的值。这也适用于字符串文字:2
char (*p_a)[6] = &"hello";
这里的p_a 是一个指向数组的指针。这和之间的区别:
char *p_c = "world";
p_c 是一个指向单个char 的指针,而不是指向(整个)数组的指针。因此p_c[1] 是该数组中的第二个 char,即'o'。但是p_a[1] 将是第一个数组"hello" 之后的整个6-char-long 数组,如果有这样的数组(没有这样的数组,因此尝试访问p_a[1] 会产生未定义的行为)。
那么,从字面上看,所问问题的答案是“都不是”。这些表达式都没有同时指向整个数组!
因为我们知道"computer" 由 9 个chars 组成——八个可见的chars 和一个终止'\0'——我们可以获取存储在str1 中的值,它是一个指向9 个chars 中的第一个,并将其(通过强制转换和/或赋值)转换为正确的类型char (*)[9],以获得“(整个)数组的地址”:
char (*answer_1)[9] = (void *)str1;
或:
((char (*)[9]) str1); /* answer2: produce the value, then ignore it */
例如。
不过,关于 C 中的指针和数组的一点是,指向数组的 第一个元素 的指针,就所有实际目的而言,与指向 的指针“一样好”。 em>整个数组。因此,简单的str1(指向 9-char-long 数组的第一个元素的指针)与指向整个 9-char-long 数组的指针“一样好”。
对于刚接触 C 的人来说,这一切听起来很奇怪。很久以前,我制作了一个图形来显示关键区别;可以找到here。区别的本质是“圆的大小”而不是“地址值”本身。
列表中的第 2 项(或者我应该说“ii”)&str1 使用了这样一个事实,即您有一个实际对象 str1,类型为“指向char”的指针。地址运算符& 为您提供指向该对象的指针:类型为“指向(指向char)的指针”或char ** 的值。值本身可以被认为是指向变量str1 的箭头,而存储在str1 中的值本身可以被认为是指向"computer" 中的c 的箭头。
1这里的主要例外是当字符串字面量用作初始值设定项时。例如:
char ch_array[9] = "computer";
完全填充 9-char-long 数组ch_array,而不必在任何地方创建第二个数组。在这些情况下,您可以控制结果数组的实际const-ness; ch_array 不是 const 并且 是 您可以写入的东西,因此您可以更改此数组中的字母,例如,如果您愿意,可以将其设为“catputer”。
C 标准还允许您将数组中的一个元素设置为“太短”,即无法容纳终止符 '\0'。在这种情况下,终结符根本不存储在任何地方。没有标准的方法可以在不创建这样的对象的情况下执行此操作,尽管有一个提议,在字符串文字中使用最终的 \z 转义以获得相同的结果,但没有成功。
注意字符串连接:
char *x = "com" "put" "er";
不在部件之间插入终止符'\0':在 连接之后添加终止符。而且,由于在此处生成的匿名数组上写入会产生未定义的行为,因此编译器也可以(并且好的编译器可以)share 字符串文字,可能带有“尾部共享”,3 这样:
char *s1 = "hello world", *s2 = "world";
可能只生成一个数组,包含"hello world" 字符串,并使s2 指向该字符串中的w。
2早在 1980 年代,很多编译器都犯了这个错误。公平地说,当时没有 C 标准:最初的 ANSI C 标准于 1989 年 12 月问世,所以直到 1990 年才有任何官方规则可以适用于编译器编写者。
3“尾巴共享”不是一个艺术术语,只是我在这里编造的一个短语,试图描述这个过程。