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

Открытие бинарного файла для чтения - 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
Эксперт С++
1042 / 821 / 48
Регистрация: 30.04.2011
Сообщений: 1,659
22.06.2011, 12:52
О Боже! Когда ж вы книжки-то читать начнете!!!!

Обычно различают текстовые и двоичные файлы. Текстовые файлы состоят из строк, которые завершаются символом «конец строки». В программе на С++ этот символ обозначается как ‘\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);         // относительная позиция
17
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru