从二进制文件读取/写入十六进制字节

WDR*_*KKS 1 c++ fstream stl ifstream ofstream

我需要以二进制模式读取文件并将字节作为十六进制值存储在任何STL容器中(最好是std :: list).后来我需要将它们写回文件,也是二进制模式.所以,我宣布,

typedef unsigned char BYTE;

std::ifstream File("File_Name", std::ios::binary);

std::list<BYTE> File_Bytes;
Run Code Online (Sandbox Code Playgroud)

通过所有的搜索,我理解了一些事情.可以使用std :: istream :: read()或std :: istreambuf_iterator进行读取(我可能非常错误.请纠正我.)而read()函数只将char*作为内存中存储字节的参数输入流的大小.

如果我必须将文件中的字节读入BYTE列表并再次使用istream和ostream分别从BYTE列表写入文件,我将如何执行此操作?请为我澄清一下.谢谢.

注意:这实际上是用于霍夫曼编码器/解码器,我需要在程序内部压缩和解压缩,并将解压缩的位写为输出文件.这是为了验证压缩的无损性和程序的正确性.另外,任何人都可以告诉我如何将编码的二进制位写入文件以及编码的Huffman文件具有哪些文件扩展名?非常感谢你.

Mik*_*han 5

正如注释所阐明的那样,您希望将二进制文件的字节加载到某个STL容器中char- 或者更准确地说,uint8_t- 并将这样的容器保存回二进制文件.

有很多方法可以做到这一点,包括你发现的,使用std::basic_istream::readstd::basic_ostream::write,或者std::istream_iteratorstd::ostream_iterator.

后一种方法产生了最简单的代码.该fread/ fwrite方法产生速度最快的代码,但更简单的是什么显然将是你的程序仅仅是序幕和尾声操作更好.

这是一对匹配的模板函数,分别为:

返回参数类型的STL容器Container,填充输入文件的字节序列.

将参数类型的STL容器的元素复制Container到输出文件中的字节序列.

#include <fstream>
#include <iterator>
#include <algorithm>
#include <stdexcept>
#include <cstdint>

template<class Container>
Container binary_load(std::string const & bin_file_name)
{
    std::ifstream in(bin_file_name,std::ios::binary);
    if (!in) {
        throw std::runtime_error("Could not open \"" + bin_file_name + 
            "\" for reading");
    }
    std::noskipws(in); // PON 1
    return Container(std::istream_iterator<std::uint8_t>(in),
                        std::istream_iterator<std::uint8_t>()); //PON 2

}

template<class Container>
void binary_save(Container && data, std::string const & bin_file_name)
{
    std::ofstream out(bin_file_name,std::ios::binary);
    if (!out) {
        throw std::runtime_error("Could not open \"" + bin_file_name + 
            "\" for writing");
    }
    std::copy(data.begin(),data.end(),
        std::ostream_iterator<std::uint8_t>(out,"")); // PON 3  
}
Run Code Online (Sandbox Code Playgroud)

要编译基本用例,请附加:

#include <vector>
#include <string>

using namespace std;

int main(int argc, char *argv[])
{
    string infile = argv[1];
    string outfile = infile + ".saved";
    auto data(binary_load<vector<std::uint8_t>>(infile));
    binary_save(data,outfile);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这可以编译为C++ 11或更高版本.生成的程序将您指定的文件作为其第一个命令行参数加载到a中std::vector<std::uint8_t>,然后将该向量保存到具有附加扩展名的同名文件中.saved.当然,您的程序将加载一个向量并保存另一个向量.

注意事项(PON):

  1. 该语句需要通知流in它应该提取所有字节,而不是跳过空白字节.

  2. 此语句直接构造填充Container[begin,end) 迭代器范围,以每个STL容器的构造方式.该begin迭代器std::istream_iterator<char>(in)启动的流 迭代器inend迭代器std::istream_iterator<char>()最终的流迭代器对每个流.

  3. 该语句将字节序列复制到std::ostream_iterator<char>最初位于开头的连续位置 out.""迭代器的构造函数的参数通知它空字符串(即什么都没有)应该将连续的输出字节分开.

这些函数模板比​​您严格要求的更通用:

  • Container您调用的类型binary_load不必是uint8_t相同大小的容器,甚至也不需要是相同大小的容器.它只需要是一个容器类型,可以从一系列的迭代器范围构造uint8_t.

  • 同样Container,您调用的类型binary_save只需要一个元素属于E可隐式转换为的类型,uint8_t但需要注意的是,如果您无意中选择保存任何E不可表示的内容,则会发生截断uint8_t.

因此,将这些放在一起,不会造成任何伤害,例如,如果您在示例程序中替换vector<uint8_t>vector<long>.

当然,如果您错误地使用不满足模板要求的容器类型调用任一函数模板 Container,则代码将无法编译.

继续OP的评论

我可以使用unsigned char代替[uint8_t]吗?

是的,uint8_t几乎不可避免地unsigned char由您的编译器定义,并且任何8位类型的整数类型都可以.uint8_t最清楚地说是"字节".如果您希望针对"字节"类型进一步参数化模板函数,您可以这样做:

...
#include <type_traits>

template<class Container, typename Byte = std::uint8_t>
Container binary_load(std::string const & bin_file_name) {

    static_assert(sizeof(Byte) == 1,"Size of `Byte` must be 1");

    // `std::uint8_t` becomes `Byte` 
    ...
}

template<class Container, typename Byte = std::uint8_t>
void binary_save(Container && data, std::string const & bin_file_name) {

    static_assert(sizeof(Byte) == 1,"Size of `Byte` must be 1");
    // `std::uint8_t` becomes `Byte` 
    ...
}
Run Code Online (Sandbox Code Playgroud)

关于霍夫曼编码文件的正确文件扩展名,没有事实上的标准.选择你喜欢的.

除非您需要为您的控制台版本使用MS VC10(支持补丁C++ 11),否则无需使用.Bang最新的GCC工具链可免费用于Windows和支持IDE:CodeLite,Code :: Blocks