如何使用 std::imbue 来设置 std::wcout 的区域设置

How can I use std::imbue to set the locale for std::wcout?

本文关键字:设置 std wcout 区域 imbue 何使用      更新时间:2023-10-16

我正在尝试使用 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来激活(时,我得到了另一个问题中提到的错误。

那些有兴趣尝试代码或编译器设置或两者的人可能希望使用此实时代码。

我的问题

  1. 为什么这行不通? 是因为wcout已经开放了吗?
  2. 有没有办法使用imbue而不是设置全局区域设置来做我想做的事情?

如果它有所作为,我使用的是 g++ 4.8.3。完整代码如下所示。

得到的话.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";
}

首先,我使用您的代码进行了更多测试,我可以确认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(程序中使用的区域设置是...对 Unicode 一无所知的C区域设置。所有可打印的 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(拉丁语 1((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。根据 Gnu 实现C++标准库关于 locale 的文档:(C++ locale 机制(与 C 语言环境机制只有一个关系:如果将命名的 C++ locale 对象设置为全局语言环境,则修改全局 C 语言环境",即: std::locale::global(std::locale(""));影响 C 函数,就好像进行了以下调用一样: std::setlocale(LC_ALL, ""); .另一方面,反之亦然,也就是说,调用 setlocale 对 C++ locale 机制没有任何影响,特别是在 locale("( 的工作上

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

在这个答案中,我以相反的顺序回答问题,并添加另一个(有答案(在此过程中出现的问题。

有没有办法使用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 流运行。

我不知道你打算支持什么语言,但有些语言你的算法不适用,例如。日语。我建议查看Unicode国际组件中的迭代器一词。http://userguide.icu-project.org/boundaryanalysis