我正在用C++编写一个跨平台应用程序。所有字符串都是内部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位字符串是UTF-8编码的,因此这段代码可以正常工作。
然而,在Windows上,std::cout
期望8位字符串为Latin-1或类似的非Unicode格式(取决于代码页)。这将导致以下输出:
希腊语:;德语:Berger Berger
在Windows上,如何使std::cout
将8位字符串解释为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<<
的行中导致以下Assert错误:
Microsoft Visual C运行时库
调试Assert失败!
程序:d:\visual studio 2015\Projects\utf8test\logg\utf8test.exe文件:minkernel\crts\ucrt\src\appcrt\stdio\fputc.cpp行:47
表达式:((_Stream.is_string_backed())||(fn = _fileno(_Stream.public_stream()),((_textmode_safe(fn)== __crt_lowio_text_mode::ansi)&&!_tm_unicode_safe(fn)
有关程序如何导致Assert失败的信息,请参见有关Assert的Visual C文档。
9条答案
按热度按时间sh7euo9m1#
最后,我让它工作了。这个答案结合了Miles Budnek,Paul和mkluwe的意见以及我自己的一些研究。首先,让我从 * 将在Windows 10* 上工作的代码开始。在那之后,我会带你看一下代码,并解释为什么它不能在Windows 7上开箱即用。
代码首先设置代码页as suggested by Miles Budnik。这将告诉控制台将接收到的字节流解释为UTF-8,* 而不是 * ANSI的某种变体。
接下来,Visual Studio附带的STL代码中有一个问题。
std::cout
将其数据打印到std::basic_filebuf
类型的流缓冲区。当该缓冲区接收到一个字符串(通过std::basic_streambuf::sputn()
)时,它不会将其作为一个整体传递给底层文件。相反,它将单独传递每个字节。As explained by mkluwe,如果控制台接收到UTF-8字节序列 * 作为单个字节 *,它不会将它们解释为单个代码点。相反,它会将它们视为多个字符。UTF-8字节序列中的每个字节本身都是一个无效的代码点,所以你会看到的是""。有a related bug report for Visual Studio,但它被关闭为By Design。解决方法是为流启用缓冲。作为一个额外的奖励,这将给你给予更好的表现。但是,您现在可能需要定期刷新流,就像我对std::endl
所做的那样,否则您的输出可能不会显示。最后,Windows控制台支持光栅字体和TrueType字体。正如Paul所指出的,光栅字体将简单地忽略控制台的代码页。因此,只有当控制台设置为TrueType字体时,非ASCII Unicode字符才有效。在Windows 7之前,默认字体是光栅字体,因此用户必须手动更改它。幸运的是,Windows 10 changes the default font to Consolas,所以这部分问题应该会随着时间的推移而自行解决。
1aaf6o9v2#
问题不在于
std::cout
,而在于windows控制台。使用C-stdio,在设置UTF-8代码页(使用SetConsoleOutputCP
或chcp
)* 和 * 在cmd的设置中设置Unicode支持字体后,您将获得ü
和fputs( "\xc3\xbc", stdout );
(Consolas应该是support over 2000 characters,并且有注册表黑客可以向cmd添加更多功能字体)。如果你用
putc('\xc3'); putc('\xbc');
一个字节接一个字节地输出,你会得到双豆腐,因为控制台会把它们单独解释为非法字符。这可能就是C++流所做的。参见UTF-8 output on Windows console进行详细讨论。
对于我自己的项目,我最终实现了一个
std::stringbuf
来转换到Windows-1252。如果你真的需要完整的Unicode输出,这将不会真正帮助你,但是。另一种方法是使用
fputs
的streambuf作为实际输出:我在这里关闭了输出缓冲,以防止它干扰未完成的UTF-8字节序列。
okxuctiv3#
std::cout
正在做它应该做的事情:它会将UTF-8编码的文本沿着发送到控制台,但控制台将使用其当前代码页解释这些字节。您需要将程序的控制台设置为UTF-8代码页:如果Windows将默认代码页切换为UTF-8,那就太好了,但由于向后兼容性问题,他们可能无法做到。
gdx19jrr4#
忘记你所知道的关于Windows控制台及其Unicode/UTF-8支持(或者说缺乏支持)的一切。这是2020年,这是一个新的世界。这不是对上述问题的直接回答,而是一种现在更有意义的替代方案,一种以前不可能的新方法。
每个人都是对的,根本问题是Windows控制台。但有一个新的球员在城里,它的Windows终端。安装并启动Windows终端。使用此程序:
这个程序通过普通的
cout
发送UTF-8。输出:
命令
chcp 65001
或SetConsoleOutputCP(CP_UTF8)
是Windows终端中的cmd选项卡所必需的,但它看起来并不在Powershell选项卡中。Powershell是否默认为UTF-8?在我看来,根除核心问题cmd是现在最好的选择。传出去
k2arahey5#
使用以下Windows API调用将控制台输出编码设置为UTF-8:
该函数的文档可以在Windows Dev Center上找到。
ruarlubt6#
自从我开始使用{fmt}库,我所有的编码问题都消失了。
一个简单的用途:
trnvg8h37#
即使更改了代码页,某些Unicode字符也无法在控制台窗口中正确显示,因为您的字体不支持它。例如,如果要显示阿拉伯字符,则需要安装支持阿拉伯语的字体。
This stackoverflow page应该会有帮助。
顺便说一句,Unicode版本的控制台API(如WriteConsoleW)不会来拯救,因为它们在内部调用相应的Windows代码页版本API(如WriteConsoleA)。std::wcout也没有帮助,因为它会在内部将wchar_t字符串转换为char字符串。
看来windows控制台窗口不支持Unicode,我建议你使用MessageBox代替。
2j4z5cfb8#
我也遇到了同样的问题,为此编写了一个非常小的库,名为libpu 8:https://github.com/jofeu/libpu8
对于windows控制台,它取代了cin,cout和cerr的streambufs,以便它们在前端接受并生成utf-8,并以UTF-16与控制台对话。在非windows操作系统上,或者如果cin,cout,cerr被附加到文件/管道而不是控制台,它什么也不做。它还将C++ main()函数的参数转换为Windows上的UTF-8。
使用示例:
p8h8hvxi9#
自STD C++ 20起
使用clang:clang版本17.0.2目标:x86_64-pc-windows-msvc线程模型:POSIX
对于MSVC,你必须做同样的事情,但添加/UTF-8标志:
https://github.com/microsoft/STL/issues/4110