- •Потоковый ввод/вывод дисковых файлов
- •Форматированный файловый ввод/вывод
- •Запись данных
- •Чтение данных
- •Двоичный ввод/вывод
- •Оператор reinterpret_cast
- •Закрытие файлов
- •Объектный ввод/вывод
- •Запись объекта на диск
- •Чтение объекта с диска
- •Совместимость структур данных
- •Ввод/вывод множества объектов
- •Файловый ввод/вывод с помощью методов
- •Как объекты записывают и читают сами себя
- •Статические функции
- •Размеры порожденных объектов
- •Использование функции typeid()
cln » name;
cout « "Введите возраст: "; cln » age;
}
};
int main ()
{
person pers;
//создать объект pers.getData ();
//получить данные
//создать объект ofstream
ofstream outfile("PERSON.DAT", ios::binary); // записать в него
outfile.write(reinterpret_cast<char*>(&pers), sizeof(pers)); return 0;
};
Метод getData () класса person вызывается для того, чтобы запросить у пользователя информацию, которая помещается в объект pers.
Содержимое данного объекта записывается на диск с помощью функции write (). Для нахождения длины данных объекта pers используется оператор sizeof ().
Чтение объекта с диска
Листинг 12.11. Программа IPERS #include <fstream>
#include <iostream> using namespace std;
//для файловых потоков class person
{
protected : char name[80];
short age; public: void showData ()
//класс person
//Имя человека // его возраст
//вывести данные
cout « "Имя: " « name « endl; cout « "Возраст: " « age « endl; int main ()
{
person pers;
// переменная типа person
ifstream infile (“PERSON. DAT”, ios::binary);// создать поток // чтение потока
infile.read( reinterpret_cast<char*>(&pers), sizeof (pers));
pers.showData (): |
// вывести данные |
return 0; |
|
} |
|
В результате работы программы будет выведено на экран все то, что было помещено в файл PERSON.DAT программой OPERS:
Совместимость структур данных
Для корректной работы программы чтения и записи объектов (такие, как OPERS и IPERS) должны иметь в виду один класс объектов. Например, объекты класса person имеют длину ровно 82 байта, из которых 80 отведено под имя человека, 2 — под возраст в формате short. Если бы программы не знали длину полей, то одна из них не смогла бы корректно прочитать из файла то, что записала другая.
Несмотря на то что классы person в OPERS и IPERS имеют одинаковые компонентные данные, у них могут быть совершенно разные методы. Скажем, в первой программе есть функция getData (), тогда как во второй — showData (). He имеет значения, какие используются методы в классах, — они в файл вместе с данными не записываются. Это для данных важен единый формат, а разногласие между методами ни к каким последствиям не приводит. Впрочем, это утверждение справедливо только для обычных классов, в которых не используются виртуальные функции.
Если вы пишете в файл и читаете объекты производных классов, необходимо соблюдать большую осторожность. В этих объектах есть загадочное число, которое ставится перед началом их области данных в памяти. Оно помогает идентифицировать класс объекта при использовании виртуальных функций. Когда вы записываете объект в файл, это число записывается наряду с другими данными. Если меняются методы класса, идентификатор также изменяется. Если в классе имеются виртуальные функции, то при чтении объекта с теми же данными, но другими методами возникнут большие трудности. Отсюда мораль: класс, использующийся для чтения объекта, должен быть идентичен классу, использовавшемуся при его записи.
Можно даже не пытаться произвести дисковый ввод/вывод объектов, компонентами которых являются указатели. Легко догадаться, что значения указателей не будут корректными при чтении объекта в другую область памяти.
Ввод/вывод множества объектов
Предыдущие программы записывали и читали только один объект за раз. В следующем примере в файл записывается произвольное число объектов. Затем они считываются, и на экран выводится целиком содержимое файла.
Листинг 12.12. Программа DISKFUN
#include <fstream> |
// для файловых потоков |
|
#include <iostream> |
|
|
using namespace std; |
|
|
class person |
|
|
{ |
|
|
protected: |
|
|
char name[80]: |
|
|
int age: |
|
|
public: |
|
|
void getData () |
|
|
{ |
Введите имя: "; |
|
cout « "\n |
||
cln » name: |
Введите возраст: "; |
|
cout « " |
||
cln » age: |
|
|
} |
|
// вывод данных о человеке |
void showData () |
||
{ |
Имя: " « name; |
|
cout « "\n |
||
cout « "\n |
Возраст: " « age; |
|
} |
|
|
int main ()
{
char ch; person pers; fstream file;
file.open («GROUP. DAT», ios::app | ios::out | ios: :in | ios::binary ) do
{
cout « "Введите данные о человеке:"; pers.getData (); // получить данные // записать их в файл
file.write( reinterpret_cast<char*>(&pers). slzeof(pers) ); cout « "Продолжить ввод (y/n)? ";
cln » ch;
}
wh11e(ch=='y' ); flle.seekg(O);
//поставить указатель на начало файла
//считать данные о первом человеке
file.read ( reinterpret_cast<char*>(&pers), slzeof(pers) ); while ( !file.eof () ) // Выход по EOF
{
cout « "\nПерсона:"; //вывести данные
pers.showData (); //считать данные о следующем file.read(reinterpret_cast<char*>(&pers).s1zeof(pers));
}
cout « endl ; return 0;
}
В настоящей, серьезной программе некоторые из этих функций должны использоваться после каждой операции ввода/вывода, и тогда не останется сомнений в том, что все идет как нужно.
Файловый ввод/вывод с помощью методов
Ранее мы сводили все функции работы с файлами в секцию main () наших программ. Но при написании сложных классов логичнее включать операции файлового ввода/вывода в
методы класса. В этом параграфе мы продемонстрируем две программы, в которых это проделано. В первой из них используются обычные методы, каждый объект отвечает за запись самого себя в файл и чтение из файла. Вторая показывает, как файловый ввод/вывод всех объектов можно осуществить с помощью статических функций-членов класса.
Как объекты записывают и читают сами себя
Иногда имеет смысл разрешить каждому компоненту класса читать и записывать самого себя. Это довольно прозрачный подход и отлично работает, если нужно писать и читать небольшое число объектов. В нашем примере мы добавили два метода — diskOut () и diskln () — в класс person. Эти функции позволяют объектам person записывать себя в файл и читать себя же из него.
Примем некоторые упрощающие допущения. Во-первых, все объекты будут храниться в одном и том же файле PERSFILE.DAT. Во-вторых, новые объекты всегда будут добавляться к концу файла. Аргумент функции diskln () позволяет читать данные о любом человеке из файла. Для предотвращения попыток прочитать данные, выходящие за пределы файла, мы включаем в программу статический метод diskCount (), возвращающий число людей, информация о которых хранится в файле. При вводе данных следует использовать только
фамилии людей, пробелы не допускаются. Листинг 12.16. Программа REWOBJ
//rewobj.cpp
//Файловый ввод/вывод объектов person #include <fstream>
//Для файловых потоков
#include <iostream> using namespace std; class person
{
protected: char name[40]; int age; public:
void getData()
{
cout « "\n Введите фамилию: "; cin » name; cout « " Введите возраст: "; cin » age;
}
void showData()
{
cout « "\nИмя: " « name; cout « "\nВозраст: " « age; void diskln(int);
//чтение из файла void diskOutO;
//запись в файл
static int diskCount ();
// Число человек в файле }:
void person::diskln(int pn) // Чтение данных о числе
{
//человек из файла ifstream infile;
//создать поток
infile.open (“PERSFILE.DAT”, ios::binary); // открыть его infile.seekg( pn*sizeof(person) );
//сдвиг
//файлового указателя
infile.read( (char*)this, sizeof(*this) ); // чтение данных
//об одном человеке
}
void person::diskOut()
{
ofstream outfile;
//запись в конец файла
//создать поток
//открыть его
outfile.open (“PERSFILE.DAT", ios::app | ios::binary); outfile.write((char*)this.sizeof(*this)); //записать в него
}
int person::diskCount()
{
//число людей в файле ifstream infile;
infile.open («PERSFILE.DAT», ios::binary); infile.seekg(0, ios::end); // перейти на позицию «О байт //от конца файла» // вычислить количество людей
return (int) infile.tellg () / sizeof(person);
}
int main ()
{
person p; char ch;
//создать пустую запись
do {