【问题标题】:How to use std::string to store bytes (unsigned chars) in a right way?如何使用 std::string 以正确的方式存储字节(无符号字符)?
【发布时间】:2019-11-30 16:13:59
【问题描述】:

我正在编写 LZ77 压缩算法,但无法将无符号字符存储在字符串中。为了压缩任何文件,我使用它的二进制表示,然后将其读取为chars(因为 1 char 等于 1 字节,afaik)到std::stringchars 一切正常。但经过一段时间的谷歌搜索后,我了解到char 并不总是 1 个字节,因此我决定将其换成unsigned char。事情开始变得棘手:

  • 压缩纯 .txt 时,一切都按预期工作,我在解压缩前后得到相同的文件(我认为应该如此,因为我们基本上在字节转换前后处理文本)
  • 但是,当尝试压缩 .bmp 时,与输入文件相比,解压缩文件会丢失 3 个字节(尝试将无符号字符保存到 std::string 时会丢失这 3 个字节)

所以,我的问题是 – 有没有办法将无符号字符正确保存到 一个字符串?

我尝试使用 typedef basic_string<unsigned char> ustring 并将所有相关函数换成它们的基本替代品以与 unsigned char 一起使用,但我仍然丢失了 3 个字节。

更新:我发现 3 个字节(符号)丢失不是因为 std::string,但因为std::istream_iterator(我使用 std::istreambuf_iterator) 创建无符号字符串 (因为std::istreambuf_iterator 的参数是字符,不是无符号的 字符)

那么,有什么办法可以解决这个特定的问题吗?

例子:

std::vector<char> tempbuf(std::istreambuf_iterator<char>(file), {}); // reads 112782 symbols

std::vector<char> tempbuf(std::istream_iterator<char>(file), {}); // reads 112779 symbols

示例代码:

void LZ77::readFileUnpacked(std::string& path)

{


std::ifstream file(path, std::ios::in | std::ios::binary);

if (file.is_open())
{
    // Works just fine with char, but loses 3 bytes with unsigned
    std::string tempstring = std::string(std::istreambuf_iterator<char>(file), {});
    file.close();
}
else
    throw std::ios_base::failure("Failed to open the file");
}

【问题讨论】:

  • char 何时不是 1 个字节?
  • 为什么不是 std::vector?
  • 我认为您正在寻找的是 std::vector。我不确定标准是否保证了对 uint8_t 的支持,但如果是,它将恰好有 8 位。
  • 您不想使用std::string - 在许多地方都假定字符串是空终止的,这不是您想要的。使用std::vector。无需担心char 不是 8 位。这样的系统很少见,而且除了您的小型存档代码之外,肯定不会有其他东西可以在上面工作。
  • @asymmetriq "我读到在某些平台上 char 不能等于 1 字节(因为标准显然没有定义它的确切大小)" - 标准明确假设char 的大小为一个字节(sizeof(char) 始终返回 1)。它没有说的是一个字节有多大。是的,有些平台(尽管现在很少见)一个字节的大小不是 8 位。请参阅CHAR_BIT 了解给定平台上的实际大小

标签: c++ lz77


【解决方案1】:

所有形式的char(以及std::byte,与unsigned char 同构)始终是系统支持的最小可能类型。 C++ 标准定义 sizeof(char) 及其变体应始终为 1。

“一”是什么?这是实现定义的。但是系统中的每种类型的大小都是sizeof(char) 的倍数。

所以你不应该太担心char 不是一个字节的系统。如果您在CHAR_BITS 不是 8 的系统下工作,那么该系统根本无法直接处理 8 位字节。所以unsigned char 在这个目的上不会有任何不同/更好。


关于你的问题的细节,istream_iteratoristreambuf_iterator 迭代器根本不同。后者的目的是允许迭代器访问作为值序列的实际流。 istream_iterator&lt;T&gt; 的目的是允许访问流,就像通过使用 T 值执行重复的 operator &gt;&gt; 调用序列一样。

因此,如果您正在执行istream_iterator&lt;char&gt;,那么您就是说您想要读取流,就好像您为每个迭代器访问执行了stream &gt;&gt; some_char; 变量一样。直接访问流的字符实际上并不是同构的。具体来说,FormattedInputFunctions likeoperator&gt;&gt; 可以执行诸如跳过空格之类的操作,具体取决于您设置流的方式。

【讨论】:

  • 澄清一点... C++17 的第 4.4 节(IIRC 这是该标准的新内容)说“C++ 内存模型中的基本存储单元是字节。一个字节位于最小到足以包含基本执行字符集 (5.3) 的任何成员和 Unicode UTF-8 编码形式的八位代码单元,并且由连续的位序列组成,其数量由实现定义。”所以我认为,从 C++17 开始,CHAR_BITS 必须至少为 8。
  • @UriRaz,CHAR_BITS 自 C90 以来必须至少为 8。
  • @UriRaz -- CHAR_BITS 始终必须至少为 8(在 C 标准的库部分中查找其定义)。但它可以(有时是)超过 8 个。在一些(古老的)系统上,最小的可寻址存储单元是 9 位宽(所以CHAR_BIT 是 9),整数是 36 位。在一些现代系统(特别是 DSP)上,最小的可寻址存储单元是 32 位宽,所以 CHAR_BIT 是 32。
【解决方案2】:

istream_iterator 正在使用operator&gt;&gt; 读取,这通常会跳过空格作为其功能的一部分。如果您想禁用该行为,则必须这样做

#include <ios>

file >> std::noskipws;

【讨论】:

    猜你喜欢
    • 2016-05-21
    • 1970-01-01
    • 1970-01-01
    • 2020-08-09
    • 2013-12-05
    • 1970-01-01
    • 1970-01-01
    • 2019-05-29
    • 1970-01-01
    相关资源
    最近更新 更多