在 Windows 控制台中正确打印 utf8 字符

Posted

技术标签:

【中文标题】在 Windows 控制台中正确打印 utf8 字符【英文标题】:Properly print utf8 characters in windows console 【发布时间】:2012-06-08 14:00:21 【问题描述】:

这是我尝试的方式:

#include <stdio.h>
#include <windows.h>
using namespace std;

int main() 
  SetConsoleOutputCP(CP_UTF8);
   //german chars won't appear
  char const* text = "aäbcdefghijklmnoöpqrsßtuüvwxyz";
  int len = MultiByteToWideChar(CP_UTF8, 0, text, -1, 0, 0);
  wchar_t *unicode_text = new wchar_t[len];
  MultiByteToWideChar(CP_UTF8, 0, text, -1, unicode_text, len);
  wprintf(L"%s", unicode_text);

而且效果是只显示 us ascii 字符。没有显示错误。源文件采用utf8编码。

那么,我在这里做错了什么?

到WouterH:

int main() 
  SetConsoleOutputCP(CP_UTF8);
  const wchar_t *unicode_text = L"aäbcdefghijklmnoöpqrsßtuüvwxyz";
  wprintf(L"%s", unicode_text);

这也不起作用。效果是一样的。我的字体当然是 Lucida Console。

第三次拍摄:

#include <stdio.h>
#define _WIN32_WINNT 0x05010300
#include <windows.h>
#define _O_U16TEXT  0x20000
#include <fcntl.h>

using namespace std;

int main() 
    _setmode(_fileno(stdout), _O_U16TEXT);
    const wchar_t *u_text = L"aäbcdefghijklmnoöpqrsßtuüvwxyz";
    wprintf(L"%s", u_text);

好的,有些东西开始起作用了,但输出是:ańbcdefghijklmno÷pqrs▀tuŘvwxyz

【问题讨论】:

this question 可能重复。 哪个编译器?你可能想检查strlen(text);我预计不是 30 岁。 【参考方案1】:

默认情况下,Windows 上的宽打印功能不处理 ascii 范围之外的字符。

有几种方法可以将 Unicode 数据传输到 Windows 控制台。

直接使用控制台 API,WriteConsoleW。您必须确保您实际上是在写入控制台,并在输出到其他内容时使用其他方式。

将标准输出文件描述符的模式设置为“Unicode”模式之一,_O_U16TEXT 或 _O_U8TEXT。这会导致宽字符输出函数正确地将 Unicode 数据输出到 Windows 控制台。如果它们用于不代表控制台的文件描述符,那么它们会导致字节输出流分别为 UTF-16 和 UTF-8。注:设置这些模式后,相应流上的非宽字符函数将无法使用并导致崩溃。您只能使用宽字符函数。

如果您使用正确的函数,可以通过将控制台输出代码页设置为 CP_UTF8 将 UTF-8 文本直接打印到控制台。大多数较高级别的函数(例如 basic_ostream&lt;char&gt;::operator&lt;&lt;(char*))都不能以这种方式工作,但您可以使用较低级别的函数或实现自己的 ostream 来解决标准函数存在的问题。

第三种方法的问题是这样的:

putc('\302'); putc('\260'); // doesn't work with CP_UTF8

puts("\302\260"); // correctly writes UTF-8 data to Windows console with CP_UTF8 

与大多数操作系统不同,Windows 上的控制台不仅仅是另一个接受字节流的文件。它是由程序创建和拥有的特殊设备,可通过其自己独特的 WIN32 API 访问。问题是,当控制台被写入时,API 可以准确地看到在使用其 API 时传递的数据的范围,并且从窄字符到宽字符的转换发生时没有考虑到数据可能不完整。 当使用多次调用控制台 API 传递多字节字符时,每个单独传递的部分都被视为非法编码,并被视为非法编码。

解决这个问题应该很容易,但微软的 CRT 团队认为这不是他们的问题,而在控制台上工作的任何团队都可能不在乎。

您可以通过实现自己的 streambuf 子类来解决这个问题,该子类可以正确处理到 wchar_t 的转换。 IE。考虑到多字节字符的字节可能单独出现,保持写入之间的转换状态(例如,std::mbstate_t)。

【讨论】:

You'll have to ensure you're actually writing to a console and use other means when the output is to something else.你能详细说明一下吗?【参考方案2】:

另一个技巧是在stdout 上使用_setmode,而不是SetConsoleOutputCP

// Includes needed for _setmode()
#include <io.h>
#include <fcntl.h>

