字符数组和整数数组的区别

3
  1. char *s = "Hello"
  2. char s[6] = "Hello"

以上任意一种语法都可以正常工作。

但是以下这种语法呢?

  1. int a[3] = {1,2,3} (这种方法非常好用)
  2. 但为什么下面这种方法不行呢,int *a = {1, 2, 3};?

解释以及对比情况[2]和[4]将会很有帮助。


请注意,实际上情况1是例外,而不是其他情况。此外,特别是对于C语言,最好为编译器增加额外的检查级别,因为您可以轻松创建相当复杂的表达式,只有专家才能轻松推断语义。 - too honest for this site
7个回答

5
这是因为"Hello"被替换成了字符串常量Hello的地址。因此,char *s = "Hello"的意思是“将字符串常量Hello的地址分配给指针s”。
与此同时,{1, 2, 3}并不构成一个地址,也没有被替换。你不能给指针赋值除地址以外的任何东西,所以你不能写int *a = {1, 2, 3}

5

无法工作的原因是初始化器的数据类型未定义。对于字符串文字,这是由语法隐式给出的。但是{1,2,3}可以是数组、结构体或许多其他变体。

您必须指定数据类型:

int *ia = (int []){1,2,3};

这里使用了复合字面量(C99)。

需要注意的是这不仅适用于初始化,还可在普通代码中使用。


能否具体列举 “许多其他变体”的例子? - ravipandhi
@ravipandhi:带有匿名联合体的结构体,依赖于强制转换的浮点数数组?够了吗?发挥你的想象力。 - too honest for this site

3
  1. char *s="Hello"

    这里,s 是一个指向 char 类型的指针,它指向字符串常量 "Hello" 的基地址。

  2. char s[6]="Hello"

    这里,s 是一个由 6 个 char 元素组成的数组,初始值为 Hello\0

  3. int a[3]={1,2,3}

    这里,a 是一个由 3 个元素组成的 int 数组,其初始值为 123

注意:上述三个示例都是合法的。

  1. int *a={1,2,3} 是无效的。

    这里,a 的类型是 int *,而花括号中的列表并没有提供 int * 类型的值。因此,这不是一种定义行为,是无效的。


如果's'指向字符数组“Hello”的基地址,那么为什么'a'不指向整数数组{1,2,3}的基地址?这里有什么特别之处吗? - ravipandhi
@ravipandhi 是的,"Hello" 被称为 _字符串字面值_,它返回字面值的基地址。{1,2,3} 不是 int 字面值,它被称为 _花括号包含的初始化列表_。 - Sourav Ghosh
就像s++通过将1个字节(char的大小)添加到基础来遍历字符数组中的下一个元素一样,似乎case 4也像"a++通过将机器相关整数大小添加到基础来遍历整数数组中的下一个元素"一样在逻辑上是正确的吗? - ravipandhi
@ravipandhi 理解这里的基本问题,"Hello" 返回一个 char *,你可以将其放入另一个 char * 中,但 {1,2,3} 首先并不会返回一个 int * - Sourav Ghosh
为什么{1,2,3}不能返回int指针?为什么一开始就没有提供这个功能呢?如果“Hello”可以返回char指针,为什么{1,2,3}不能返回呢?在这里有哪些低级细节起了作用?如果提供了这个功能会有什么问题呢? - ravipandhi
1
@ravipandhi:那只是语言设计的原因。字符串字面量很特别,与{1, 2, 3}这样的初始化器处理方式不同。像"Hello"这样的字符串字面量是一个数组表达式,在大多数情况下,数组表达式会“衰减”为指针表达式。初始化器{1,2,3} 本身 不是一个数组表达式,因为语言定义是这样规定的。正如以下的余昊所指出的,(int[]){1,2,3}(自C99起) 一个数组表达式,它会衰减为指针表达式,所以你可以写成 int *a = (int[]){1,2,3} - John Bode

3

不支持案例4而支持其他案例的原因是历史原因。这些因素包括早期有影响力的程序员和编译器供应商之间的游说和政治相互作用,而不是经过深思熟虑的技术决策。

