为什么C在使用三元运算符时不允许连接字符串?

Jos*_* D. 95 c string concatenation

以下代码编译没有问题:

int main() {
    printf("Hi" "Bye");
}
Run Code Online (Sandbox Code Playgroud)

但是,这不编译:

int main() {
    int test = 0;
    printf("Hi" (test ? "Bye" : "Goodbye"));
}
Run Code Online (Sandbox Code Playgroud)

这是什么原因?

Sou*_*osh 134

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

相邻的字符串文字标记是连接的.

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

printf("Hi" (test ? "Bye" : "Goodbye"));
Run Code Online (Sandbox Code Playgroud)

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


为了详细说明原因部分,在预处理阶段,相邻的字符串文字被连接起来并表示为单个字符串文字(标记).相应地分配存储,并且连接的字符串文字被视为单个实体(一个字符串文字).

另一方面,在运行时连接的情况下,目标应该有足够的内存来保存连接的字符串文字,否则将无法访问预期的连接输出.现在,在的情况下,字符串常量,它们已经在编译时分配的内存,并且不能扩展,以适应任何多个输入输入附加到原始内容.换句话说,无法将连接结果作为单个字符串文字进行访问(呈现).所以,这种结构固有地不正确.

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

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

strcat()功能追加字符串的副本指向s2(包括终止空字符)到的端部指向的字符串s1.最初的字符s2覆盖了结尾处的空字符s1.[...]

所以,我们可以看到,这s1是一个字符串,而不是字符串文字.但是,由于内容s2不会以任何方式改变,因此很可能是字符串文字.


Vla*_*cow 119

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

1翻译语法规则的优先级由以下阶段指定.6)

  1. 相邻的字符串文字标记是连接的.

只有在那之后

  1. 分隔标记的空白字符不再重要.每个预处理令牌都转换为令牌.由此产生的标记在语法和语义上进行分析并翻译为翻译单元.

在这种结构中

"Hi" (test ? "Bye" : "Goodbye")
Run Code Online (Sandbox Code Playgroud)

