如何使用 std::imbue 来设置 std::wcout 的区域设置
How can I use std::imbue to set the locale for std::wcout?
我正在尝试使用 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
来激活(时,我得到了另一个问题中提到的错误。
那些有兴趣尝试代码或编译器设置或两者的人可能希望使用此实时代码。
我的问题
- 为什么这行不通? 是因为
wcout
已经开放了吗? - 有没有办法使用
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
的方法,它是cout
和wcout
的祖先。它确实作用于数字转换,但在我的所有测试中,它对用于输出的字符集没有影响。
默认情况下,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-8
和 export LANG=fr_FR.ISO-8859-1
(进行了尝试,并在正确的字符集中正确èé
。我也在Windows XP下尝试过,代码页851(oem(和1252(ansi((或(。 chcp 850
和 chcp 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
- 按索引设置 std::variant 的值
- 为什么不能通过在错误输入后设置 std::cin.clear() 来使用 std::cin?
- 如何取消设置 std::cout 精度?
- 如何使用 gdb 设置 std::map 变量
- 如何在C++中设置std::list参数的默认值?
- C 设置STD :: MAP值为结构实例的指针
- C++ 在标准::unordered_map中设置 std::tuple 值
- 我可以在 c++ 中正确设置 std::vector by operator[] 的第 n 个值吗?
- 设置 std::function 变量以引用 std::sin 函数
- ISO C++禁止声明 .没有设置 --std=c++0x 的类型
- 使用printf样式的格式设置std::字符串的内容
- 如何设置std::vector的初始大小
- 在类构造函数中设置 std::vector
- 如何在 eclipse makefile 项目中的编译器选项中设置 -std=c++0x
- 尝试通过CGAL_CXX_FLAGS设置 --std=c++0x 或 -std=gnu++0x 会忽略值
- 有没有更有效的方法从流中设置 std::vector
- 在 C++11 中设置 std::线程优先级的便携式方法
- 直接使用运算符[]设置std::vector内容
- 如何使用 std::imbue 来设置 std::wcout 的区域设置
- 如何设置' std::exponential_distribution '对象的参数