【问题标题】:Why allow concatenation of string literals?为什么允许字符串文字的连接?
【发布时间】:2011-01-31 01:32:21
【问题描述】:

我最近被一个微妙的虫子咬了。

char ** int2str = {
   "zero", // 0
   "one",  // 1
   "two"   // 2
   "three",// 3
   nullptr };

assert( int2str[1] == std::string("one") ); // passes
assert( int2str[2] == std::string("two") ); // fails

如果你有神一样的代码审查能力,你会注意到我忘记了"two" 后面的,

在为找到该错误付出了相当大的努力之后,我不得不问为什么会有人想要这种行为?

我知道这对宏魔法有多大用处,但为什么这是现代语言(如 python)的“功能”?

您曾经在生产代码中使用过字符串文字连接吗?

【问题讨论】:

  • 我有一个这样的错误,但我在不同的行上有数字,缺少逗号后的行有一个负号,所以我没有收到编译错误。
  • 我猜是 C++0x' 用户定义的文字:public.research.att.com/~bs/C++0xFAQ.html#UD-literals
  • 本着同样的精神,如果您忘记了字符串文字后面的_s 会发生什么?
  • 我能够在 C 和 C++ 以及 Python 中挖掘此功能的实际原理,详细信息在下面的答案中。

标签: python c++ c d string-literals


【解决方案1】:

我当然会同时使用 C 和 C++。顺便说一句,我认为它的实用性与语言的“现代”程度之间没有太大关系。

【讨论】:

  • 我并不是说不应该现代地使用旧语言。我指的是最初编写语言规范的时间。 C# 确实从 Java 中学习并避免了它的缺陷。从 C++ 学习 Java/C# 也是如此。
【解决方案2】:

这样您就可以跨行拆分长字符串文字。

是的,我已经在生产代码中看到过。

