如何在Windows上打印UTF-8string到std :: cout?

我正在用C ++编写一个跨平台的应用程序。 所有的string在内部都是UTF-8编码的。 考虑下面的简化代码:

#include <string> #include <iostream> int main() { std::string test = u8"Greek: αβγδ; German: Übergrößenträger"; std::cout << test; return 0; } 

在Unix系统上, std::cout希望8位string是UTF-8编码,所以这段代码工作正常。

然而,在Windows上, std::cout需要8位string为Latin-1或类似的非Unicode格式(取决于代码页)。 这导致以下输出:

希腊文:╬▒╬▓╬│╬┤; 德语:├£bergr├Â├ƒentr├ñger

我能做些什么来使std::cout在Windows上将8位string解释为UTF-8?

这是我试过的:

 #include <string> #include <iostream> #include <io.h> #include <fcntl.h> int main() { _setmode(_fileno(stdout), _O_U8TEXT); std::string test = u8"Greek: αβγδ; German: Übergrößenträger"; std::cout << test; return 0; } 

我希望_setmode能做到这一点。 但是,这会导致调用operator<<的行中出现以下断言错误:

Microsoft Visual C ++运行时库

debugging断言失败!

程序:d:\ visual studio 2015 \ Projects \ utf8test \ Debug \ utf8test.exe文件:minkernel \ crts \ ucrt \ src \ appcrt \ stdio \ fputc.cpp行:47

expression式:((_Stream.is_string_backed())||(fn = _fileno(_Stream.public_stream()),((_textmode_safe(fn)== __crt_lowio_text_mode :: ansi)&&!_tm_unicode_safe(fn))))

有关程序如何导致断言失败的信息,请参阅有关断言的Visual C ++文档。

问题不是std::cout而是Windows控制台。 使用C-stdio你将得到与fputs( "\xc3\xbc", stdout ); 设置UTF-8代码页(使用SetConsoleOutputCPchcp在cmd的设置中设置一个支持Unicode的字体(Consolas应该支持超过2000个字符,并有注册表黑客为cmd添加更多的字体)。

如果用putc('\xc3'); putc('\xbc');输出一个字节putc('\xc3'); putc('\xbc'); putc('\xc3'); putc('\xbc'); 你会得到双豆腐作为控制台得到他们分开为非法字符。 这可能是C ++流所做的。

请参阅Windows控制台上的UTF-8输出进行简短的讨论。

对于我自己的项目,我终于实现了一个std::stringbuf转换到Windows-1252。 我真的需要完整的Unicode输出,但这并不能真正帮助你。

另一种方法是覆盖cout的streambuf,使用实际输出的fputs

 #include <iostream> #include <sstream> #include <Windows.h> class MBuf: public std::stringbuf { public: int sync() { fputs( str().c_str(), stdout ); str( "" ); return 0; } }; int main() { SetConsoleOutputCP( CP_UTF8 ); setvbuf( stdout, nullptr, _IONBF, 0 ); MBuf buf; std::cout.rdbuf( &buf ); std::cout << u8"Greek: αβγδ\n" << std::flush; } 

我在这里关闭了输出缓冲,以防止它干扰未完成的UTF-8字节序列。

std::cout正在做它应该做的事情:它将你的UTF-8编码文本一起发送到控制台,但是你的控制台将使用它当前的代码页来解释这些字节。 您需要将程序的控制台设置为UTF-8代码页:

 #include <string> #include <iostream> #include <Windows.h> int main() { std::string test = u8"Greek: αβγδ; German: Übergrößenträger"; SetConsoleOutputCP(CP_UTF8); std::cout << test; } 

如果Windows将默认代码页切换为UTF-8,那将是非常好的,但是由于向后兼容性问题,它们可能不会。

使用以下Windows API调用将控制台输出编码设置为UTF-8:

 SetConsoleOutputCP(65001); 

Windows开发人员中心提供了该功能的文档。

即使您更改了代码页,某些Unicode字符也无法在控制台窗口中正确显示,因为您的字体不支持。 例如,如果要显示阿拉伯字符,则需要安装支持阿拉伯语的字体。

这个计算器页面应该是有帮助的。

顺便说一下,Unicode版本的控制台API(如WriteConsoleW)不会来救援,因为它们在内部调用其相应的Windows代码页版本API(如WriteConsoleA)。 既不会std :: wcout帮助,因为它会将wchar_t字符串转换为字符串内部。

好像windows的控制台窗口不支持Unicode,我建议你用MessageBox代替。

最后,我已经工作了。 这个答案将Miles Budnek,Paul和mkluwe的输入与我自己的一些研究结合起来。 首先,让我从使用Windows 10的代码开始。 之后,我会引导您查看代码,并解释为什么在Windows 7上无法正常工作。

 #include <string> #include <iostream> #include <Windows.h> #include <cstdio> int main() { // Set console code page to UTF-8 so console known how to interpret string data SetConsoleOutputCP(CP_UTF8); // Enable buffering to prevent VS from chopping up UTF-8 byte sequences setvbuf(stdout, nullptr, _IOFBF, 1000); std::string test = u8"Greek: αβγδ; German: Übergrößenträger"; std::cout << test << std::endl; } 

代码从设置代码页开始, 正如Miles Budnik所建议的那样 。 这将告诉控制台将其接收的字节流解释为UTF-8, 而不是 ANSI的一些变体。

接下来,Visual Studio附带的STL代码存在问题。 std::cout其数据打印到std::basic_filebuf类型的流缓冲区。 当缓冲区接收到一个字符串(通过std::basic_streambuf::sputn() ),它不会把它作为一个整体传递给底层文件。 相反,它会分别传递每个字节。 正如mkluwe所解释的 ,如果控制台收到一个UTF-8字节序列作为单个字节 ,它将不会将它们解释为一个单独的代码点。 相反,它会把它们当作多个字符。 UTF-8字节序列中的每个字节本身都是无效的代码点,所以您将看到 。 Visual Studio有一个相关的错误报告 ,但是它被封闭为By Design。 解决方法是为流启用缓冲。 作为一个额外的好处,那会给你更好的表现。 但是,现在您可能需要像std::endl一样定期刷新流,否则您的输出可能不会显示。

最后,Windows控制台支持光栅字体和TrueType字体。 正如Paul指出的那样 ,栅格字体将会忽略控制台的代码页。 因此,如果将控制台设置为TrueType字体,则非ASCII字符只能起作用。 直到Windows 7,默认是一个光栅字体,所以用户将不得不手动改变它。 幸运的是, Windows 10将默认字体更改为Consolas ,所以这部分问题应该随着时间的推移而自行解决。