【问题标题】:C++ binary file I/O to/from containers (other than char *) using STL algorithms使用 STL 算法与容器(char * 除外)进行 C++ 二进制文件 I/O
【发布时间】:2010-12-23 18:21:38
【问题描述】:

我正在尝试使用 STL 复制算法对二进制文件 I/O 进行简单测试,以将数据复制到容器和二进制文件中。见下文:

 1 #include <iostream>
 2 #include <iterator>
 3 #include <fstream>
 4 #include <vector>
 5 #include <algorithm>
 6 
 7 using namespace std;
 8
 9 typedef std::ostream_iterator<double> oi_t;
10 typedef std::istream_iterator<double> ii_t;
11 
12 int main () {
13
14   // generate some data to test
15   std::vector<double> vd;
16   for (int i = 0; i < 20; i++)
17   {
18     double d = rand() / 1000000.0;
19     vd.push_back(d);
20   }
21 
22   // perform output to a binary file
23   ofstream output ("temp.bin", ios::binary);
24   copy (vd.begin(), vd.end(), oi_t(output, (char *)NULL));
25   output.close();
26 
27   // input from the binary file to a container
28   std::vector<double> vi;
29   ifstream input ("temp.bin", ios::binary);
30   ii_t ii(input);
31   copy (ii, ii_t(), back_inserter(vi));
32   input.close();
33 
34   // output data to screen to verify/compare the results
35   for (int i = 0; i < vd.size(); i++)
36     printf ("%8.4f  %8.4f\n", vd[i], vi[i]);
37 
38   printf ("vd.size() = %d\tvi.size() = %d\n", vd.size(), vi.size());
39   return 0;
40 }

结果输出如下,有两个问题,afaik:

1804.2894  1804.2985
846.9309    0.9312
1681.6928    0.6917
1714.6369    0.6420
1957.7478    0.7542
424.2383    0.2387
719.8854    0.8852
1649.7605    0.7660
596.5166    0.5171
1189.6414    0.6410
1025.2024    0.2135
1350.4900    0.4978
783.3687    0.3691
1102.5201    0.5220
2044.8978    0.9197
1967.5139    0.5114
1365.1805    0.1815
1540.3834    0.3830
304.0892    0.0891
1303.4557    0.4600
vd.size() = 20  vi.size() = 20

1) 从二进制数据中读取的每个double 都缺少小数位前的信息。 2) 数据在小数点后第 3 位(或更早)被破坏,并且引入了一些任意错误。

如有任何帮助,我们将不胜感激。 (我希望有人能指点我之前关于此的帖子,因为我在搜索中遇到了不足)

