Как я могу использовать std::imbue для установки локали для std:: wcout?
Я пытаюсь использовать std::locale
механизм в C++11 для подсчета слов в разных языках. В частности, у меня std::wstringstream
который содержит название известного русского романа ("Преступление и наказание" на английском языке). То, что я хочу сделать, это использовать соответствующую локаль (ru_RU.utf8
на моей машине Linux) чтобы прочитать stringstream, подсчитайте слова и распечатайте результаты. Я также должен отметить, что моя система настроена на использование en_US.utf8
locale.
желаемый результат это:
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
to #define USE_CODECVT 1
) я получаю ошибку, упомянутых в это другой вопрос.
те, кто заинтересован в экспериментировании с кодом или с настройками компилятора или оба могут использовать этот живой код.
мои вопросы
- почему это не работает? Это потому что
wcout
уже открыт? - есть ли способ, чтобы использовать
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";
}
3 ответов
сначала я сделал еще несколько тестов, используя ваш код, и я могу это подтвердить 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)... the C
locale, который ничего не знает о unicode. Все печатаемые символы ASCII (ниже 128) выводятся как есть, а другие заменяются ?
. Это именно то, что делает ваша программа.
чтобы он работал правильно, вы должны выбрать локаль, которая знает о символах Юникода с setlocale
. Как только это будет сделано, вы можете изменить числовое преобразование, вызвав imbue
, и как вы выбрали кодировку Юникод все будет хорошо.
таким образом, если ваш текущий язык использует кодировку 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-8
и export LANG=fr_FR.ISO-8859-1
) и я получил правильно èé
в правильной кодировке. Я попробовал это также под Windows XP, с кодовой страницей 851 (oem) и 1252 (ansi) (соотв. chcp 850
и chcp 1252
С консольной кодировкой Lucida), и получил èé
на консоли тоже.
Edit 2:
конечно, вы также можете установить глобальную локаль C++ с locale::global(locale("");
С локали по умолчанию или locale::global(locale("ru_RU.UTF-8");
С русским языком, но это больше, чем просто вызов setlocale
. Согласно документации реализации Gnu стандартной библиотеки C++ о locale : существует только одно отношение (языкового стандарта C++ механизм) к механизму локали C: глобальная локаль C изменяется, если именованный объект локали c++ задан как глобальная локаль", то есть: std::locale::global(std::locale(""));
влияет на функции C, как если бы был сделан следующий вызов:std::setlocale(LC_ALL, "");
. С другой стороны, нет наоборот, то есть вызов setlocale не имеет никакого отношения к механизму локали C++, в частности к работе locale("").
таким образом, действительно похоже, что была базовая библиотека c mechanizme, которая должна быть первой включена setlocale
разрешить imbue
преобразования для правильной работы.
в этом ответе я беру вопросы в обратном порядке и добавляю еще один (с ответом), который появился на этом пути.
есть ли способ, чтобы использовать imbue
вместо того, чтобы устанавливать глобальную локаль, чтобы делать то, что я хочу?
да. по умолчанию std::wcout
синхронизируется с базовым stdout
С потока. Так что std::wcout
can использовать 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"
locale, который является US English ASCII, так что это, по-видимому, означает, что stdout
по умолчанию будет иметь отображение ASCII. Поскольку кириллические символы не представлены в ASCII, базовый stdout
это то, что превращает правильный русский язык в серию ?
символы.
почему sync_with_stdio
звонок перед imbue
?
по данным 27.5.3.4 стандарта:
если какая-либо операция ввода или вывода произошла с использованием стандартных потоков до вызова, эффект определяется реализацией. В противном случае, вызываемый с ложным аргументом, он позволяет стандартным потокам работать независимо от стандартных потоков C.
Я не знаю, какие языки вы собираетесь поддерживать, но есть языки, где ваш алгоритм не применяется, напр. Японский. Я предлагаю проверить итераторы слов в международных компонентах для Unicode. http://userguide.icu-project.org/boundaryanalysis