为啥 C 在使用条件运算符时不允许连接字符串?

Posted

技术标签:

【中文标题】为啥 C 在使用条件运算符时不允许连接字符串?【英文标题】:Why does C not allow concatenating strings when using the conditional operator?为什么 C 在使用条件运算符时不允许连接字符串? 【发布时间】:2016-09-12 13:41:55 【问题描述】:

以下代码编译没有问题:

int main() 
    printf("Hi" "Bye");

但是,这不会编译:

int main() 
    int test = 0;
    printf("Hi" (test ? "Bye" : "Goodbye"));

这是什么原因?

【问题讨论】:

字符串连接是早期词法分析阶段的一部分;它不是 C 的表达式 synatx 的一部分。换句话说,没有“字符串文字”类型的 value。相反,字符串字面量是源代码中形成值的词法元素。 只是为了澄清@KerrekSB 的答案 - 字符串的连接是代码文本 prior 编译它的预处理的一部分。在运行时计算三元运算符时,在编译代码之后(或者如果一切都是常量,则可以在编译时完成)。 详细信息:在这篇文章中,"Hi""Bye"字符串文字,而不是 C 标准库中使用的 字符串。使用字符串文字,编译器将连接"H\0i" "B\0ye"。与sprintf(buf,"%s%s", "H\0i" "B\0ye");不一样 或多或少与你不能做的原因相同a (some_condition ? + : - ) b 请注意,即使printf("Hi" ("Bye")); 也不起作用——它不需要三元运算符;括号就足够了(尽管printf("Hi" test ? "Bye" : "Goodbye") 也不会编译)。只有有限数量的标记可以跟在字符串文字后面。逗号,,左方括号[,右方括号](如1["abc"]——是的,这很可怕),右方括号),右大括号(在初始化程序中或类似的上下文),分号; 是合法的(和另一个字符串文字);我不确定还有其他人。 【参考方案1】:

根据 C11 标准,第 5.1.1.2 章,相邻字符串文字的连接:

连接相邻的字符串文字标记。

发生在翻译阶段。另一方面:

printf("Hi" (test ? "Bye" : "Goodbye"));

涉及条件运算符,在运行时进行评估。因此,在编译时,在翻译阶段,不存在相邻的字符串文字,因此不可能进行连接。语法无效,因此由您的编译器报告。


为了详细说明为什么部分,在预处理阶段,相邻的字符串文字被连接起来并表示为单个字符串文字(令牌)。相应地分配存储空间,并将连接的字符串文字视为单个实体(一个字符串文字)。

另一方面,在运行时串联的情况下,目的地应该有足够的内存来保存串联的字符串文字,否则,预期的将无法实现em> 可以访问连接的输出。现在,对于字符串文字,它们在编译时已经分配了内存,并且不能扩展以适应更多传入的输入into附加到原始内容。换句话说,连接的结果将无法作为单个字符串文字访问(呈现)。所以,这个结构本质上是不正确的。

仅供参考,对于运行时字符串不是文字)连接,我们有库函数strcat() 连接两个字符串 .注意,描述中提到:

char *strcat(char * restrict s1,const char * restrict s2);

strcat() 函数附加了s2 指向的字符串的副本(包括 终止空字符)到s1所指向的字符串的末尾。初始字符 s2 覆盖 s1 末尾的空字符。 [...]

所以,我们可以看到,s1 是一个字符串,而不是一个字符串文字。然而,s2 的内容并没有以任何方式改变,它很可能是一个字符串文字

【讨论】:

【参考方案2】:

根据 C 标准(5.1.1.2 翻译阶段)

1 翻译的语法规则之间的优先级由 以下阶段。6)

    连接相邻的字符串文字标记。

只有在那之后

    分隔标记的空白字符不再重要。每个 预处理令牌转换为令牌。 结果 标记在句法和语义上被分析和翻译为 翻译单元

在这个结构中

"Hi" (test ? "Bye" : "Goodbye")

没有相邻的字符串文字标记。所以这个构造是无效的。

【讨论】:

这只是重复了它在 C 中不允许的断言。它没有解释 为什么,这是问题所在。不知道为什么它在 5 小时内累积了 26 个赞成票……而且接受,不少于!恭喜。 这里必须同意@LightnessRacesinOrbit。为什么(test ? "Bye" : "Goodbye") 不应该评估为基本上制作 "Hi" "Bye""Hi Goodbye" 的字符串文字? (我的问题在其他答案中得到了回答) @LightnessRacesinOrbit,因为当人们通常问为什么某些东西不能用 C 编译时,他们要求澄清它违反了哪条规则,而不是为什么古代标准作者选择它是这样的。 @LightnessRacesinOrbit 您描述的问题可能会离题。我看不出任何无法实现它的技术原因,因此如果没有规范作者的明确回答,所有答案都将基于意见。而且它通常不属于“实用”或“可回答”问题的类别(正如help center 表明我们要求的那样)。 @LightnessRacesinOrbit 它解释了为什么:“因为 C 标准是这么说的”。关于为什么将这条规则定义为已定义的问题将是题外话。【参考方案3】:

