Форум программистов, компьютерный форум CyberForum.ru
Наши страницы

Открытие бинарного файла для чтения - C++

Войти
Регистрация
Восстановить пароль
Другие темы раздела
C++ Занесение текста с пробелами в файл http://www.cyberforum.ru/cpp-beginners/thread324265.html
Вообщем целью было написать консольный чат. Пока что он работает, но есть одна загвоздка - я не могу его научить заносить в текстовый файл сообщения с пробелами (то есть заносится только первое...
C++ Поиск в файле. Программный продукт предназначен для работы с текстовыми файлами а) Определить, входит в файл заданный пользователем сочетания символов; б) Подсчитать число вхождений в файл заданного... http://www.cyberforum.ru/cpp-beginners/thread324260.html
C++ Наглядное пособие, как не надо составлять задaчи
Сочинено специально, поэтому просьба не ржать по-лошадиному и не спрашивать, где я это откопал. И так: "Сделать класс одиннадцатимерного массива со внутренней реализацией на основе...
C++ Code::blocks
Помогите как включить #include <graphics.h> #include <windows.h> в Code::block - се ,
C++ Ввести с клавиатуры строку, заменить в ней все буква "а" на букву "b" http://www.cyberforum.ru/cpp-beginners/thread324232.html
Здравствуйте) Вот возникла такая проблема.... не умею работать с указателями еще(( Помогите написать задачку: "используя указатели, написать следующую программу. Ввести с клавиатуры строку,...
C++ Ввод целого выражения Здравствуйте. Как научить программу принимать потоки сознания от пользователей вроде "x^2 + 3x - 10 + sin (3x) = 0" для дальнейшей работы с данными? Спасибо. подробнее

Показать сообщение отдельно
ValeryLaptev
Эксперт С++
1041 / 820 / 48
Регистрация: 30.04.2011
Сообщений: 1,659
22.06.2011, 13:44
Цитата Сообщение от Deviaphan Посмотреть сообщение
Я в шоке! Нельзя для бинарных файлов <<, >> использовать. А так хотелось. Пичалька.
Можно. Перегрузите их для своих нужд - и работайте...
Вот небольшой текст по этому поводу:

Ввод/вывод объектов в двоичные файлы
При изучении процедурной библиотеки ввода/вывода мы рассмотрели вопрос о размерах структуры при записи на диск (см. листинг 10.13). Однако «за кадром» остался важный вопрос: влияет ли наличие виртуальных функций на размер выводимого объекта? Кроме того, нужно разобраться, каким образом организовать вывод информации из контейнера во внешний файл. Разберемся сначала с первым вопросом, написав простейший пример (листинг 10.28).
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Листинг 10.28. Вывод объекта с виртуальными функциями
#include <iostream>
#include <fstream>
#include <conio.h>
using namespace std;
class Object                            // класс с виртуальными функциями
{  public:
   virtual void print() { cout << "virtual method!" << endl; }
};
int main(int argc, char* argv[])
{  Object t;                            // объект создан
   cout << sizeof(Object) << endl;      // в памяти занимает 4 байта
   ofstream outstrm ("d:/oonumber1.bin", std::ios::binary);
// выводим объект в файл
   if(outstrm.is_open()) outstrm.write((char *)&t, sizeof(Object));
   outstrm.close();
   return 0;
}
Хотя в классе нет полей, размер объекта в памяти составляет 4 байта. Как известно (см. гл. 5 «Наследование»), эта величина представляет собой размер указателя на таблицу виртуальных функций. На диск записываются те же 4 байта. Однако совершенно очевидно, что этому указателю на диске не место — он реально никуда не показывает. Следовательно, записывать объект в файл, не учитывая его внутреннюю структуру, просто нельзя. Особенно если объект представляет собой не единичную скалярную величину, а динамический контейнер (например, динамический массив TArray, см. листинг 9.1).
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
Листинг 9.1. Интерфейс динамического массива с изменяемым размером
template<typename T> 
class TArray {
public:
  // типы
  typedef T                                     value_type;            
  typedef T*                                    iterator;              
  typedef const T*                              const_iterator;        
  typedef T&                                    reference;             
  typedef const T&                              const_reference;       
  typedef std::size_t                           size_type;             
  // конструкторы/копирование/деструктор
  TArray(const size_type& n = minsize);
  TArray(const TArray<T>& array);
  template <class Iterator> TArray(Iterator first, Iterator last);
  ~TArray() { delete [] elems; elems = 0; }
  Tarray<T>& operator=(const TArray<T>&);
  template<typename U> TArray& operator=(const TArray<U>&);
// итераторы
  iterator begin() { return elems; }
  const_iterator begin() const { return elems; }
  iterator end() { return elems+Count; }
  const_iterator end() const { return elems+Count; }
// размеры
  size_type size() const                // длина массива
  { return Count; }
  bool empty() const                    // есть ли элементы
  { return (Count == 0); }
  size_type capacity() const            // потенциальный размер
  { return Size; }
  void resize(size_type newsize);       // изменить размер
// доступ к элементам
  reference operator[](size_type)
  { rangecheck(i);                      // проверка индекса
    return elems[i]; 
  }
  const_reference operator[](size_type) const 
  { rangecheck(i);                      // проверка индекса
    return elems[i]; 
  }
  reference front() { return elems[0]; }
  const_reference front() const { return elems[0]; }
  reference back() { return elems[size()-1]; }
  const_reference back() const { return elems[size()-1]; }  
// методы-модификаторы
  void push_back(const T& v);
  void pop_back()                        // удалить последний элемент
  { if (!empty()) --Count; 
    else throw std::domain_error("array<>: empty array!");
  }
  void clear() { Count = 0; }           // очистить массив
  void swap(TArray<T>& other)           // обменять с другим массивом
  {  std::swap(elems, v.elems);         // стандартная функция обмена
     std::swap(Size, v.Size);
     std::swap(Count, v.Count);
  }
  void assign(const T& v)               // заполнить массив
  { if (!empty()) 
      for(size_type i = 0; i < Count; ++i) 
          elems[i] = v;
  } 
private:
  static const size_type minsize = 10;  // минимальный размер массива
  size_type Size;                       // выделено элементов в памяти
  size_type Count;                      // количество элементов
  value_type * elems;                   // указатель на данные
// проверка индекса
    void rangecheck (size_type i) 
    { if (i >= size()) 
        throw std::range_error("array<>: index out of range");
    }
};
// обмен – внешняя функция
template<typename T> void swap(TArray<T>&, TArray<T>&)
inline void swap(TArray<T>& x, TArray<T>& y)
{ x.swap(y); }
// сравнения
template<typename T> 
bool operator==(const TArray<T>& x, const TArray<T>& y)
{ if (x.size() == y.size())
  { for(size_type i = 0; i < x.size(); ++i) 
        if (x[i]!=y[i]) return false;
    return true;
  }
  else return false;
}
template<typename T> 
bool operator!=(const TArray<T>& x, const TArray<T>& y)
{ return !(x==y); }
Фактически при выводе объекта на внешний носитель осуществляется его «развертывание» в последовательность байтов, а при вводе происходит обратный процесс — из последовательности байтов конструируется объект. В объектно-ориентированном программировании придумали специальный термин для обозначения этого процесса: сериализация. Сериализация — это обратимый процесс преобразования произвольного набора структур данных С++ в последовательность байтов. Обратимость означает, что сериализованный объект можно снова «собрать» из последовательности байтов.

Никто не знает структуру объекта лучше, чем сам объект. Поэтому, если требуется сохранять объект на диске, можно в классе реализовать два метода: save() и load(). Методы save() и load(), очевидно, должны быть симметричны — процесс сериализации должен быть обратим. Эти методы должны работать с полями объекта. Например, для класса TDate (листинг 10.35)
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Листинг 10.35. Класс TDate, обеспечивающий форматирование дат
class TDate
{   public:
        typedef unsigned int fmtflags;
    // константы-флаги
        static const fmtflags pointDate  = 0x00;
        static const fmtflags intDate    = 0x01;
        static const fmtflags stringDate = 0x02;
    // методы доступа к флагам
        fmtflags getflags() const { return fmt; } 
        void pointdate() { fmt = pointDate; }
        void intdate()   { fmt = intDate; }
        void stringdate(){ fmt = stringDate; }
    // конструкторы даты    
        TDate():date(0) {}
        TDate(unsigned long date):date(date){}
        TDate(unsigned int d, unsigned int m, unsigned int y)
        :date(y*10000+m*100+d) {}
        TDate(unsigned int d, string month, unsigned int y);
        TDate(const TDate &d):date(d.date){}
    // ввод/вывод
        friend istream& operator>>(istream& is, TDate &data);
        friend ostream& operator<<(ostream& os, const TDate &data);
    // манипуляторы
    friend ostream& intdate   (ostream &os) { fmt = intDate; return os; }  
    friend ostream& pointdate (ostream &os) { fmt = pointDate; return os; }
    friend ostream& stringdate(ostream &os) { fmt = stringDate; return os; }  
    private:
        unsigned long date;
        static fmtflags fmt;
        static const string m[12];
};
TDate::fmtflags TDate::fmt;
const string TDate::m[12] = 
{ "янв","фев","мар","апр","май","июн","июл","авг","сен","окт","ноя","дек" };
требуется выводить поля
C++
1
2
unsigned long date;
static fmtflags fmt;
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Листинг 10.36. Форматированный вывод даты
ostream& operator<<(ostream& os, const TDate &data)
{   ostringstream s;                    // строковый поток
    int day   = data.date%100;
    int year  = data.date/10000;
    int month = data.date/100%100;
    switch(TDate::fmt)                  // флаг-переключатель
    { case TDate::pointDate:                // формат dd.mm.yyyy
      s << setfill('0') 
        << setw(2) << day   << '.'
        << setw(2) << month << '.'
        << setw(4) << year;
      break;
    case TDate::intDate:                // формат yyyymmdd
      s << data.date;
      break;
    case TDate::stringDate:             // формат dd-mmm-yyyy
      s << setfill('0') 
        << setw(2) << day << '-'
        << TDate::m[month-1] << '-'
        << setw(4) << year;
      break;
    }
    return os << s.str();
}
Функция, в зависимости от установленного флага, собирает в строковом потоке нужный вид даты, а потом выводит буфер строкового потока в поток-параметр. Все форматы даты выводятся с ведущими нулями. Нужно подключить библиотеку <iomanip>, так как используются манипуляторы с аргументами.

Один из вариантов реализации методов save()/load() представляет собой инструмент для создания «моментального снимка» объекта во внешней памяти, который в дальнейшем можно восстановить. Прототипы методов выглядят просто:
C++
1
2
void save();
void load();
В методе save() нужно определить локальный выходной поток и открыть его как двоичный. Поток, естественно, должен быть связан с файлом, который метод load() через свой локальный поток должен открывать как входной. Поэтому оба метода должны как-то иметь доступ к одному и тому же имени файла. Очевидно, что в качестве файла можно использовать временный файл, который можно создать с помощью функций библиотеки <cstdio> tmpfile() или tmpnam().
Функции метода save() понятны, и написать его несложно. Единственная проблема — передать информацию об имени файла в метод load(). Это проще всего сделать, определив поле-строку в классе в соответствии с требованиями функции tmpnam(). Тогда метод save() будет заносить в это поле сгенерированное имя, а метод load() будет открывать файл с этим именем.

Ввод/вывод скалярных объектов относительно прост, так как поля в классе известны и занимают фиксированное количество байт. Значительно сложнее написать сериализацию динамических контейнеров, ведь требуется выводить не указатели, а значения, записанные в динамической памяти. Например, для динамического массива TArray (листинг 9.1) требуется выводить, как минимум, поле Count и содержимое динамического массива, расположенного по адресу, записанному в elems.

Реализуем методы для класса-шаблона TArray (листинг 10.39).
Если метод save() написать относительно просто, то метод load() требует более пристального внимания. Метод похож на операцию присваивания: нужно создать новый динамический массив, в который скопировать сохраненные данные, а прежний массив уничтожить. Таким образом, метод save() должен сохранить поле Size, поле Count и сами элементы.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Листинг 10.39. Методы save() и load() для TArray
template <class T>
void TArray<T>::save()
{ tmpnam(name);                 // генерация случайного имени
  if(file.is_open())
  { file.write((char*) &Size, sizeof(size_type));  
    file.write((char*) &Count, sizeof(size_type));
    file.write((char*) elems, sizeof(value_type)*Count);
  }
  file.close();
  return;
}
template <class T>
void TArray<T>::load()
{ std::ifstream file(name, std::ios::binary);
  if(file.is_open())
  { file.read((char*) &Size, sizeof(size_type));  
    file.read((char*) &Count, sizeof(size_type));
    value_type *p = elems;      // сохранили для возврата
    elems = new T[Size];        // новый массив
    file.read((char*) elems, sizeof(value_type)*Count);
    delete[]p;
  }
  file.close();
  return;
}
Поле name описано в приватной части класса TArray как
C++
1
char name[13];
Метод save() в начале работы генерирует случайное имя, открывает поток на запись и выводит данные в файл, который тут же и закрывается. Метод load() соответственно открывает поток как входной и читает данные из потока. При этом прежняя память возвращается, а данные из файла записываются в новый динамический массив.
Если нам нужна возможность ввода/вывода объектов в произвольный файл, то нужно передавать методам save()/load() в качестве параметра поток, связанный с этим файлом, например
C++
1
2
void save(ofstream &os);
void load(ifstream &is);
В этом случае методы, естественно, упрощаются, так как не требуется никаких временных файлов, не нужно открывать и закрывать поток. Класс ничего не знает о том, куда реально будет выведен объект, он только обеспечивает правильную сериализацию объекта. Аналогично метод чтения не знает, правильно ли установлена головка чтения/записи в потоке — он просто выполняет чтение и конструирование объекта. Ответственность за правильное обращение с файлом возлагается на программу-клиента, использующую объекты данного класса.

Можно пойти дальше, и реализовать независимую библиотеку сериализации. Одна из библиотек Boost так и называется Serialization — библиотека сериализации объектов. Это сложная профессиональная работа, использующая нетривиальные возможности шаблонов, RTTI и множественное наследование


Вместо save и load - и перегрузите...
Или как внешюю функцию.
1
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru