Задача 244.244. Скласти програму, яка вказує місця можливих переносів у слові.

Розв’язання: Одразу домовимось про обмеження, які ми накладемо на нашу програму. По–перше, ми будемо вводити тільки одне слово українською мовою. По–друге, ми повинні визначитись з правилами машинного переносу, оскільки навіть серед вчителів–мовників інколи виникають суперечки з приводу вірності того чи іншого переносу.

Отже, приступимо. Правило переносу по складах, відоме нам ці школи, тут не примінимо, так як навчити вірно поділяти слова на склади є проблемою, яку можна використати як тему для наукової роботи не тільки учня, а навіть на здобуття вченого ступеня.

Тому ми поступимо таким чином: на підставі конкретних прикладів спробуємо розробити «власні» правила переносу, які будуть більш зрозумілими нашому електронному другу. Цілком зрозуміло, що не мішало б підрахувати кількість голосних літер, так як переносів не може бути більше за кількість голосних – 1: у кожному складі слова є тільки одна голосна літера, тому слово з двох складів може мати можливість для переносу не більше ніж у одному місці, слово з трьох складів – не більше ніж у двох і т.д. (коб-ра, ма-як, пас-каль, ре-чен-ня). Ви мабуть звернули увагу, що слово «паскаль» ми написали з малої літери. Це ще одне обмеження, яке ми свідомо наклали на нашу програму: всі літери у слові повинні бути малими. Зроблено це з метою спростити текст самої програми.

Отже, ми зупинились на тому, що нам потрібно рахувати голосні літери, раз ми не вміємо рахувати склади. Одразу стає зрозумілим, що бажано у програмі було б ввести множину голосних літер українського алфавіту. Крім того, введемо множину приголосних літер, та літер, що не відносяться ні до голосних, ні до приголосних – маються на увазі літери «й» та «ь». Стає зрозумілим, що алфавіт мови буде являти собою об’єднання всіх трьох множин. Тобто, ми вже можемо керувати введенням інформації з клавіатури виключно на українській мові. Якщо символ, що вводиться з клавіатури, належить множині малих літер українського алфавіту, то програма його відображає на екрані, в противному випадку – просто ігнорує. Все описане вище реалізовано в процедурі wwod. В ній же показано спосіб виводу літер «є», « ї » та « і », яких на русифікованих клавіатурах досить часто немає, навіть на «українських комп’ютерах» фірми «Діавест»!

Разом з підрахунком голосних літер будемо заповнювати масив pol в якому будуть міститися номери голосних літер – саме на підставі значень цього масиву ми і будемо створювати свої правила переносу.

Правило перше: практично завжди перенос можна зробити після літер «й» та «ь», якщо вони не стоять в кінці слова. Ми поступимо таким чином: якщо нам у слові зустрілась літера «й» або «ь», то просто перенесемо положення попередньої голосної на місце цієї літери і скажемо, що у цьому місці перенос точно можна зробити, якщо це не остання літера слова (май-ка, дунь-ка).

Правило друге: якщо ми маємо дві голосні, що йдуть підряд і вони не стоять на початку або в кінці слова, то між ними можливо вставити перенос (ба-ян, маха-он).

Правило третє: між складами з двох літер потрібно ставити перенос після голосної літери (ба-рабан, ма-карон).

Правило четверте: якщо дві приголосні йдуть підряд, то між ними також можливий перенос (бар-кас, гам-бур-гер).

Правило п’яте: у випадку, якщо підряд йде більше двох приголосних, то, в принципі, можна керуватись правилом четвертим.

Будемо вважати, що все. Практично, ми зробили майже все (але точно не все – дещо залишили і для вас!), тому можемо написати повний текст програми. Найголовніші коментарі ми записали, а з деталями рекомендуємо розібратися самостійно.

program perenos;

uses dos, crt;

var alfavit, golosni, prig, drugi : set of char;

slovo, maket : string;

i, j, k, k1, k2, kolgolosni, kolperenos : integer;

pol : array[1..10] of byte;

ch : char;

procedure wwod;

begin

slovo := ‘’;

repeat

ch := readkey;

if ch in alfavit then

begin

slovo:=slovo+ch;

write(chr(ord(ch)));

end

else if ch = ‘ э’ then

begin

ch := chr(243); slovo := slovo+ch;

write(chr(ord(ch)));

end

else if ch = ‘ы’ then

begin

ch := ‘і’; slovo := slovo+ch;

write(chr(ord(ch)))

end

else if ch = ‘ ъ’ then

begin

ch := chr(245); slovo := slovo+ch;

write(chr(ord(ch)));

end;

until ch = #13;

writeln;

end;

begin

clrscr;

prig:=[‘б’,‘в’,‘г’,‘д’,‘ж’,‘з’,‘к’,‘л’,‘м’,‘н’,‘п’,‘р’,‘с’,‘т’,‘ф’,‘х’,‘ц’,‘ч’,‘ш’,‘щ’];

