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

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

Восстановить пароль Регистрация
 
Рейтинг: Рейтинг темы: голосов - 155, средняя оценка - 4.63
AnDron45
1 / 1 / 0
Регистрация: 11.03.2011
Сообщений: 35
22.06.2011, 11:49     Открытие бинарного файла для чтения #1
MS VS 2010

Есть функция, ей передаётся имя бинарного файла для чтения:

C++
1
2
3
4
5
6
7
8
float detect(string name)
{
    int t;
    
    ofstream ff(name,ios::binary|ios::in);
    ff.open(name,ios::binary|ios::in);
    ff.read((char*)&t, sizeof(int));
}
Проблема в том, что read отсутствует в ofstream, но есть в fstream. Но fstream затирает файл.
Лучшие ответы (1)
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
22.06.2011, 12:07     Открытие бинарного файла для чтения #2
Логично. Для чтения нужно использовать ifstream.
AnDron45
1 / 1 / 0
Регистрация: 11.03.2011
Сообщений: 35
22.06.2011, 12:40  [ТС]     Открытие бинарного файла для чтения #3
Поправил, но теперь обнаружил, что файл не открывается



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
int c,i,n=5;
string name="numbers.dat";
 
float detect(string name)
{
    int t;
    ifstream ff(name,ios::binary|ios::in);
    ff.open(name,ios::binary|ios::in);
    if (!ff) cout<<"error";
}
 
 
void main()
{
    srand(time(0));
    fstream f(name,ios::binary|ios::out);
    
    for (i=0;i<n;i++) //заполняю файл
    {
        c=rand()%20;
        f.write((char*)&c, sizeof(int));
    }
    f.close();
 
    f.open(name,ios::binary|ios::in);
    for (i=0;i<n;i++)  //вывожу файл на экран
    {
        f.read((char*)&c, sizeof(int));
        cout<<c<<' ';
    }
    cout<<'\n';
    f.close();
 
    detect(name);
    getch();
}
9 строка выдаёт error
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
22.06.2011, 12:44     Открытие бинарного файла для чтения #4
Удали строку 8.

Добавлено через 1 минуту
Вместо f.write((char*)&c, sizeof(int));
можно просто
f << c;

Добавлено через 1 минуту
И вместо f.read((char*)&c, sizeof(int));
можно
f >> c;
ValeryLaptev
Эксперт C++
1005 / 784 / 46
Регистрация: 30.04.2011
Сообщений: 1,595
22.06.2011, 12:52     Открытие бинарного файла для чтения #5
Сообщение было отмечено автором темы, экспертом или модератором как ответ
О Боже! Когда ж вы книжки-то читать начнете!!!!

Обычно различают текстовые и двоичные файлы. Текстовые файлы состоят из строк, которые завершаются символом «конец строки». В программе на С++ этот символ обозначается как ‘\n’.

ПРИМЕЧАНИЕ
В системе Windows строки в текстовом файле завершаются комбинацией двух байтов 0x0D0A, поэтому при операциях ввода/вывода система выполняет преобразование.

Обычно операции обмена с текстовым файлом сопровождаются преобразованием информации аналогично тому, как это происходит для стандартных потоков. Двоичные файлы не разбиваются на строки, и никаких преобразований при обмене не выполняется. Это, во-первых, означает, что операции обмена для двоичных файлов выполняются быстрее. А во-вторых, при операции записи в двоичный файл попадает ровно столько байтов, сколько записываемый объект занимает в памяти. Например, целое число, записанное в двоичный файл, займет на диске sizeof(int) байтов. Это существенно отличается от записи в текстовый файл, где количество записываемых по умолчанию символов зависит от величины числа. Например, число 12 в текстовом файле займет 2 или 3 байта (в зависимости от того, выводится ли число со знаком или без него), а 123456 — 6 или 7 байтов. А при явном указании в спецификаторе формата ширины поля — еще больше!
Примером двоичного файла является файл выполняемой программы (с расширением .exe).

Двоичные файлы

