【问题标题】:Unicode literal - how does this even make sense?Unicode 文字 - 这有什么意义?
【发布时间】:2017-04-27 16:20:11
【问题描述】:
int main() {    
    std::cout << "\u2654" << std::endl; // Result #1: ♔
    std::cout << U'\u2654' << std::endl; // Result #2: 9812
    std::cout << U'♔' << std::endl; // Result #3: 9812
    return 0;
}

我无法理解 Unicode 如何与 C++ 协同工作。为什么字面量在终端不输出字面量?

我有点想要这样的东西;

char32_t txt_representation() { return /* Unicode codepoint */; } 

注意:源是 UTF-8,终端也是如此,它位于 macOS Sierra、CLion 上。

【问题讨论】:

  • 为什么要混合宽字符输入和窄字符输出?
  • 你认为L'\u2654'等的类型是什么...(提示:它不是char)。没有"\2654" 不是 std::string
  • 只有第一个是(可能)utf8,第二个 2 是 utf32ucs16 宽字符编码。为了确保第一个使用u8"\u2654"
  • @Galik:哦,你说得对!我会改变的。我对所有这些编码都很困惑,所以请理解一下..
  • 后续问题可能是 Entalpi 想问的问题:stackoverflow.com/questions/41107667/…

标签: c++ unicode character-encoding


【解决方案1】:

C++ 在其类型系统中实际上并没有“字符”的概念。 charwchar_tchar16_tchar32_t 都被认为是 integer 的种类。因此,'x'L'x'U'x' 等字符文字都是 数字。有一个operator&lt;&lt; 专门用于char,这就是为什么

cout << "endl is almost never necessary" << '\n';

做同样的事情

cout << "endl is almost never necessary\n";

*char_t 没有类似物,因此您的宽字符文字被静默转换为int 并照此打印。我个人从不使用 iostreams,因此我实际上不知道如何说服 operator&lt;&lt; 打印一个数字作为其 Unicode 代码点,但可能有一些方法可以做到这一点。

在类型系统中,“字符串”和“整数数组”之间有更强的区别,因此当您提供字符串文字时,您确实会得到您期望的输出。但是请注意,cout &lt;&lt; L"♔" 不会给出您期望的输出,并且cout &lt;&lt; "♔" 甚至不能保证编译。 cout &lt;&lt; u8"♔" 在符合 C++11 的系统上工作,其中窄字符编码实际上是 UTF-8,但如果字符编码是其他字符,则可能会产生 mojibake。

(是的,这一切都比它存在的任何借口都复杂得多,也没那么有用。这部分是因为从 C 继承的向后兼容性限制,部分是因为它都是在 1990 年代设计的,当时 Unicode 接管了世界,部分原因是 C++ 字符串和流类中的许多设计错误在修复它们为时已晚之前并不明显。)

【讨论】:

  • 请注意有std::wcout可以处理宽字符输出(std::wstring/wchar_t*)。
  • @NathanOliver 是的,但是你不能打印窄字符。
  • @zwol:“你不能打印窄字符”不正确。但是,有限功能的实现(仅支持char const*)可能有点缺乏,具体取决于编译器。
  • 在原帖和解决方案中同时指出std::endl 的过度使用是多么美妙而微妙的方式:-)
【解决方案2】:

Unicode 和 C++

有几种unicode编码:

  • UTF-8 将每个 unicode 字符编码为一到四个(8 位)字节的序列 (char)
  • UTF-16(可以是 BE 和 LE,取决于字节序)将每个 unicode 字符编码为一个或两个 16 位字的序列 (char16_t)。
  • UTF-32(同样是 BE 或 LE)将每个 unicode 字符编码为一个 32 位字(char32_t)。

这是 James McNellis 的 excellent video tutorial on unicode with C++。他解释了您需要了解的有关字符集编码、unicode 及其不同编码以及如何在 C++ 中使用它的所有信息。

您的代码

"\u2654" 是一个窄字符串文字,其类型数组为charwhite chess king unicode character 将被编码为对应于 UTF-8 编码 ({ 0xe2, 0x99, 0x94 }) 的 3 个连续字符。因为我们在一个字符串中,所以里面有几个字符是没有问题的。由于您的控制台语言环境肯定使用 UTF8,因此它会在显示字符串时正确解释解码序列。

