Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > C++ и Unicode


Автор: Ulysses4j 7.7.2008, 18:17
Извините, что дублирую тему, которая сейчас обсуждается рядом, но как-то она меня не радует, и толькаться не хотелось бы. Дело в том, что волнуют меня близкие вещи к тому, что обсуждается там, но то обсуждение на мой взгляд как-то малоконструктивно, потому я решил задать максимально конкретный, четкий, простой вопрос.

Может кто-нибудь привести работающий пример чтения и вывода на консоль файла в UTF-8 (или любой другой Unicode-кодировки). Вот, что написал я:
Код
#include <fstream>
#include <iostream>
#include <string>

using namespace std;

int main() {
    wifstream f("test.txt");
    if (!f.is_open())
        throw 42;
    wstring s;
    getline(f, s);
    wcout << s << endl;
}

Выводит пустую строку. Содержимое файла test.txt в UTF-8, это одна строка в кириллице.

Автор: JackYF 7.7.2008, 20:04
Интересно. На деле с Unicode я работал только средствами Qt, а этот код у меня тоже выводит пустую строку.
Более того, код "f >> s;" вообще генерирует

Цитата

terminate called after throwing an instance of 'std::ios_base::failure'
  what():  basic_filebuf::underflow invalid byte sequence in file


Возможно, оно хочет BOM в начале файла. Не знаю...

Автор: Annihilator 7.7.2008, 21:20
может http://www.cl.cam.ac.uk/~mgk25/unicode.html#c поможет

Автор: Ulysses4j 7.7.2008, 21:51
JackYF, скажу насчет Qt: я искал в сети ответ на свой вопрос, разумеется. Так вот все Unicode-решения на C++ были завязаны на некую библиотеку. Boost предлагает пользоваться поделием от IBM, есть варианты с MFC, ну и Qt, да. Это очень печально, если никак нельзя получить из коробки поддержку таких простейших операций, как ввод-вывод. Стандарт C++ устаревает все сильней.

Annihilator, не устраивает по двум параметрам: 
1. C — не люблю этот язык программирования.
2. Привязка к вендору (gcc, glibc).
Забавно, что даже учитывая то, что ISO C99 попытался как-то осмысленно подойти к поддержке Unicode, некоторые стандартные строковые функции C остаются неприсобленными к нему.

Однако, спасибо за отклик.

Автор: bsa 7.7.2008, 22:19
Ulysses4j, функция mbstowcs соответствует стандарту C99 и не зависит от библиотек и платформ. Другое дело, что далеко не все производители поддержали этот стандарт.

Автор: Ulysses4j 8.7.2008, 00:59
bsa, она даже http://c.snippets.org/code/stdstuff.txt, но дело в том, что, как я уже говорил, в первую очередь меня не устраивает то, что это C. Я и тему соответствующим образом назвал. Это мне придется, чтобы использовать mb*, открывать файл как FILE*, потом fread какие-нибудь, это по определенным причинам в данном случае недопустимо. Мне хотелось бы решения на C++.

Автор: Ulysses4j 8.7.2008, 01:15
Собственно, в стандарте C++ сказано, что mbstowcs с сотоварищи включены. И хотя мне нельзя завязываться на C-интерфейсы, я решил в качестве эксперимента попробовать:
Код
#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

int main() {
    char cs[] = "Здравствуй, мир";
    const size_t len = 32; // с запасом
    wchar_t wcs[len];
    mbstowcs(wcs, cs, len);
    wstring s(wcs);
    wcout << s << endl;
}

Текст программы в UTF-8, соответственно, там же должны быть литералы. Выводятся кракозябры. 

Я так понимаю, нужно еще иметь представление о локалях. Я с ними плохо дружу. Системная у меня ru_RU.UTF-8 (в частности, это содержимое переменной окружения LANG).

Автор: Torsten 8.7.2008, 20:51
Ulysses4j
Цитата
JackYF, скажу насчет Qt: я искал в сети ответ на свой вопрос, разумеется. Так вот все Unicode-решения на C++ были завязаны на некую библиотеку. Boost предлагает пользоваться поделием от IBM, есть варианты с MFC, ну и Qt, да. Это очень печально, если никак нельзя получить из коробки поддержку таких простейших операций, как ввод-вывод. Стандарт C++ устаревает все сильней.


Причем тут стандарт ?
Какая у тебя кодировка в консоли (или куда ты там выводишь) установлена он в той и выводит. В linux все просто. Установи нужную кодировку через контекстное меню (для konsole) и все выведет нормально. Правда исходник нужно поправить, чтобы было нормальный std::string и std::ifstream - тогда все будет нормально.

Написать классы для поддержки utf-8, utf-16 и русских кодировок совсем не сложно.

Автор: Ulysses4j 8.7.2008, 21:07
Цитата(Torsten @  8.7.2008,  20:51 Найти цитируемый пост)
Причем тут стандарт ?

При том, что в современных языках, где поддержка Unicode реализована изначально и осмысленно, никаких вопросов не возникает. С этими wchar_t какая-то байда, потому что когда писался Стандарт 98, люди, по всей видимости, во-первых, не воспринимали Unicode достаточно серьезно (не сказать: не понимали достаточно глубоко), во-вторых, у них как обычно, не было времени над этим подумать. Если откроешь http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1810.html, то увидишь, что в первых рядах «желаемых предложений» стоит поддержка Unicode.


Цитата(Torsten @  8.7.2008,  20:51 Найти цитируемый пост)
Какая у тебя кодировка в консоли (или куда ты там выводишь) установлена он в той и выводит.

