Ответ 1
Вы должны связаться с SO, чтобы не получить ответы на свой первый вопрос после 4-и с половиной месяцев. Это хороший вопрос, и ответы на самые полезные вопросы (ну или плохо) в течение нескольких минут. Две вероятные причины для вашего пренебрежения:
-
Вы не отметили его как "С++", поэтому многие программисты на C++, которые могли бы помочь, никогда не будут заметил это. (Я теперь отметил его "С++".)
-
Ваш вопрос касается обработки потока unicode, что является некой идеей крутого кодирования.
Заблуждение, которое сорвало ваши исследования, похоже на это: вы, кажется,
считают, что широкосимвольный поток, std::wfstream
и широкоформатная строка, std::wstring
,
являются соответственно такими же, как "поток Unicode" и "строка Unicode", и, в частности, что
они, соответственно, совпадают с потоком UTF-16 и строкой UTF-16. Ни одна из этих вещей не верна.
An std::wifstream
(std::basic_ifstream<wchar_t>
) - входной поток, который преобразует
внешняя последовательность байтов во внутреннюю последовательность wchar_t
, в соответствии с заданной
или стандартное кодирование внешней последовательности.
Аналогично std::wofstream
(std::basic_ofstream<wchar_t>
) - выходной поток, который
преобразует внутреннюю последовательность wchar_t
во внешнюю последовательность байтов, согласно
указанное или стандартное кодирование внешней последовательности.
И std::wstring
(std::basic_string<wchar_t>
) - это строковый тип, который просто хранит
последовательность wchar_t
, без знания кодировки - if-any, из которой они привели.
Unicode - это семейство кодировок с байтовой последовательностью - UTF-8/-16/-32 и еще несколько неясных других -
связанный по принципу, что UTF-N кодирует алфавиты, используя последовательность из 1 или более
N-битовые единицы на символ. UTF-16, по-видимому, является кодировкой, которую вы пытаетесь прочитать
в std::wstring
. Вы говорите:
Я бы подумал, что std:: istreambuf_iterator, специализирующийся на wchar_t, должен был привести к тому, что файл читался два байта за раз, не так ли? Если нет, то какова его цель?
Но как только вы узнаете, что wchar_t
не обязательно 2 байта (это в библиотеках Microsoft C,
как 32, так и 64-битные, но в GCC - 4 байта), а также что кодовая точка UTF-16 (символ)
не нужно вписываться в 2 байта (для этого может потребоваться 4), вы увидите, что указание выделения
единица wchar_t
не может быть все, что нужно для декодирования потока UTF-16.
Когда вы создаете и открываете свой входной поток:
std::wifstream ifFile ("MyFile.txt", std::ios_base::binary);
Готово извлечь символы (из некоторого алфавита) из "MyFile.txt" в значения
типа wchar_t
, и он будет извлекать эти символы из байтовой последовательности в
файл в соответствии с кодировкой, указанной std::locale
который работает в потоке, когда он выполняет извлечение.
В вашем коде не указывается std::locale
для вашего потока, поэтому значение по умолчанию вступает в силу.
Это значение по умолчанию - это глобальная локаль С++, которая по умолчанию является
"C" локаль; и язык "C" предполагает
"кодирование идентичности" последовательностей байтов ввода-вывода, то есть 1 байт = 1 символ (
исключая исключение новой строки для ввода/вывода текстового режима).
Таким образом, когда вы используете std::istreambuf_iterator<wchar_t>
для
извлечение символов, извлечение продолжается путем преобразования каждого байта
в файле wchar_t
, который он добавляет к std::wstring wsData
. Байты
в файле, как вы говорите:
0xFF, 0xFE, 'A', 0x00, 'B', 0x00, 'C', 0x00
Первые два, которые вы опускаете как "младшие байты Unicode", действительно являются UTF-16 (BOM), но в кодировке по умолчанию они просто являются тем, чем они являются.
Соответственно, широкие символы, присвоенные wsData
, как вы заметили:
0x00FF, 0x00FE, L'A ', 0x0000, L'B', 0x0000, L'C ', 0x0000
Это как если бы файл все еще читался байтом байтом, а затем просто переводился в отдельные символы wchar_t.
потому что это именно то, что происходит.
Чтобы остановить это, вам нужно что-то сделать, прежде чем вы начнете извлекать символы из потока
чтобы сказать, что он должен декодировать последовательность символов UTF-16. Способ сделать это
концептуально довольно извилистая. Вам нужно imbue
поток с std::locale
, который обладает
std::locale::facet
, который является экземпляром
std::codecvt<InternT, ExternT, StateT>
(или получен из таких)
который предоставит потоку правильные методы из декодирования UTF-16 в wchar_t
.
Но суть в том, что вам нужно подключить нужный кодировщик/декодер UTF-16 в поток и на практике это (или должно быть) достаточно просто. Я предполагаю, что ваш компилятор - это недавний MS VС++. Если это право, то вы можете исправить свой код:
- Добавление
#include <locale>
и#include <codecvt>
в заголовки -
Добавление строки:
ifFile.imbue(std::locale(ifFile.getloc(),new std::codecvt_utf16<wchar_t,0x10ffff,std::little_endian>));
сразу после:
std::wifstream ifFile ("MyFile.txt", std::ios_base::binary);
Эффект этой новой строки заключается в том, чтобы "imbue" ifFile
создать новую локаль, которая является той же
как тот, который у него уже был - ifFile.getloc()
- но с измененной фазой кодировщика/декодера
- std::codecvt_utf16<wchar_t,0x10ffff,std::little_endian>
. Этот фасет codecvt
который будет декодировать символы UTF-16 с максимальным значением 0x10ffff
в little-endian
wchar_t
значения (0x10ffff
являются максимальным значением кодовых точек UTF-16).
Когда вы отлаживаетесь в исправленный таким образом код, вы теперь обнаружите, что wsData
имеет только 4 широкоформатных символа
и что эти символы:
0xFEFF, L'A', L'B', L'C'
как вы ожидаете от них, причем первый из них представляет собой спецификацию UTF-16 little-endian.
Обратите внимание, что порядок FE
, FF
является обратным тому, что было до применения
факела codecvt
, показывая нам, что декодирование с малым порядком было выполнено по запросу.
И это должно было быть. Просто отредактируйте новую строку, удалив std::little_endian
,
снова отлаживаем его, и вы обнаружите, что первый элемент wsData
становится 0xFFFE
и что другие три широких символа становятся пиктограммами
IICore пиктографический
набор символов (если ваш отладчик может отображать их). (Теперь, когда коллега
жалуется, что их код превращает английский Unicode в "китайский",
вы узнаете вероятное объяснение.)
Если вы хотите заполнить wsData
без ведущей спецификации, вы можете сделать это
снова изменить новую строку и заменить std::little_endian
на
std::codecvt_mode(std::little_endian|std::consume_header)
Наконец, вы, возможно, заметили ошибку в новом коде, а именно, что 2 байта wchar_t
недостаточно широко для представления кодовых точек UTF-16 между 0x100000 и 0x10ffff
который можно прочитать.
Вам это не удастся, пока все кодовые коды, которые вы должны прочитать, лежат в
UTF-16 Основной многоязычный план,
который охватывает [0,0xffff], и вы можете знать, что все входы будут вечно подчиняться этому
ограничение. В противном случае 16-разрядный wchar_t
не подходит для цели. Заменить:
-
wchar_t
с помощьюchar32_t
-
std::wstring
сstd::basic_string<char32_t>
-
std::wifstream
сstd::basic_ifstream<char32_t>
и код полностью подходит для чтения произвольного кодированного UTF-16 файла в строку.
(Читатели, работающие с библиотекой GNU С++, найдут, что с v4.7.2
он еще не предоставляет стандартный заголовок <codecvt>
. Заголовок <bits/codecvt.h>
существует и, предположительно, когда-нибудь будет выпускником, чтобы быть <codecvt>
, но на данный момент он только
экспортирует специализации class codecvt<char, char, mbstate_t>
и
class codecvt<wchar_t, char, mbstate_t>
, которые являются соответственно тождеством
преобразования и преобразования между ASCII/UTF-8 и wchar_t
. Чтобы решить проблему ОП
вам потребуется подкласс std::codecvt<wchar_t,char,std::char_traits<wchar_t>::state_type>
себя, как этот ответ)