Chr*_*ner 11 c++ unicode istream-iterator wstring wifstream
在Scott Meyers的"Effective STL"一书中,有一个将整个文本文件读入std :: string对象的好例子:
std::string sData;
/*** Open the file for reading, binary mode ***/
std::ifstream ifFile (“MyFile.txt”, std::ios_base::binary); // Open for input, binary mode
/*** Read in all the data from the file into one string object ***/
sData.assign (std::istreambuf_iterator <char> (ifFile),
std::istreambuf_iterator <char> ());
Run Code Online (Sandbox Code Playgroud)
请注意,它以8字节字符的形式读取.这非常有效.最近虽然我需要读取包含Unicode文本的文件(即每个字符两个字节).但是,当我尝试(天真地)更改它以将数据从Unicode文本文件读取到std :: wstring对象时,如下所示:
std::wstring wsData;
/*** Open the file for reading, binary mode ***/
std::wifstream ifFile (“MyFile.txt”, std::ios_base::binary); // Open for input, binary mode
/*** Read in all the data from the file into one string object ***/
wsData.assign (std::istreambuf_iterator <wchar_t> (ifFile),
std::istreambuf_iterator <wchar_t> ());
Run Code Online (Sandbox Code Playgroud)
我得到的字符串虽然是宽字符,但仍然具有备用空值.例如,如果文件包含Unicode字符串"ABC",则文件的字节(忽略Unicode引导字节0xFF,0xFE)为:<'A'> <0> <'B'> <0> <' C'> <0>
上面的第一个代码片段将正确地导致(char)字符串的以下内容:
sData [0] ='
A'sData [1] = 0x00
sData [2] ='
B'sData [3] = 0x00
sData [4 ] ='
C'sData [5] = 0x00
但是,当运行第二个代码片段时,会不合需要地导致(wchar_t)字符串的以下内容:
wsData [0] = L'
A'wsData [1] = 0x0000
wsData [2] = L'
B'wsData [ 3] = 0x0000
wsData [4] = L'
C'wsData [5] = 0x0000
就好像文件仍在逐字节读取,然后只是简单地翻译成单独的wchar_t字符.
我本以为std :: istreambuf_iterator,专门用于wchar_t,应该导致文件一次读取两个字节,不应该吗?如果没有,那么它的目的是什么呢?
我已经追溯到模板(没有简单的壮举;-),并且迭代器确实似乎仍然逐字节地读取文件并将其传递给其内部转换例程,该例程尽职地说明转换是在每个字节之后完成的(不是只有在收到2个字节后).
我已经搜索了网络上的一些网站(包括这个网站),看似这个看似微不足道的任务,但是没有找到对这种行为的解释,也没有找到一个不需要更多代码而不是我觉得应该是必要的替代方案(例如Google搜索网络产生的第二个代码片段与可行的代码片段相同.
我发现的唯一有用的是以下内容,我认为这是一个骗子,因为它需要直接访问wstring的内部缓冲区,然后在那里输入-cerces.
std::wstring wsData;
/*** Open the file for reading, binary mode ***/
std::wifstream ifFile (“MyFile.txt”, std::ios_base::binary); // Open for input, binary mode
wsData.resize (<Size of file in bytes> / sizeof (wchar_t));
ifFile.read ((char *) &wsData [0], <Size of file in bytes>);
Run Code Online (Sandbox Code Playgroud)
哦,并预防不可避免的"为什么在二进制模式下打开文件,为什么不在文本模式下"问题,打开是有意的,好像文件是在文本模式下打开(默认),这意味着CR/LF("\ r \n"或0x0D0A)序列将被转换为仅LF("\n"或0x0A)序列,而文件的纯字节读取将保留它们.无论如何,对于那些顽固分子来说,改变这一点并不令人惊讶,没有任何影响.
所以这里有两个问题,为什么第二种情况不能像人们预期的那样工作(即,这些迭代器会发生什么),以及你最喜欢的将"加载Unicode字符文件"加入wstring的"kosher STL-way" ?
我在这里想念的是什么; 它必须是愚蠢的东西.
克里斯
Mik*_*han 11
你必须对SO感到失望,因为在4个半月后你的第一个问题没有得到答案.这是一个很好的问题,大多数好问题都会在几分钟内得到解答(好或坏).两个忽视你的原因可能是:
你没有把它标记为"C++",所以很多能够提供帮助的C++程序员都不会注意到它.(我现在已将其标记为"C++".)
你的问题是关于unicode流处理,这是一个很酷的编码的想法.
阻碍您调查的误解似乎是这样的:您似乎认为宽字符流std::wfstream和宽字符串std::wstring分别与"unicode stream"和"unicode string"相同,具体而言它们分别与UTF-16流和UTF-16字符串相同.这些都不是真的.
一个std::wifstream(std::basic_ifstream<wchar_t>)是转换字节的内部序列的外部序列的输入流wchar_t,根据外部序列的指定或默认的编码.
同样,a std::wofstream(std::basic_ofstream<wchar_t>)是一个输出流,它根据wchar_t外部序列的指定或默认编码将内部序列转换为外部字节序列.
而a std::wstring(std::basic_string<wchar_t>)是一种字符串类型,它只是存储一系列的序列wchar_t,而不知道它们产生的编码 - 如果是 - 任何 - .
Unicode是一系列字节序列编码 - UTF-8/-16/-32,还有一些更模糊的其他编码 - 与UTF- N使用每符号一个或多个N位单元序列编码字母
的原则相关.UTF-16显然是你试图读入的编码std::wstring.你说:
我本以为std :: istreambuf_iterator,专门用于wchar_t,应该导致文件一次读取两个字节,不应该吗?如果没有,那么它的目的是什么呢?
但是一旦你知道它wchar_t不一定是2字节宽(它在微软的C库中,32位和64位,但在GCC中是4字节宽),还需要UTF-16代码点(字符)不适合2个字节(它可能需要4个),你会看到指定一个提取单元wchar_t不能全部解码UTF-16流.
使用以下内容构造和打开输入流时:
std::wifstream ifFile ("MyFile.txt", std::ios_base::binary);
Run Code Online (Sandbox Code Playgroud)
它准备从"MyFile.txt"中提取字符(某些字母表)到类型的值,wchar_t它将根据std::locale
流上的操作指定的编码从文件中的字节序列中提取这些字符.提取.
您的代码没有std::locale为您的流指定,因此库的默认值生效.该默认值是全局C++语言环境,默认情况下是
"C"语言环境 ; 并且"C"语言环境假设I/O字节序列的"标识编码",即1字节= 1字符(为文本模式I/O留出换行异常).
因此,当您使用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 VC++.如果那是对的,那么您可以通过以下方式修复代码:
#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);
Run Code Online (Sandbox Code Playgroud)
这个新行的效果是"灌输" 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小端BOM.
请注意顺序FE,FF是什么它是应用前反向codecvt小,显示出我们的要求little-endian的解码已完成.它需要.只需通过删除std::little_endian,再次调试来编辑新行,然后您将发现第一个元素wsData变为0xFFFE
其他三个宽字符成为
IICore象形字符集的象形图(如果您的调试器可以显示它们).(现在,每当一位同事惊讶地发现他们的代码将英文Unicode转换成"中文"时,你就会知道一个可能的解释.)
如果您想要在wsData没有前导BOM 的情况下进行填充,您可以通过再次修改新行并替换std::little_endian为来实现
std::codecvt_mode(std::little_endian|std::consume_header)
最后,你可能已经注意到新代码中的一个错误,即2字节wchar_t
的宽度不足以表示可以读取的0x100000和0x10ffff之间的UTF-16代码点.
只要您必须阅读的所有代码点都位于UTF-16 基本多语言平面(跨越[0,0xffff]),您就会知道所有输入将永远服从该约束.否则,16位wchar_t不适合用途.更换:
wchar_t 同 char32_tstd::wstring 同 std::basic_string<char32_t>std::wifstream 同 std::basic_ifstream<char32_t>并且代码完全适合将abitrary UTF-16编码文件读入字符串.
(正在使用GNU C++库的读者会发现,从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.要解决OP的问题,你需要std::codecvt<wchar_t,char,std::char_traits<wchar_t>::state_type>
自己子类化,按照这个答案)