Попередні приклади ілюструють засоби запису та читання файлів даних, що містять або числа, або текстову інформацію. Оскільки дані типу структура можуть складатися з полів різного типу, то для роботи з ними доцільно використовувати двійкові файли, які являють собою послідовність байтів інформації різного призначення.
Наприклад, якщо структура описана у вигляді:
stuct rab
{
char fio[20]; // фамилия рабочего
int gr; // год рождения и зарплата
float zarpl;
} sp; //sp — переменная типа структура
то ввести в пам’ять комп’ютера поля змінної sp типу структура можна таким чином:
AnsiString(sp.fio) = InputBox(“”, “Введите фамилию”, “”);
AnsiString р = InputBox(“”, “Введите год рождения”, “”);
sp.gr = StrToInt(p);
р = InputBox(“”, “Введите зарплату”, “”);
sp.zarpl = StrToFloat(p);
Запис змінної sp у файл та читання її з файла здійснюється відповідно операторами
Write((char*)&sp, sizeof(sp)) та Read((char*)&sp, sizeof(sp));.
Ці операції можна робити, якщо попередньо був відкритий відповідний файл.
Виведення значеннь полів змінної sp після читання файла можна здійснити з використанням будь-якого розглянутого раніш компонента виведення, наприклад:
Edit1->Text=sp.fio+” “+IntToStr(sp.gr)+ ” “+FloatToStrF(sp.zarpl,ffFixed,8,2);
За необхідності розміщення на формі великої кількості кнопок, що породжують події, доцільно використовувати компоненти MainMenu або PopUpMenu, які дозволяють створювати меню команд різного вигляду.
Приклад 16.7. Розробити додаток, що організує роботу з розкладом занять і реалізує виконання таких команд:
— введення назви групи;
— запис розкладу занять групи у файл;
— читання розкладу занять з файла;
— читання розкладу занять на заданий день;
— виведення днів, коли є заняття із заданого предмета;
— закінчити роботу додатка.
Для реалізації прикладу будемо використовувати компонент MainMenu, що дозволяє створювати систему головних команд меню і для кожної з них — групу команд підменю.
Спочатку розмістимо на формі компонент MainMenu у місці, яке менш за все використовується, наприклад, у її лівому нижньому куті. Потім здійснемо конструювання самого меню. Для цього необхідно двічі клацнути лівою клавішею мишки на компоненті, й у лівому верхньому куті форми з’явиться прямокутник, призначений для створення першої головної команди меню. У властивість Caption редактора Object Inspector треба ввести ім’я цієї команди («Расписание»), Потім слід перевести курсор нижче цього прямокутника і знову клацнути один раз клавішею миші. На цьому місці з’явиться новий прямокутник, призначений для розміщення підкоманди головної команди. Ім’я підкоманди знову вводимо в Object Inspector. Цей процес повторюється стільки разів, скільки підкоманд передбачається розмістити в даній команді (у даному випадку їх шість). Далі курсор слід розмістити .праворуч від першої головної команди і знову клацнути мишею. З’явиться прямокутник для розміщення нової головної команди, для якої треба ввести ім’я і вищеописаним способом організувати розміщення її підкоманд (це — «Дни недели»). Кожну з підкоманд можна перейменувати, зробивши її активною, тобто клацнути клавішею миші. Виділена команда виводиться яскраво-синім кольором. Якщо в системі підкоманд необхідно вивести розділювальну рису, слід замість введення імені натиснути клавішу «—».
Для виведення результатів розмістимо на формі компонент Меmо. Після закінчення формування зовнішнього вигляду команд меню рекомендується записати створений проект на диск, як це робилося в попередніх прикладах.
Вигляд форми і текст програмного коду даного додатка наведені нижче.
#include< vcl.h>
#pragma hdrstop
#include< fstream.h>
#include< Dialogs.hpp>
#include< string.h>
#include “Unit R5.h”
#pragma package (smart_init)
#pragma resource “*.dfm”
TForm1 “Form1;
Наведемо пояснення до розробки програмного коду подій для кожної підкоманди. Спочатку слід увести імена заголовних файлів, для даного прикладу це — fstream.h, string.h і Dialogs.hpp.
Далі необхідно описати глобальні змінні до першого оброблювача додатка. У нашому випадку це змінна fname, що зберігав повне ім’я файла, в якому запишеться розклад, та змінна типу структура sp.
Структура має вигляд:
struct rasp
{ char den [15];
char str[3][80]; } sp;,
де char den[15]; — поле, яке зберігає назви дня тижня;
str[3][80] — масив з трьох рядків, необхідний для запису в кожному рядку назви предмета, номера пари, на якій він читається, та номера аудиторії.
Варто зауважити, що більшість компонентів і функцій С++ Builder передбачає запис у їхніх полях введення рядкових даних типу AnsiString. Синонімом цього службового слова є String (із заголовної літери). І, здавалося б, рядкові поля структури слід описувати як елементи цього типу. Однак оскільки довжина таких даних залежить від фактичної довжини рядка, то при записі їх у двійковий файл практично не можливо домогтися, щоб усі компоненти файла мали однакову довжину, через що система дає збій.
Тому рядкові поля структури слід оголошувати як дані типу char заданої довжини, а при їх використанні в компонентах і функціях головної бібліотеки VCL системи С++ Builder робити необхідні перетворення типів.
В прикладі, що розглядається, для введення значення поля структури sp.den, як і інших полів, використано два оператори:
Sting dn = InputВох(” “.”Введите” + IntToStr(I+l) + “день”, ” “);
strcpy(sp.den, dn.c_str());
Перший з них здійснює присвоювання даного, що вводиться, тобто назву дня тижня змінній dn типу AnsiSting. У другому операторі це дане передавалося в поле sp.den за допомогою функції копіювання strcpy() при одночасному його перетворенні до типу char за допомогою функції dn.c_str();.
Визначивши глобальні змінні, необхідно приступити до написання коду оброблювача першої команди меню. Вона називається «Имя группы» і призначена для зазначення шифру групи. Шифр потрібний, тому, що додаток передбачає створення необмеженої кількості розкладів для груп. Цей оброблювач складається з двох операторів, у першому з який змінній sg присвоюється фактичне ім’я групи, а в другому це ім’я додається до глобальної змінної name, завдяки чому в змінній fname формується повне ім’я файла. Наприклад, якщо при виконанні функції InputBox() ввести шифр групи «ФБЕ», то змінна fname буде зберігати значення:
“D:\\Users\\Rasp\yi>BE”;.
Другий оброблювач забезпечує реалізацію наступної команди меню — «Записать». Він починається функцією MessageBox(), що попереджав користувача про випадкове виконання цієї команди, яке приведе до затирання раніше записаного розкладу. Ця функція виводить на екран вікно з написом: «Хотите переписать расписание?» і дві кнопки «ОК» і «Cancel». Натисканням однієї з них користувач підтверджує чи скасовує своє рішення.
Далі в оброблювачі відкривається файл для запису і передбачається введення полів структури, як це вказувалося раніше на прикладі введення дня тижня. Для цього організовано два цикли for, один із яких (зовнішній) забезпечує введення даних для кожного з днів тижня, а другий (внутрішній) служить для забезпечення введення назви предмета, номера пари й аудиторії кожного дня. Передбачається, що кількість пар не перевищує трьох. Після введення даних одного дня занять вони записуються в один рядок масиву str[k], який є полем раніше описаної структури. Цей запис здійснюється за допомогою функції sprintf() і потім записується у файл оператором ft.write(). Він є останнім оператором зовнішнього циклу, що повторюється шість разів, забезпечуючи введення і запис розкладу на кожен день тижня.
Наступний оброблювач команди «Прочитать», призначений для читання всього розкладу в поле введення компоненти Memo1, записано наступним у програмному коді додатка під назвою «Чтение расписания». У ньому спочатку відкривається створений файл для читання, очищається поле компонента Memo1, потім виводиться заголовок: «Расписание занятий» і включаються такі ж цикли, як і при записі файла.
У зовнішньому циклі спочатку читається черговий компонент файла за допомогою оператора fin.read(), у Memo1 записується назва чергового дня тижня і включається внутрішній цикл, у якому виводяться в Memo1 раніше записані рядки str[k], що містять інформацію про назву предмета, номер пари і аудиторії.
У наступних оброблювачах наводяться програмні коди читання розкладу за заданим днем тижня. Спочатку відкривається файл для читання, потім функцією seekg(n*sizeof(rasp)) встановлюється покажчик читання на потрібний компонент файла, здійснюється читання цього компонента і виведення інформації у вікно Memol. Тут n — це номер компонента, що починається з нуля і відповідає в даній програмі номеру чергового дня тижня, sizeof(rasp) — кількість байтів, займаних змінною rasp. При виконанні оператора відкриття файла цьому покажчику автоматично присвоюється початкове нульове значення. В оброблювачі виведення розкладу на понеділок (перший день тижня) функція seekg(n*sizeof(rasp)) не використовується, тому що n = 0. Вона призначена для організації прямого доступу до компонентів файла, на відміну від раніше використовуваного послідовного доступу. Прямий доступ можна організувати не тільки при читанні, але і при записі файлів, під час їх коригування. Для цього треба використовувати функцію seekp() (див. розділ 10).
Для визначення позиції, в якій знаходиться покажчик потоку в даний момент, використовуються функції:
in.tellg() та in.tellp() — відповідно при читанні та записі файла,
де іn — це ім’я потоку, створеного операторами ifstream() або ofstream().
У програмному коді даного прикладу наведено оброблювачі виведення розкладу тільки на перші три дні тижня, інші опущені заради економії місця, оскільки вони відрізняються один від одного тільки значенням параметра n у функції seekg().
Оброблювач команди «Предмет» служить для забезпечення виведення розкладу, коли є заняття з даного предмета. У ньому за допомогою функції InputBox() вводиться назва предмета, яка привласнюється змінній st. При цьому можна вводити предмет як без указівки виду занять, що показано в додатку, так і з указівкою, наприклад, «Информатика пз.». Потім визначається довжина введеного повідомлення, відкривається файл для читання й організується зовнішній цикл за допомогою оператора while(), що забезпечує послідовне читання компонентів файла, доки функція eof() не прийме значення true при досягненні маркера кінця файла. При читанні чергового значення змінної sp у внутрішньому циклі for відбувається порівняння поля sp.str[k] зі значенням змінної st, у якій записана назва предмета і, якщо вони збігаються, у поле Memo1 буде виведено день, предмет, пару та аудиторію, тобто відповідні поля структури str[k]. Приклад виконання цього оброблювача ілюструє наведена раніше третя форма.
Попередні приклади використовували в основному методи (функції) класів мови С++: ofstream, ifstream і fstream. Однак середовище С++ Builder має свій клас, в якому визначені властивості і методи роботи з файлами даних. Цей клас входить до складу бібліотеки візуальних компонентів VCL і має ім’я TFileStream. Він частково інкапсулює вище зазначені потокові класи С++, тому є можливість використовувати їхні методи й у даному випадку, тобто обидва способи організації файлового введення-виведення взаємозамінними. Методи класу VCL роблять програмний код більш чітким і лаконічним [2].
Для відкриття файла даних треба записати оператор:
TFileStream * fp=new TFileStream (“іф” режим);,
де fp — ім’я потоку, що створюється;
іф — повне ім’я файла (з указівкою шляху);
режим — режим відкриття файла.
Тут можна використовувати такі основні режими:
- fmCreate — створити файл і відкрити його для запису;
- fmOpenWrite — відкрити файл тільки для запису з повною заміною поточного його змісту;
- fmOpenRead — відрити файл тільки для читання;
- fmOpenReadWrite — відкрити файл для читання і запису при модифікації його поточного змісту.
Для класу TFileStream визначені властивості і методи:
- fp->Position = n; — служить як для визначення поточного значення покажчика позиціювання файла, так і для завдання цього значення;
- fp->Size — визначає поточний загальний розмір даних файла;
- fp->Read(& р, sizeof(p)); — служить для читання змінної р будь-якого типу з файла;
- fp->Write(*p, sizeof(p)); — записує у файл змінну р.
Наприклад, відкрити файл в режимі читання можна так:
TFileStream*p = new TFileStream(nmyfile.dat”, fmOpenRead) .
Розглянемо особливості використання методів, що наведено вище, на конкретному прикладі.
Приклад 16.8. Записати у файл такі дані:
— прізвище та ініціали працівників підприємства;
— рік народження;
— стать;
— посадовий оклад.
При читанні створеного файла забезпечити виконання таких дій:
— вивести зміст усього файла на екран;
— вивести прізвища чоловіків або жінок заданого року народження;
— вивести дані заданого працівника.
Для розв’язання прикладу на формі створимо меню, яке складається з двох основних команд: «Файл» та «Вывод». Перша команда складається з підкоманд, що мають назви: «Записать», «Прочитать» і «Выход», а друга команда містить підкоманди: «Мужчин», «Женщин» та «Работника». Для виведення інформації розташуємо на формі таблицю StringGrid, яка дозволяє наочно представляти виведені дані і, за необхідності, корегувати їх.
Форми і програмний код мають вигляд:
С++: Розв’язання задач з файлами
Для того, щоб не писати щоразу довге і не дуже зручне слово StringGrid, задамо в його властивості Name ім’я «Тb». Крім того, встановимо інші властивості компонента, а саме: кількість стовпців, що буде дорівнювати 5, та кількість рядків, яка буде мати значення 10.
У програмному коді підключимо ті ж заголовні файли, що й у попередньому прикладі, потім опишемо глобальні дані: константу name — для збереження повного імені файла, змінну n — для задания кількості працівників і структуру вигляду:
struct trab {
char fio[20]; // фамилия работника
char pol[5]; // пол
int gr; // год рождения
float zrp; //зарплата
} rab;,
де rab — змінна типу структура trab.
Опишемо функцію користувача Del(), що служить для очищення комірок таблиці шляхом запису порожніх рядків.
Перший оброблювач має ім’я FormCreate й ініціюється при створенні форми, тобто перед запуском програми на виконання. Він активізується при подвійному клацанні на будь-якому місці форми і служить для заповнення заголовка таблиці. Крім того, у ньому визначається кількість працівників, тобто значення глобальної змінної n.
Потім після запуску додатка на виконання заповнимо таблицю — запишемо у відповідних комірках необхідну інформацію. Вигляд заповненої таблиці наведено на першій формі прикладу 16.8.
Щоб зберегти значення цієї таблиці, треба записати їх спочатку у відповідні поля структури (поля змінної rab), а потім у файл. Ці операції виконуються в оброблювачі з ім’ям N2Click, у якому відкривається файл з ім’ям nаmе для запису.
Комірки таблиці зберігають інформацію у вигляді рядків типу AnsiString, тому при передачі інформації в поля структурної змінної rab виконується перетворення даних до типів, що оголошені при описі цих полів.
У кожному рядку таблиці Тb записані всі дані полів структурної змінної rab про одного працівника. Після їхньої передачі здійснюється запис цієї змінної у файл за допомогою оператора Write(). Цикл, у якому виконуються ці операції, повторюється п разів (за числом працівників), у результаті дані всіх працівників будуть записані у файл.
При виконанні команди меню «Прочитать» відкривається файл для читання, клітки таблиці очищаються від раніше записаної інформації за допомогою функції Del(), покажчик позиціонування файла встановлюється в початкове нульове положення, і у циклі for, що повторюється n разів, спочатку з файла читається черговий запис, тобто значення полів змінної rab за допомогою оператора Read(), а потім вони передаються у відповідні клітки таблиці.
При виконанні команд меню «Мужчин» чи «Женщин», після відкриття файла для читання, на екрані з’являється вікно функції InputBox(), у якому запитується рік народження. Після його введення відбувається очищення таблиці і читання файла в циклі for. При цьому в операторі if здійснюється перевірка: якщо значення статі дорівнює заданому і рік народження збігається з введеним, то в таблицю виводяться дані відповідного працівника. Це ілюструє друга форма додатка.
При виконанні команди «Работника» використовуються такі ж операції, як і в попередніх оброблювачах, за винятком того, що запитується прізвище працівника і при читанні файла здійснюється його порівняння з полем rab.fio. Якщо вони збігаються, то в таблицю виводяться всі дані цього працівника.
З описаного випливає, що методика роботи з файлами даних за допомогою методів потокового класу VCL принципово мало чим відрізняється від такої ж роботи з використанням потокових класів мови С++.