3

是否可以在不使用平台特定功能的情况下打印 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 name not valid

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

在另一个问题(“我如何 cin 和 cout 一些 unicode 文本? ”)中,Philipp 写道:“wcin 和 wcout 在 Windows 上不起作用,就像等效的 C 函数一样。只有本机 API 起作用。” MinGW也是真的吗?

谢谢你的任何提示!

平台:
MinGW/GCC
Windows 7

4

2 回答 2

6

我没有在 Windows 的 mingw 环境中使用 gcc,但据我所知,它不支持 C++ 语言环境。

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

ws1打印,而且只打印一次的原因是当打印字符时\u20AC,流失败并且设置了失败位。您必须先清除错误,然后再打印任何内容。


C++11 引入了一些可移植处理 UTF-8 的东西,但还不是所有东西都支持,而且添加的东西并不能完全解决问题。但这是目前的情况:

char16_tchar32_t在 VS 中作为原生类型而不是 typedef 被支持时,您将能够使用标准的 codecvt facet specializationscodecvt<char16_t,char,mbstate_t>并且codecvt<char32_t,char,mbstate_t>分别需要在 UTF-16 或 UTF-32 和UTF-8(而不是执行字符集或系统编码)。这还行不通,因为在当前的 VS(和 VS11DP)中,这些类型只是 typedef,模板专业化不适用于 typedef,但代码已经在 VS 2010 的标头中,只是在#ifdef.

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

std::wcout.imbue(std::locale(std::locale::classic(),new std::codecvt_utf8_utf16<wchar_t>()));
std::wcout << L"ØÀéîðüýþ\n";

这将通过附加到 wcout 的任何内容输出 UTF-8 代码单元。如果输出已被重定向到文件,则打开它将显示一个 UTF-8 编码的文件。但是,由于 Windows 上的控制台模型,以及标准流的实现方式,这种方式将无法在命令提示符中正确显示 Unicode 字符(即使您使用 将控制台输出代码页设置为 UTF-8 SetConsoleOutputCP(CP_UTF8)) . UTF-8 代码单元一次输出一个,控制台将查看传递给它的每个单独的块,期望传递的每个块(即在这种情况下为单个字节)是完整且有效的编码。当显示字符串时,块中不完整或无效的序列(在这种情况下是所有多字节字符表示的每个字节)将被替换为 U+FFFD。

如果不使用 iostreams 而是使用 C 函数puts写出整个 UTF-8 编码字符串(并且如果控制台输出代码页设置正确),那么您可以打印 UTF-8 字符串并将其显示在控制台中。相同的 codecvt facets 可以与其他一些 C++11 convinence 类一起使用来执行此操作:

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

上面的内容仍然不是很便携,因为它假设 wchar_t 是 UTF-16,在 Windows 上是这种情况,但在大多数其他平台上不是这样,而且它不是标准要求的。(实际上我的理解是它在技术上不符合标准,因为 UTF-16 需要多个代码单元来表示某些字符,并且标准要求所选编码中的所有字符都必须可以在单个 wchar_t 中表示)。

std::wstring_convert<std::codecvt_utf8<wchar_t>,wchar_t> convert;

以上将可移植地处理 UCS-4 和 USC-2,但不能在使用 UTF-16 的平台上的基本多语言平面之外工作。

您可以使用conditional类型特征根据大小在这两个方面之间进行选择,wchar_t并获得最有效的东西:

std::wstring_convert<
    std::conditional<sizeof(wchar_t)==2,std::codecvt_utf8_utf16<wchar_t>,
                                        std::codecvt_utf8<wchar_t>
    >::type,
    wchar_t
> convert;

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

于 2012-02-10T02:01:20.840 回答
1

Windows 对 UTF-8 的支持很差,虽然可以使用 Windows API 来做到这一点,但它一点也不好玩,而且,你的问题表明你不想使用平台特定的功能......

至于在“标准 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 几乎无处不在,而且实际上是一个事实上的标准,所以我认为这不是一个大问题。

于 2012-02-10T18:45:36.663 回答