golosni := [‘а’, ‘е’, chr(243), ‘и’, ‘і’, chr(245), ‘о’, ‘у’, ‘ю’, ‘я’,];

drugi := [‘й’, ‘ь’];

alfavit := prig + golosni + drugi;

write(‘ Введіть слово: ’);

wwod;

kolgolosni := 0;

maket := '';

for i := 1 to 10 do pol[i] := 0;

k := 1;

for i := 1 to length(slovo) do

begin

if slovo[i] in golosni then

begin

inc(kolgolosni);

pol[k]:=i;

inc(k);

end

else if slovo[i] in drugi then pol [ k - 1] := i;

end;

dec(kolgolosni);

kolperenos := 0;

{ підрахунок кількості переносів і сам перенос }

for i:= 1 to kolgolosni do

begin

k1 := pol[i];

k2 := pol[i+1];

{ 1. перенос після літер «й» та «ь» }

if slovo[k1] in drugi then

begin

slovo:=copy(slovo,1,k1) + ‘-’ + copy(slovo,k1+1,length(slovo)-k1+1);

for k1:=i+1 to k-1 do inc(pol[k1]);

inc(kolperenos)

end

else

{ 2. перенос між двома голосними, якщо вони не в кінці слова }

if (k2-k1=1) and (length(slovo)-k2>0) then

begin

slovo:=copy(slovo,1,k1) + ‘-’ + copy(slovo,k1+1,length(slovo)-k1+1);

for k1:=i+1 to k-1 do inc(pol[k1]);

inc(kolperenos)

end else

{3. перенос між складами з двох літер і перша літера слова не голосна}

if (k2-k1=2) and (k1<>1) then

begin

slovo:=copy(slovo,1,k1) + ‘-’ + copy(slovo,k1+1,length(slovo)-k1+1);

for k1:=i+1 to k-1 do inc(pol[k1]);

inc(kolperenos)

end else

{4. перенос між двома приголосними }

if k2-k1=3 then

begin

slovo:=copy(slovo,1,k1+1) + ‘-’ + copy(slovo,k1+2,length(slovo)-k1+1);

for k1:=i+1 to k-1 do inc(pol[k1]);

inc(kolperenos)

end

else

{5. перенос між кількома приголосними }

if k2-k1>3 then

begin

slovo:=copy(slovo,1,k1+1)+ ‘-’ + copy(slovo,k1+2,length(slovo)-k1+1);

for k1:=i+1 to k-1 do inc(pol[k1]);

inc(kolperenos)

end

end;

if kolperenos = 0 then writeln(‘ Перенос не можливий ’) else

if kolperenos = 1 then writeln(‘ Можна зробити 1 перенос’)

else if kolperenos < 5 then writeln(‘ Можна зробити ’,kolperenos, ‘ переноси’)

else writeln(‘ Можна зробити ’,kolperenos, ‘ переносів ’);

writeln(‘ Можливі переноси: ’,slovo);

readln;

end.

Якщо ви знайшли приклади слів для тестування, у яких перенос ставиться не вірно, ми вас вітаємо – це і є оті маленькі підводні камінчики, які ми залишили для вас. Якщо не знайшли, то повідомимо деякі слова для перевірки і подальшої самостійної модифікації програми з метою видалення з них «авторських проколів», зроблених, до речі, цілком свідомо, отже слова для тестування: «заєць», «електростанція».

Ми сердечно вітаємо всіх, хто справиться з цією проблемою. Повірте на слово, що якщо ви знайшли спосіб усунення помилок, то ваше задоволення повинне бути не меншим нашого – значить цю книгу було написано не даремно!

 

Записи

 

Досить часто в повсякденному житті ми маємо справу не з одним видом інформації, а з комбінацією деяких видів інформації. Як приклад можна привести:

Ø телефонні довідники, де розміщено відомості про абонентів (прізвище, ім’я, по батькові, адреса, номер телефону);

Ø медична картка з різноманітними записам про пацієнта, але більшість з них співпадають (прізвище, ім’я, по батькові, дата народження, адреса, вага, зріст);

Ø облікова картка володаря автомобіля (марка автомобіля, державний та заводський номери, дата проходження технічного огляду, колір) і т.д.

У програмуванні подібні однотипні записи прийнято називати записами. Запис – це структурований тип даних, який об’єднує в собі різнотипні елементи, які називають полями запису. Запис описується спеціальною конструкцією, яка розпочинається словом Record. Наприклад, для ведення власного бібліотечного каталогу можна було б використати таку конструкцію:

Type { розділ опису типів}

Book = Record { початок запису }

AuthorFamile : string[20]; { прізвище автора }

AuthorName : string[15]; { ім’я автора }

Title : string[90]; { назва книги }

Date : word; { рік видання }

Page : integer; { кількість сторінок }