U'\u2654'char32_t 类型的字符文字(因为大写 U)。由于它是 char32_t(而不是 char),因此它不会显示为 char,而是显示为整数值。十进制值是 9812。如果你使用十六进制,你会立即认出它。

最后一个U'♔' 遵循相同的逻辑。但是请注意,您在源代码中嵌入了 unicode 字符。只要编辑器的字符编码与编译器期望的源代码编码相匹配,这就可以了。但是,如果将文件复制(不进行转换)到需要不同编码的环境,这可能会导致不匹配。

【讨论】:

  • 最后一段不正确。神圣标准,需要从源代码编码转换为 Unicode。并向下转换为用于窄字符串文字的任何编码。查看翻译的各个阶段。原始字符串文字并不完全清楚,但这是一个非常好的细节。
  • @Cheersandhth.-Alf 感谢您强调我模棱两可的表述。关于上下映射,您是完全正确的。但是我想提请注意源文件编码与编译器的预期输入编码不匹配的情况,例如,如果 UTF-8 文件将被复制到非 unicode 环境并且实现依赖映射将解释为不同的本地字符将被映射到不同的 unicode 代码点 - 但是好吧,Win1252 不再那么常见了 ;-) 我已经编辑澄清
【解决方案3】:

不支持将宽字符打印到窄流,并且根本不起作用。 (它“有效”,但结果不是你想要的)。

不支持将多字节窄字符串打印到宽流,并且根本不起作用。 (它“有效”,但结果不是你想要的)。

在支持 Unicode 的系统上,std::cout &lt;&lt; "\u2654" 按预期工作。 std::cout &lt;&lt; u8"\u2654" 也是如此。大多数正确设置的基于 Unix 的操作系统都支持 Unicode。

在支持 Unicode 的系统上,如果您正确设置程序区域设置,std::wcout &lt;&lt; L'\u2654' 应该可以正常工作。这是通过这个调用完成的:

 ::setlocale(LC_ALL, "");

或者这个

 ::std::locale::global(::std::locale(""));

注意“应该”;对于某些编译器/库,此方法可能根本不起作用。这是这些编译器/库的缺陷。我在看着你,libc++。它可能正式也可能不是一个错误,但我认为它是一个错误。

您应该在所有希望使用 Unicode 的程序中真正设置您的语言环境,即使这看起来没有必要。

在同一程序中混合coutwcout 不起作用且不受支持。

std::wcout &lt;&lt; U'\u2654' 确实 not 起作用,因为这是将wchar_t 流与char32_t 字符混合在一起。 wchar_tchar32_t 是不同的类型。我想正确设置的std::basic_stream&lt;char32_t&gt; 可以与char32_t 字符串一起使用,标准库没有提供任何内容。

基于char32_t 的字符串非常适合存储和处理Unicode 代码点。不要将它们直接用于格式化输入和输出。 std::wstring_convert 可用于来回转换它们。

TL;DR 可以使用 std::streams 和 std::strings,或者(如果您未使用 libc++)std::wstreams 和 std::wstrings。

【讨论】:

    【解决方案4】:

    在我的系统上,我无法将 std::coutstd::wcout 混合使用并获得合理的结果。所以你必须分开做这些。

    您应该使用 std::locale::global(std::locale(""));locale 设置为本机系统的设置。

    对于后两个输出也使用 宽流

    要么:

    std::locale::global(std::locale(""));
    
    std::cout << "\u2654" << std::endl;
    

    或者:

    std::locale::global(std::locale(""));
    
    std::wcout << L"\u2654" << std::endl;
    std::wcout << L'♔' << std::endl;
    

    这应该会鼓励输出流在本地系统的编码和utf8(第一个示例)或ucs16/utf32(第二个示例)之间进行转换。

    我认为第一个例子最安全(编辑器可以有其他编码)最好在字符串前面加上u8

    std::cout << u8"\u2654" << std::endl;
    

    【讨论】:

      猜你喜欢
      • 2022-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-15
      • 2011-06-28
      • 2016-05-26
      • 1970-01-01
      相关资源
      最近更新 更多