Вывод в двоичные файлы обычно выполняется методом write(), который мы уже рассматривали выше при выводе символов и строк. Все же обычно с его помощью выводят как раз не символы, а данные других типов. Метод имеет прототип
C++
1
ostream& write(const char *str, streamsize count);
Метод записывает count символов символьного массива str в поток данных. Тип streamsize [1-27.4.1] обычно представляет собой знаковую версию size_t. Никакие символы-ограничители не влияют на вывод. Возвращает ссылку на поток, поэтому после операции можно проверить состояние потока.
Метод делает то же самое, что и функция fwrite() библиотеки <cstdio>. Используя преобразование указателей, можно вывести в выходной двоичный поток значение переменной любого типа, например
C++
1
2
3
4
int i = 5;
to.write((char *)&i, sizeof(i));
TMoney d(200.56);
to.write(reinterpret_cast<char *>(&d), sizeof(TMoney));
Можно вывести и массив, например
C++
1
2
long t[10];
to.write((char *)&t[0], sizeof(t));
Ввод из двоичных файловых потоков делается методом read(), который имеет такой же прототип
C++
1
istream& read(char *str, streamsize count);
Метод читает count символов в символьный массив str. Размер символьного массива должен быть достаточен, чтобы вместить count символов. Возвращает ссылку на поток, поэтому после операции можно проверить состояние потока. Никакие символы-разделители и символы-завершители не влияют на ввод. Если обнаружен конец файла, то устанавливаются флаги eofbit и failbit.
Метод работает так же, как и соответствующая функция fread() из библиотеки <cstdio>. Используя преобразование указателей, можно ввести из входного двоичного потока значение переменной любого типа, например
C++
1
2
3
4
5
int ii; TMoney dd;
from.read((char *)&ii, sizeof(ii));
from.read(reinterpret_cast<char *>(&dd), sizeof(TMoney));
long t[10];
from.read((char *)&t[0], sizeof(t));
Существует еще один метод ввода, имеющий прототип [1-27.6.1.3]
C++
1
streamsize readsome(char *str, streamsize count);
Метод работает аналогично методу read(), но возвращает не ссылку на поток, а количество введенных символов.
Теперь легко переписать примеры (см. листинг 10.12) обработки двоичных файлов. В первом примере мы создаем два двоичных файла из одного массива (листинг 10.27).
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
Листинг 10.27. Создание и обработка двоичных файлов
#include <fstream>
#include <iostream>
#include <ctime>
using namespace std;
int main()
{   int m[10]={0};
    srand((unsigned)time(NULL));        // инициализация датчика случайных чисел
   /* заполняем массив m числами */
    for(int i = 0; i < 10; i++)
    { m[i] = rand()%10;
      cout << m[i] << ' ';          // контрольный вывод
    }
    cout << '\n';
     /* открываем файл для записи */
    ofstream outstrm ("c:/binfiles/oonumber1.bin", std::ios::binary);
    if(outstrm.is_open())
    { for(int i = 0; i < 10; i++)   // выводим массив в файл поэлементно
        outstrm.write((char *)&m[i], sizeof(int));  
      outstrm.close();
    }
    /* открываем другой файл для записи */
    outstrm.open("c:/binfiles/oonumber2.bin", std::ios::binary);
    if(outstrm.is_open()) 
    { outstrm.write((char*)m, sizeof(m));   // выводим массив в файл 
      outstrm.close();
    }
// вывод двоичного файла на экран 
    // открываем второй файл для чтения 
    {   ifstream instrm ("c:/binfiles/oonumber2.bin", std::ios::binary);
        int a = 0;
        // читаем числа по одному из файла и выводим 
        while(instrm.read((char *)&a, sizeof(int))) 
            cout << a << ' ';
        cout << '\n';
    }
// открываем первый файл для чтения 
  ifstream instrm ("c:/binfiles/oonumber1.bin", std::ios::binary);
  int t[10] = {0};
  instrm.read((char *)t, sizeof(t));    // чтение файла в массив
  instrm.close();                       // закрываем
  for(int i = 0; i < 10; i++)           
        cout << t[I] << ' ';
  cout << '\n';  
 char ch = getchar();
return 0;
}
В этом примере (как и в примере 10.12) два двоичных файла из одного массива создаются разными способами: в файл oonumber1.bin массив выводится поэлементно, а в файл oonumber2.bin — сразу весь одним оператором. Если мы заглянем в каталог BinFiles, то увидим, что эти два файла имеют одинаковый размер в 40 байт (как и файлы number1.bin и number1.bin, созданные в примере 10.12).
Затем те же файлы открываются как входные, читаются и выводятся на экран. Сначала открывается файл oonumber2.bin (в который мы писали массив целиком), и чтение из него выполняется по одному числу. На экране видно, что чтение выполняется совершенно правильно.
Первый файл oonumber1.bin, который записывался в цикле по одному числу, читается сразу целиком в массив t, одним оператором, и поток тут же закрывается. И снова мы наблюдаем на экране, что чтение выполнилось совершенно правильно.
Копирование и дозапись двоичных файлов можно выполнить той же функцией filecopy(), открыв потоки как двоичные, например
C++
1
2
3
4
5
6
7
8
9
10
11
// копирование файлов
{ ifstream instrm ("c:/binfiles/oonumber1.bin", std::ios::binary);
  ofstream outstrm("c:/binfiles/oonumber.new", std::ios::binary);
  if (instrm) filecopy(instrm, outstrm);
}
// дозапись нового файла в конец старого
{ ifstream instrm ("c:/binfiles/oonumber2.bin", std::ios::binary);
  ofstream outstrm("c:/binfiles/oonumber.new",
                    std::ios::app|std::ios::binary);
  if (instrm) filecopy(instrm, outstrm);
}
Как и при обработке файлов функциями библиотеки <cstdio>, разница заключается только в режиме открытия.