因此,如果你正在寻找一个强有力的技术理由,你不会找到。

从历史上看,案例2和3在C语言的演变过程中早就得到了支持。你的案例2实现了与其他案例相同的效果。

  char s[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

对于除了char类型以外的任何数组初始化,都没有与字符串字面量相对应的东西。

历史上,情况1是程序员引入的异常情况,他们想要实现的效果是

  char s_temp[] = "Hello";
  char *s = temp_s;

使用更少的键入(即作为一个单独的语句)。游说支持案例1最终获胜(它被引入主流编译器,后来成为标准)。案例1是标准中唯一的情况,其中指针可以直接使用数组初始化器进行初始化,而无需进行任何类型转换。

从历史上看,程序员从未要求或游说过案例4。

无论你喜不喜欢,这就是为什么支持案例1、2、3,但不支持案例4的原因。

案例2和案例4之间没有真正的比较,因为它们(试图)实现不同的功能。如案例2中的字符串字面值,是一个只适用于char数组的数组初始化器 - 没有非char类型的对应物。案例4试图使用数组初始化器初始化指针。


1
从编译器的角度来看,实际上有很好的理由不支持这个。编译器可能已经变得太复杂,无法推断出实际类型。随着C99的复合字面量的引入,一种更优越的(更明确和通用的)替代方案已经出现了。 - too honest for this site
@Olaf 嗯,过去支持这个功能变得太复杂了?使用 C99,他们提出了一种将类型转换为特定类型以提供支持的解决方案?是这样吗?那么问题就解决了! :) - ravipandhi
@ravipandhi:不要聪明。那不是类型转换,而是复合字面量。强制转换将把一种类型转换/重新解释为另一种类型,而复合初始化程序中的大括号告诉编译器实际类型。您必须查看整个结构:带括号的类型加块,而不仅仅是第一部分。在块上进行类型转换实际上是没有意义的。而且,如果编译器必须从左侧推断类型,或者如果它已经像任何其他表达式一样出现在右侧,则确实存在差异。正如Peter所写的那样,实际上`char *cp = ""是hack。 - too honest for this site
还要注意的是,我们谈论的是80年代的编译器;它们已经足够努力地尝试优化代码了,因此在解析方面减少工作量和内存占用对它们来说是非常愉快的事情。 - too honest for this site
@Olaf:类型转换的问题,抱歉我的失误!非常感谢您的解释! - ravipandhi
显示剩余2条评论

2
int *a = {1, 2, 3};

这段代码在语法上是不正确的,因为{1, 2, 3}不能用于初始化指针。

但是稍作修改就可以使其正常工作:

int *a = (int []){1, 2, 3};

这是一个C99复合文字。


1

使用字符串初始化字符数组是一个特殊情况:char s[6] = "hello" 被视为代码 char s[6] = { 'h', 'e', 'l', 'l', 'o', '\0'};。由于使用字符串初始化字符数组是一种常见情况,因此这种用法很有意义。


在我看来,这并没有回答问题。 - Spikatrix
char s[6] = { 'h', 'e', 'l', 'l', 'o', '\0'};会使它可写吗?我认为是的。 - Eregrith
@Eregrith,是的。s是一个本地数组,它的初始化方式不会改变它的可变性。 - William Pursell
@WilliamPursell 哦,是的,抱歉我忘记了char s[6] ="hello"也是可写的。我的错误。 - Eregrith

0
  1. int a[3] = {1,2,3};

这是数组的正常初始化语法。

  1. char s[6] = "Hello";

这是一个特殊情况下的字符数组初始化语法,您可以在右侧写入字符串文字,它将扩展为上述正常初始化语法,即char s [6] = {'H','e','l','l','o','\ 0'};

  1. char *s = "Hello";

这是标量变量的正常初始化语法,初始化为右侧的表达式。 在这里,"Hello"是有效的C表达式。

  1. int *a = {1, 2, 3};

这与(1)不同,因为{1, 2, 3}不是有效的C表达式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接