Re[7]: тысяча третий раз про Юникод
От: Rakafon Украина http://rakafon.blogspot.com/
Дата: 14.10.09 15:52
Оценка: 3 (1)
Здравствуйте, pepsicoca, Вы писали:
P>Тогда скажите, чем отличается std::wofstream от std::ofstream? В Вашей формулировке они не отличаются ничем. Тогда какой смысл иметь std::wofstream, если он ничем не отличатется от std::ofstream?

Смысл в том, чтобы открыть файл с содержанием "Hello World!!!" (в кодировке ASCII), и прочитать эту строчку в std::wstring с помощью std::wofstream, созданного без флага std::ios::binary, чтобы можно было потом отдать её (std::wstring строку) туда, где хотят wchar_t* текст. Т.е. если в софтине вы используете широкие строки, то пользуется std::wstring/std::wofstream/std::wifstream/std::wstringstream, если в софтине вы используете однобайтовые строки, то используете std::string/std::ofstream/std::ifstream/std::stringstream соответственно. При этом и std::ofstream и std::wofstream будет подразумевать, что в файле лежит однобайтовый текст, закодированный в активной системной локали, просто внутри программы они будут оперировать с разными символами char и wchar_t соответственно.

Как вы наверняка знаете, std::basic_ofstream/std::basic_ifstream могут работать в двух режимах: текстовом и бинарном. Это определяется отсутствием или наличием флага std::ios::binary соответственно. Текстовый режим работает по умолчанию. Бинарный включается руками во время конструирования или во время вызова метода open(). Если std::basic_ofstream работает в текстовом режиме, то он вправе изменять переданные строки перед сохранием, например он заменяет символ '\n' на последовательность символов "\r\n" на винде, на символ '\n' на Линуксе и на символ '\r' на маке. То же саме для std::basic_ifstream, только в обратную сторону. Если std::basic_ofstream работает в бинарном режиме, то он просто пишет то что ему дали и всё.

Текстовыми файлами являются файлы которые содержат данные в однобайтовой кодировке, например ASCII. Остальные файлы являются бинарными: то есть для их корректного чтения и правильного представления в памяти необходимы соответствующие алгоритмы. Если в файле хранится юникод-текст в кодировке UTF-8, то в самом начале файла лежит сигнатура, состоящая из байтов EF BB BF, если в кодировке UTF-16BE, то сигнатура — FE FF, если UTF-16LE — FF FE, если UTF-32BE или UTF-32LE, то 00 00 FE FF и FF FE 00 00. Так вот эти сигнатуры "говорят" нам: "мы не просто текстовые файлы, мы файлы с некими данными, хранящимися в определённом формате, и чтобы работать с этими данными, вам понадобятся определённые алгоритмы".