没有相邻的字符串文字标记.所以这种结构无效.

  • @LightnessRacesinOrbit,因为当人们通常会问为什么某些东西不能在C中编译时,他们要求澄清它破坏了哪个规则,而不是为什么*古代的标准作者*选择它就是这样. (48认同)
  • 这只重复了C中不允许的断言.它没有解释_why_,这就是问题.不知道为什么它在5个小时内积累了26个赞成....并且接受,不会少!恭喜. (41认同)
  • @LightnessRacesinOrbit它解释了*为什么*:"因为C标准这么说".关于为什么将此规则定义为已定义的问题将不在话题范围内. (12认同)
  • 在这里必须同意@LightnessRacesinOrbit.为什么不应该`(测试?"再见":"再见")`对任何一个字符串文字进行评价,基本上是_making_""嗨""再见"或"好再见"` (我的问题在其他答案中回答) (4认同)
  • @LightnessRacesinOrbit你描述的问题可能不在话题.我看不出任何*技术*原因导致无法实现这一点,因此如果没有规范作者的明确答案,所有答案都将基于意见.它通常不属于"实际"或"可回答"问题的类别(因为[help/dont-ask]表示我们需要). (4认同)
  • @LightnessRacesinOrbit:而不是批评答案,这是一个善意的尝试来回答这个问题,而是批评这个问题."为什么"这些问题含糊不清*.为什么程序X表现出行为Y?因为这就是规范所说的.规范为什么这么说?因为这就是设计师所写的.设计师为什么写这个?因为他们对利弊进行了二十个小时的激烈辩论,并作为一个合理的妥协达成了这一点.为什么这个妥协而不是另一个?Blah等等,它会永远持续下去.**拒绝问题**. (4认同)
  • @LightnessRacesinOrbit*'和答案"因为它是"100%无用.'* - 很多关于C/C++问题的答案都是*"这就是为什么:<spec from the spec>"*.他们都没用?这个答案可能会更好,但这是一个有效的答案. (3认同)
  • @ user11153:答案"因为它"是100%无用的.看看我的答案是如何接近这个问题的(并且没有关于它的"主题" - 什么?!) (2认同)
  • @LightnessRacesinOrbit一个足够聪明的编译器(无视标准)可以在这种情况下内联三元运算符,使得当前规则可以保留可连接的内容.在你的回答中,你只是将"为什么"移动到"为什么预处理器不知道`test`的价值".怎么样的"一个"(0?"b":"c")`?当然预编译器会知道"0"吗?这些是需要标准参考的确切问题.解释动机可能很有意思,但推断它没有这样的标准通常是不正确的. (2认同)
  • @thebjorn:这些连接怎么可能在编译时执行,当"连接的东西"直到四年之后才知道,两百英里之外?你必须预先存储两种可能性的结果(这是一个可用的选择 - 不是一个好的但是嘿)或者在运行时进行连续.后者可能涵盖了"你可以做到这一点"的部分.在_this_点你可以说"标准决定文字concat将在编译时发生",很好;)虽然有哲学原因你可以给予它来完成故事. (2认同)
  • @LightnessRacesinOrbit足够公平,虽然在这种情况下,在编译时简单的常量评估将不需要任何运行时支持:-) fwiw/imho,Sourav Ghosh可能是目前最好的答案. (2认同)

Lig*_*ica 38

字符串文字串联由预处理器在编译时执行.这种连接没有办法知道test在程序实际执行之前不知道的值.因此,这些字符串文字不能连接.

因为一般情况是你不会有这样的结构用于编译时已知的值,所以C标准被设计为将自动连接功能限制为最基本的情况:当文字确实是彼此并排的时候.

但即使它没有以这种方式说明这种限制,或者如果限制是不同构造的,如果不将连接作为运行时进程,你的例子仍然是不可能实现的.而且,为此,我们有库函数,如strcat.

  • @Zaibis:来源就是我.弗拉德的答案根本不是解释; 它只是对问题前提的确认.当然,它们都不是"脱离主题"(你可能想要查找该术语的含义).但你有权得到你的意见. (11认同)
  • 我只是读了假设.虽然你所说的几乎是有效的,但你不能提供它的来源,因为没有.关于C的唯一来源是标准文档(虽然它在很多情况下是晦涩的)并没有说明为什么有些事情是他们的方式,而只是声明他们必须是那种特定的方式.所以,从莫斯科的回答中对弗拉德的挑剔是不合适的.因为OP可以分解为"为什么会那样?" - 唯一正确的源答案是"因为它是C,这就是C的定义方式",这是唯一正确的答案. (3认同)
  • 我无法分辨为什么这个答案对你来说是可以接受的,而且@ VladfromMoscow不是,当他们都说同样的话,并且当他被引用支持而你的不是. (2认同)

Uns*_*ned 30

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

C允许在编译时组合相邻的文字,如第一个示例中所示.C编译器本身对字符串有一些了解.但是这些信息在运行时不存在,因此不能进行连接.

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

int main() {
    static const char char_ptr_1[] = {'H', 'i', 'B', 'y', 'e', '\0'};
    printf(char_ptr_1);
}
Run Code Online (Sandbox Code Playgroud)

请注意编译器在执行程序之前如何将两个字符串组合到单个静态数组中.

但是,你的第二个例子被"翻译"成这样的东西:

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));
}
Run Code Online (Sandbox Code Playgroud)

应该清楚为什么这不编译.?当"字符串"不再存在时,三元运算符在运行时而不是编译时进行评估,但仅作为指针char引用的简单数组char*.与相邻的字符串文字不同,相邻的char指针只是语法错误.

  • @Ankush是的.但是,虽然`static const char str [] = {'t','e','s','t','\ 0'};`与`static const char str [] ="test"相同; `,``static const char*ptr ="test";`是_not_与`static const char*ptr = {'t','e','s','t','\ 0'}相同;` .前者是有效的,将编译,但后者无效,并做你期望的. (3认同)
  • 很好的答案,可能是最好的."应该清楚为什么这不能编译." 您可以考虑使用"因为三元运算符是在*运行时*不是*编译时**的条件评估"来扩展它. (2认同)

Eri*_*ric 12

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

#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;
}
Run Code Online (Sandbox Code Playgroud)


use*_*414 10

这是什么原因?

使用三元运算符的代码有条件地在两个字符串文字之间进行选择.无论条件是已知还是未知,都无法在编译时进行评估,因此无法编译.即使这个陈述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
}
Run Code Online (Sandbox Code Playgroud)

  • 所以不是教程网站.你应该给OP一个答案,而不是教程. (3认同)

pmg*_*pmg 7

printf("Hi" "Bye");你有烧焦的两个连续阵列,编译器可以使一个阵列.

printf("Hi" (test ? "Bye" : "Goodbye"));你有一个数组后跟一个指向char的指针(一个数组转换为指向其第一个元素的指针).编译器无法合并数组和指针.