为啥 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 可空结构的属性?
为啥我不能在没有括号的插值字符串中使用条件运算符? [复制]