Klass : byte; { для визначення тематики книги }

End; { кінець запису }

Описаний тип можна графічно представити так:

Book

 

AuthorFamileAuthorNameTitleDatePageKlass

Тобто ми маємо тип, у якого характерним є те, що він містить у собі дані різних типів (поля). У свою чергу, поля можуть бути записами, так наприклад, ми могли б більш точно задати поле Date, розбивши його в свою чергу на поля Day (день), Month (місяць), Year (рік).

Для роботи з такими даними ми повинні у розділі змінних ввести довільні змінні, наприклад BookOld та BookNew типу Book і працювати з ними далі можна як і з звичайними змінними. Відмітимо, що крім доступу до змінних, ми ще можемо мати доступ і до окремих полів. Так, якщо нам, наприклад, потрібно зробити запис у поле Year, то це здійснюється при допомозі команди BookNew.Year := 1999. Як видно, ми спочатку вказуємо назву змінної, а потім, через крапку, назву поля.

Втім, для кращого розуміння сформулюємо і розв’яжемо задачу, в якій і покажемо методи роботи з записами.

Задача: 245.245. Скласти програму заповнення та редагування бази даних про домашній електронний телефонний довідник.

Розв’язання: Перед тим, як приступити до створення програми до поставленого завдання, ми повинні чітко усвідомити, які дані буде містити наша програма. Оскільки в задачі про це нічого не сказано, всю відповідальність покладено на нас.

Домовимось для простоти, оскільки нас цікавить сам метод роботи з записами, що у нашому довіднику будуть міститися лише прізвища абонентів та їх телефони. Причому прізвища ми оголосимо в розділі сталих, а номери телефонів візьмемо довільні, наприклад, будемо їх утворювати при допомозі генератора псевдовипадкових чисел. У цьому випадку наша програма може мати хоча б такий вигляд:

Program demoRecord;

uses dos, crt;

const n = 10;

Name : array[1..n] of string[15] = ('Петренко','Козлюк',

'Василенко','Носенко','Деричуб','Вернигора',

'Котигорошко','Iвасюк','Iваненко','Коваленко');

Type Abonent = Record

FName : string[15];

Phone : integer;

end;

Var Klient : array[1..2*n] of abonent;

i, kol : integer;

o, m : byte;

Procedure NewKlient;

begin

inc(kol);

write('Прiзвище нового абонента: '); readln(Klient[kol].FName);

write('Телефон нового абонента: '); readln(Klient[kol].Phone);

writeln;

end;

Procedure EditKlient;

begin

write('Номер по списку абонента: '); readln(m);

if (m > kol) or (m<1) then writeln('Будьте уважнi!')

else begin

write('Новий телефон абонента: ');

readln(Klient[m].Phone);

end;

writeln;

end;

Begin

for i := 1 to n do

begin

Klient[i].Fname := Name[i];

Klient[i].Phone := Random(10001)+20001;

end;

kol := n;

writeln;

repeat

for i := 1 to kol do writeln(i:2,'. ',Klient[i].Phone,' - ',Klient[i].Fname);

writeln;

write('1-Новий абонент 2-Редагувати телефон 3-Закiнчити роботу');

readln(o);

if o = 1 then NewKlient;

if o = 2 then EditKlient;

until o > 2;

end.

Як видно з приведеного тексту програми, робота з записами не вимагає нічого аж занадто складного, просто потрібно уважно працювати з відповідними змінними, не забуваючи вказувати потрібні поля. Ще раз наголошуємо, що при створенні власних програм, що будуть працювати з записами найбільш відповідальним є момент створення структури самого запису. Уявімо собі таку ситуацію, що у запропонованому варіанті розв’язання останньої задачі ми захотіли ввести прізвище ’Недеридоверхуноса’. Прізвище цілком можливе у тому розумінні, що може зустрітися прізвище, у якому більше ніж 15 символів. Що ж буде у цьому випадку? Нічого такого, що могло б заставити програму видати повідомлення про помилку – просто у поле FName буде занесено значення ’Недеридоверхуно’. Як бачите, наша примітивна база даних поступила досить розумно – вона просто відкинула від прізвища “зайві” символи, тобто ніби укоротила прізвище. У нашому випадку це не становить нічого страшного, але у випадку солідної програми, що працює ну хоча б в банку, така ситуація просто недопустима. Тому на етапі проектування структури запису потрібно бути дуже уважним і передбачити всі крайні випадки. Більше того, у нашій програмі ми поле для номера телефону зробили типу Integer, тобто ми наперед свідомо “запрограмували” можливість помилок в роботі програми.

Давайте ще раз запустимо на виконання нашу програму і введемо нового абонента з довільним прізвищем і телефоном ’2–22–22’. Спробуйте, і побачите, що програма видасть помилку і перестане працювати. Як ви вже здогадались, це пов’язано з тим, що ми вводили значення типу String, а програма очікувала отримати дані типу Integer. Спробуйте самостійно і досконало розібратись з питанням захисту програми від помилок під час її роботи, а ми до цього питання повернемось у наступному параграфі.