字符串文字连接由预处理器在编译时执行。这种连接无法知道test 的值,直到程序实际执行才知道。因此,这些字符串文字不能串联。

因为一般情况下,对于编译时已知的值,您不会有这样的构造,所以 C 标准旨在将自动连接功能限制在最基本的情况下:当文字字面上正确时并排。

但是,即使它没有以这种方式表达此限制,或者如果限制的构造不同,如果不将串联作为运行时过程,您的示例仍然无法实现。为此,我们有诸如strcat 之类的库函数。

【讨论】:

我只是阅读假设。虽然你所说的非常有效,但你不能提供它的来源,因为没有。关于 C 的唯一来源是标准文档(虽然它在许多情况下是显而易见的)并没有说明为什么有些事情是这样的,而只是说明它们必须是那种特定的方式。因此,从莫斯科的回答中对弗拉德挑剔是不合适的。由于 OP 可以分解为“为什么会这样?” - 唯一正确的来源答案是“因为它是 C,这就是 C 的定义方式”,这是唯一一个直截了当的正确答案。 这是(承认)缺乏解释。但是这里再次提到,弗拉德的回答比你的回答更能解释核心问题。再次表示:虽然您提供的信息我可以确认是相关且正确的,但我不同意您的投诉。虽然我也不认为你是离题的,但从我的 POV 来看,它比 Vlads 实际上更离题。 @Zaibis:来源是我。弗拉德的回答根本不是解释。这只是对问题前提的确认。当然,它们都不是“离题”(您可能想查找该术语的含义)。但你有权发表你的意见。 即使在阅读了上面的 cmets 之后,我仍然想知道谁对这个答案投了反对票 ᶘ ᵒᴥᵒᶅ 我相信这是一个完美的答案,除非 OP 要求进一步澄清这个答案。 我无法区分为什么这个答案是你可以接受的,而@VladfromMoscow 的却不是,当他们都说同样的话,并且当他的引文支持而你的不是。 【参考方案4】:

因为 C 没有 string 类型。字符串文字被编译为char 数组,由char* 指针引用。

C 允许在编译时组合相邻的 文字,就像在您的第一个示例中一样。 C 编译器本身对字符串有一些了解。但此信息在运行时不存在,因此无法进行连接。

在编译过程中,您的第一个示例被“翻译”为:

int main() 
    static const char char_ptr_1[] = 'H', 'i', 'B', 'y', 'e', '\0';
    printf(char_ptr_1);

在程序执行之前,请注意编译器如何将这两个字符串组合成一个静态数组。

然而,你的第二个例子被“翻译”成这样的东西:

int main() 
    static const char char_ptr_1[] = 'H', 'i', '\0';
    static const char char_ptr_2[] = 'B', 'y', 'e', '\0';
    static const char char_ptr_3[] = 'G', 'o', 'o', 'd', 'b', 'y', 'e', '\0';
    int test = 0;
    printf(char_ptr_1 (test ? char_ptr_2 : char_ptr_3));

应该清楚为什么这不能编译。三元运算符? 在运行时而不是编译时评估,此时“字符串”不再存在,而只是简单的char 数组,由char* 指针引用。与相邻的字符串字面量不同,相邻的字符指针只是一个语法错误。

【讨论】:

优秀的答案,可能是这里最好的答案。 “应该清楚为什么这不能编译。”您可能会考虑将其扩展为“因为三元运算符是在运行时而不是编译时进行条件评估”。 不应该 static const char *char_ptr_1 = 'H', 'i', 'B', 'y', 'e', '\0';static const char *char_ptr_1 = "HiBye"; 并且对于其余的指针也是如此? @CoolGuy 当您编写static const char *char_ptr_1 = "HiBye"; 时,编译器会将行转换为static const char *char_ptr_1 = 'H', 'i', 'B', 'y', 'e', '\0';,所以不,它不应该写成“像字符串一样”。正如答案所说,字符串被编译为一个字符数组,如果您以最“原始”的形式分配一个字符数组,您将使用逗号分隔的字符列表,就像static const char *char_ptr_1 = 'H', 'i', 'B', 'y', 'e', '\0'; @Ankush 是的。但是尽管static const char str[] = 't', 'e', 's', 't', '\0';static const char str[] = "test"; 相同,static const char* ptr = "test";static const char* ptr = 't', 'e', 's', 't', '\0';相同。前者是有效的并且会编译,但后者是无效的并且会按照你的期望进行。 我已经充实了最后一段并更正了代码示例,谢谢!【参考方案5】:

