在C++中打印Unicode字符

8

我试图编写一个简单的命令行应用程序来学习日语,但好像无法打印Unicode字符。我错过了什么吗?

#include <iostream>
using namespace std;

int main()
{
        wcout << L"こんにちは世界\n";
        wcout << L"Hello World\n"
        system("pause");
}

在这个例子中,只显示了“按任意键继续”。在Visual C++ 2013上进行了测试。

10
如果不使用 chcp 65001 命令,Windows 操作系统无法将 Unicode 输出到终端窗口,即使使用该命令也不能保证完全正常工作。对于使用 wcout 的 C++ 运行时我并不清楚它如何影响这种情况。 - Mark Ransom
那个方法可行,SetConsoleCP()也可以。问题在于找到一个固定宽度并支持日语字形的字体。Consolas和Lucida在西方机器上不支持。 - Hans Passant
我尝试了system("chcp 65001"),但那也没用... - Jeff Linahan
2
在Windows控制台上打印宽字符串的常见方法是执行_setmode(_fileno(stdout), _O_WTEXT);。当然,您还需要一个支持这些字符的字体(如果您没有这样的字体,仍然可以将输出重定向到文件并使用记事本打开)。有关详细信息,请参阅MSDN - Cubbi
3个回答

7

在Windows上这并不容易。即使你成功地将文本显示到Windows控制台,你仍需要配置cmd.exe才能够显示日语字符。


#include <iostream>

int main() {
  std::cout << "こんにちは世界\n";
}

这在任何满足以下条件的系统上都可以正常工作:
  • 编译器的源和执行编码包括这些字符。
  • 输出设备(例如控制台)期望使用与编译器执行编码相同的编码来显示文本。
  • 具有适当字符的字体可用(通常不是问题)。
现在大多数平台默认使用UTF-8进行所有这些编码,因此可以使用类似上面的代码支持整个Unicode范围。不幸的是,Windows不是其中之一。
wcout << L"こんにちは世界\n";

在此行中,字符串文字数据在编译时从源编码转换为执行宽编码,然后在运行时wcout使用其所附加的区域设置将wchar_t数据转换为char数据进行输出。问题在于默认语言环境仅必须支持基本源字符集中的字符,这甚至不包括所有ASCII字符,更不用说非ASCII字符了。
因此,转换会导致错误,使得wcout处于错误状态。必须清除错误才能使wcout再次正常工作,这就是为什么第二个打印语句不输出任何内容的原因。
您可以通过对wcout注入可成功转换字符的语言环境来解决一定范围内的字符限制。不幸的是,以此方式支持整个Unicode范围所需的编码是UTF-8;尽管Microsoft的流实现支持其他多字节编码,但它非常明确地不支持UTF-8。
例如:
wcout.imbue(std::locale(std::locale::classic(), new std::codecvt_utf8_utf16<wchar_t>()));

SetConsoleOutputCP(CP_UTF8);

wcout << L"こんにちは世界\n";

在这里,wcout将正确地将字符串转换为UTF-8,如果输出被写入文件而不是控制台,则文件将包含正确的UTF-8数据。然而,即使在此处配置为接受UTF-8数据,Windows控制台也不会接受以这种方式编写的UTF-8数据。


有几个选项:

  • Avoid the standard library entirely:

    DWORD n;
    WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), L"こんにちは世界\n", 8, &n, nullptr);
    
  • Use non-standard magical incantation that will break standard code:

    #include <fcntl.h>
    #include <io.h>
    
    _setmode(_fileno(stdout), _O_U8TEXT);
    std::wcout << L"こんにちは世界\n";
    

    After setting this mode std::cout << "Hello, World"; will crash.

  • Use a low level IO API along with manual conversion:

    #include <codecvt>
    #include <locale>
    
    SetConsoleOutputCP(CP_UTF8);
    std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> convert;
    std::puts(convert.to_bytes(L"こんにちは世界\n"));
    

使用任何一种方法,cmd.exe 将尽其所能显示正确的文本,我指的是它将显示无法阅读的方框。对于给定的字符串,会显示七个小方框。

Little Boxes

你可以将文本从 cmd.exe 复制到记事本或其他应用程序中,以查看正确的字形。


为什么会这样?我可以正确地打印出任何U+2095及以前的字符,但是之后即使字体包含正确的字形,控制台也会失败。有什么办法可以解决这个问题吗? - Banderi
@Banderi 好的,你正在使用哪种字体,想要显示什么字符? - bames53
我已经尝试了_Consolas_和_Lucida Console_,并且正在尝试显示日语字符。然而,U+2095之后的每个Unicode字符都会表现相同。 - Banderi
1
@Banderi 我相信你遇到的问题是Consolas和Lucida Console不包含你尝试显示的字符。cmd.exe非常原始,不幸的是它不支持任何字体回退机制,因此它只会显示所选字体中直接存在的字符。由于其他程序和文本渲染API支持字体回退,你可能会误以为字体包含更多字符。使用charmap.exe来检查字体,看看它们真正包含哪些字符。 - bames53
我明白了!非常感谢。 - Banderi
显示剩余2条评论

4

有一篇关于在Windows控制台处理Unicode的文章

http://alfps.wordpress.com/2011/11/22/unicode-part-1-windows-console-io-approaches/
http://alfps.wordpress.com/2011/12/08/unicode-part-2-utf-8-stream-mode/

基本上,你可以通过实现自己的streambuf来使用WriteConsoleW,从而在Windows控制台中写入UTF-8(或任何其他Unicode),而不依赖于区域设置、控制台代码页甚至不使用宽字符。
这可能看起来并不是很直接,但它是一个方便和可重用的解决方案,还能为您提供可移植的utf8-everywhere风格的用户代码。请不要因我的英语而打我:)


-1

或者您可以将Windows区域设置更改为日语。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接