【问题标题】:Printing em-dash to console window using printf? [duplicate]使用 printf 将 em-dash 打印到控制台窗口? [复制]
【发布时间】:2017-09-14 19:08:43
【问题描述】:

一个简单的问题:我正在用 C++(但它主要是 C 风格)为一个类编写一个聊天室程序,并且我正在尝试打印“#help — 显示命令列表...”到输出窗口。虽然我可以使用两个连字符 (--) 来实现 大致 相同的效果,但我更愿意使用 em-dash (-)。但是,printf() 似乎不支持打印破折号。相反,控制台只是在其位置打印出字符ù,尽管直接在提示符中输入破折号可以正常工作。

如何让这个简单的 Unicode 字符显示出来?

查看 Windows alt 键代码,我发现 alt+0151 是“—”而 alt+151 是“ù”的方式很有趣。这与我的问题有关,还是一个简单的巧合?

【问题讨论】:

  • 问题是 Windows 控制台使用代码页而不是 Unicode。
  • 问题是 em-dash 是一个 unicode 字符,您尝试将其打印在 ascii 字符串中
  • 试试这个 std::wcout stackoverflow.com/questions/33029906/…
  • @sata300.de 链接的答案是在许多情况下方便地执行此操作的关键,即在程序启动时调用_setmode(_fileno(stdout), _O_U16TEXT) 并使用宽字符C/C++ I/O,例如@987654324 @ 和 std::wcout.
  • @RichardCritten 的赞成评论可能只是措辞含糊。我认为它指的是控制台(例如conhost.exe)如何使用其当前输出代码页(即GetConsoleOutputCP)解码写入它的字节。我不认为评论意味着控制台通常不支持 Unicode。尽管对于后者,控制台仅限于 BMP(例如,代理代码显示为默认字符,而不是解码 UTF-16 代理对);不支持组合码;并且需要带有字符字形的等宽字体(手动字体链接有帮助)。

标签: c++ c windows visual-studio windows-console


【解决方案1】:

windows 是 unicode (UTF-16) 系统。控制台 unicode 也是如此。如果你想打印 unicode 文本 - 你需要(这是最有效的)使用 WriteConsoleW

BOOL PrintString(PCWSTR psz)
{
    DWORD n;
    return WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), psz, (ULONG)wcslen(psz), &n, 0);
}
PrintString(L"—");

在这种情况下,您的二进制文件将是宽字符 (2 字节 0x2014)并按原样在控制台打印。

如果为输出控制台调用 ansi(多字节)函数 - 例如 WriteConsoleAWriteFile - 控制台首先通过 MultiByteToWideChar 将多字节字符串转换为 unicode 并放置在 CodePage将使用GetConsoleOutputCP 返回的值。如果您使用字符> 0x80

,这里(翻译)可能会出现问题

首先编译器可以给你警告:文件包含一个不能在当前代码页中表示的字符(数字)。以 Unicode 格式保存文件以防止数据丢失。 (C4819)。但即使您以 Unicode 格式保存源文件,也可以是下一个:

wprintf(L"ù"); // no warning
printf("ù"); //warning C4566

因为L"ù"binary 文件中保存为宽字符字符串(原样) - 这里一切正常,没有任何问题和警告。但"ù" 保存为 char string(单字节字符串)。编译器需要将 wide string "ù" 从源文件转换为 multi-byte string 在二进制 (.obj 文件,链接器从中创建 pe 比)。和编译器用于此 WideCharToMultiByteCP_ACP当前系统默认的 Windows ANSI 代码页。

如果你说打电话printf("ù"); 会发生什么?

  1. unicode 字符串 "ù" 将被转换为多字节 WideCharToMultiByte(CP_ACP, ) 这将在编译时。生成的多字节字符串将保存在二进制文件中
  2. 控制台 run-time 将您的 multi-byte 字符串转换为 宽字符 MultiByteToWideChar(GetConsoleOutputCP(), ..) 和 打印这个字符串

所以您获得了 2 次转化:unicode -> CP_ACP -> multi-byte -> GetConsoleOutputCP() -> unicode

默认情况下GetConsoleOutputCP() == CP_OEMCP != CP_ACP 即使您在编译它的计算机上运行程序。 (尤其是在另一台电脑上还有另一个CP_OEMCP

不兼容的转换问题 - 使用了不同的代码页。但即使您将控制台代码页更改为您的CP_ACP - 无论如何转换也可能会错误地翻译某些字符。

关于 CRT api wprintf - 接下来是这种情况:

wprintf 首先使用内部当前locale 将给定字符串从 unicode 转换为多字节(并注意 crt 区域设置独立且不同于 console语言环境)。然后用多字节字符串调用WriteFile。控制台将此多字节字符串转换回 unicode

unicode -> current_crt_locale -> multi-byte -> GetConsoleOutputCP() -> unicode

所以要使用wprintf,我们需要首先将当前crt语言环境设置为GetConsoleOutputCP()

char sz[16];
sprintf(sz, ".%u", GetConsoleOutputCP());
setlocale(LC_ALL, sz);
wprintf(L"—");

但无论如何,我在屏幕上查看(在我的电脑上)- 而不是。如果在此之后调用PrintString(L"—");(使用WriteConsoleW),-— 也是如此。

所以只有可靠的方式打印任何 unicode 字符(windows 支持) - 使用 WriteConsoleW api。

【讨论】:

    【解决方案2】:

    通过 cmets 后,我发现 eryksun 的解决方案是最简单的(......也是最容易理解的):

    #include <stdio.h>
    #include <io.h>
    #include <fcntl.h>
    
    int main()
    {
        //other stuff
        _setmode(_fileno(stdout), _O_U16TEXT);
        wprintf(L"#help — display a list of commands...");
    

    便携性不是我关心的问题,这解决了我最初的问题——不再——我心爱的 em-dash 正在展出。

    我承认这个问题本质上是 the one linked by sata300.de 的重复。尽管用printf 代替cout,并用不必要的杂乱无章代替相关信息。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-27
      • 2012-10-13
      • 1970-01-01
      • 1970-01-01
      • 2013-06-30
      • 2013-03-29
      相关资源
      最近更新 更多