Важливою особливістю записів є те, що звертання до полів запису є трохи громіздким, особливо при використанні довгих імен змінних та полів. Для розв’язання цієї проблеми в мові Паскаль передбачено використання оператора with, який дозволяє лише один раз вказавши змінну типу запис в операторі with працювати з іменами полів як з звичайними змінними.

Так, у приведеній вище програмі, замість послідовності операторів

...

Klient[i].Fname := Name[i];

Klient[i].Phone := Random(10001)+20001;

...

можна було б записати так:

...

with Klient[i] do

begin

Fname := Name[i];

Phone := Random(10001)+20001;

end;

...

Для кращого розуміння оператора with рекомендуємо самостійно модифікувати попередню програму, використавши в ній вказаний оператор.

 

 

Файли

 

Ми не випадково винесли операції з файлами у кінець книги. Матеріал цього параграфу є досить важливим, оскільки практично при розв’язанні більшості задач практичного характеру виникає потреба зчитувати вхідні дані та записувати отримані результати у вигляді файлів – інформації, що зберігається в ПЕОМ. Для початку уточнимо поняття файлу, відоме вам з інших джерел.

Під файлом будемо розуміти деяку інформацію, що міститься у певному місці може мати певне ім’я. У випадку файлу, який розміщено на диску, це поняття можна було б подати у дещо іншому вигляді: файл – частина дискового простору (зовнішнього запам’ятовуючого пристрою), яка заповнена даними і має ім’я, що складене за певними правилами. Визначення, приведені нами, не є зовсім точними, але для розуміння організації роботи з файлами у мові Паскаль нам цього цілком досить.

З деякими операціями для роботи з текстовими файлами ми познайомились на початку книги (що поробиш – життя заставило!). Тепер настала черга більш детально розібратись з питанням роботи з довільними файлами.

У мові Паскаль розрізняють наступні види файлів: типізовані, текстові і нетипізовані. Слід відмітити, що у мові Паскаль файлова система найбільш повно використовує можливості операційної системи по передачі та обробці даних. Кожному файлу в мові ставиться у відповідність файлова змінна певного типу, тому перед початком роботи з файлом необхідно встановити дану відповідність. Для цього в мові Паскаль використовується процедура

Assign(f, Name);

де f – змінна довільного файлового типу, а Name – повне ім’я файлу, що задовольняє вимогам операційної системи. Якщо файл розміщено не в поточному каталозі, то слід вказувати ім’я файлу разом з повним шляхом до нього.

Процедура Assign завжди передує іншим процедурам роботи з файлами, так як ставить у відповідність конкретному файлу на зовнішньому пристрої логічну файлову змінну мови, до якої в майбутньому будуть звертатись всі інші файлові процедури. Слід відмітити, що недопустимо використовувати процедуру Assign для вже відкритого файлу.

Зручність використання процедури Assign при роботі з зовнішніми файлами не обмежується тільки дисковими файлами. Замість дискового файлу може виступати довільний пристрій введення–виведення інформації: клавіатура, принтер або монітор. Єдиною відмінністю від наведеного вище опису буде використання параметру Name, який в даному випадку буде містити символічне ім’я пристрою введення–виведення, що відповідає стандартним іменам операційної системи.

Для використання доступні такі символьні імена:

а) CON – пристрій консолі, для якого виведення здійснюється на екран монітора, а введення – з клавіатури. Стандартні текстові файли Input та Output, описані в розділі Interface модуля System, при ініціюванні по замовчуванню встановлюються на пристрій Con, що відповідає рядкам:

Assign(Input, 'CON');

Reset(Input);

Assign(Output, 'CON');

Rewrite(Output);

Після цього всі процедури запису Write і читання Read працюють відповідно з файлами Input та Output.

б) PRN – виведення інформації на принтер.

Задача: 246.246. Скласти програму, що виводить на принтер символи з 32-го по 255-й.

Розв’язання: Враховуючи вище сказане, та розглянуті набагато раніше процедури для роботи з текстовими файлами, програмна реалізація не становить труднощів.

var

txt : text;

i : integer;

st : string;

begin

Assign(txt, 'PRN');

Rewrite(txt);

writeln(txt,' Приклад друку на принтер: ');

for i := 32 to 255 do

begin

st := chr(i);

write(txt,st);

if i mod 40 = 0 then writeln(txt,'');

end;

Close(txt);

end.

Інші стандартні пристрої введення–виведення інформації ми розглядати не будемо, оскільки нас більше цікавлять інші питання.

Отже, процедура Assign дозволяє зв’язати довільну файлову змінну в програмі з конкретним зовнішнім файлом або пристроєм. Розглянемо далі загальні операції, які можна виконувати над усіма зовнішніми файлами, незалежно від їх специфіки.

