gpt4 book ai didi

c++ - 求 istreambuf_iterator 澄清,读取 Unicode 字符的完整文本文件

转载 作者:行者123 更新时间:2023-12-01 21:39:37 25 4
gpt4 key购买 nike

在 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> ());

请注意,它以 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> ());

我返回的字符串虽然是宽字符,但仍然具有备用空值。例如,如果文件包含 Unicode 字符串“ABC”,则文件的字节(忽略 0xFF、0xFE 的 Unicode 前导字节)为:
<'A'> <0> <'B'> <0> <'C'> <0>

上面的第一个代码片段将正确生成 (char) 字符串的以下内容:
sData [0] = ‘A’
数据 [1] = 0x00
数据 [2] = ‘B’
数据 [3] = 0x00
sData [4] = ‘C’
数据 [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 字符。

我原以为专门用于 wchar_t 的 std::istreambuf_iterator 应该导致文件一次读取两个字节,不是吗?如果不是,那它的目的是什么?

我已经追踪到模板(这不是一件容易的事;-),并且迭代器确实似乎仍然在逐字节读取文件并将其传递给其内部转换例程,该例程尽职尽责地声明在每个字节之后完成转换(不是仅在接收到 2 个字节后)。

我已经在网络上搜索了许多网站(包括这个网站)来寻找这个看似微不足道的任务,但没有找到这种行为的解释或不涉及比我认为必要的更多代码的好的替代方案(例如,谷歌网络搜索也会生成相同的第二个代码片段作为可行的代码)。

我发现唯一有效的是以下内容,我认为这是作弊,因为它需要直接访问 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

wsData.resize (<Size of file in bytes> / sizeof (wchar_t));

ifFile.read ((char *) &wsData [0], <Size of file in bytes>);

哦,为了防止不可避免的“为什么以二进制模式打开文件,为什么不以文本模式打开文件”的问题,打开是有意的,好像文件是以文本模式打开的(默认),这意味着 CR/LF ("\r\n"或 0x0D0A) 序列将仅转换为 LF(“\n”或 0x0A)序列,而对文件的纯字节读取会保留它们。无论如何,对于那些顽固的人来说,改变这一点,不出所料,没有任何效果。

所以这里有两个问题,为什么第二种情况不像人们预期的那样工作(即,这些迭代器发生了什么),以及将 Unicode 字 rune 件加载到 wstring 中你最喜欢的“kosher STL 方式”是什么?

我在这里错过了什么;它必须是愚蠢的。

克里斯

最佳答案

您必须对 SO 感到失望,才能在此之后没有收到您的第一个问题的答案
四个半月。这是一个好问题,大多数好问题都得到了回答
(好或坏)在几分钟内。忽视你的两个可能的原因是:

  • 您没有将其标记为“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 ,根据规定
    或外部序列的默认编码。

    同样是 std::wofstream ( std::basic_ofstream<wchar_t> ) 是一个输出流
    转换 wchar_t 的内部序列到外部字节序列,根据
    外部序列的指定或默认编码。

    还有一个 std::wstring ( std::basic_string<wchar_t> ) 是一个字符串类型,它只存储 wchar_t的序列, 不知道编码 - 如果有的话 - 产生它们。

    Unicode 是一系列字节序列编码 - UTF-8/-16/-32,还有一些更模糊的其他编码 -
    与 UTF-N 使用 1 个或多个序列对字母进行编码的原则相关
    每个符号的 N 位单位。 UTF-16 显然是您尝试阅读的编码
    std::wstring .你说:

    I would have thought that the std::istreambuf_iterator, being specialized to wchar_t, should have resulted in the file being read two bytes at a time, shouldn't it? If not, what's its purpose then?



    但是一旦你知道 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" locale ;并且“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

    It's as if the file were still being read byte by byte and then just simply translated into individual wchar_t characters.



    因为这正是正在发生的事情。

    要阻止这种情况发生,您需要在开始从流中提取字符之前做一些事情
    告诉它应该解码 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);

    这条新线的作用是“灌输” ifFile使用相同的新语言环境
    正如它已经拥有的那样 - ifFile.getloc() - 但具有修改的编码器/解码器方面
    - std::codecvt_utf16<wchar_t,0x10ffff,std::little_endian> .此 codecvt方面是
    一种将解码 UTF-16 字符的最大值为 0x10ffff小端 wchar_t值( 0x10ffff 是 UTF-16 代码点的最大值)。

    当您调试到如此修改的代码中时,您现在会发现 wsData只有 4 个宽字符长
    这些字符是:
    0xFEFF, L'A', L'B', L'C'
    正如您所期望的那样,第一个是 UTF-16 小端 BOM。

    注意订单 FE , FF与申请前相反
    codecvt facet,向我们展示了 little-endian 解码是按要求完成的。
    它必须如此。只需通过删除 std::little_endian 来编辑新行,
    再调试一下,就会发现 wsData的第一个元素变成 0xFFFE并且其他三个宽字符成为
    IICore象形的
    字符集(如果您的调试器可以显示它们)。 (现在,每当同事
    惊讶地提示他们的代码正在将英文 Unicode 变成“中文”,
    你会知道一个可能的解释。)

    如果您想填充 wsData没有领先的 BOM,您可以通过
    再次修改新行并替换 std::little_endianstd::codecvt_mode(std::little_endian|std::consume_header)
    最后,您可能已经注意到新代码中的一个错误,即 2 字节 wchar_t宽度不足以表示 0x100000 和 0x10ffff 之间的 UTF-16 代码点
    可以阅读。

    只要您必须阅读的所有代码点都位于
    UTF-16 Basic Multilingual Plane ,
    它跨越 [0,0xffff],你可能知道所有输入将永远遵守
    约束。否则,一个 16 位 wchar_t不适合目的。代替:
  • wchar_tchar32_t
  • std::wstringstd::basic_string<char32_t>
  • std::wifstreamstd::basic_ifstream<char32_t>

  • 并且该代码完全适合将任意的 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>您自己,根据 this answer )

    关于c++ - 求 istreambuf_iterator <wchar_t> 澄清,读取 Unicode 字符的完整文本文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14167611/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com