在C++中,如果单引号用于多个字符时,它会产生什么作用?

284

我对这段代码感到好奇:

cout << 'test'; // Note the single quotes.

这个代码输出的是1952805748

我的问题是:这个输出是内存中的地址还是其他什么东西?


10
请注意,实际值由实现定义。https://dev59.com/lm865IYBdhLWcg3wKLJP - FireAphis
5个回答

287

这是一个多字符字面量。 19528057480x74657374,可以分解为

0x74 -> 't'
0x65 -> 'e'
0x73 -> 's'
0x74 -> 't'

编辑:

C++标准,§2.14.3/1 - 字符字面值

(...) 包含多个c-char的普通字符字面值是多字符字面值。多字符字面值的类型为int,实现定义了其值。


11
你没有提到这是实现定义。 - Andreas Bonini
2
我认为这个定义最有趣的事情是sizeof(int)也是由实现定义的。因此,存储顺序不仅是由实现定义的,而且这些的最大长度也是如此。 - bobobobo

77
不,这不是一个地址。这是所谓的多字节字符。
通常,它是四个字符的ASCII值组合而成。
't' == 0x74; 'e' == 0x65; 's' == 0x73; 't' == 0x74; 

所以0x74657374是1952805748。

但在其他编译器上,它也可以是0x74736574。C和C++标准都指出多字节字符的值是“实现定义的”。因此一般强烈不建议使用它。


这样的多字节字符的长度是否限制为4个字节?即它是否表示一个以字符形式写出的int? - Giorgio
2
@Giorgio:标准只是说它是实现定义的,没有更多的细节。在实践中,由于大多数机器上的int是4个字节,我认为使用超过4个字节没有意义。是的,它旨在成为编写某些常量的便捷方式,但不幸的是,不同的编译器对其进行了不同的解释,因此现在大多数编码风格都不鼓励使用它。 - chys
2
@chys:而且它的实现是定义不明确的,这意味着它甚至不需要保持一致。例如,符合规范的编译器可以给所有多字符字面值赋值为0(尽管这样做并不友好)。 - Keith Thompson
2
必须要问为什么这个疯狂的特性存在于标准中。它似乎是如此罕见的用例,而且实现也是定义的,如果需要的话,可以使用普通的位移和或运算来清晰地完成。 - Boann
1
@Boann _是的_,正是我的想法。但是你可以安全地在开关和其他情况下使用它,因为直接比较符号==应该可以检查通过。 - bobobobo

18
一个包含多个c-char的普通字符字面量是一个多字符字面量。一个多字符字面量具有int类型和实现定义的值。
实现定义行为需要被实现文档化。例如,在gcc中,您可以在这里找到它。
编译器逐个字符地评估多字符字符常量,将先前的值左移目标字符每个位的数量,然后或入新字符的位模式,该位模式被截断为目标字符的宽度。最终位模式具有int类型,因此是有符号的,无论单个字符是否有符号。
请查看此页面的说明以获取更多详细信息。

10

它们实际上只是 int。例如,在核心音频API枚举中,它们被广泛使用,例如在CoreAudioTypes.h头文件中。

enum
{
    kAudioFormatLinearPCM               = 'lpcm',
    kAudioFormatAC3                     = 'ac-3',
    kAudioFormat60958AC3                = 'cac3',
    kAudioFormatAppleIMA4               = 'ima4',
    kAudioFormatMPEG4AAC                = 'aac ',
    kAudioFormatMPEG4CELP               = 'celp',
} ;

有人说这不是"平台独立"的,但当你使用一个为特定平台设计的API时,谁会在意可移植性呢?在同一平台上检查相等将永远不会失败。这些使用 enum 枚举的值更易读,并且它们实际上包含了 它们的值中的身份信息,这非常好。

下面我尝试将多字节字符文字包装起来以便能够打印出来(在 Mac 上运行正常)。奇怪的是,如果您没有使用完所有四个字符,则下面的结果会变得不正确。

#include <stdio.h>

#define MASK(x,BYTEX) ((x&(0xff<<8*BYTEX))>>(8*BYTEX))

struct Multibyte
{
  union{
    int val ;
    char vals[4];
  };

  Multibyte() : val(0) { }
  Multibyte( int in )
  {
    vals[0] = MASK(in,3);
    vals[1] = MASK(in,2);
    vals[2] = MASK(in,1);
    vals[3] = MASK(in,0);
  }
  char operator[]( int i ) {
    return val >> (3-i)*8 ; // works on mac
    //return val>>i*8 ; // might work on other systems
  }

  void println()
  {
    for( int i = 0 ; i < 4 ; i++ )
      putc( vals[i], stdout ) ;
    puts( "" ) ;
  }
} ;

int main(int argc, const char * argv[])
{
  Multibyte( 'abcd' ).println() ;  
  Multibyte( 'x097' ).println() ;
  Multibyte( '\"\\\'\'' ).println() ;
  Multibyte( '/*|' ).println() ;
  Multibyte( 'd' ).println() ;

  return 0;
}

7
在同一平台上检查相等性永远不会失败,但实际上可能会失败。如果你升级到 Visual Studio 的版本 xyz,问题仍然存在。这个库做出了一个非常糟糕的决定。 - Lightness Races in Orbit
@LightnessRacesinOrbit "升级到 Visual Studio xyz 并咬紧你的舌头。" Core Audio API 是 OS X 的系统音频 API,因此与此无关。 - Jean-Michaël Celerier
7
好的,请升级您的OSX Clang版本,并克制一下不满情绪。 - Lightness Races in Orbit
1
@LightnessRacesinOrbit 或者干脆使用另一个编译器。这种行为是“编译器相关的”,而不是“平台相关的”。平台依赖性意味着假设在默认环境中,$HOME 始终存储以 /Users/ 开头的值。如果库总是与其依赖项同时编译,那么这不是一个可怕的想法(只是一个糟糕的想法),但二进制格式会持续存在,有人会对其产生依赖,这是一场等待发生的噩梦。 - MooseBoys

1

当您正在构建解析器时,这种功能非常好。考虑以下内容:

byte* buffer = ...;
if(*(int*)buffer == 'GET ')
  invoke_get_method(buffer+4);

这段代码可能只适用于特定的字节序,并且在不同的编译器中可能会出现错误。

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