cplus-plus.ru logo
Мы переехали на cplus-plus.ru
Главная страница В закладкиО сайтеКарта сайта
Хостинг от uCoz
Добавить в закладки

Меню сайта

Полезные ссылки

Наша рассылка
Подписаться на рассылку
"C++ : cplus-plus.ru :
Рассылка статей C++"


Друзья сайта
alsproject.ru Выбор выходного разделительного конденсатора

Приветствую Вас, Гость · rss 29-Мар-2024, 00:17
Главная » 2010 » Ноябрь » 25 » Кодировки в C++
12:09
Кодировки в C++

Кодировки в C++
Всем рано или поздно приходится работать с различными кодировками. Заметив в коде своей команды различные, порой странные, подходы к решению этих проблем, пришлось провести разъяснительную беседу. Ниже поделюсь своим видением правильной работы с не-ASCII символами в коде. Буду рад конструктивной критике
 
Принцип работы

Логика работы с различными кодировками в C++ проста и прозрачна. В общем виде она отражена в схеме. Программа работает в одной — своей внутренней кодировке, а правильно локализованные потоки отвечают за преобразование кодировки данных из внешнего кода во внутренний и обратно. Внутреннюю кодировку программы лучше всего зафиксировать раз и навсегда. Если программа работает с не-ASCII символами, то самый логичный выбор для внутренней кодировки — Юникод, причём использование UTF-8 и char для параметризации STL как правило неоправданно (хотя существуют ситуации, в которых это необходимо); более логично перейти на расширенные символы wchar_t и использовать UCS-2. За преобразование данных из внешней кодировки во внутреннюю отвечает фасет codecvt. Локализованные потоки сами вызовут соответствующие функции фасета при получении данных (о том кто такие фасеты я писал ранее).
Вышесказанное поясню комментированным примером, в котором прочитаем данные из cp1251 файла, покажем как boost::xpressive работает с юникодом и выведем кириллицу в cout в кодировке cp866 (консоль windows по умолчанию).

Кодировка исходников

Прежде чем приступить к рассмотрению примера, следует (на всякий случай) пару слов сказать о кодировке исходных текстов программы. Все свои исходники я держу в UTF-8 (если они содержат широкие строковые константы с не-ASCII символами, то в файлы добавляю BOM), что и всем советую. Современные компиляторы сами преобразуют «широкие» символы, помеченные в исходниках как L"" в UCS-2 (или UCS-4). Понятно, что правильное преобразование зависит от кодировки исходников. gcc по-умолчанию считает, что работает с UTF-8 текстом, чтобы его переубедить придётся специально указывать значение параметра -finput-charset. Компилятору от MS нужно немножечко помочь — добавить в UTF-8 файл BOM (Byte Order Mark). К сожалению у Borland C++ Compiler version 5.5 с UTF-8 проблемы.
Для тех, кто собрался кинуть в меня камень поясню два момента: первый — мне не удобно читать код с «unicode escape» типа:
std::wstring wstr(L"\u0410\u0411\u0412\u0413\u0413");
второй — речь идёт не только об интерфейсе пользователя, поэтому вынести все широкие строковые константы в отдельный модуль и как-то с ними работать (наподобие gettext), не вариант.
Итак решено — исходники в UTF-8 с BOM. Если кто не знает, в vim BOM можно добавить к файлу с помощью команды «set bomb». Если BOM в файле уже есть vim его никуда не денет.

Пример работы с различными кодировками

Ну вот и подобрались к самому интересному. Как я и говорил, код простой и понятный. Маленькое замечание по стандартным потокам — по умолчанию фасеты для них не задействуются так как они синхронизированы с stdio для производительности. Следует указать sync_with_stdio(false).

#include <boost/xpressive/xpressive.hpp>
#include <locale>
#include <fstream>
#include <iostream>
#include <iterator>

#include "codecvt_cp866.hpp"
#include "codecvt_cp1251.hpp"
#include "unicyr_ctype.hpp"

using namespace std;
using namespace boost::xpressive;

int main()
{
  // Пусть имеется файл input.txt в кодировке cp1251, содержащий банальный
  // "Привет, мир!"
  ofstream ofile("input.txt", std::ios::binary);
  ostreambuf_iterator<char> writer(ofile);
  writer = 0xCF; // П
  ++writer = 0xF0; // р
  ++writer = 0xE8; // и
  ++writer = 0xE2; // в
  ++writer = 0xE5; // е
  ++writer = 0xF2; // т
  ++writer = 0x2C; // ,
  ++writer = 0x20; //  
  ++writer = 0xEC; // м
  ++writer = 0xE8; // и
  ++writer = 0xF0; // р
  ++writer = 0x21; // !
  ofile.close();

  // Читаем файл
  wifstream ifile("input.txt");
  // Локаль для ввода
  locale cp1251(locale(""), new codecvt_cp1251<wchar_t, char, mbstate_t>);
  ifile.imbue(cp1251);
  wchar_t wstr[14];
  ifile.getline(wstr, 13);

  // Стандарт C++ предписывает синхронизировать cout, cin, cerr и
  // clog, как и их расширенные варианты с stdio, поэтому поумолчанию
  // для этих потоков фасет не будет задействован (во всяком случае при 
  // компиляции gcc, msvc 7 не особо придерживается стандартов). Следует 
  // сообщить ios, что мы не будем синхронизироваться с stdio.
  ios_base::sync_with_stdio(false);
  // Локаль для вывода
  locale cp866(locale(""), new codecvt_cp866<wchar_t, char, mbstate_t>);
  // Сообщаем потоку, что перед выводом необходимо выполнять
  // преобразование
  wcout.imbue(cp866);

  // Локаль с правильным ctype
  locale cyrr(locale(""), new unicyr_ctype);
  wsregex_compiler xpr_compiler;
  xpr_compiler.imbue(cyrr);
  wsregex xpr = xpr_compiler.compile(L"МИР", regex_constants::icase);
  wsmatch match;
  if(regex_search(wstring(wstr), match, xpr))
  wcout << L"icase сработал" << endl;
  else
  wcout << L"icase не сработал" << endl;

  return 0;
}