【讨论】:

    【解决方案3】:

    这可能有用的情况:

    • 生成包含由预处理器定义的组件的字符串(这可能是 C 语言中最大的用例,也是我经常看到的一种)。
    • 将字符串常量拆分为多行

    为前者提供一个更具体的例子:

    // in version.h
    #define MYPROG_NAME "FOO"
    #define MYPROG_VERSION "0.1.2"
    
    // in main.c
    puts("Welcome to " MYPROG_NAME " version " MYPROG_VERSION ".");
    

    【讨论】:

      【解决方案4】:

      这是一个很棒的功能,可以让您将预处理器字符串与您的字符串结合起来。

      // Here we define the correct printf modifier for time_t
      #ifdef TIME_T_LONG
          #define TIME_T_MOD "l"
      #elif defined(TIME_T_LONG_LONG)
          #define TIME_T_MOD "ll"
      #else
          #define TIME_T_MOD ""
      #endif
      
      // And he we merge the modifier into the rest of our format string
      printf("time is %" TIME_T_MOD "u\n", time(0));
      

      【讨论】:

      • @STingRaySC,PRIx32PRIuLEAST32 和朋友呢? opengroup.org/onlinepubs/9699919799/basedefs/inttypes.h.html
      • @STingRaySC - 虽然我同意在 C++ 中有更好的方法来做到这一点,但他的问题也被标记为 C(这非常有用)。
      • printf(和朋友的)格式字符串是编译时常量有一个非常很好的理由——编译器可以告诉你参数类型是否' t 匹配格式字符串。
      • @STingRaySC:这可能不是必需的,但它是一种常见用途。我希望看到一个简单示例的指针,该示例是该问题的替代解决方案,它不使用预处理器进行比较。
      • @STingRaySC - printf 可以优化为特定于该字符串中包含的格式的一系列调用,而不是作为单个库调用实现 - 这就是为什么它只需要一个常量字符串为它的第一个论点!如果您正在为一个小型嵌入式平台进行编译,那么不需要使用包含大量您永远不会使用链接的代码的全能打印功能可能是一个巨大的胜利(请记住嵌入式空间是一个的市场 C 仍然占主导地位,因此有很多人对此很重要)。
      【解决方案5】:

      当然,这是让您的代码看起来不错的简单方法:

      char *someGlobalString = "very long "
                               "so broken "
                               "onto multiple "
                               "lines";
      

      不过,最好的理由是奇怪的 printf 格式,比如强制类型:

      uint64_t num = 5;
      printf("Here is a number:  %"PRIX64", what do you think of that?", num);
      

      定义了很多,如果您有字体大小要求,它们可以派上用场。检查他们所有at this link。几个例子:

      PRIo8 PRIoLEAST16 PRIoFAST32 PRIoMAX PRIoPTR
      

      【讨论】:

      • 这说明了它出现在 C/C++ 中的原因,但实际上表明在 Python 中不可能实现类似的想法。
      【解决方案6】:

      我不确定其他编程语言,但例如 C# 不允许您这样做(我认为这是一件好事)。据我所知,如果您可以使用一些特殊的运算符进行字符串连接,那么大多数说明为什么这在 C++ 中有用的示例仍然有效:

      string someGlobalString = "very long " +
                                "so broken " +
                                "onto multiple " +
                                "lines"; 
      

      这可能不太舒服,但肯定更安全。在您的激励示例中,除非您添加 , 以分隔元素或添加 + 以连接字符串,否则代码将无效...

      【讨论】:

      • 那是无效的。在编译之前,这些字符串中至少有一个必须转换为 std::string 。此外,问题用 C 标记。
      • @BillyONeal:这个问题是用 Python/C++ 标记的,它询问为什么“Python 等现代语言”允许这样做,所以我想我会发布一个反例。而且我想表明您不需要该功能(通常)来支持换行符和宏扩展等功能。
      • 这是一个有用的答案 - 它说明了为什么它 (IMO) 确实是 Python 中的一个错误功能,会导致问题并且实际上没有必要。
      【解决方案7】:

      来自 python 词法分析参考,第 2.4.2 节:

      此功能可用于减少 拆分所需的反斜杠数 长字符串方便地跨越长 行,甚至添加 cmets 到 部分字符串

      http://docs.python.org/reference/lexical_analysis.html

      【讨论】:

      • ''' 字符串不会做同样的事情吗?
      • @Caspin - 原始字符串(r'' 或三引号)将包含所有换行符和空格。单独的字符串文字只会被连接起来。
      【解决方案8】:

      虽然人们已经从我口中说出了有关该功能的实际用途的话,但迄今为止没有人试图为语法选择辩护。

      据我所知,可能会漏掉的错字可能只是被忽略了。毕竟,针对拼写错误的鲁棒性似乎并不是 Dennis 最关心的问题,如下所示:

      if (a = b);
      {
          printf("%d", a);
      }
      

      此外,有一种可能的观点是,不值得使用额外的符号来连接字符串文字 - 毕竟,其中两个可以做的事情不多,并且有一个符号可能制造尝试将其用于运行时字符串连接的诱惑,这超出了 C 的内置功能级别。

      一些现代的、基于 C 语法的高级语言已经放弃了这种表示法,大概是因为它容易出现拼写错误。但是这些语言都有一个用于字符串连接的运算符,例如+(JS,C#)、.(Perl、PHP)、~(D,尽管这也保留了 C 的并列语法)和常量折叠(无论如何,在编译语言中)意味着没有运行时性能开销。

      【讨论】:

        【解决方案9】:

        我看到了几个 CC++ 的答案,但没有一个真正的答案 为什么 或者这个功能的基本原理是什么?在 C++ 中,这是来自 C99 的特性,我们可以通过转到 Rationale for International Standard—Programming Languages—C 部分 6.4.5 字符串文字 找到该特性的基本原理上面写着(强调我的):

        一个字符串可以通过使用反斜杠-换行符续行来跨多行,但这要求字符串的续行从下一行的第一个位置开始。 为了允许更灵活的布局,并解决一些预处理问题(参见 §6.10.3),C89 委员会引入了字符串文字连接。将连续的两个字符串文字粘贴在一起,中间没有空字符,以组成一个组合字符串文字。 C 语言的这一新增功能允许程序员将字符串文字扩展到物理行的末尾之外,而不必使用反斜杠-换行机制,从而破坏程序的缩进方案。 没有引入显式连接运算符,因为连接是词法构造而不是运行时操作。

        Python 似乎有同样的原因,这减少了丑陋的 \ 继续长字符串文字的需要。这在2.4.2 String literal concatenation 部分中介绍 Python 语言参考

        【讨论】:

        • 这似乎是没有引入显式连接运算符的真正原因,因为连接是词法构造而不是运行时操作。其他人都效仿了。
        • 如果您需要在 C 文字中表示大量文本,这将非常有用。例如,对于 CLI 程序的冗长“使用”消息,或者如果您不幸使用 C 编写 CGI 程序。
        【解决方案10】:

        出于基本原理,扩展和简化 Shafik Yaghmour 的回答:字符串文字连接起源于 C(因此由 C++ 继承),正如该术语一样,有两个原因(参考来自 Rationale for the ANSI C Programming Language):

        • 对于格式化:允许长字符串文字以适当的缩进跨越多行 - 与破坏缩进方案的行继续相反(3.1.4 String literals);和
        • 对于宏魔术:允许通过宏(通过字符串化)构造字符串文字 (3.8.3.2 The # operator)。

        它包含在现代语言 Python 和 D 中,因为它们是从 C 中复制而来的,尽管在这两种语言中都建议弃用它,因为它容易出错(如您所述)且不必要(因为人们可以有一个连接运算符和 constant folding 用于编译时评估;你不能在 C 中这样做,因为字符串是指针,所以你不能添加它们)。

        删除它并不简单,因为这会破坏兼容性,并且您必须注意优先级(隐式连接发生在词法分析期间,在运算符之前,但是用运算符替换它意味着您需要注意优先级),因此为什么它仍然存在。

        是的,它在使用的生产代码中。 Google Python Style GuideLine length 指定:

        当文字字符串不适合单行时,使用括号进行隐式行连接。

        x = ('This will build a very long long '
             'long long long long long long string')
        

        有关更多详细信息和参考资料,请参阅 Wikipedia 上的“String literal concatenation”。

        【讨论】:

        • 持续折叠消除了所有优势。如果仅限于字符串文字,这甚至可以在 C/C++ 中使用。
        • 谢谢!确实,字符串文字可能是特殊情况,尽管这也是一种技巧,并且以自己的方式令人困惑:为什么"foo" + "bar" 有效但string s = "bar"; "foo" + s 无效?我认为原因是字符串文字绝对属于char [] (C)/const char [] (C++) 类型。但是,它确实在 C++14 中使用新的字符串标准文字(带有 s 后缀)工作:"foo"s + "bar"s 是合法的,并且可以折叠。
        【解决方案11】:

        我在野外看到的另一个鬼鬼祟祟的错误是人们认为两个单引号是一种转义引号的方法(因为它通常使用for double quotes in CSV files, for example),所以他们会在@987654322 中编写如下内容@:

        print('Beggars can''t be choosers')
        

        它输出Beggars cant be choosers,而不是所需的编码器Beggars can't be choosers

        至于最初的“为什么”问题:为什么这是现代语言(如 python)中的“功能”? - 在我看来,我同意 OP,它不应该是。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-05-31
          • 1970-01-01
          • 2011-03-05
          • 2021-07-18
          • 2016-09-12
          • 2012-03-07
          • 2021-11-05
          相关资源
          最近更新 更多