Для роботи з файлом перед усе його потрібно відкрити. В мові Паскаль для цього передбачено дві процедури:

Reset(f); – відкриває існуючий файл.

Rewrite(f); – створює і відкриває новий файл для запису.

У описі обох процедур параметр f означає файлову змінну довільного типу. Зауважимо, що відкриття неіснуючого файлу може призвести до помилки при виконанні програми. Тому такі помилкові ситуації необхідно завжди відсліжувати при допомозі функції IOResult. Ми ж надалі вважатимемо, що у нас необхідні файли розміщені там, де потрібно і тому помилок у нас не повинно бути.

Існують деякі відмінності у використанні процедури Reset при відкритті різних типів файлів. У відношенні текстових файлів (типу text) дія процедури означає відкриття файлу тільки для читання. Для нетипізованих файлів в описі процедури додається ще один параметр RecSize типу word, який встановлює довжину запису для функцій обміну з файлом. У цьому випадку процедура Reset має вигляд:

Reset(f, RecSize);

Процедура Rewrite створює і відкриває новий файл для запису. Використання даної процедури вимагає особливої уваги. При спробі створити і відкрити новий файл з іменем вже існуючого файлу, дія процедури Rewrite призведе до видалення існуючого файлу і створення нового пустого файлу під тим же іменем. На практиці, щоб запобігти подібним випадкам, спочатку створюють резервні копії тих файлів, над якими можуть проводитись подібні критичні дії.

При відкритті нових нетипізованих файлів для вказування довжини запису в описі процедури Rewrite додається додатковий параметр RecSize типу word. У цьому випадку процедура має вигляд:

Rewrite(f, RecSize);

Якщо процедура Rewrite використовується для текстового файлу, то для нього в подальшому можуть бути використані лише операції запису.

Операція закриття файлу є логічним закінченням роботи з довільним відкритим файлом. Для цього служить процедура

Close(f);

Використання вказаної процедури призводить до закриття файлу, якому відповідає файлова змінна f і далі для того, щоб знову можна було працювати з цим файлом, над ним потрібно знову провести описані вище дії: поставити у відповідність файловій змінній ім’я файлу і відкрити його.

Задача: 247.247. Скласти програму, що виводить у файл ’Demo.txt’ символи з 32-го по 255-й.

Розв’язання: Ми частково використали умову попередньої задачі для того, щоб показати повний ланцюжок команд для створення простого текстового файлу і виведення в нього певної інформації.

var

f : text;

i : integer;

st : string;

begin

Assign(f, 'DEMO.TXT'); { Поставили у відповідність ім’я файлу }

Rewrite(f); { Відкрили файл для запису }

writeln(' Приклад виведення інформації у файл: ');

for i := 32 to 255 do

begin

st := chr(i);

write(f, st);

if i mod 40 = 0 then writeln(f,'');

end;

Close(f); { Закрили файл }

end.

До мовних засобів, що відіграють досить важливу роль у роботі з файлами відносяться і процедури переіменування та знищення (видалення) файлу.

Процедура Rename(f, NewName) перейменовує файл, поставлений у відповідність файловій змінній f і задає йому нове ім’я, що міститься у змінній NewName.

А процедура Erase(f) видаляє невідкритий зовнішній файл, якому поставлено у відповідність файлову змінну f. Наголошуємо, що файл, який видаляється, не повинен бути перед цим відкритий жодною з відповідних процедур відкриття файла.

Для того, щоб опрацьовувати таку і інші критичні ситуації у мові передбачено спеціальну функцію IoResult, яка працює без параметрів і повертає значення типу Integer, яке повідомляє статус останньої виконаної операції введення–виведення. Якщо функція повернула 0, то операція виконана успішно, в противному випадку ні і про характер помилки можна судити з значення, що повернула функція. Детально про кожне значення можна дізнатись в технічній документації до версії середовища програмування, яким ви користуєтесь.

Для використання функції IoResult потрібно відключити стандартну перевірку операцій введення–виведення. Здійснюється це при допомозі спеціальної вказівки інтегрованої системи програмування, яку програмно можна задати директивою компілятору {$I}. Покажемо це на прикладі такої задачі.

Задача: 248.248. У текстовому файлі INPUT.TXT у кожному рядку записано або слово, або число. Порахувати кількість чисел, записаних у файлі.

Розв’язання: Існує декілька шляхів розв’язання цієї задачі. Ми використаємо той, який базується на використанні функції IoResult. Суть способу розв’язання буде полягати у тому, що ми відключимо стандартну обробку операцій введення–виведення і будемо вважати, що у кожному рядку записано число. Якщо при зчитуванні з файлу змінної числового типу (наприклад, real) у нас функція IoResult буде повертати нуль, значить ми зчитали число, якщо ж довільне інше значення – значить у рядку містилось слово. Описаний алгоритм реалізуємо у програмі:

