[ЗМІСТ] [Далі] [Назад] [Початок розділу]
7.9. Файли у мовах
програмування
У мовах програмування
робота з файлами відрізняється від роботи з іншими типами даних тим, що дані
файлів звичайно зберігаються не у оперативній пам’яті комп’ютера, а на
зовнішніх пристроях: магнітних дисках, стрічках, виводяться на пристрій друку
або екран монітора тощо. З точки зору операційних систем, які забезпечують
роботу комп’ютерів, файл - це область пам'яті на зовнішньому носії, яка
призначена для зберігання інформації та має власне ім’я. Файл є одиницею
збереження інформації на зовнішньому носії. У файлах можуть зберігатися
програми, дані, тексти документів, інструкцій, закодовані зображення тощо. Для
використання файлів програмою у деякій мові програмування необхідно виконати
зв’язування змінної типу файл (файлової змінної) з тим чи іншим файлом на зовнішньому
носії. Таке зв’язування звичайно передує будь-яким діям з файлами.
У мові
Паскаль тип даних файл із t описується як
type tf = file of t;
де в якості базового типу t може виступати
будь-який з раніше розглянутих типів
(частіше за все t - це тип запису), а
файлові змінні визначаються, наприклад, таким чином:
var F: tf
або
var F: file of t.
Приклад описів:
const D_Name = 80; D_Num = 7;
type StrF = string[D_Name]; StrN = string[D_Num];
Phone = record
Num: StrN;
Name: StrF
end;
var FL: file of Phone.
Треба
зазначити, що Паскаль не розрізняє на синтаксичному рівні послідовні файли та
файли прямого доступу, тобто, будь-який файл можна обробляти як послідовний
файл або як файл прямого доступу. Нумерація записів у файлах починається з 0.
Нехай F
- файлова змінна з компонентами типу t, Str - рядок символів, р - змінна
типу t, n - цілий вираз. Над даними файлового типу визначені такі процедури:
assign(F,
Str) – зв’язати файлову змінну F з файлом на зовнішньому пристрої, що має ім'я
Str. Наприклад, щоб зв'язати файлову
змінну А з файлом що має ім'я Rez.dat, необхідно записати assign(А,'Rez.dat');
rewrite(F)
- створити новий файл. Створюється новий файл з ім’ям, яке заздалегідь
визначене в процедурі assign. Якщо на
зовнішньому пристрої вже був файл з
таким ім'ям, він знищується. Маркер файла встановлюється на перший запис з
номером 0;
reset(F)
- встановити маркер до початку файла
F. Процедура застосовується тільки до існуючого
файла;
read(F,
р) - читати з файла F запис, на який вказує маркер,
у змінну p. Після закінчення процедури маркер вказує
на наступну компоненту файла;
write(F,
р) - писати у файл. Значення змінної р присвоюється
тій компоненті файла F на яку вказує
маркер. Якщо маркер встановлено у кінці файла, запис p додається до файла. Після виконання
процедури маркер вказує на наступну компоненту файла;
seek(F,
n) - встановити маркер в файлі F на запис з номером n (починаючи з 0);
close(F)
- закрити файл F. В кінці роботи з
файлом його завжди закривають, що гарантує збереження записаної інформації;
і функції:
eof(F)
– перевірка, чи досягнуто кінець файла;
filesize(F)
- довжина файла. Функція повертає ціле значення, яке дорівнюе кількості записів
файла F. Якщо filesize(F)=0, то файл порожній,
інакше він містить дані;
filepos(F) – позиція маркера у
файлі, починаючи з 0.
Очевидно,
що перехід до кінця файла можна здійснити за допомогою процедури seek (F,
filesize(F)).
Таким чином, процес створення
нового файла включає кроки:
- assign;
- rewrite;
- write;
- close;
доступ до
компонентів існуючого файла складається з дій:
- assign;
- reset;
- read або seek (до потрібного запису);
- обробка необхідного запису;
- close;
і, нарешті, розширення
файла складається з етапів:
- assign;
- reset;
- маркер на кінець файла;
- write;
- close.
Приклад 7P. Телефонний довідник.
Необхідно реалізувати створення довідника, його заповнення, доповнення, пошук
телефона та заміну телефона за прізвищем
program Ref_Book;
const D_Name = 80; D_Num = 20;
type StrF = string[D_Name]; StrN = string[D_Num];
Phone = record
Num : StrN;
Name: StrF
end;
FOP = file of Phone;
var FL: FOP; R: byte; FName: string;
{ ***** Запис інформації у файл ***** }
procedure WriteToFile (var F: FOP);
var A: Phone; Yes: char;
begin
repeat
write('Номер телефону: ?'); readln(A.Num);
writeln('Дані про власника телефону:');
readln(A.Name); write(F,A);
write('Продовжувати введення [Y/N]: ?'); readln(Yes)
until (Yes = 'N') or (Yes = 'n');
writeln; close(F)
end;
{ ***** Заповнення файла ***** }
procedure FillFile (var F: FOP);
begin
rewrite(F); WriteToFile(F)
end;
{ ***** Розширення файла ***** }
procedure AppFile (var F: FOP);
begin
reset(F); seek(F,filesize(F)); WriteToFile(F)
end;
{ ***** Пошук у файлі ***** }
procedure SearchPhone (var F: FOP);
var P: boolean; N: StrF; A: Phone;
begin
reset(F); P:= False;
write('Задайте власника телефону для пошуку: ');
readln(N); writeln;
while not eof(F) and not P do begin
read(F,A);
P:= A.Name=N
end;
if P then writeln('*** Телефон *** ',A.Num)
else writeln('Телефон абонента ',N,' відсутній!!!');
close(F);
end;
{ ***** Заміна телефона ***** }
procedure ReplacePhone (var F: FOP);
var P: boolean; N: StrF; T: StrN;
A: Phone;
i,m: longint;
begin
reset(F);
write('Задайте власника телефону для заміни: ');
readln(N);
write('Задайте новий телефон: ');
readln(T);
writeln;
m:=filesize(F); i:=0; P:= False;
while (i<m) and not P do begin
i:=i+1;
read(F,A);
P:= A.Name=N
end;
if P then begin
seek(F,i-1); A.Num:=T; write(F,A);
writeln('*** Заміну виконано
*** ')
end
else writeln('Телефон абонента ',N,' відсутній!!!');
close(F);
end;
{ ***** Основна програма ***** }
begin
write('Ім''я файла: ?'); readln(FName);
assign(FL,FName);
repeat
writeln(' Вкажіть режим роботи :');
writeln(' 1 : заповнення');
writeln(' 2 : розширення');
writeln(' 3 : пошук');
writeln(' 4 : заміна');
writeln(' 5 : кінець роботи');
write(‘>’); readln(R);
case R of
1: FillFile(FL);
2: AppFile(FL);
3: SearchPhone(FL);
4: ReplacePhone(FL);
end
until R = 5;
end.
У цьому прикладі інструкції заповнення файла та пошуку використовують файл як послідовний, а доповнення та заміни - як файл прямого доступу.
Текстові файли у мові Паскаль
описують так:
var F: text;
Для текстових файлів залишаються визначеними
процедури та функції:
assign;
reset;
rewrite;
close;
eof.
Використовувати текстовий файл як файл прямого
доступу заборонено. Для текстових файлів також забороняється одночасно читати
та писати дані.
Читання
та запис у текстовий файл здійснюється за допомогою процедур:
read(F, x1, x2, ..., xn) –
читати з файла F значення
змінних x1, x2, ..., xn;
readln(F, x1, x2, ..., xn) –
читати з файла F значення
змінних x1, x2, ..., xn та перейти до наступного рядка файла
(пропустити все до кінця поточного рядка, включаючи символи завершення рядка);
write(F, e1, e2, ..., en) –
писати у файл F значення
виразів e1, e2, ..., en;
writeln(F, e1, e2, ..., en) –
писати у файл F значення виразів
e1, e2, ..., en та
перейти до наступного рядка файла (записати символи завершення рядка у файл);
Процедури
read, readln, write, writeln є, в деякій мірі,
аналогами інструкцій читати, читати#, писати
та писати# відповідно. Різниця полягає в тому, що процедури у Паскалі можуть мати
довільну кількість параметрів (>1), а також самі
параметри можуть належати не тільки до символьного типу або типу рядок, але й
до стандартних числових типів Паскаля (для виведення допустимий також
булівський тип). При введенні або виведенні даних числових типів виконується
неявне перетворення. При введенні числові дані перетворюються з типу рядок до
внутрішнього формату збереження чисел, а при виведенні виконується обернене
перетворення. Дані символьного типу та типу рядок вводяться та виводяться без
перетворень. Наприклад, нехай маємо описи:
var X: real; N: integer;
C: char; B: boolean;
S: string[3];
Нехай, також частина текстового файла F після маркера має вигляд:
^2.67ÿÿ#ÿÿÿ-356ÿabcÿ#ÿ12
де символом ÿ позначено пропуск, ^ - маркер, а # - символ кінця рядка. Тоді після введення даних за
допомогою процедури
read(F,X,N,C,S)
змінні набудуть таких значень: X=2.67, N=-356, C=’ ’, S=’abc’. Положення маркера буде таким:
2.67ÿÿ#ÿÿÿ-356ÿabc^ÿ#ÿ12
Якщо введення здійснювати за допомогою
readln(F,X,N,C,S)
то значення змінних будуть тими ж самими, а
положення маркера –
2.67ÿÿ#ÿÿÿ-356ÿabcÿ#^ÿ12
Тобто, при введенні числових даних з текстового
файла Паскаль пропускає всі пропуски або символи кінця рядка до першого
символа, що не є пропуском. Далі послідовність символів до першого пропуска
інтерпретується як позиційний запис числа відповідного типу. Ця послідовність
вводиться та виконується необхідне перетворення.
Розглянемо
тепер запис у текстовий файл. Нехай наші змінні мають такі значення: X=2.67, N=-356, C=’ ’, S=’abc’, B=true. Якщо записати їх у
файл за допомогою
write(F,X,N,C,S,B)
то додана частина файла буде мати вигляд:
2.6700000000E+00-356ÿabcTRUE^
Якщо ж скористатися
writeln(F,X,N,C,S,B)
то у файл будуть додатково записані символи кінця
рядка:
2.6700000000E+00-356ÿabcTRUE#^
Отже, під час запису у файл Паскаль не відділяє
числові дані пропусками і для повторного читання даних з файла треба це робити
явно у write або writeln, наприклад,
writeln(F,X,’ ’,N)
Для текстових файлів
у мові Паскаль додатково визначаються:
процедура append(F) – відкрити існуючий
текстовий файл для запису та встановити маркер у кінець файла;
функція
eoln(F) – перевіряє, чи досягнуто кінець рядка файла.
Мова
Паскаль дозволяє працювати з двома стандартними текстовими файлами: input та output. Файл input за угодою пов’язаний з введенням з
клавіатури, а файл output – з
виведенням на екран у текстовому режимі. Для цих файлів у програмі не потрібно
використовувати assign, reset, rewrite, close. Крім цього, у процедурах read, readln, write, writeln можна не вказувати перший параметр, відповідно input або output. Наприклад, замість
readln(input,x); writeln(output,y)
можна писати
readln(x); writeln(y)
Таким чином, введення та виведення, яке ми робили
до цього часу, насправді є читанням та записом даних стандартних текстових
файлів input та output.
Приклад 8P. Замінити всі входження у
текстовий файл символа c рядком A. (задача 7.18)
Program ReplaceChars;
procedure ReplaceText (c: char; A: string; var F,G: text);
function Replace(S, A: string; c: char): string;
var S1: string; i: word;
begin
S1 := ‘’;
for i:=1 to length(S) do
if S[i] = c then S1 := S1+A
else S1 := S1+S[i];
Replace := S1
end;
var S, S1: string;
begin
reset(F); rewrite(G);
while not eof(F) do begin
readln(F,S);
S1 := Replace(S,A,c);
writeln(G,S1)
end;
close(F); close(G)
end;
var F, G: text;
FN, GN, A: string;
c: char;
begin
write(‘Ім’’я вхідного файла: ’);
readln(FN); assign(F,FN);
write(‘Ім’’я результуючого файла: ’);
readln(GN); assign(G,GN);
write(‘Символ для заміни:’); readln(c);
write(‘Рядок заміни:’); readln(A);
ReplaceText(c,A,F,G)
end.
У мові Сі всі файли безтипові, тобто тип
записів файла не фіксований та визначається програмістом. Опис файлової змінної
є описом вказівника на структуру FILE, наприклад:
FILE *fp;
У мові
Сі, на відміну від Паскаля послідовні файли та файли прямого доступу
розрізняються способом їх відкриття. Нумерація записів у файлах починається з
0.
Нехай fp - файлова змінна з компонентами типу t, str - рядок символів, р - змінна
типу t, n - цілий вираз. Над даними файлового типу визначені такі підпрограми:
fp = fopen(str,mode) – зв’язати
файлову змінну fp з файлом на
зовнішньому пристрої, що має ім'я str, та відкрити файл для обробки згідно з режимом, який заданий рядком mode. Рядок mode може мати значення: “rb”, “wb”, “ab” – для послідовних
файлів; “r+b”, “w+b”, “a+b” – для файлів прямого доступу. “r” означає відкриття
існуючого файла для читання; “w” – створення файла для запису; “a” – відкриття існуючого
файла для доповнення. Наявність ‘+’ означає можливість
заміни записів у файлі, що характерно для файлів прямого доступу. Наприклад,
fp = fopen(“rez.dat”, “wb”);
fp = fopen(“mod.dat”, “r+b”);
fread(&р, sizeof(p), 1, fp); - читати з файла fp
запис, на який вказує маркер, у змінну
p. Після закінчення процедури маркер вказує на
наступну компоненту файла;
fwrite(&р, sizeof(p), 1, fp); - писати у файл. Значення змінної р присвоюється
тій компоненті файла fp на яку вказує маркер. Якщо маркер
встановлено у кінці файла, запис p додається до файла. Після виконання процедури маркер вказує на наступну
компоненту файла;
fseek(fp, n*sizeof(t), SEEK_SET) - встановити маркер в файлі fp на запис з номером n (починаючи з 0). Значення третього параметра окрім
константи SEEK_SET, що означає відлік позиції маркера від
початку файла, може також бути SEEK_CUR та SEEK_END що означає зсув позиції відносно поточного положення маркера та кінця
файла відповідно;
fclose(fp) - закрити файл fp. В кінці роботи
з файлом його завжди закривають, що гарантує збереження записаної інформації;
feof(fp) – перевірка, чи досягнуто кінець файла. На
відміну від Паскаля, перевірити, чи досягнуто кінець файла, можна тільки
спробувавши прочитати запис за допомогою fread;
ftell(fp) – позиція маркера у файлі, починаючи з 0. ftell повертає фактично номер поточного байта, а не поточного запису, як filepos у Паскалі. Для того, щоб знайти номер
поточного запису, можна використати вираз: ftell(fp)/sizeof(t).
Таким чином, процес створення
нового файла включає кроки:
- fopen(str, “wb”);
- fwrite;
- fclose;
доступ до
компонентів існуючого файла складається з дій:
- fopen(str, “rb”) (або fopen(str, “r+b”));
- fread або fseek (до
потрібного запису);
- обробка необхідного запису;
- fclose;
і, нарешті, розширення
файла складається з етапів:
- fopen(str, “ab”);
- fwrite;
- fclose.
Приклад 7С. Телефонний довідник.
Необхідно реалізувати створення довідника, його заповнення, доповнення, пошук
телефона та заміну телефона за прізвищем
#include
<stdio.h>
#include
<string.h>
/* RefBook */
#define FALSE
0
typedef struct
{
char name[30];
char tel[20];
} phone;
char
fname[81];
fillfile()
{
phone p;
int i,n;
FILE *fp;
fp=fopen(fname,"wb");
printf("\nВведіть n:");
scanf("%d",&n); getchar();
for (i=1;i<=n;i++)
{
printf("Дані про власника телефону
%d:",i);
gets(p.name);
printf("Номер телефону %d:",i);
gets(p.tel);
fwrite(&p,sizeof(p),1,fp);
}
fclose(fp);
}
appfile()
{
phone p;
FILE *fp;
fp=fopen(fname,"ab");
printf("Дані про власника
телефону:");
gets(p.name);
printf("Номер телефону:");
gets(p.tel);
fwrite(&p,sizeof(p),1,fp);
fclose(fp);
}
findtel(char
s[], char t[])
{
phone p;
FILE *fp;
int b;
fp=fopen(fname,"rb");
b=FALSE; strcpy(t,"");
do
{
fread(&p,sizeof(p),1,fp);
if (!feof(fp) && (b=strcmp(p.name,s)==0))
strcpy(t,p.tel);
}
while (!feof(fp) && !b);
fclose(fp);
}
changetel(char
s[], char t[])
{
phone p;
FILE *fp;
char t1[20];
int b;
long pos;
fp=fopen(fname,"r+b");
b=FALSE;
do
{
fread(&p,sizeof(p),1,fp);
if (!feof(fp)) b=strcmp(p.name,s)==0;
}
while (!feof(fp) && !b);
if (!b) printf("Телефон не знайдено,
замінити неможливо");
else
{
pos=ftell(fp);
fseek(fp,pos-sizeof(p),SEEK_SET);
strcpy(p.tel,t);
fwrite(&p,sizeof(p),1,fp);
}
fclose(fp);
}
main()
{
char s[30],t[20],c;
int k;
printf("Ім’я файла: ");
gets(fname);
do
{
printf("\nВведіть режим роботи
(1-5):");
scanf("%d",&k); getchar();
switch (k)
{
case 1: fillfile(); break;
case 2: appfile(); break;
case 3: printf("\nЗадайте власника
телефону для пошуку:");
gets(s);
findtel(s,t);
if (strlen(t)>0)
printf("\nТелефон: %s",t);
else printf ("\nТелефон не
знайдено");
break;
case 4: printf("\nЗадайте власника телефону для заміни:");
gets(s);
printf("\nЗадайте новий
телефон:");
gets(t);
changetel(s,t);
break;
}
}
while (k != 5);
}
У цьому
прикладі інструкції заповнення, доповнення файла та пошуку використовують файл
як послідовний, а інструкція заміни - як файл прямого доступу.
Текстові
файли у мові Сі описують так само, як і розглянуті раніше послідовні файли та
файли прямого доступу. Вказати, що файл текстовий, можна підчас відкриття файла
за допомогою fopen:
fp = fopen(str,mode)
Рядок mode для текстових файлів може мати значення: “r”, “w” або “a”. “r” означає відкриття
існуючого файла для читання; “w” – створення файла для запису; “a” – відкриття існуючого
файла для доповнення. Наприклад,
fp = fopen(“rez.txt”, “w”);
fp = fopen(“mod.txt”, “r”);
Для текстових файлів залишаються також визначеними
підпрограми:
fclose;
feof.
Читання
та запис у текстовий файл здійснюється за допомогою підпрограм:
fscanf(fp, format, &x1, &x2, ..., &xn) – читати з файла fp
значення змінних x1, x2, ..., xn. format – це рядок форматів введення аналогічно
форматам, визначеним для scanf;
c=fgetc(fp) – читати з файла fp
значення літери c;
fgets(str, n, fp) – читати з файла fp не
більше (n-1) символів та присвоїти результат
рядку str. Якщо до кінця рядка файла залишилось менше (n-1) символів, то будуть
прочитані тільки символи до кінця рядка а також символи кінця рядка;
fprintf(fp, format, e1, e2, ..., en) – писати у файл F значення виразів e1, e2, ..., en. format – це рядок форматів виведення аналогічно форматам, визначеним для printf;
fputc(c,fp) – писати у файл fp значення літери c;
fputs(str,fp) – писати у файл fp рядок str.
Підпрограми
fscanf, fprintf для числових типів даних діють аналогічно
read, write Паскаля. Для запису у текстовий файл
символа кінця рядка треба явно вказати його (‘\n’) у рядку форматів fprintf або у рядку str fputs.
У мові
Сі, як і у Паскалі, є два стандартних текстових файли: для введення та для
виведення. Файл для введення за угодою пов’язаний з введенням з клавіатури, а
файл для виведення – з виведенням на екран у текстовому режимі. Для цих файлів
у програмі не потрібно використовувати fopen та fclose. Введення для стандартного файла виконується підпрограмами: scanf, getchar, gets, а виведення – підпрограмами: printf, putchar, puts.
Приклад 8С. Замінити всі входження у
текстовий файл символа c рядком A. (задача 7.18)
# include <stdio.h>
# include <string.h>
/* ReplaceChars */
#define MAXLEN 256
#define MAXREPL 10
char fn[81], gn[81];
replace(char s[], char a[], char c, char r[])
{
unsigned i;
strcpy(r, “”);
for (i=0;
i<strlen(s); i++)
if (s[i] == c) strcat(r,a);
else
{
r[strlen(r)+1] = ‘\0’;
r[strlen(r)] = s[i];
}
}
replace_text (char c, char a[])
{
FILE *fp, *gp;
char s[MAXLEN],
s1[MAXLEN*MAXREPL];
fp = fopen(fn, “r”);
gp = fopen(gn, “w”);
while(1)
{
fgets(s,MAXLEN,fp);
if (feof(fp)) break;
replace(s,a,c,s1);
fputs(s1,gp);
}
fclose(fp);
fclose(gp);
}
main()
{
char c, a[MAXREPL];
printf(“Ім’я вхідного файла: ”); gets(fn);
printf(“Ім’я результуючого файла: ”); gets(gn);
printf(“Рядок заміни:”); gets(a);
printf(“Символ для заміни: ”); c=getchar();
printf(“\n”);
replace_text(c,a);
}