C++ C++;使用STL算法向/从容器(除char*)进行二进制文件I/O

C++ C++;使用STL算法向/从容器(除char*)进行二进制文件I/O,c++,stl,binary,iterator,io,C++,Stl,Binary,Iterator,Io,我正在尝试一个简单的二进制文件I/O测试,使用STL复制算法将数据复制到容器和二进制文件中。见下文: 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_i

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

 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 }
1) 从二进制数据中读取的每个
double
都缺少小数点前的信息。 2) 数据在小数点后第三位(或更早)处被损坏,并且引入了一些任意错误

如果您有任何帮助,我们将不胜感激。(我希望有人能给我指出之前关于这一点的帖子,因为我在搜索中遇到了一些问题)

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

关于问题2)

#包括

输出您可以使用Tristram指出的
setprecision
设置精度,并且您是否需要分隔符。请参阅以查看
运算符=
如何工作。没有设置格式,因此需要在输出时设置格式:

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

output.flags(ios_base::fixed)//或输出以使用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;
};
模板
结构oi\u t:公共迭代器
{
oi_t(标准::奥斯特雷姆和str)
:m_str(str)
{}
oi_t&operator++(){return*this;}//增量没有任何作用。
oi_t&运算符++(int){return*this;}
oi_t&operator*(){return*this;}//Dereference返回对此的引用
//所以当任务完成后,我们
//实际编写这个类中的数据
oi_t和运算符=(t常量和数据)
{
//以二进制格式写入数据
m_str.write(重新解释强制转换和数据),sizeof(T));
归还*这个;
}
私人:
std::奥斯特拉姆&穆街;
};
因此,对std::copy的调用是:

copy (vd.begin(), vd.end(), oi_t<double>(output));
复制(vd.begin()、vd.end()、oi_t(输出));
输入迭代器稍微复杂一些,因为我们必须测试流的结尾

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:公共迭代器
{
ii_t(标准::istream和str)
:m_str(&str)
{}
二()
:m_str(空)
{}
ii_t&operator++(){return*this;}//增量什么也不做。
ii_t&operator++(int){return*this;}
T&运算符*()
{
//在取消引用时,我们实际上将数据读入本地/////静态////
//因此,我们可以返回一个引用
静态T结果;
m_str->read(重新解释强制转换和结果),sizeof(T));
返回结果;
}
//如果任一迭代器具有空指针,则它是流迭代器的end()。
//输入迭代器只有在读取超过流末尾时才相等。
布尔运算符!=(ii_t const&rhs)
{
bool lhsPastEnd=(m_str==NULL)|(!m_str->good());
bool rhsPastEnd=(rhs.m_str==NULL)| |(!rhs.m_str->good());
返回!(左侧和右侧);
} 
私人:
std::istream*m_街;
};
读取输入的调用现在是:

ii_t<double> ii(input);
copy (ii, ii_t<double>(), back_inserter(vi));
ii_t ii(输入);
复制(ii,ii_t(),背面插入器(vi));

我提出了一种更好的二进制I/O设计。基本方法是有三种方法:
在流上设置大小、从缓冲区加载、以及
将缓冲区存储到缓冲区。它们进入一个接口类,以便所有支持二进制I/O的类都继承它

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

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

store\u to\u buffer
方法将数据存储到给定的缓冲区并递增指针。

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

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

这种设计也更具可移植性,因为对象可以处理持久性。有一个简单的方法,这是留给读者。

我已经扩展了这个概念,将POD(普通旧数据)类型也纳入其中,这是留给读者的练习。

只是好奇-为什么ostream\u迭代器定义中需要分隔符?每次使用oi指定一个空格时,一个字节是否真的被用完了?我们不能不使用额外的字节,只读取与double对应的正确字节数并放弃分隔符吗?事实上,您的文件不是您期望的二进制文件。只需在文本编辑器中打开temp.bin文件。我不认为你可以使用流操纵器来以二进制格式存储数据。是的,我注意到它们不是以二进制格式存储的。从STL容器输出二进制数据的惯用方法是什么?@TristramGräbener对于使用用户定义的类变量而不是list
operator*
的列表,上述代码是否会保持不变,
operator++
可能会返回一个引用而不是副本,而不是复制迭代器
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));