我正在用C++编写一个跨平台的应用程序。所有字符串都是内部编码的.考虑以下简化代码:
#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位字符串为拉丁文1或类似的非Unicode格式(取决于代码页)。这将导致以下输出:
希腊语:╬▒╬▓╬│╬┤;德语:├ger├├ƒentr├ger
如何使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<<
的行中出现以下断言错误
MicrosoftVisualC++运行时库 调试断言失败! 程序: d:\visual studio 2015\Projects\utf8test\Debug\utf8test.exe文件:2015\Projects\utf8test\Debug\utf8test.exe行: 47 表达式:(_Stream.is_string_backed()) x= _fileno(_Stream.public_stream()),((_textmode_safe (fn ) == __crt_lowio_text_mode::ansi)和!_tm_unicode_safe(Fn) 有关程序如何导致断言失败的信息,请参阅“断言”上的VisualC++文档。
发布于 2017-08-09 10:44:13
问题不是std::cout
,而是windows控制台。使用cmd,您将在设置UTF-8代码页(使用ü
或chcp
)和在cmd设置中设置支持字体的Unicode (Consolas应该支持2000多个字符,并且有注册表hacks可以向cmd添加更多的字体)之后,获得带有fputs( "\xc3\xbc", stdout );
的chcp
。
如果使用putc('\xc3'); putc('\xbc');
输出一个字节又一个字节,那么当控制台将其单独解释为非法字符时,您将得到双豆腐。这可能就是C++流所做的。
有关宽大的讨论,请参见Windows控制台上的UTF-8输出。
对于我自己的项目,我最终实现了一个std::stringbuf
,它执行到Windows1252的转换。我真的需要完整的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字节序列。
发布于 2017-08-10 20:18:57
最后,我让它起作用了。这个答案结合了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附带的STL代码中存在一个问题。std::cout
将其数据打印到std::basic_filebuf
类型的流缓冲区。当缓冲区接收到一个字符串(通过std::basic_streambuf::sputn()
)时,它不会将它作为一个整体传递给底层文件。相反,它将分别传递每个字节。正如mkluwe所解释的,如果控制台以单个字节的形式接收UTF-8字节序列,则不会将其解释为单个代码点。相反,它将把它们视为多个字符。UTF-8字节序列中的每一个字节本身都是一个无效的代码点,因此您将看到std::endl
的代码点。这里有�,但它是通过设计关闭的。解决方法是为流启用缓冲。作为额外的好处,这将给您带来更好的性能。然而,您现在可能需要像我对std::endl
那样定期刷新流,否则输出可能不会显示。
最后,Windows支持光栅字体和TrueType字体。正如保罗所指出的,光栅字体将简单地忽略控制台的代码页。因此,只有当控制台设置为TrueType字体时,非ASCII Unicode字符才能工作。在Windows 7之前,默认的字体是光栅字体,所以用户必须手动更改它。幸运的是,Windows 10将默认字体更改为Consolas。,所以问题的这一部分应该随着时间的推移自行解决。
发布于 2017-08-08 19:26:16
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,那就太好了,但由于向后兼容性的考虑,它们很可能无法实现。
https://stackoverflow.com/questions/45575863
复制相似问题