int main() 
    _setmode(_fileno(stdout), _O_U16TEXT);  
    wchar_t * unicode_text = L"aäbcdefghijklmnoöpqrsßtuüvwxyz";
    wprintf(L"%s", unicode_text);
    return 0;

不要忘记删除对SetConsoleOutputCP(CP_UTF8);的调用

【讨论】:

'_O_U16TEXT' was not declared in this scope - :( 它在fcntl.h 中定义为#define _O_U16TEXT 0x20000 /* file mode is UTF16 no BOM (translated) */。但是你应该告诉我们更多关于你使用的是哪个编译器。 我使用 MinGW(它在我的问题的标签中),确实,该文件中有 #define _O_U16TEXT 0x20000 定义。 在我与 Windows 纠缠了大约两个小时后,这在我的情况下奏效了。谢谢! 这是我在互联网上发现的唯一在 Windows 中输出 unicode 文本的东西(也输出希腊语)。甚至不需要system("chcp 65001")。经过约 3 小时的搜索,这可行。谢谢!现在我还需要学习如何从文件中输出 unicode utf-8 文本。酷刑从未停止。【参考方案3】:
//Save As UTF8 without signature
#include<stdio.h>
#include<windows.h>
int main() 
  SetConsoleOutputCP(65001);
  const char unicode_text[]="aäbcdefghijklmnoöpqrsßtuüvwxyz";
  printf("%s\n", unicode_text);

结果: aäbcdefghijklmnoöpqrsßtuüvwxyz

【讨论】:

cp65001 有错误,例如putchar('\302'); putchar('\260'); fails but puts("\302\260"); works。 它不工作。我得到类似:“aäbcdefghijklmnoöpqrsßtuüvwxyz”。 @Slav 您是否使用 UTF-8 编码而不是默认的 ANSI 编码保存文件?重要的是,无论使用 UTF-8 字节序列,您都可以确保字符串是正确的 UTF-8 编码,例如 "\xc3\x85" @JohnLeidegren 是的,它是 UTF8。我尝试使用 BOM 和不使用 BOM。 @Slav 有事要发生了。我可以重现问题,也可以通过多种方式解决问题,但我也可以让它发挥作用。【参考方案4】:

我也遇到过类似的问题,但现有的答案都没有对我有用。我观察到的另一件事是,如果我将 UTF-8 字符粘贴在 plain 字符串文字中,它们会正确打印,但如果我尝试使用 UTF-8 文字 (u8"text"),则字符被编译器处理(通过一次打印一个字节的数值来证明;raw 文字具有正确的 UTF-8 字节,正如在 Linux 上验证的那样机器,但 UTF-8 文字是垃圾)。

经过一番摸索,我找到了解决方案:/utf-8。有了它,一切正常;我的来源是 UTF-8,我可以使用明确的 UTF-8 文字,并且无需其他更改即可输出。

【讨论】:

这是唯一对我有用的解决方案。很好的答案。【参考方案5】:

可以将控制台设置为显示 UTF-8 字符:@vladasimovic 答案SetConsoleOutputCP(CP_UTF8) 可以用于此。或者,您可以通过 DOS 命令chcp 65001 或通过主程序中的系统调用system("chcp 65001 &gt; nul") 来准备您的控制台。不要忘记将源代码也保存为 UTF-8。

要检查 UTF-8 支持,请运行

#include <stdio.h>
#include <windows.h>

BOOL CALLBACK showCPs(LPTSTR cp) 
  puts(cp);
  return true;


int main() 
  EnumSystemCodePages(showCPs,CP_SUPPORTED);

65001 应该出现在列表中。

Windows 控制台默认使用OEM codepages,大多数默认光栅字体仅支持国家字符。 Windows XP 和更新版本还支持 TrueType 字体,它应该显示缺失的字符(@Devenec 在他的回答中建议使用 Lucida Console)。

为什么 printf 失败

正如@bames53 在他的回答中指出的那样,Windows 控制台不是流设备,您需要写入多字节字符的所有字节。有时printf 会搞砸工作,将字节一一放入输出缓冲区。尝试使用sprintf 然后puts 结果,或者强制只刷新累积的输出缓冲区。

如果一切都失败了

注意UTF-8 format:一个字符显示为1-5个字节。使用此函数转移到字符串中的下一个字符:

const char* ucshift(const char* str, int len=1) 
  for(int i=0; i<len; ++i) 
    if(*str==0) return str;
    if(*str<0) 
      unsigned char c = *str;
      while((c<<=1)&128) ++str;
    
    ++str;
  
  return str;

...这个函数将字节转换为 unicode 数字:

int ucchar(const char* str) 
  if(!(*str&128)) return *str;
  unsigned char c = *str, bytes = 0;
  while((c<<=1)&128) ++bytes;
  int result = 0;
  for(int i=bytes; i>0; --i) result|= (*(str+i)&127)<<(6*(bytes-i));
  int mask = 1;
  for(int i=bytes; i<6; ++i) mask<<= 1, mask|= 1;
  result|= (*str&mask)<<(6*bytes);
  return result;

然后你可以尝试使用一些狂野/古老/非标准的winAPI函数,比如MultiByteToWideChar(之前不要忘记调用setlocale()!)

或者您可以使用自己的从 Unicode 表映射到您的活动工作代码页。示例:

int main() 
  system("chcp 65001 > nul");
  char str[] = "příšerně"; // file saved in UTF-8
  for(const char* p=str; *p!=0; p=ucshift(p)) 
    int c = ucchar(p);
    if(c<128) printf("%c\n",c);
    else printf("%d\n",c);
  

这应该打印出来

p
345
237
353
e
r
n
283

如果您的代码页不支持该捷克语解释,您可以映射 345=>r、237=>i、353=>s、283=>e。至少有 5(!)种不同的字符集仅适用于捷克语。在不同的 Windows 语言环境中显示可读字符是一件很可怕的事情。

【讨论】:

类似于ucshiftmbrlen 来自wchar.h。不过我更喜欢你的功能。少废话了。【参考方案6】:

我通过以下方式解决了这个问题:

Lucida Console 似乎不支持变音符号,因此例如将控制台字体更改为 Consolas 即可。

#include <stdio.h>
#include <Windows.h>

int main()

    SetConsoleOutputCP(CP_UTF8);

    // I'm using Visual Studio, so encoding the source file in UTF-8 won't work
    const char* message = "a" "\xC3\xA4" "bcdefghijklmno" "\xC3\xB6" "pqrs" "\xC3\x9F" "tu" "\xC3\xBC" "vwxyz";

    // Note the capital S in the first argument, when used with wprintf it
    // specifies a single-byte or multi-byte character string (at least on
    // Visual C, not sure about the C library MinGW is using)
    wprintf(L"%S", message);

编辑:修复了愚蠢的拼写错误和字符串文字的解码,对此感到抱歉。

【讨论】:

我得到“标识符 UTF_8 未定义”,即使包含与您相同的内容。【参考方案7】:

UTF-8 不适用于 Windows 控制台。时期。我尝试了所有组合都没有成功。由于不同的 ANSI/OEM 字符分配会出现问题,因此一些答案说没有问题,但这些答案可能来自使用 7 位纯 ASCII 或具有相同 ANSI/OEM 代码页(中文、日文)的程序员

您要么坚持使用 UTF-16 和宽字符函数(但您仍然受限于 OEM 代码页的 256 个字符 - 中文/日文除外),或者您使用源文件中的 OEM 代码页 ASCII 字符串。

是的,一团糟。

对于多语言程序,我使用字符串资源,并编写了一个 LoadStringOem() 函数,该函数使用 WideCharToMultiByte() 将 UTF-16 资源自动转换为 OEM 字符串,而无需中间缓冲区。当 Windows 自动从资源中选择正确的语言时,它有望加载可转换为目标 OEM 代码页的语言的字符串。

因此,您不应将 8 位印刷字符用于英语-美国语言资源(如省略号 ... 和引号“”),因为当未检测到语言匹配(即回退)时,Windows 会选择英语-美国. 例如,您有德语、捷克语、俄语和英语-美国的资源,而用户有中文,如果您使文本看起来很漂亮,他/她将看到英语加上垃圾而不是您精心制作的排版。

现在,在 Windows 7 和 10 上,SetConsoleOutputCP(65001/*aka CP_UTF8*/) 可以正常工作。您应该将源文件保存在没有 BOM 的 UTF-8 中,否则,您的字符串文字将被编译器重新编码为 ANSI。此外,控制台字体必须包含所需的字符,并且不得为“终端”。不幸的是,即使同时安装了两个语言包,也没有涵盖变音符号和汉字的字体,因此您无法真正一次显示所有字符形状。

【讨论】:

以上是关于在 Windows 控制台中正确打印 utf8 字符的主要内容,如果未能解决你的问题,请参考以下文章

使用Windows 7在Sublime Text 2的控制台中打印utf-8字符串

mysql存入中文乱码解决方法(windows环境)

如何在 Win x64 上使用 WinAPI 正确安装虚拟打印机?

iOS 控制台打印unicode 转中文汉字 UTF8String

如何从 Windows 上的 c++ 控制台应用程序打印 UTF-8

在 Windows 中使用 vCard 进行 UTF8 编码