program Pidrachunok;

uses dos, crt;

var f : text;

kol, kod : integer;

r : real;

begin

Assign( f,'INPUT.TXT');

Reset( f );

kol := 0;

{$I-}

while not(eof( f )) do

begin

readln( f, r );

kod := ioresult;

writeln(kod,' r = ',r,' kol = ',kol );

if kod = 0 then inc(kol);

end;

{$I+}

Close( f );

write('Кiлькiсть чисел у файлi = ', kol);

readln

end.

У даній програмі ми використали ще одну функцію, яка застосовується при роботі з файлами – eof( f). Дана функція повертає булівське значення True, якщо вказівник кінця файлу знаходиться відразу ж за останнім компонентом і False у противному випадку. Іншими словами, дана функція повідомляє, чи досягли ми кінця файлу, чи ні. Подібним чином діє ще одна функція – eoln(f), яка повідомляє про досягнення кінця рядка.

Для роботи з текстовими файлами застосовують ще одну процедуру – Append(f); вона відкриває вже існуючий файл і встановлює вказівник у кінець файлу, тобто, використавши дану процедуру ми маємо змогу дописувати інформацію у кінець файлу.

А як бути у випадку, коли нам потрібно записати інформацію десь в середині файлу, або на початку? На практиці у цьому випадку опрацювання текстових файлів зводиться до зчитування всього файлу в пам’ять, а потім вже до запису модифікованого файлу на диск. Інколи (при опрацюванні великих за розмірами файлів) використовують тимчасові файли для збереження проміжної інформації, які по закінченню роботи сама ж програма видаляє з диску.

При роботі з типізованими файлами, якими є файли чітко визначеного типу, застосовується дещо специфічні методи та способи роботи, які ми й розглянемо далі. Одразу ж відмітимо, що записи, розглянуті в попередньому параграфі, є прикладом типізації даних для файлів.

У цьому випадку оголошення файлової змінної в програмі можна здійснити, наприклад, таким чином:

Type FileRec = Record

FName : string[15];

Phone : integer;

end;

var f : file of FileRec;

Саме у цьому і полягає відмінність між текстовими і типізованими файлами: якщо у текстовому файлі їх вміст розглядається як набір символів, підготовлених спеціальним чином з врахуванням загальноприйнятих домовленостей про представлення текстової інформації, то в типізованих файлах їх вміст розглядається як послідовність записів певного типу. Що є досить важливим, так це те, що одиницею вимірювання такого набору даних є сам запис. Довжина запису визначається як SizeOf(FileRec). Іншими словами можна сказати, що мова йде про файли прямого доступу.

Процедура Seek(f, N) встановлює поточну файлову змінну f на запис з номером N. f – файлова змінна для типізованих і нетипізованих наборів даних. Запису у таких файлах нумеруються підряд, починаючи з нуля. Для тих, хто звик починати нумерувати з 1, це може призвести до виникнення помилок читання–запису, що в свою чергу може призвести до порушення, або навіть знищення важливої інформації, що опрацьовується програмістом. Справа ускладнюється ще й тим, що неправильне встановлення позиції на запис при допомозі процедури Seek, як правило, не призводить до видимих помилок введення–виведення, на які можна відреагувати. Винятки становлять випадки, коли немає доступу до файлу, файл не відкрито або встановлено позицію на неіснуючий запис – такі випадки, як вже відмічалось, опрацьовуються при допомозі функції IoResult.

Задача: 249.249. Удосконалити програму про домашній електронний телефонний довідник таким чином, щоб інформація зберігалась на диску.

Розв’язання: Оскільки ми вже домовились, що наша програма служить виключно демонстраційним цілям, то збережемо структуру запису, запропоновану при розв’язанні однієї з попередніх задач. Отже, нашим завданням по суті справи є модифікація тексту програми таким чином, щоб ми могли працювати з уже існуючою базою даних.

Для початку створимо базу даних на диску. Структура бази даних залишиться такою ж, як у згадуваній вище задачі. Це можна зробити при допомозі, наприклад, такої програми:

program CreateBasePhone;

uses dos, crt;

const n = 10;

Name : array[1..n] of string[15] = ('Петренко','Козлюк',

'Василенко','Носенко','Деричуб','Вернигора',

'Котигорошко','Iвасюк','Iваненко','Коваленко');

Type Abonent = Record

FName : string[15];

Phone : integer;

end;

Var Klient : array[1..n] of abonent;

i, kol : integer;

o, m : byte;

f : file of abonent;

Begin

Assign(f,'PHONE0.DBF');

Rewrite(f);

for i := 1 to n do

begin

Klient[i].Fname := Name[i];

Klient[i].Phone := Random(10001)+20001;

write(f,klient[i]);

end;

Close(f);

end.