ПРИМЕЧАНИЕ
Существует гораздо более простой и быстрый способ копирования файлов, который мы рассмотрим далее при изучении буферизации.

Произвольный доступ
Как уже было сказано выше, С++ позволяет выполнять позиционирование в любых потоках, кроме стандартных. Методы позиционирования приведены в табл. 10.11. Методы позиционирования различаются для входных и выходных потоков: для входных потоков имена [1-27.6.1.3] методов заканчиваются символом ‘g’ (от слова get), а методы выходных потоков [1-27.6.2.4] заканчиваются символом ‘p’ (от слова put).
Таблица 10.11. Методы позиционирования
Метод Описание
pos_type tellg()Получить текущую (абсолютную) позицию чтения
istream& seekg (pos_type p) Установить абсолютную позицию чтения
istream& seekg (off_type p, ios::seekdir s) Установить относительную позицию чтения
pos_type tellp()Получить текущую (абсолютную) позицию записи
istream& seekp (pos_type p) Установить абсолютную позицию записи
istream& seekp (off_type p, ios::seekdir s) Установить относительную позицию записи
Функции tellp() и tellg() возвращают абсолютное смещение от начала потока. Символы потока нумеруются, как и индексы массива, начиная с нуля. Однако тип pos_type не является целым типом. Поэтому получать текущую позицию чтения в файловом потоке нужно так:
C++
1
ios::pos_type p = file.tellg();
Можно сохранить текущую позицию и в переменной типа streampos, например
C++
1
streampos pos = file.tellp();
Переход к позиции, сохраненной в переменной pos, делается так
C++
1
file.seekp(pos);
Несмотря на то, что тип значения позиции в потоке не является целым значением, тем не менее, целые константы можно использовать в качестве аргумента в методах установки позиции — выполняется преобразование по умолчанию. Символы в потоке нумеруются, начиная с нулевого, поэтому позиционирование в начало потока можно выполнить, например, так
C++
1
file.seekg(0);
После этого опять можно будет прочитать первый символ потока.
Функции установки относительной позиции практически не отличаются от функции fseek() библиотеки <cstdio>. В классе ios_base определены константы [1 27.4.2.1.5]
C++
1
2
3
static const seekdir beg,   // позиционирование от начала потока 
                     cur,   // позиционирование от текущей позиции
                     end;   // позиционирование от конца потока
