我正在尝试使用cout输出UTF-8编码的字符串但没有成功。我想在我的程序中使用Boost.Locale。我找到了一些关于Windows控制台的信息。例如,本文http://www.boost.org/doc/libs/1_60_0/libs/locale/doc/html/running_examples_under_windows.html说我应该将输出控制台代码页设置为65001,并使用BOM以UTF-8编码保存我的所有源代码。所以,这是我的简单例子:
#include <windows.h>
#include <boost/locale.hpp>
using namespace std;
using namespace boost::locale;
int wmain(int argc, const wchar_t* argv[])
{
//system("chcp 65001 > nul"); // It's the same as SetConsoleOutputCP(CP_UTF8)
SetConsoleOutputCP(CP_UTF8);
locale::global(generator().generate(""));
static const char* utf8_string = u8"♣☻▼►♀♂☼";
cout << "cout: " << utf8_string << endl;
printf("printf: %s\n", utf8_string);
return 0;
}
我使用Visual Studio 2015进行编译,它在控制台中生成以下输出:
cout: ���������������������
printf: ♣☻▼►♀♂☼
为什么printf做得好,而cout没有? Boost的语言环境生成器可以帮忙吗?或者我应该使用其他东西在流模式下以类似cout的方式在控制台中打印UTF-8文本?
答案 0 :(得分:1)
看起来std::cout
在这里太聪明了:它试图将你的utf8编码的字符串解释为ascii,并找到21个非ascii字符,它输出为未映射的字符�
。 AFAIK Windows C ++控制台驱动程序,坚持将每个字符从一个窄字符串映射到屏幕上的位置,并且不支持多字节字符集。
在幕后发生的事情:
utf8_string是以下的char数组(只需查看Unicode表并执行utf8转换):
utf8_string = { '0xe2', '0x99', '0xa3', '0xe2', '0x98', '0xbb', '0xe2', '0x96',
'0xbc', '0xe2', '0x96', '0xba', '0xe2', '0x99', '0x80', '0xe2', '0x99',
'0x82', '0xe2', '0x98', '0xbc', '\0' };
即21个字符,其中没有一个在ascii范围0-0x7f。
在另一侧,printf
只输出字节而不进行任何转换,从而得到正确的输出。
对不起,即使经过多次搜索,我也找不到一种简单的方法,可以使用std::cout
这样的窄流在Windows控制台上正确显示UTF8输出。
但是你应该注意到你的代码无法将助推器语言环境灌输到cout
答案 1 :(得分:0)
关键问题是,经过漫长而痛苦的冒险之后cout << "some string"
的实施会为每个角色调用WriteFile
。
如果您想调试它,请在CRT源的_write
文件中的write.c
函数内设置断点,写一些内容到cout
,您将看到所有故事。< / p>
所以我们可以重写你的代码
static const char* utf8_string = u8"♣☻▼►♀♂☼";
cout << utf8_string << endl;
与等效(和更快!)一:
static const char* utf8_string = u8"♣☻▼►♀♂☼";
const size_t utf8_string_len = strlen(utf8_string);
DWORD written = 0;
for(size_t i = 0; i < utf8_string_len; ++i)
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), utf8_string + i, 1, &written, NULL);
输出:���������������������
用WriteFile
的单个调用替换循环,UTF-8控制台变得非常棒:
static const char* utf8_string = u8"♣☻▼►♀♂☼";
const size_t utf8_string_len = strlen(utf8_string);
DWORD written = 0;
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), utf8_string, utf8_string_len, &written, NULL);
输出:♣☻▼►♀♂☼
我在msvc.2013和msvc.net(2003)上对它进行了测试,两者的行为完全相同。
显然,控制台的Windows实现需要调用WriteFile
/ WriteConsole
时的整个字符,并且不能通过单个字节获取UTF-8字符。 :)
我们可以在这做什么?
我的第一个想法是使输出缓冲,就像在文件中一样。这很简单:
static char cout_buff[128];
cout.rdbuf()->pubsetbuf(cout_buff, sizeof(cout_buff));
cout << utf8_string << endl; // works
cout << utf8_string << endl; // do nothing
输出:♣☻▼►♀♂☼
(只有一次,我稍后解释)
第一个问题是控制台输出变得延迟,它等到行尾或缓冲区溢出。
第二个问题 - 它不起作用。
为什么呢?第一次缓冲区刷新后(首先<< endl
)cout
切换到错误状态(badbit
设置)。这是因为WriteFile
通常会返回*lpNumberOfBytesWritten
个写入的字节数,但对于UTF-8控制台,它会返回写入的字符数(问题描述) here)。 CRT检测到写入和写入请求的字节数不同,并停止写入“失败”流。
我们可以做些什么?
好吧,我想我们可以实现自己的std::basic_streambuf
来编写控制台正确的方法,但这并不容易,我没有时间。如果有人想要,我会很高兴。
另一个决定是(a)使用std::wcout
和wchar_t
字符串,(b)使用WriteFile
/ WriteConsole
。有时可以接受这些解决方案。
在Microsoft版本的C ++中使用UTF-8控制台非常糟糕。