C ++ двоичный файл ввода / вывода в / из контейнеров (кроме char *) с использованием алгоритмов STL - PullRequest
2 голосов
/ 06 декабря 2009

Я пытаюсь провести простой тест ввода / вывода двоичных файлов, используя алгоритм копирования 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 }

Полученный результат выглядит следующим образом и имеет две проблемы, 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) Данные искажены в третьем десятичном знаке (или ранее), и вводится некоторая произвольная ошибка.

Пожалуйста, любая помощь будет оценена. (Я хотел бы, чтобы кто-то указал мне на предыдущий пост об этом, так как я потерпел неудачу в поиске)

Ответы [ 4 ]

3 голосов
/ 06 декабря 2009

Для записи двоичных данных с использованием 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));
3 голосов
/ 06 декабря 2009

Для вопроса 1) Вам необходимо указать разделитель (например, пробел). Недесятичная часть была прикреплена к десятичной части предыдущего числа. Приведение и использование NULL, как правило, неправильно в C ++. Должен был быть намек;)

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

На вопрос 2)

#include <iomanip>
output << setprecision(9);
1 голос
/ 06 декабря 2009

Вы можете установить точность, используя setprecision, как указал Тристрам, и вам нужен разделитель. См. 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 цифр», поэтому они жестко закодировали точность везде. Это дорогостоящие ошибки, которые нужно исправить.

0 голосов
/ 06 декабря 2009

Я придумал лучший дизайн для двоичного ввода / вывода. Фундаментальный подход состоит в том, чтобы иметь три метода: size&#95;on&#95;stream, load&#95;from&#95;buffer, и store&#95;to&#95;buffer. Они входят в интерфейсный класс, так что все классы, которые поддерживают бинарный ввод / вывод, наследуют его.

Метод size&#95;on&#95;stream возвращает размер данных, передаваемых в потоке. Как правило, это не включает байты заполнения. Это должно быть рекурсивным, чтобы класс вызывал метод для всех своих членов.

Методу load&#95;from&#95;buffer передается ссылка на указатель на буфер ( unsigned char * & ). Метод загружает элементы данных объекта из буфера, увеличивая указатель после каждого элемента (или увеличивая один раз после всех элементов).

Метод store&#95;to&#95;buffer сохраняет данные в заданном буфере и увеличивает указатель.

Клиент вызывает size&#95;on&#95;stream, чтобы определить размер всех данных. Буфер такого размера выделяется динамически. Другой указатель на этот буфер передается в store&#95;to&#95;buffer для сохранения членов объекта в буфере. Наконец, клиент использует двоичную запись (fwrite or std::ostream::write) для передачи буфера в поток.

Некоторые из преимуществ этого метода: упаковка, абстракция и блок ввода / вывода. Объекты упаковывают свои элементы в буфер. Процесс записи в буфер скрыт от клиента. Клиент может использовать функции блочного ввода-вывода, которые всегда более эффективны, чем передача отдельных членов.

Этот дизайн также более переносим, ​​поскольку объекты могут заботиться о Endianess. Для этого есть простой метод, который оставлен на усмотрение читателя.

Я расширил эту концепцию, включив в нее также типы POD (Plain Old Data), что оставлено для читателя в качестве упражнения.

...