Первый аргумент, который обычно задается целым числом (выполняется преобразование по умолчанию), является смещением (в символах) от указанной позиции. Положительное число означает смещение вперед — ближе к концу файла, отрицательное — смещение назад, к началу файла. Например, позиционироваться в конец файла можно так:
C++
1
file.seekg(0, ios::end);
При позиционировании нужно следить, чтобы позиция оставалась внутри файла. Попытка позиционироваться до начала потока или после конца приводит поток в состояние bad(). Небольшой пример (листинг 10.38) показывает, как работает относительное позиционирование.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Листинг 10.38. Относительное позиционирование
int main()
{   fstream strm ("c:/binfiles/number2.bin", ios::binary|ios::in|ios::out);
    int a = 0;
    // читаем числа из файла и выводим 
    strm.seekg(2*sizeof(int), ios::beg);        // выставляемся от начала
    strm.read((char *)&a, sizeof(int));         // читаем
    cout << a << ' ' << '\n';
    // записываем новое число на ту же позицию
    a = 12021;
    strm.seekp(2*sizeof(int), ios::beg);        // выставляешься от начала
    strm.write((char *)&a, sizeof(int));    // записываем
    // возвращаемся, читаем снова и выводим
    strm.seekg(2*sizeof(int), ios::beg);        // выставляемся от начала
    strm.read((char *)&a, sizeof(int));         // опять читаем
    cout << a << ' ' << '\n';
    return EXIT_SUCCESS;
}
В примере открывается существующий двоичный файл в режиме чтения/записи. Оба флага (ios::in|ios::out) задавать обязательно, иначе по умолчанию файловый поток открывается только для записи. Сначала читается третье от начала число, затем выполняется возврат на ту же позицию и записывается новое число. Результат можно наблюдать на экране.

Этот пример, кроме всего прочего, показывает, что файл number2.bin, записанный функциями библиотеки <cstdio> (см. листинг 10.12), без всяких проблем читается и перезаписывпается средствами объектно-ориентированной библиотеки. Более того, даже двоичный файл, записанный средствами языка Pascal можно без проблем обрабатывать в программе на С++. Это естественно, так как файлы — прерогатива операционной системы, а не С++. Программа должна только правильно связать поток с нужным файлом.
Например, в С++, в отличие от языка программирования Pascal, отсутствуют файлы с записями (file of type). В языке Pascal такие файлы являются двоичными и состоят из записей. Записи нумеруются, начиная с нуля, что обеспечивает прямой доступ к записи.

Такие файлы по умолчанию открываются в режиме чтения/записи.
Мы вполне можем обработать такие файлы и обеспечить прямой доступ к записям по номерам средствами С++. Для этого нужно объявить в программе структуру, имеющую поля соответствующих типов, заданные в том же порядке, как в записи в программе на Pascale. Здесь нужно внимательно отнестись к проблеме выравнивания — структура в С++ должна иметь точно такой же размер, что и запись в Pascal. Далее нужно открыть файл как двоичный в режиме чтения/записи. Чтение из файла выполняется оператором
C++
1
strm.read((char *)&record, sizeof(T));
а запись — оператором
C++
1
strm.write((char *)&record, sizeof(T));
Здесь sizeof(T) — размер структуры в С++, который должен совпадать с размером записи в Pascal.
Методы относительного позиционирования позволяют реализовать прямой доступ к записям двоичного файла. Например, позиционирование на k-ю запись для чтения выполняется оператором
C++
1
strm.seekg(k*sizeof(T), beg);
Если необходимо сместиться вперед от текущей позиции на одну запись, можно воспользоваться любой из следующих инструкций:
C++
1
2
3
// эквивалентные вызовы seekg
stream.seekg(stream.tellg() + sizeof(T));   // абсолютная позиция
stream.seekg(sizeof(T), ios::cur);          // относительная позиция
Сместиться назад на одну запись можно так:
C++
1
2
stream.seekg(stream.tellg() - sizeof(T));   // абсолютная позиция
stream.seekg(-sizeof(T), ios::cur);         // относительная позиция
AnDron45
1 / 1 / 0
Регистрация: 11.03.2011
Сообщений: 35
22.06.2011, 12:53  [ТС]     Открытие бинарного файла для чтения #6
Цитата Сообщение от Deviaphan Посмотреть сообщение
Удали строку 8.

