Форум программистов, компьютерный форум, киберфорум
Наши страницы
С++ для начинающих
Войти
Регистрация
Восстановить пароль
 
Рейтинг: Рейтинг темы: голосов - 155, средняя оценка - 4.63
AnDron45
1 / 1 / 1
Регистрация: 11.03.2011
Сообщений: 35
#1

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

22.06.2011, 11:49. Просмотров 22353. Ответов 12
Метки нет (Все метки)

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 затирает файл.
0
Лучшие ответы (1)
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
22.06.2011, 11:49
Я подобрал для вас темы с готовыми решениями и ответами на вопрос Открытие бинарного файла для чтения (C++):

Открытие файла для чтения/записи
Помогите реализовать открытие файла под компилятор g++ или DevC++ вечно...

Ошибка чтения из бинарного файла
Вызвано исключение: нарушение доступа для чтения. _Pnext было 0x114999C. ...

Каким образом после чтения из файла, узнать позицию для чтения следующего куска байт
Здравствуйте, задача такова есть файл, размером 1000 байт, нужно открыть его...

Реализуйте класс, для которого [] перегружено для реализации случайного чтения символов из файла
Здравствуйте, прошу помочь с таким вот заданием : " Реализуйте класс, для...

Функция для чтения из файла в матрицу
#include <iostream> #include <vector> #include <fstream> using namespace...

Нужно создать класс для чтения из файла
Нужно создать класс для чтения из файла.

12
Deviaphan
Делаю внезапно и красиво
Эксперт С++
1306 / 1221 / 72
Регистрация: 22.03.2011
Сообщений: 3,744
22.06.2011, 12:07 #2
Логично. Для чтения нужно использовать ifstream.
0
AnDron45
1 / 1 / 1
Регистрация: 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
1
Deviaphan
Делаю внезапно и красиво
Эксперт С++
1306 / 1221 / 72
Регистрация: 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;
2
ValeryLaptev
Эксперт С++
1049 / 828 / 60
Регистрация: 30.04.2011
Сообщений: 1,659
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);         // относительная позиция
18
AnDron45
1 / 1 / 1
Регистрация: 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
0
Deviaphan
Делаю внезапно и красиво
Эксперт С++
1306 / 1221 / 72
Регистрация: 22.03.2011
Сообщений: 3,744
22.06.2011, 12:58 #7
Цитата Сообщение от AnDron45 Посмотреть сообщение
так не можно
С каких пор оператор потокового ввода-вывода перестал работать? Это в текстовом формате не адекватен будет, без разделителя, а в бинарном самое оно. И не нужно извращаться с потоком байт.
0
AnDron45
1 / 1 / 1
Регистрация: 11.03.2011
Сообщений: 35
22.06.2011, 12:59  [ТС] #8
Цитата Сообщение от ValeryLaptev Посмотреть сообщение
О Боже! Когда ж вы книжки-то читать начнете!!!!
А не подскажите их? В нашедших мною книгах файлы описываются по Сишному, а не потоками.
0
ValeryLaptev
Эксперт С++
1049 / 828 / 60
Регистрация: 30.04.2011
Сообщений: 1,659
22.06.2011, 13:00 #9
Цитата Сообщение от AnDron45 Посмотреть сообщение
А не подскажите их? В нашедших мною книгах файлы описываются по Сишному, а не потоками.
Найди в инете мои книжки и читай...
1
AnDron45
1 / 1 / 1
Регистрация: 11.03.2011
Сообщений: 35
22.06.2011, 13:03  [ТС] #10
Цитата Сообщение от Deviaphan Посмотреть сообщение
С каких пор оператор потокового ввода-вывода перестал работать? Это в текстовом формате не адекватен будет, без разделителя, а в бинарном самое оно. И не нужно извращаться с потоком байт.
Ну не знаю, у меня выдает 5 одинаковых 7-8 разрядных чисел
0
Deviaphan
Делаю внезапно и красиво
Эксперт С++
1306 / 1221 / 72
Регистрация: 22.03.2011
Сообщений: 3,744
22.06.2011, 13:11 #11
Я в шоке! Нельзя для бинарных файлов <<, >> использовать. А так хотелось. Пичалька.
0
ValeryLaptev
Эксперт С++
1049 / 828 / 60
Регистрация: 30.04.2011
Сообщений: 1,659
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 - и перегрузите...
Или как внешюю функцию.
1
Deviaphan
Делаю внезапно и красиво
Эксперт С++
1306 / 1221 / 72
Регистрация: 22.03.2011
Сообщений: 3,744
22.06.2011, 13:50 #13
Писать что-то ручками? Ну уж спасибо, у меня есть boost::serialization.
Потому я про эту "особенность" и не помню, что напрямую с потоками не помню когда общался.)
0
22.06.2011, 13:50
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
22.06.2011, 13:50
Привет! Вот еще темы с решениями:

Организовать запись и чтения из файла для 3 программ
Добрый вечер.Помогите пожалуйста организовать запись и чтения из файла для 3...

Ошибка при открытии файла для чтения
#include &quot;stdafx.h&quot; #include &quot;nhash.h&quot; #include &lt;iostream&gt; #include...

Создание бинарного дерево из бинарного файла
struct Bin { string name; string city; int players; int...

Создание бинарного дерева из бинарного файла
struct Bin { string name; string city; int players; int score; };...


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

Или воспользуйтесь поиском по форуму:
13
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru