使用STL算法的C ++二进制文件I / O到/来自容器(除了char *之外)

时间:2009-12-06 15:36:57

标签: c++ stl binary iterator io

我正在尝试使用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个小数位(或更早)被破坏,并且引入了一些任意错误。

请任何帮助将不胜感激。 (我希望有人能够指出我之前关于此事的帖子,因为我在搜索中做得很短)

4 个答案:

答案 0 :(得分:3)

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

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

对于问题2)

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

答案 1 :(得分:3)

使用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));

答案 2 :(得分:1)

您可以使用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位数”,所以他们硬编码精确到处都是。这些都是必须纠正的代价高昂的错误。

答案 3 :(得分:-1)

我已经为二进制I / O提出了更好的设计。基本方法是有三种方法:size_on_stream, load_from_buffer, store_to_buffer。这些进入接口类,以便所有支持二进制I / O的类继承它。

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

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

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

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

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

这种设计也更加便携,因为对象可以处理Endianess。有一种简单的方法,这取决于读者。

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