Зверніть увагу, що ми описали наш файл як типізований файл типу Abonent. У всіх подальших процедурах для даної задачі ми повинні чітко дотримуватись запропонованої нами ж структури. Отже, надалі ми будемо працювати тільки з базою даних, що місить на диску, тому у нас вже немає потреби описувати початкові дані про базу даних у розділі констант.

Приведена вище програма нам була потрібна лише як засіб для створення початкової бази даних. Програма ж, що реалізовує завдання, поставлене в умові задачі, може мати такий вигляд:

program BasePhone;

uses dos, crt;

const n = 100;

Type Abonent = Record

FName : string[15];

Phone : integer;

end;

Var Klient : array[0..n] of abonent;

i, kol : integer;

o, m : byte;

f : file of abonent;

Procedure NewKlient; { додавання нового абонента в базу даних }

begin

inc(kol);

write('Прiзвище нового абонента: '); readln(Klient[kol].FName);

write('Телефон нового абонента: '); readln(Klient[kol].Phone);

Seek(f,kol);

write(f,klient[kol]);

writeln;

end;

Procedure EditKlient; { редагування вибраного абоненту в базі даних }

begin

write('Номер по списку абонента: '); readln(m);

if (m > kol) or (m<1) then writeln('Будьте уважнi!')

else begin

write('Новий телефон абонента: ');

readln(Klient[m-1].Phone);

Seek(f,m-1);

write(f,klient[m-1]);

end;

writeln;

end;

Begin

Assign(f,'PHONE0.DBF');

Reset(f);

i := 0;

while not eof(f) do

begin

read(f,Klient[i]);

inc(i);

end;

kol := i-1;

writeln;

repeat

for i := 0 to kol do

writeln(i+1:2,'. ',Klient[i].Phone,' - ',Klient[i].Fname);

writeln;

write('1-Новий абонент 2-Редагувати телефон 3-Закiнчити роботу ');

readln(o);

if o = 1 then NewKlient;

if o = 2 then EditKlient;

until o > 2;

Close(f);

end.

Ми рекомендуємо вам уважно розібратись з нумерацією записів у базі даних і звертанням до номерів записів у процедурах редагування запису та додавання нового запису. Важливим є ще два моменти – ми не використовуємо процедур Rewrite та Append, саме тому звертаємо увагу на той факт, що закрили файл ми у самому кінці програми. Встановлення потрібного номеру запису ми здійснювали при допомозі процедури Seek. Рекомендуємо самостійно деталізувати структуру запису Abonent, додати процедури для видалення, пошуку (за різними критеріями) та сортування записів – таким чином можна отримати досить пристойну програму для роботи з базою даних.

Типізовані файли дають можливість організувати роботу в прямому режимі читання–запису. Ця можливість відіграє вирішальну роль при визначенні типу файлів, яким віддати перевагу для більшості прикладних задач. Інформація в типізованих наборах даних представлена в тому ж вигляді, що і в пам’яті комп’ютера під час виконання програми. Саме тому відпадає потреба відсліжувати керуючі послідовності типу кінець рядка.

Для роботи з файлами прямого доступу служать ще дві спеціальні функції: FilePos і FileSize.

Функція FilePos(f) – повертає для файла f поточну файлову позицію, тобто номер запису, на який вона встановлена, а FileSize(f) – повертає для файла f його розмір, тобто кількість записів. Обидві функції повертають значення типу longint. Уважні читачі мабуть помітили, що у нашій останній версії телефонного довідника ми не використовували цих функцій. Так, наприклад, встановлення файлового покажчика в кінець файлу для доповнення бази новим абонентом, можна було б організувати і таким чином: Seek(f, FileSize(f)).

Уявимо тепер собі ситуацію, що ми зробили нашу базу даних про абонентів телефонної мережі досить великою, скажімо в ній містяться дані про приватні телефони жителів області. Навіть на швидкодіючих комп’ютерах пошук у такій базі даних по прізвищу чи по номеру телефону вимагає досить багато часу. Як же бути у такому випадку?

Коли записи розміщуються у невідсортованому, скажімо за прізвищем, порядку, пошук потрібно здійснювати послідовно по всьому файлу. З логічної точки зору таке розміщення записів не є вдалим, так як потребує значних затрат часу і ресурсів системи для пошуку потрібного набору даних. Для прискорення пошуку (і досить значного) за відповідним критерієм розробники програмного забезпечення створюють так звані індексні файли по відношенню до головного файлу бази даних. Індексні файли містять номери записів головного файлу, відсортовані за певним ключем. Така побудова бази даних значно зменшує час роботи з зовнішніми носіями інформації, оскільки саме на ці операції звичайно витрачається найбільше часу.

Настала черга розглянути файли, роботу з якими організовано з максимально можливою швидкістю – це нетипізовані файли. Нетипізований файл розглядається як сукупність символів або байтів. З огляду на раніш розглянутий матеріал, такі файли можна було б описати як fileofchar та fileofbyte. Оскільки символьна і байтова інформація тісно пов’язані між собою, то немає потреби здійснювати організацію двох типів файлів, а їх можна описати одним типом. Саме такий тип, який працює з файлами усіх раніше розглянутих типів і відноситься до нетипізованих файлів.