Чтобы прочитать простой текстовый файл программе, использующей char* строки, необходимо:
  • открыть этот файл с помощью std::ifstream
  • возможно, потребуется сделать вызов strm.unsetf(std::ios::skipws), чтобы не поскипать пробелы, табуляции, переходы строк и прочие пустые символы
  • читать оттуда строки и помещать их в char* контейнер, например в std::string

    Чтобы записать простой текстовый файл программе, использующей char* строки, необходимо:
  • открыть этот файл с помощью std::оfstream
  • писать туда строки из char* контейнеров, например из std::string

    Чтобы прочитать простой текстовый файл программе, использующей wchar_t* строки, необходимо:
  • открыть этот файл с помощью std::wifstream
  • возможно, потребуется сделать вызов strm.unsetf(std::ios::skipws), чтобы не поскипать пробелы, табуляции, переходы строк и прочие пустые символы
  • читать оттуда строки и помещать их в wchar_t* контейнер, например в std::wstring

    Чтобы записать простой текстовый файл программе, использующей wchar_t* строки, необходимо:
  • открыть этот файл с помощью std::wоfstream
  • писать туда строки из wchar_t* контейнеров, например из std::wstring

    Чтобы прочитать бинарный файл программе, не важно какие строки использующей, необходимо:
  • открыть этот файл с помощью std::ifstream, указав флаг std::ios::binary
  • hint: при этом не надо использовать std::wifstream для этих целей, потому что каждый байт файла будет помещён в wchar_t переменную, что логически не верно (я не знаю точно таким ли будет поведение std::wifstream, однако предпочитаю и советую использовать для бинарного чтения узкие потоковые классы)
  • не помню, надо ли здесь strm.unsetf(std::ios::skipws) делать :)
  • читать оттуда блоки данных и помещать их в char* контейнер
  • работать с этими данными

    Чтобы записать бинарный файл программе, не важно какие строки использующей, необходимо:
  • открыть этот файл с помощью std::оfstream, указав флаг std::ios::binary
  • hint: при этом не надо использовать std::wоfstream для этих целей, потому что каждое двубайтие будет впихнуто только в один байт при записи в файл, при этом будет происходить потеря старшего байта двубайтия (я не знаю точно таким ли будет поведение std::wоfstream, однако предпочитаю и советую использовать для бинарной записи узкие потоковые классы)
  • записать туда бинарные данные, взятые из char* контейнера

    Чтобы прочитать юникодный текстовый файл программе, использующей char* строки, необходимо:
  • открыть этот файл с помощью std::ifstream
  • узнать кодировку этого файла, используя хранящуююся в начале соответствующую сигнатуру (или если её там нет, то тогда софтина должна "знать" в какой unicode-кодировке ей подсунули текст, например она может спросить это у юзверя)
  • возможно, потребуется сделать вызов strm.unsetf(std::ios::skipws), чтобы не поскипать пробелы, табуляции, переходы строк и прочие пустые символы
  • читать оттуда строки и помещать их в char* контейнер, например в std::vector<[i]char*>[/i]
  • транслировать полученные данные из известной кодировки (UTF-8/UTF-16BE/UTF-16LE/etc.) в текстовую кодировку, с которой работает программа, например CP1251, при этом символы, эквивалента которых нет в целевой кодировке (например CP1251) будут заменены какой-то хернёй; результат поместить в в char* контейнер, например в std::string
  • работать с полученным однобайтовым текстом

    Чтобы записать юникодный текстовый файл программе, использующей char* строки, необходимо:
  • открыть этот файл с помощью std::оfstream
  • транслировать свои данные из кодировки, с которой работает программа, в нужную кодировку (UTF-8/UTF-16BE/UTF-16LE/etc.) и поместить результат в char* контейнер, например std::vector<[i]char*>[/i]
  • записать в начало файла сигнатуру нужной out кодировки (например для UTF-8 -> EF BB BF)
  • писать в файл данные из полученного char* контейнера, в котором хранится юникодный текст

    Чтобы прочитать юникодный текстовый файл программе, использующей wchar_t* строки, необходимо:
  • открыть этот файл с помощью std::ifstream
  • узнать кодировку этого файла, используя хранящуююся в начале соответствующую сигнатуру (или если её там нет, то тогда софтина должна "знать" в какой unicode-кодировке ей подсунули текст, например она может спросить это у юзверя)
  • возможно, потребуется сделать вызов strm.unsetf(std::ios::skipws), чтобы не поскипать пробелы, табуляции, переходы строк и прочие пустые символы
  • читать оттуда строки и помещать их в char* контейнер, например в std::vector<[i]char*>[/i]
  • транслировать полученные данные из известной кодировки (UTF-8/UTF-16BE/UTF-16LE/etc.) в текстовую кодировку, с которой работает программа, например USC-2 на винде позднее Windows NT или UTF-16LE на винде позднее Windows 2000, результат поместить в в wchar_t* контейнер, например в std::wstring
  • работать с полученным двубайтовым текстом

    Чтобы записать юникодный текстовый файл программе, использующей wchar_t* строки, необходимо:
  • открыть этот файл с помощью std::оfstream
  • транслировать свои wchar_t* строки из кодировки, с которой работает программа (например USC-2 на винде позднее Windows NT или UTF-16LE на винде позднее Windows 2000), в нужную кодировку (UTF-8/UTF-16BE/UTF-16LE/etc.) и поместить результат в char* контейнер, например std::vector<[i]char*>[/i]
  • записать в начало файла сигнатуру нужной out кодировки (например для UTF-16LE -> FF FE)
  • писать в файл данные из полученного char* контейнера, в котором хранится юникодный текст

    Для того чтобы и на входе (чтение unicode-текста из файлов), и на выходе (запись unicode-текста в файлы), и внутри алгоритмов программы поддержать не все возможные кодировки Юникода (UTF-8/UTF-16BE/UTF-16LE/etc.), а только лишь одну широкую кодировку (например нативную для винды — UTF-16LE), и при этом пользовать потоковые широкие классы, люди конфигурируют свои широкие потоковые классы с помощью фацетов и локалей, примерно как здесь: standard-file-streams-and-stdlocale_20.html или здесь: read-unicode-text-file-in-c.html. При этом, конечно, теряется вся гибкость работы с Unicode-кодировками, т.е. программа, использующая std::wstring и отконфигурированные std::wofstream и std::wifstream, и работающая на Windows 2000 and later, может писать и читать только в кодировке UTF-16LE, и если ей потребуется записать/почитать текст в другой unicode-кодировке (UTF-8/UTF-16BE/USC-2/UTF-32BE/UTF-32LE/etc.), то ей придётся заморочиться с пунктами, приведенными выше.

    Возврящаясь к нашим баранам. Т.е. к консоли. Если консоль — есть главное устройство ввода/вывода и интеракции с пользователем, как в GNU/Linux например, то ей по статусу положено быть навороченной, писать текст всеми цветами радуги, в локальной кодировке и в кодировках Юникода и пр. Если же консоль — аппендикс операционки, пережиток времён DOS'а, и главным устройством ввода/вывода и интеракции с пользователем является ГУЙ, как в винде например, тогда такой консоли по статусу не положено быть навороченной. Т.е. вы можете представить себе консоль, как простой текстовый файл, данные в котором хранятся в активной в системе локальной однобайтовой кодировке. У винды с русской локализацией (CP1251) консоль работает в кодировке DOS-866? чтобы поддержать всякой досовское старьё видимо. Соответственно для того чтобы std::wcout, который как мы знаем на винде с активной русской локалью CP1251 пишет текст в кодировке CP1251, для того чтобы std::wcout мог внятно по-русски написать в консоль, надо ему подсказать, что данные, которые ему заходят через оператор << в текстовой кодировке CP1251, в целевом файле (т.е. в консоли) должны сидеть в кодировке DOS-866, а не в кодировке CP1251, и это достигается вызовом std::wcout.imbue(std::locale("rus_rus.866"));. Вот в принципе и всё.


  • "Дайте мне возможность выпускать и контролировать деньги в государстве и – мне нет дела до того, кто пишет его законы." (c) Мейер Ансельм Ротшильд , банкир.
    unicode
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.