如何在不使用平台特定功能的情况下打印UTF-8字符串?

时间:2012-02-09 12:22:12

标签: utf-8 locale cout multiplatform wstring

是否可以在不使用平台特定功能的情况下打印UTF-8字符串?

#include <iostream>
#include <locale>
#include <string>

using namespace std;

int main()
{
    ios_base::sync_with_stdio(false);
    wcout.imbue(locale("en_US.UTF-8")); // broken on Windows (?)

    wstring ws1 = L"Wide string.";
    wstring ws2 = L"Wide string with special chars \u20AC";  // Euro character

    wcout << ws1 << endl;
    wcout << ws2 << endl;
    wcout << ws1 << endl;
}

我收到此运行时错误:

  

在抛出'std :: runtime_error'实例后终止调用   what():locale :: facet :: _ S_create_c_locale名称无效

如果我删除了行wcout.imbue(locale("en_US.UTF-8"));,我只会打印ws1一次。

在另一个问题(“How can I cin and cout some unicode text?”)中,菲利普写道: “wcin和wcout在Windows上不起作用,就像等效的C函数一样。只有原生API才有效。” MinGW也是如此吗?

感谢您的任何提示!

平台:
的MinGW / GCC
Windows 7

2 个答案:

答案 0 :(得分:5)

我没有在Windows上的mingw环境中使用gcc,但从我收集它不支持C ++语言环境。

由于它不支持C ++语言环境,因此这并不是真正相关,但是FYI,Windows并没有像大多数其他平台那样使用相同的语言环境命名方案。它们使用类似的language_country.encoding,但语言和国家/地区不是代码,编码是Windows代码页编号。因此,语言环境将是“English_United States.65001”,但这不是受支持的组合(代码页65001(UTF-8)不支持作为任何语言环境的一部分)。

只有ws1打印的原因,只有一次是打印字符\u20AC时,流失败并设置了失败位。在进一步打印之前,您必须清除错误。


C ++ 11引入了一些可以轻松处理UTF-8的东西,但并不是所有东西都支持,并且添加并不能完全解决问题。但现在的情况是:

当VS中支持char16_tchar32_t作为本机类型而不是typedef时,您将能够使用标准的codecvt构面特化codecvt<char16_t,char,mbstate_t>codecvt<char32_t,char,mbstate_t>分别在UTF-16或UTF-32和 UTF-8 之间进行转换(而不是执行字符集或系统编码)。这还不行,因为在当前VS(和VS11DP)中,这些类型只是typedef,模板特化不适用于typedef,但代码已经在VS 2010的标题中,只是在{{1 }}

该标准还定义了一些支持的特殊用途codecvt facet模板,codecvt_utf8和codecvt_utf8_utf16。前者根据您使用的宽字符类型的大小在UTF-8和UCS-2或UCS-4之间进行转换,后者在UTF-8和UTF-16代码单元之间进行转换,与宽字符的大小无关类型。

#ifdef

这将通过附加到wcout的任何内容输出UTF-8代码单元。如果输出已重定向到文件,则打开它将显示UTF-8编码文件。 然而,由于Windows上的控制台模型以及标准流的实现方式,您无法以这种方式在命令提示符中正确显示Unicode字符(即使您设置了控制台输出代码使用std::wcout.imbue(std::locale(std::locale::classic(),new std::codecvt_utf8_utf16<wchar_t>())); std::wcout << L"ØÀéîðüýþ\n"; )将页面转换为UTF-8。 UTF-8代码单元一次输出一个,并且控制台将查看传递给它的每个单独的块,期望传递的每个块(即,在这种情况下为单个字节)是完整且有效的编码。当显示字符串时,块中不完整或无效的序列(在这种情况下,所有多字节字符表示的每个字节)将被替换为U + FFFD。

如果不是使用iostream,而是使用C函数SetConsoleOutputCP(CP_UTF8)写出一个完整的UTF-8编码字符串(如果控制台输出代码页设置正确),那么你可以打印一个UTF-8字符串和将它显示在控制台中。相同的codecvt方面可以与其他一些C ++ 11节点类一起使用来执行此操作:

puts

上面仍然不太可移植,因为它假设wchar_t是UTF-16,在Windows上就是这种情况,但在大多数其他平台上都没有,并且它不是标准所要求的。 (事实上​​,我的理解是它在技术上并不符合,因为UTF-16需要多个代码单元来表示某些字符,并且标准要求所选编码中的所有字符必须能够在单个wchar_t中表示。)

std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t> convert;
puts(convert(L"ØÀéîðüýþ\n).to_bytes().c_str());

上述内容将可以轻松处理UCS-4和USC-2,但在使用UTF-16的平台上无法在Basic Multilingual Plane之外使用。

您可以使用std::wstring_convert<std::codecvt_utf8<wchar_t>,wchar_t> convert; 类型特征根据conditional的大小在这两个方面之间进行选择,并获得最适用的内容:

wchar_t

如果您的编码标准允许使用宏,则只需使用预处理器宏来定义合适的typedef。

答案 1 :(得分:1)

Windows对UTF-8的支持非常差,尽管使用Windows API可以实现它并不是很有趣,但是,您的问题指出您不想使用特定于平台的功能... < / p>

至于在'标准C ++'中这样做,我不确定在Windows下是否可能没有特定于平台的代码。但是,有许多第三方库可以抽象出这些平台细节,并允许您编写可移植代码。

我最近更新了我的应用程序,以便在Boost.Locale库的帮助下在内部使用UTF-8。 http://www.boost.org/doc/libs/1_48_0/libs/locale/doc/html/index.html

它的语言环境生成类将允许您生成基于UTF-8的语言环境对象,然后您可以将其添加到所有标准流等中。

我现在通过MinGW-w64在MSVC和GCC下成功使用了这个!我强烈建议你看看。是的,不幸的是,它在技术上并不是“标准C ++”,但是Boost几乎无处不在,实际上是一个事实上的标准,所以我认为这不是一个大问题。