如果你真的想让两个分支都生成编译时字符串常量,以便在运行时选择,你需要一个宏。

#include <stdio.h>
#define ccat(s, t, a, b) ((t)?(s a):(s b))

int
main ( int argc, char **argv)
  printf("%s\n", ccat("hello ", argc > 2 , "y'all", "you"));
  return 0;

【讨论】:

【参考方案6】:

这是什么原因?

您的代码使用三元运算符有条件地在两个字符串文字之间进行选择。无论条件已知还是未知,这都无法在编译时进行评估,因此无法编译。甚至这条语句printf("Hi" (1 ? "Bye" : "Goodbye")); 也无法编译。原因在上面的答案中得到了深入的解释。 使用对编译有效的三元运算符制作这样的语句的另一种可能性,还涉及一个格式标记和格式化为附加参数 到printf。即便如此,printf() 打印输出也会给人一种印象,即仅在运行时,并且早在运行时“连接”这些字符串。

#include <stdio.h>

int main() 
    int test = 0;
    printf("Hi %s\n", (test ? "Bye" : "Goodbye")); //specify format and print as result

【讨论】:

SO 不是教程网站。您应该对 OP 给出答案,而不是教程。 这不能回答 OP 的问题。这可能是为了解决 OP 的根本问题,但我们并不真正知道那是什么。 printf需要格式说明符;如果仅在编译时完成连接(事实并非如此),OP 对 printf 的使用将是有效的。 感谢您的评论,@David Conrad。我草率的措辞确实看起来好像声明printf() 需要一个格式标签,这绝对不是真的。已更正! 那是一个更好的措辞。 +1 谢谢。【参考方案7】:

printf("Hi" "Bye"); 中有两个连续的 char 数组,编译器可以将它们组合成一个数组。

printf("Hi" (test ? "Bye" : "Goodbye")); 中,您有一个数组,后跟一个指向 char 的指针(一个转换为指向其第一个元素的指针的数组)。编译器不能合并一个数组和一个指针。

【讨论】:

【参考方案8】:

要回答这个问题 - 我会转到 printf 的定义。函数 printf 需要 const char* 作为参数。任何字符串文字,例如“Hi”,都是 const char*;但是,(test)? "str1" : "str2" 这样的表达式不是 const char*,因为这种表达式的结果只能在运行时找到,因此在编译时是不确定的,这一事实适时导致编译器抱怨。另一方面 - 这工作得很好printf("hi %s", test? "yes":"no")

【讨论】:

*然而像(test)? "str1" : "str2"这样的表达式不是const char*...当然是!它不是一个常量表达式,但它的类型 const char *。写printf(test ? "hi " "yes" : "hi " "no") 就好了。 OP的问题与printf无关,"Hi" (test ? "Bye" : "Goodbye")无论表达式上下文是什么都是语法错误。 同意。我将表达式的输出与表达式本身混淆了【参考方案9】:

这不会编译,因为 printf 函数的参数列表是

(const char *format, ...)

("Hi" (test ? "Bye" : "Goodbye"))

不符合参数列表。

gcc 试图通过想象来理解它

(test ? "Bye" : "Goodbye")

是一个参数列表,并抱怨“Hi”不是一个函数。

【讨论】:

欢迎来到 Stack Overflow。你是对的,它与printf() 参数列表不匹配,但这是因为表达式在任何地方都无效——不仅仅是在printf() 参数列表中。换句话说,你为这个问题选择了一个过于专业的原因;一般问题是"Hi" ( 在C 中无效,更不用说调用printf()。我建议您在此答案被否决之前删除它。 C 不是这样工作的。这不会被解析为试图调用像 php 这样的字符串文字。

以上是关于为啥 C 在使用条件运算符时不允许连接字符串?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 C# 6.0 在使用 Null 传播运算符时不允许设置非 null 可空结构的属性?

为啥 C# 6.0 在使用 Null 传播运算符时不允许设置非 null 可空结构的属性?

为啥我不能在没有括号的插值字符串中使用条件运算符? [复制]

为啥条件运算符不能正确地允许使用“null”来分配给可为空的类型? [复制]

为啥条件运算符是右结合的?

使用三元条件运算符对系列进行 Python 字符串连接