[ЗМІСТ]      [Далі]       [Назад]      [Початок розділу]

 

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);

}

 

[ЗМІСТ]      [Далі]       [Назад]      [Початок розділу]