У розділі оголошень нетипізований файл описується так:

Var f : file;

Опис дещо схожий на опис типізованих файлів, але на цьому ця схожість і закінчується. Головною відмінністю нетипізованих файлів від раніше розглянутих типів файлів є те, що для роботи з ними використовують не стандартні операції введення–виведення Read і Write а процедури BlockRead і BlockWrite, які дають можливість працювати набагато швидше, оскільки можуть працювати з набагато більшими об’ємами інформації.

Процедура BlockRead(var f:file; var Buf; Count:word {; result:word}); зчитує з файлу визначену кількість блоків в пам’ять, починаючи з першого байта. Параметр Buf є змінною, яка використовується для накопичення інформації з файлу f, а параметр Count задає кількість блоків, які буде прочитано. Параметр Result є необов’язковим і містить в собі після виклику процедури кількість дійсно прочитаних записів.

Відповідно для швидкого запису у нетипізований файл призначено процедуру Blockwrite(var f:file; var Buf; Count:word {; result:word}). Всі параметри процедури аналогічні попереднім.

Необхідно відмітити, що обидві процедури працюють блоками. Об’єм блоку в байтах визначається за формулою:

Об’єм = Count · RecSize,

де RecSize –розмір запису файла, заданий при його відкритті. Загальний об’єм одноразового обміну не повинен перевищувати 64 Кбайт. Ще однією перевагою використання цих двох процедур є те, що у нас є можливість самостійно визначати розмір буфера для файлових операцій. При розв’язанні завдань, які вимагають чіткого планування ресурсів системи, ця можливість відіграє значну роль.

При написанні архіваторів, сервісних програм для роботи з файлами та дисками, часто виникає потреба у розбитті файлу на частини або злитті декількох файлів. Уявімо собі, що база даних, створена нами дещо раніше, в результаті багаторічних доповнень переросла у базу даних всесвітньої телефонної мережі (а чому б і ні!). Навіть у заархівованому виді, вона займає декілька десятків Мбайт. Як же нам перенести інформацію з одного віддаленого комп’ютера на інший, якщо у нашому розпорядженні є лише можливість переносу інформації на гнучких дисках розміром 3’5 (1,44 Мбайт)? І як нам зібрати інформацію разом на новому місці.

Звичайно, архіватори дозволяють розбивати архіви на диски, але архіватори писали програмісти і, цілком можливо, що і вам захочеться написати колись власний архіватор, який буде стискувати інформацію краще, ніж всі відомі архіватори на сьогоднішній день. Як довільна системна програма, ваш архіватор повинен передбачити всі можливі випадки, у тому числі і випадок розбиття файлу на окремі файли, які потім можна було б, знову ж таки, з використанням вашої програми, об’єднати в один файл.

 

Вправи та завдання

 

250.250.Дано файл f, компоненти якого є цілими числами. Знайти:

а) найбільше число, записане у файлі;

б) найменше число, записане у файлі;

в) суму найбільшого і найменшого чисел файлу;

г) кількість чисел у файлі;

д) кількість парних чисел у файлі;

е) кількість чисел у файлі, що діляться на 3;

є) кількість квадратів непарних чисел.

251.251. Дано два текстових файли f і g. Поміняти місцями вміст файлів.

252.252. Дано файл f, компоненти якого є символами. Отримати файл g, утворений з файлу f заміною всіх малих літер на великі (прописні).

253.253. У файлі f записано деякі цілі числа. Впорядкувати у даному файлі всі числа за неспаданням.

254.254. Дано текстовий файл f, у якому окремі слова відокремлено пропуском або символом переведення рядка. Підготувати файл g для друку слів у дві колонки по N рядків на сторінці. Слова повинні бути розміщені у файлі для друку таким чином:

1–е слово N+1 – е слово

2–е слово N+2 – е слово

3–е слово N+3 – е слово

... ...

N–1–е слово N+N–1 – е слово

N–е слово 2N – е слово

а потім повинна йти наступна сторінка і т.д.

255.255. У текстовому файлі записано деякий текст з розділовими знаками. Замінити у ньому всі слова “книга” на “підручник”. Врахувати можливість написання літер у слові великими літерами.

256.256. У текстовому файлі замінити всі групи пропусків, більші за 1 на 1 пропуск.

257.257. У файлі, що складається з дійсних чисел, знайти розмір самої довгої неспадаючої послідовності.

258.258. У файлі зберігається деякий текст на українській мові. Складіть програму, яка під час роботи при кожному звертанні до файлу повертала б чергове слово тексту.

259.259. Складіть програму кодування та розкодування текстового файлу з використанням пароля.