Да что ты говоришь, а я-то думал, что он случайным образом кодировку выбирает. У меня GNU/Linux с локалью ru_RU.UTF-8, в консоли (никакая не Konsole, естественно, потому что не KDE), ясное дело, UTF-8.

Цитата(Torsten @  8.7.2008,  20:51 Найти цитируемый пост)
Написать классы для поддержки utf-8, utf-16 и русских кодировок совсем не сложно.

Не надо меня лечить: или выложи свою реализацию, или скажи почему приведенный мной код не работает.

Автор: bsa 8.7.2008, 22:22
Ulysses4j, попробуй добавить setlocale(LC_CTYPE, "") первой строчкой в main()

Автор: Ulysses4j 8.7.2008, 22:38
Спасибо, bsa, заработало. Получается, возвращаясь к первоначальной задаче: прочитать файл в UTF-8 — нужно читать файл в char[] а потом конвертить в wchar_t, а потом уже загонять в wstring. Как-то не по человечески это.

Автор: Ulysses4j 9.7.2008, 02:02
Если кому-то интересно, вот что еще удалось разузнать.

wstring можно инициализировать литералами (если исходник в Unicode) вот так:
Код
wstring ws(L"Здравствуй, мир"); // L это не опечатка, а знак того, что литерал надо понимать как wchar_t[]

Далее про чтение файлов. Все сильно зависит от того, какие локали установлены в операционной системе. В никсах можно посмотреть locale -a а в Windows через Панель управления, Региональные и языковые настройки (или как-то так). У меня в никсах есть упоминания только про UTF-8, так что пока я вроде бы научился читать и писать файлы в UTF-8 (соответственно, в UTF-16 не получается):
Код
#include <iostream>
#include <fstream>
#include <locale>
#include <string>
#include <boost/algorithm/string.hpp>

using namespace std;

int main() {
    locale::global(locale(""));
    wifstream f("test.txt"); // содержит "Привет"
    wofstream out("out.txt");
    if (! f.is_open())
        throw 42;
    wstring s1;
    getline(f, s1);
    out << "input: " << s1 << endl <<
      "to_upper: " <<  boost::algorithm::to_upper_copy(s1) << endl;
    out << L"Здравствуй, мир" << endl;
}

После этого out.txt содержит:
Код
input: Привет
to_upper: ПРИВЕТ
Здравствуй, мир


Строка locale::global(locale("")); была изначально навеяна советом bsa, за что ему еще раз отдельное спасибо и плюс в карму по возможности (если у кого из окружающих есть полномочия). Медитация со стандартом в одной руке и Гуглом в другой привела к следующим фактам:  вначале каждой программы, неявно выставляется локаль с особым именем "C", которая существует как бы в любой системе и содержит минимальные сведения для того, чтобы программа на C/C++ могла работать. Этот минимум  может совсем не устравать. Первое, что можно сделать это явно попросить взять стандартную системную локаль: как раз вызвав setlocale(LC_ALL, "") (LC_TYPE, как советовал bsa, может хватить для действий со строками, да) и/или locale::global(locale(""));, в зависимости от того, какими средствами собираетесь пользоваться (пустая строка в обоих случаях означает, что настоящее имя системной локали знает система, это как бы конструктор по умолчанию).

Ну вот, а что может быть лучше я и не знаю. В принципе, приведенную выше программу можно назвать переносимой в следующем смысле: она будет корректно работать, если файл test.txt находится в кодировке, соответствующей системной локали. Для последних Windows это часто UTF-16. Для интернационализированных никсов это часто UTF-8. Меня сильно волнуют вопросы переносимости, так что если кто-то поиграется с этим под виндами или просто сможет высказать какие-то ценные мысли в этом русле, буду признателен.

Такие дела. Всем спасибо за участие.

Автор: JackYF 9.7.2008, 16:24
Цитата(Ulysses4j @  9.7.2008,  01:02 Найти цитируемый пост)
за что ему еще раз отдельное спасибо и плюс в карму по возможности (если у кого из окружающих есть полномочия).

У тебя есть 100 постов, подними ему репутацию.

Автор: Ulysses4j 9.7.2008, 17:15
Под Windows не получается выставить локаль, которая бы распознавала UTF-*, не знаю, какое у нее должно быть имя, пробовал разные варианты, гуглил — все попусту. У системной: «Russian_Russia.CP1251», соответственно, понимает только однобайтовую cp1251.

Автор: NovorosSupport 21.7.2009, 11:46
Цитата

не знаю, какое у нее должно быть имя, пробовал разные варианты, гуглил — все попусту


Инструкция
setlocale(LC_ALL,"Russian");
возвращает в моём и, очевидно, в твоём случае "Russian_Russia.1251" smile

Хотя мне проще запомнить и использовать
setlocale(LC_ALL, ".1251" );

Цитата

Получается, возвращаясь к первоначальной задаче: прочитать файл в UTF-8 — нужно читать файл в char[] а потом конвертить в wchar_t, а потом уже загонять в wstring. Как-то не по человечески это


После указания конкретной кодовой страницы инструкцией setlocale(LC_ALL, ".1251" ) ничего этого уже не понадобилось. Файл с кириллицей корректно считывался в объект string и затем корректно выводился в cout! Причем в моём случае даже wcout стал не нужным.

http://msdn.microsoft.com/en-us/library/x99tb11d(VS.80).aspx

Автор: jonie 21.7.2009, 12:04
http://site.icu-project.org/ пробывали?

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)