Фасет codecvt для преобразования кириллицы из cp1251 в ucs-2 и обратно
#include <locale>
#include <map>

/**@brief Фасет codecvt для преобразования символов кириллицы из cp1251 
 * в UCS-2 и обратно
 *
 * Фасет хорошо описан в книге Страуструпа (3-е специальное издание -
 * приложение). Темным пятном является лишь третий параметр шаблона -
 * состояние. Дело в том, что фасет codecvt спроектирован для работы с
 * потоками байт. Символ может представлять собой последовательность из
 * нескольких байт. В случае, если мы получили только часть байт,
 * составляющих символ, мы не можем преобразовать его. Необходимые байты
 * могут быть получены в следующий вызов функций преобразования. State
 * таким образом выступает в качестве объекта, в который мы сохраняем
 * информацию о текущем неполном преобразовании для того, чтоб ее можно 
 * было бы сообщить следующему вызову функции преобразования. */
template<class I, class E, class State>
class codecvt_cp1251 : public std::codecvt<I, E, State>
{
  public:

  // результат выполнения операции преобразования
  typedef typename std::codecvt_base::result result;
  const result ok, // преобразовано
  partial, // преобразовано частично (см описание State)
  error, // произошла ошибка
  noconv; // не преобразовано

  explicit codecvt_cp1251(size_t r=0)
  : std::codecvt<I, E, State>®,
  ok(std::codecvt_base::ok),
  partial(std::codecvt_base::partial),
  error(std::codecvt_base::error),
  noconv(std::codecvt_base::noconv)
  {
  // расширенные символы кириллицы - см стандарт
  in_tab[0xA8] = 0x401; out_tab[0x401] = 0xA8;
  in_tab[0xB8] = 0x451; out_tab[0x451] = 0xB8;
  // ... остальные коды
  }

  ~codecvt_cp1251()
  {
  }

protected:
  /**@brief Выполняет преобразование из внешней кодировки во внутреннюю 
  * строки from-from_end, записывая результат в строку in-in_end.*/
  virtual result do_in(State&,
  const E* from, const E* from_end, const E* &from_next,
  I* to, I* to_end, I* &to_next)
  const
  {
  while(from != from_end)
  {
  if(to == to_end)
  {
  from_next = ++from;
  to_next = to;
  return partial;
  }
  // ASCII
  if(0 <= *from && *from <= 0x7F)
  {
  *to = static_cast<I>(*from);
  }
  else if(0xC0 <= static_cast<unsigned char>(*from)
  && static_cast<unsigned char>(*from) <=0xFF)
  {
  *to = static_cast<I>(static_cast<unsigned char>(*from) + 0x350);
  }
  else
  {
  typename std::map<E, I>::const_iterator s;
  s = in_tab.lower_bound(*from);
  if(s == in_tab.end())
  {
  // ранее мы удостоверились, что from и next не последние
  from_next = ++from;
  to_next = ++to;
  return error;
  }
  *to = s->second;
  }
  ++to;
  ++from;
  }
  from_next = from_end;
  to_next = to;
  return ok;
  }

  /**@brief Если преобразования не нужны, возвращает true*/
  virtual int do_encoding() const throw()
  {
  return 1;
  }

  /**@brief Если преобразования не нужны, возвращает true*/
  virtual bool do_always_noconv() const throw()
  {
  return false;
  }
  /* не будем усложнять пример
  virtual result do_out(State&,
  const I* from, const I* from_end, const I* &from_next,
  E* to, E* to_end, E* &to_next);
  virtual int do_length(State& s, const E* from, const E* from_end, size_t max) const;
  virtual int do_max_length() const throw();
  */
private:
  // для хранения расширенной части символов кириллицы
  std::map<E, I> in_tab;
  std::map<I, E> out_tab;
};


Примечания

Я не стал захламлять статью лишним кодом — фасет codecvt для cp866 реализуется аналогично, а о ctype я рассказывал ранее, но если кому-то нужен рабочий пример, эти фасеты можно взять на github — git://github.com/hoxnox/cyrillic-facets.git.
И прошу прощения за отсутствие номеров строк — чтобы НЛО «проглотило» топик пришлось сокращать highight кода.

Подробней о UNICODE модно почитать тут
Подробней о фасетах — в книге Страуструпа «Язык программирования C++», третье специальное издание, приложение
Подробней о boost::xpressive тут

Источник: http://realcoding.net
Автор материала: hoxnox
Источник: Кодировки в C++
Категория: Новости | Просмотров: 974 | Добавил: FazaNaka | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]