【问题讨论】:

    标签: c++ stl binary iterator io


    【解决方案1】:

    对于问题1)您需要指定一个分隔符(例如空格)。非小数部分卡在前一个数字的小数部分。在 C++ 中强制转换和使用 NULL 通常是错误的。应该是一个提示;)

    copy (vd.begin(), vd.end(), oi_t(output, " ")); 
    

    对于问题 2)

    #include <iomanip>
    output << setprecision(9);
    

    【讨论】:

    • 只是好奇 - 为什么在 ostream_iterator 定义中需要分隔符?每次使用 oi 指定空白空间时,是否真的会用完一个字节?难道我们不能没有那个额外的字节,只读取与双精度相对应的正确字节数并放弃分隔符吗?
    • 实际上,您的文件不是您期望的二进制文件。只需在文本编辑器中打开 temp.bin 文件。我不认为你可以使用流操纵器来以二进制格式存储数据。
    • 是的,我注意到它们没有以二进制格式存储。从 STL 容器输出二进制数据的惯用方式是什么?
    • @TristramGräbener 对于具有用户定义的类变量而不是列表的列表,上述代码是否保持不变
    【解决方案2】:

    使用 std::copy() 写入二进制数据。
    我会这样做:

    template<typename T>
    struct oi_t: public iterator<output_iterator_tag, void, void, void, void>
    {
      oi_t(std::ostream& str)
        :m_str(str)
      {}
      oi_t& operator++()   {return *this;}  // increment does not do anything.
      oi_t& operator++(int){return *this;}
      oi_t& operator*()    {return *this;}  // Dereference returns a reference to this
                                           // So that when the assignment is done we
                                           // actually write the data from this class
      oi_t& operator=(T const& data)
      {
        // Write the data in a binary format
        m_str.write(reinterpret_cast<char const*>(&data),sizeof(T));
        return *this;
      }
    
      private:
        std::ostream&   m_str;
    };
    

    因此对 std::copy 的调用是:

    copy (vd.begin(), vd.end(), oi_t<double>(output));
    

    输入迭代器稍微复杂一些,因为我们必须测试流的结束。

    template<typename T>
    struct ii_t: public iterator<input_iterator_tag, void, void, void, void>
    {
      ii_t(std::istream& str)
        :m_str(&str)
      {}
      ii_t()
        :m_str(NULL)
      {}
      ii_t& operator++()   {return *this;}  // increment does nothing.
      ii_t& operator++(int){return *this;}
      T& operator*()
      {
        // On the de-reference we actuall read the data into a local //// static ////
        // Thus we can return a reference
        static T result;
        m_str->read(reinterpret_cast<char*>(&result),sizeof(T));
        return result;
      }
      // If either iterator has a NULL pointer then it is the end() of stream iterator.
      // Input iterators are only equal if they have read past the end of stream.
      bool operator!=(ii_t const& rhs)
      {
          bool lhsPastEnd  = (m_str == NULL)     || (!m_str->good());
          bool rhsPastEnd  = (rhs.m_str == NULL) || (!rhs.m_str->good());
    
          return !(lhsPastEnd && rhsPastEnd);
      } 
    
      private:
        std::istream*   m_str;
    };
    

    读取输入的调用现在是:

    ii_t<double> ii(input);
    copy (ii, ii_t<double>(), back_inserter(vi));
    

    【讨论】:

    • operator*operator++ 可能应该返回引用而不是副本,并不是说复制迭代器很昂贵
    • @Tux-D bool lhsPastEnd == bool lhsPastEnd = ?
    • @suresh:谢谢。 2 年后我已经修好了。
    • @LokiAstari 嗨,你能看看这个问题吗:stackoverflow.com/questions/8003532/…
    • @Loki Astari:如果有机会,请查看stackoverflow.com/questions/8004456/…,了解ii_t 的读取过去EOF 问题。
    【解决方案3】:

    您可以使用setprecision 来设置精度,正如 Tristram 指出的那样,您是否需要分隔符。请参阅 cppreference 以了解 operator= 的功能。没有设置格式,因此您需要在输出时设置它:

    ofstream output ("temp.bin", ios::binary);
    output.flags(ios_base::fixed);  //or output << fixed;
    copy(vd.begin(), vd.end(), oi_t(output, " "));
    output.close();
    

    我倾向于使用fixed 来消除精度问题。在很多情况下,有人认为“我们永远不需要超过 5 位数字”,所以他们在任何地方都对精度进行了硬编码。这些是必须纠正的代价高昂的错误。

    【讨论】:

      【解决方案4】:

      我想出了一个更好的二进制 I/O 设计。基本方法是拥有三种方法:size_on_stream, load_from_buffer, store_to_buffer。它们进入一个接口类,以便所有支持二进制 I/O 的类都继承它。

      size_on_stream 方法返回流上传输的数据大小。通常,这不包括填充字节。这应该是递归的,以便类调用其所有成员的方法。

      load_from_buffer 方法传递了一个指向缓冲区指针的引用 ( unsigned char * &amp; )。该方法从缓冲区加载对象的数据成员,在每个成员之后递增指针(或在所有成员之后递增一次)。

      store_to_buffer 方法将数据存储到给定的缓冲区中并递增指针。

      客户端调用size_on_stream 来确定所有数据的大小。这种大小的缓冲区是动态分配的。另一个指向该缓冲区的指针被传递给store_to_buffer 以将对象的成员存储到缓冲区中。最后,客户端使用二进制写入(fwrite or std::ostream::write) 将缓冲区传输到流。

      这种技术的一些好处是:打包、抽象和块 I/O。对象将其成员打包到缓冲区中。写入缓冲区的过程对客户端是隐藏的。客户端可以使用块 I/O 功能,这些功能总是比传输单个成员更有效。

      这种设计也更便携,因为对象可以照顾 Endianess。有一个简单的方法,这留给读者。

      我已经扩展了这个概念以包含 POD(普通旧数据)类型,这留给读者作为练习。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-03-12
        • 2010-11-01
        • 2011-05-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多