如何使用std :: imbue设置std :: wcout的语言环境?

时间:2014-10-15 16:04:31

标签: c++ c++11 locale

我正在尝试使用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 "?????????"

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

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

我的问题

  1. 为什么这不起作用?是因为wcout已经开放吗?
  2. 有没有办法使用imbue而不是设置全局区域设置来做我想要的事情?
  3. 如果它有所不同,我正在使用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";
    }
    

3 个答案:

答案 0 :(得分:11)

首先,我使用您的代码进行了一些测试,我可以确认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,请选择系统上安装的并支持UTF-8的区域设置。我使用了setlocale(LC_ALL, "fr_FR.UTF-8");,甚至setlocale(LC_ALL, "en_US.UTF-8");都使用过。

编辑:

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

#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:

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

所以看起来真的有一个底层的C库机制应该首先使用setlocale启用,以使imbue转换正常工作。

答案 1 :(得分:10)

在这个答案中,我以相反的顺序提出问题,然后在路上添加另一个(带答案)。

有没有办法使用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

中声明的对象<cstdio>关联的流缓冲区

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

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

根据标准27.5.3.4:

  

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

答案 2 :(得分:1)

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