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

Posted

技术标签:

【中文标题】如何在不使用平台特定功能的情况下打印 UTF-8 字符串?【英文标题】:How to print UTF-8 strings without using platform specific functions? 【发布时间】:2012-02-09 12:22:12 【问题描述】:

是否可以在不使用平台特定函数的情况下打印 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?”)中,Philipp 写道: “wcin 和 wcout 在 Windows 上不起作用,就像等效的 C 函数一样。只有本机 API 起作用。” MinGW 也是这样吗?

感谢您的任何提示!

平台: MinGW/GCC 视窗 7

【问题讨论】:

【参考方案1】:

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

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

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


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

当 VS 支持 char16_tchar32_t 作为本机类型而不是 typedef 时,您将能够使用标准的 codecvt facet specializations codecvt&lt;char16_t,char,mbstate_t&gt;codecvt&lt;char32_t,char,mbstate_t&gt; 在 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 字符(即使您设置了控制台输出代码使用SetConsoleOutputCP(CP_UTF8) 将页面转到UTF-8)。 UTF-8 代码单元一次输出一个,控制台将查看传递给它的每个单独的块,期望传递的每个块(即本例中的单个字节)都是完整且有效的编码。当显示字符串时,块中不完整或无效的序列(在这种情况下是所有多字节字符表示的每个字节)将被替换为 U+FFFD。

如果您使用 C 函数 puts 而不是使用 iostreams 来写出整个 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。

【讨论】:

感谢您的详尽解释!【参考方案2】:

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

【讨论】:

好吧,我认为 Boost 是标准的一部分。你知道 MinGW32 有什么问题吗?

以上是关于如何在不使用平台特定功能的情况下打印 UTF-8 字符串?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不实际使用枚举的情况下制作功能类似于枚举的代码?

如何在不显示打印对话框的情况下使用 JRPrintServiceExporter 更改边距?

如何在不向用户显示视图的情况下捕获整个滚动视图的图片

如何让机器人在不使用命令的情况下向特定频道中的特定公会发送消息

在不连接打印机的情况下,为打印机安装驱动程序

如何在不使用 cat -b 或 cat -n 选项的情况下打印文件内容以及行号[关闭]