Добавлено через 1 минуту
Вместо f.write((char*)&c, sizeof(int));
можно просто
f << c;

Добавлено через 1 минуту
И вместо f.read((char*)&c, sizeof(int));
можно
f >> c;
Не так не можно, вывод на экран не адекватен.
А за 8 строку +1
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
22.06.2011, 12:58     Открытие бинарного файла для чтения #7
Цитата Сообщение от AnDron45 Посмотреть сообщение
так не можно
С каких пор оператор потокового ввода-вывода перестал работать? Это в текстовом формате не адекватен будет, без разделителя, а в бинарном самое оно. И не нужно извращаться с потоком байт.
AnDron45
1 / 1 / 0
Регистрация: 11.03.2011
Сообщений: 35
22.06.2011, 12:59  [ТС]     Открытие бинарного файла для чтения #8
Цитата Сообщение от ValeryLaptev Посмотреть сообщение
О Боже! Когда ж вы книжки-то читать начнете!!!!
А не подскажите их? В нашедших мною книгах файлы описываются по Сишному, а не потоками.
ValeryLaptev
Эксперт C++
1005 / 784 / 46
Регистрация: 30.04.2011
Сообщений: 1,595
22.06.2011, 13:00     Открытие бинарного файла для чтения #9
Цитата Сообщение от AnDron45 Посмотреть сообщение
А не подскажите их? В нашедших мною книгах файлы описываются по Сишному, а не потоками.
Найди в инете мои книжки и читай...
AnDron45
1 / 1 / 0
Регистрация: 11.03.2011
Сообщений: 35
22.06.2011, 13:03  [ТС]     Открытие бинарного файла для чтения #10
Цитата Сообщение от Deviaphan Посмотреть сообщение
С каких пор оператор потокового ввода-вывода перестал работать? Это в текстовом формате не адекватен будет, без разделителя, а в бинарном самое оно. И не нужно извращаться с потоком байт.
Ну не знаю, у меня выдает 5 одинаковых 7-8 разрядных чисел
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
22.06.2011, 13:11     Открытие бинарного файла для чтения #11
Я в шоке! Нельзя для бинарных файлов <<, >> использовать. А так хотелось. Пичалька.
ValeryLaptev
Эксперт C++
1005 / 784 / 46
Регистрация: 30.04.2011
Сообщений: 1,595
22.06.2011, 13:44     Открытие бинарного файла для чтения #12
Цитата Сообщение от 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 - и перегрузите...
Или как внешюю функцию.
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
22.06.2011, 13:50     Открытие бинарного файла для чтения
Еще ссылки по теме:

Организовать запись и чтения из файла для 3 программ C++
C++ Каким образом после чтения из файла, узнать позицию для чтения следующего куска байт
C++ Png из resource файла в память, для чтения LodePNG

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
Deviaphan
Делаю внезапно и красиво
Эксперт C++
 Аватар для Deviaphan
1283 / 1217 / 50
Регистрация: 22.03.2011
Сообщений: 3,744
22.06.2011, 13:50     Открытие бинарного файла для чтения #13
Писать что-то ручками? Ну уж спасибо, у меня есть boost::serialization.
Потому я про эту "особенность" и не помню, что напрямую с потоками не помню когда общался.)
Yandex
Объявления
22.06.2011, 13:50     Открытие бинарного файла для чтения
Ответ Создать тему
Опции темы

Текущее время: 11:55. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2016, vBulletin Solutions, Inc.
Рейтинг@Mail.ru