【问题标题】:How can I use std::imbue to set the locale for std::wcout?如何使用 std::imbue 设置 std::wcout 的语言环境?
【发布时间】:2014-10-15 16:04:30
【问题描述】:

我正在尝试使用 C++11 中的 std::locale 机制来计算不同语言的单词。具体来说,我有std::wstringstream,其中包含着名的俄罗斯小说的标题(英文为“犯罪与惩罚”)。我想要做的是使用适当的语言环境(我的 Linux 机器上的ru_RU.utf8)来读取字符串流,计算单词并打印结果。我还应该注意到我的系统设置为使用en_US.utf8 语言环境。

想要的结果是这样的:

0: "Преступление"
1: "и"
2: "наказание"

I counted 3 words.
and the last word was "наказание"

当我设置全局语言环境时,这一切都有效,但当我尝试 imbue wcout 流时,一切都无效。当我尝试这样做时,我得到了这个结果:

0: "????????????"
1: "?"
2: "?????????"

I counted 3 words.
and the last word was "?????????"

另外,当我尝试使用 cmets 中建议的解决方案时(可以通过将 #define USE_CODECVT 0 更改为 #define USE_CODECVT 1 来激活),我收到了 this other question 中提到的错误。

有兴趣尝试代码或编译器设置或两者的人可能希望使用this live code

我的问题

  1. 为什么这不起作用?是因为wcout 已经打开了吗?
  2. 有没有办法使用imbue 而不是设置全局语言环境来做我想做的事?

如果有什么不同,我使用的是 g++ 4.8.3。完整代码如下所示。

getwords.cpp

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <locale>

#define USE_CODECVT 0
#define USE_IMBUE   1

#if USE_CODECVT
#include <codecvt>
#endif 
using namespace std;

int main()
{
#if USE_CODECVT
    locale ru("ru_RU.utf8", 
        new codecvt_utf8<wchar_t, 0x10ffff, consume_header>{});
#else
    locale ru("ru_RU.utf8");
#endif
#if USE_IMBUE
    wcout.imbue(ru);
#else
    locale::global(ru);
#endif
    wstringstream in{L"Преступление и наказание"};
    in.imbue(ru);
    wstring word;
    unsigned wordcount = 0;
    while (in >> word) {
        wcout << wordcount << ": \"" << word << "\"\n";
        ++wordcount;
    }
    wcout << "\nI counted " << wordcount << " words.\n"
        << "and the last word was \"" << word << "\"\n";
}

【问题讨论】:

  • 尝试将 utf8 转换方面安装到语言环境中:locale ru{"ru_RU.utf8", new std::codecvt_utf8&lt;wchar_t, 0x10ffff, std::consume_header&gt;{}};。这需要 &lt;codecvt&gt; 标头。
  • 不幸的是,这里没有编译。请参阅this question 了解我的确切症状。我不知道使用 g++ 的解决方法。
  • UTF-8 不依赖于语言环境。它可以表示任何语言使用的任何 Unicode 代码点。我认为问题不在于wcout 执行的转换。我会检查两件事。首先,字符串文字是否完整地进入二进制文件。做wcout &lt;&lt; (int)L'П'; - 这应该打印1055;如果不是,则该字符被编译器破坏。二、控制台是否设置为显示非英文字符。将输出重定向到文件,用十六进制查看器检查它。西里尔文'П' 应表示为两个字节D0 9F
  • 将输出重定向到文件没有区别,并且字符在字符串中正确表示。我在程序wcout &lt;&lt; "The first letter of the last word is U+0" &lt;&lt; hex &lt;&lt; (int)(word[0]) &lt;&lt; " (" &lt;&lt; word[0] &lt;&lt; ")\n"; 中添加了一个新的最后一行,它打印The first letter of the last word is U+043d (?)
  • @0x499602D2 我更喜欢非 Boost 答案,但我们将不胜感激。

标签: c++ c++11 locale


【解决方案1】:

首先,我使用您的代码进行了更多测试,我可以确认 L"Преступление и наказание" 是正确的 UTF16 字符串。我控制了单个字符的代码,它们是正确的0x41f, 0x440, 0x435, 0x441, 0x442, 0x443, 0x43f, 0x43b, 0x435, 0x43d, 0x438, 0x435, 0x20, 0x438, 0x20, 0x43d, 0x430, 0x43a, 0x430, 0x437, 0x430, 0x43d, 0x438, 0x435

我找不到任何关于它的参考资料,但看起来简单地调用imbue 是不够的。 imbue 它是 basic_ios 的一个方法,它是 coutwcout 的祖先。它确实作用于数字转换,但在我所有的测试中,它对用于输出的字符集没有影响。

默认情况下,在 C++(或 C)程序中使用的语言环境是……C 语言环境,它对 unicode 一无所知。所有可打印的 ASCII 字符(128 以下)都按原样输出,其他字符替换为?。这正是您的程序所做的。

要使其正常工作,您必须使用setlocale 选择一个了解 unicode 字符的语言环境。完成此操作后,您可以通过调用 imbue 来更改数字转换,当您选择 unicode 字符集时一切都会好起来的。

因此,如果您当前的语言环境使用 UTF-8 字符集,您只需添加

setlocale(LC_ALL, "");

作为程序的第一行,输出将如预期的那样:

0: "Преступление"
1: "и"
2: "наказание"

I counted 3 words.
and the last word was "наказание"

如果您当前的语言环境不使用 UTF-8,请选择一个安装在您的系统上并支持它的语言环境。我使用了setlocale(LC_ALL, "fr_FR.UTF-8");,甚至setlocale(LC_ALL, "en_US.UTF-8");,两者都有效。

编辑:

事实上,正确输出 unicode 到屏幕的最好方法是使用setlocale(LC_ALL, "");。它会自动适应当前的字符集。我使用 Latin1 字符集测试了一个精简的变体(我的系统说法语而不是俄语......)

#include <iostream>
#include <locale>

using namespace std;

int main() {
    setlocale(LC_ALL, "");
    wchar_t ws[] = { 0xe8, 0xe9, 0 };

    wcout << ws << endl;
}

我在 Linux 下使用 UTF-8 字符集和 ISO-8859-1 (latin1) (resp export LANG=fr_FR.UTF-8export LANG=fr_FR.ISO-8859-1) 进行了尝试,我在正确的字符集中得到了正确的 èé。我也在 Windows XP 下尝试过,代码页为 851 (oem) 和 1252 (ansi)(分别是 chcp 850chcp 1252 与 Lucida 控制台字符集),并且在控制台上也得到了 èé

编辑 2:

当然,您也可以使用locale::global(locale(""); 使用默认语言环境或locale::global(locale("ru_RU.UTF-8"); 使用俄语语言环境设置全局C++ 语言环境,但这不仅仅是调用setlocale。根据关于locale 的 C++ 标准库的 Gnu 实现文档:与 C 语言环境机制只有一个关系(C++ 语言环境机制):如果命名的 C++ 语言环境对象是,则修改全局 C 语言环境设置为全局语言环境”,即:std::locale::global(std::locale("")); 影响 C 函数,就好像进行了以下调用:std::setlocale(LC_ALL, "");。另一方面,反之亦然,也就是说,调用 setlocale 在C++ 语言环境机制,特别是关于 locale("") 的工作

所以看起来确实存在一个底层 C 库机制,应该首先使用 setlocale 启用,以允许 imbue 转换正常工作。

【讨论】:

  • 感谢您的努力。不幸的是,在我看来,setlocale(LC_ALL, ""); 与我在原始代码中的locale::global(ru); 没有本质区别。
  • 救命稻草! setlocale(LC_ALL, ""); 工作!
【解决方案2】:

在这个答案中,我以相反的顺序回答问题,并添加了沿途出现的另一个(带有答案)。

有没有办法使用imbue 而不是设置全局语言环境来做我想做的事?

是的。 默认情况下,std::wcout 与底层的stdout C 流同步。所以std::wcout可以在同步关闭的情况下使用imbue,允许C++流独立运行。因此,要修改原始代码以使用imbue 并按预期工作,只需添加一行,调用std::ios_base::sync_with_stdio

std::ios_base::sync_with_stdio(false);
std::wcout.imbue(ru);

为什么原来的版本不行?

标准(我指的是 INCITS/ISO/IEC 14882-2011[2012])很少提及与底层 stdio 流的联系,但在 27.4.3 中它说

对象wcout 控制输出到与stdout 中声明的对象stdout 关联的流缓冲区

此外,在没有明确设置全局语言环境的情况下,语言环境是"C" 语言环境,它是美国英语 ASCII,因此这似乎暗示stdout 默认情况下将具有 ASCII 映射。由于 ASCII 中没有西里尔字符,因此底层的 stdout 将正确的俄语转换为一系列 ? 字符。

为什么sync_with_stdio 调用必须在imbue 之前?

根据标准27.5.3.4:

如果在调用之前使用标准流发生了任何输入或输出操作, 效果是实现定义的。否则,使用错误参数调用,它允许标准流独立于标准 C 流运行。

【讨论】:

    【解决方案3】:

    我不知道您计划支持哪些语言,但有些语言不适用您的算法,例如。日本人。我建议查看 International Components for Unicode 中的 iterators 一词。 http://userguide.icu-project.org/boundaryanalysis

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-11-27
      • 1970-01-01
      • 2019-08-27
      • 2019-08-27
      • 2020-01-20
      • 1970-01-01
      • 1970-01-01
      • 2010-09-09
      相关资源
      最近更新 更多