Budowa pliku zdefiniowanego. Zmienne plikowe
Do przetwarzania fizycznego pliku będącego zbiorem dyskowym konieczne jest wykorzystywanie zmiennych plikowych. Dla plików zdefiniowanych deklaracja zmiennej plikowej ma postać:
Var
Zmienna : File Of TYP;
gdzie TYP jest typem elementów pliku i może być zarówno typem standardowym języka jak również typem zdefiniowanym przez programistę.
Plik zdefiniowany składa się z dowolnej liczby elementów tego samego typu. Elementy te są ułożone w pliku jeden za drugim, podobnie jak ma to miejsce w przypadku tablicy. Każdy element pliku posiada swój niepowtarzalny numer (indeks w pliku) - elementy pliku numeruje się od zera: element pierwszy ma numer zero, drugi jeden itd, ostatni element pliku zawierającego N elementów ma numer N-1.
Tryby otwarcia pliku
Każdy plik przed jakimkolwiek przetwarzaniem musi zostać otwarty do odczytu lub zapisu. Sposób otwarcia pliku nazywamy trybem otwarcia. Mówimy więc, że plik został otwarty w trybie do odczytu lub w trybie do zapisu.
Otwarcie pliku jest możliwe po wcześniejszym skojarzeniu zmiennej plikowej ze zbiorem danych fizycznie występującym na dysku. Kojarzenie to wykonuje się standardową procedurą
Assign(Zmienna plikowa, Nazwa pliku : String)
podając jako drugi argument nazwę pliku w katalogu bieżącym lub pełną ścieżkę dostępu do pliku.
Po wykonaniu skojarzenia plik można otworzyć za zapisu lub odczytu. Standardowa procedura
Reset(Zmienna plikowa)
powoduje otwarcie istniejącego pliku w trybie do odczytu lub zapisu. Dokładnie sposób otwarcia ustala się za pomocą standardowej zmiennej
FileMode : Byte;
której wartość 0 powoduje otwarcie zbioru tylko do odczytu, wartość 1 otwarcie tylko do zapisu, natomiast wartość 2 otwarcie zbioru do odczytu i zapisu równocześnie (standardowo zmienna ta ma wartość 2).
Należy pamiętać, że otwierany plik musi istnieć na dysku - próba otwarcia nieistniejącego pliku spowoduje błąd wykonania i w konsekwencji przerwanie wykonywania programu.
Nowy pusty plik tworzy się procedurą
Rewrite(Zmienna plikowa)
Po poprawnym wykonaniu wykonaniu procedury zostanie utworzony nowy pusty plik, aktyalna pozycja w pliku będzie wynosić zero i plik bedzie otwarty do zapisu. Należy pamietać, że jeżeli plik skojarzony ze zmienną plikową już istnieje, to jego zawartość zostanie skasowana - plik ten zostanie po prostu usunięty.
Po zakończeniu przetwarzania plik należy zamknąć procedurą
Close(Zmienna plikowa)
po czym można ten sam plik otworzyć ponownie lub też skojarzyć zmienną plikową z innym zbiorem danych.
Odczyt i zapis danych do pliku zdefiniowanego
Do odczytu danych używa się standardowej procedury
Read(Zmienna plikowa, Zmienna1, Zmienna2, ...)
(nie wolno używać ReadLn)podając jako pierwszy argument zmienną plikową skojarzoną z otwartym zbiorem danych. Kolejne argumenty są zmiennymi do których nastąpi odczyt danych i muszą być zmiennymi takiego samego typu jak typ elementów pliku - co ważne, nie może to być typ zgodny z typem elementów pliku.
Dla każdego otwartego przez program pliku określona jest tzw. aktualna pozycja w pliku (wskaźnik pliku). Pozycja ta jest liczbą typu LongInt i można przyjąć, że oznacza numer elementu aktywnego w pliku. Wszystkie operacje odczytu wykonywane są poczynając od aktualnej pozycji w pliku. Tuż po otwarciu aktualna pozycja w pliku wynosi 0, co oznacza że podczas pierwszej próby odczytu zostanie odczytany pierwszy element w pliku - element o numerze 0. Po każdym odczycie danych pozycja w pliku jest zwiększana o ilość odczytanych elementów.
Aktualną pozycję w pliku można otrzymać za pomocą funkcji
FilePos(Zmienna plikowa)
Liczbę wszystkich elementów pliku zwraca funkcja
FileSize(Zmienna plikowa)
Przykład 1
Dana jest nazwa pliku zawierającego tekst. Napisz program, który wydrukuje zawartość tego pliku na ekranie monitora oraz poda ilość odczytanych z pliku znaków.
Program PlikTekstowy;
Uses Crt;
Var
Plik : File Of Char;
Znak : Char;
Nazwa : String;
I, Ilosc : LongInt;
Begin
ClrScr;
Write('Nazwa pliku = ');
ReadLn(Nazwa);
Assign(Plik, Nazwa);
FileMode:=0;
Reset(Plik);
Ilosc:=FileSize(Plik);
For I:=0 To Ilosc-1 Do
Begin
Read(Plik, Znak);
Write(Znak);
End;
Close(Plik);
WriteLn;
WriteLn('Plik zawierał ', Ilosc, ' znaków.');
WriteLn('Naciśnij jakiś klawisz...');
While KeyPressed Do ReadKey;
ReadKey;
While KeyPressed Do ReadKey;
End.
Program przetwając plik musi stale kontrolować aktualną pozycję w pliku. Po odczycie z pliku ostatniego elementu aktualna pozycja wynosi FileSize(Plik) i podjęcie kolejnej próby odczytu spowoduje błąd wykonania programu. Do zmiany aktualnej pozycji w pliku służy procedura
Seek(Zmienna plikowa, Pozycja : LongInt)
której wykonanie powoduje ustawienie wskaźnika pliku na elemencie o podanym numerze. Jeżeli argument Pozycja jest równy ilości elementów pliku, to wskaźnik pliku jest ustawiany w tzw. pozycji końca pliku - tuż za ostatnim elementem. Jest to miejsce, w którym można dopisać do pliku kolejny element.
Dane do pliku zdefiniowanego wyprowadza się za pomocą procedury
Write(Zmienna plikowa, Zmienna1, Zmienna2, ...)
(nie wolno używać WriteLn) w której pierwszy argument jest zmienną plikową skojarzoną z otwartym do zapisu lub do zapisu i odczytu plikiem, pozostałe argumenty są zmiennymi typu identycznego z typem elementów pliku.
Zapis danych następuje w miejscu aktualnej pozycji w pliku. Jeżeli plik znajduje się w pozycji końca, to następuje dopisanie do niego kolejnych elementów, jeżeli jednak wskaźnik pliku jest ustawiony przed jego końcem, to wpisywane elementy zamazują poprzednio występujące w tym miejscu - elementy w żaden sposób nie są przesuwane w pliku! Po każdej operacji zapisu ulega zmianie aktualna pozycja w pliku, która jest zwiększana o ilość zapisanych elementów.
Wskaźnik pliku można ustawić również w dowolnym miejscu poza końcem pliku, np. instrukcja Seek(Plik, FileSize(Plik)+100) ustawi wskaźnik na setnym elemencie poza końcem pliku. Nie spowoduje to zmiany rozmiaru pliku ani też ilości elementów w nim zawartych. Jeżeli jednak po takim ustawieniu wskaźnika zapiszemy do pliku przynajmniej jeden element, to plik wydłuży się o 100 elementów plus liczba elementów dopisanych. Wówczas elementy "przeskoczone" procedurą Seek będą miały wartość przypadkową.
Warto jeszcze raz podkreślić, że po ustawieniu wskaźnika poza końcem pliku możliwe jest jedynie dopisane elementów do pliku - próba odczytu zakończy się błędem wykonania programu.
Bezpieczne otwieranie zbioru
Próba otwarcia nieistniejącego pliku procedurą Reset lub próba utworzenia pliku procedurą Rewrite np. na dysku zabezpieczonym przed zapisem powoduje błąd wykonania i w konsekwencji przerwanie programu.
Programista ma możliwość programowej obsługi tego błędu, wymagane jest tylko aby procedura otwierająca plik wystąpiła z zasięgu dyrektywy $I kompilatora:
Assign(Plik, Nazwa);
{$I-}
Reset{Plik);
{$I+}
Blad:=IOResult;
If Blad<>0 Then
Begin
WriteLn('Podczas próby otwarcia zbioru wystąpił błąd
wejścia-wyjścia numer ',Blad, '.');
WriteLn('Naciśnij jakiś klawisz...');
While KeyPressed Do ReadKey;
ReadKey;
While KeyPressed Do ReadKey;
Halt;
End;
...
{przetwarzanie pliku}
...
Close(Plik);
Dyrektywa {$I-} powoduje wyłączenie kontroli błędów wejścia-wyjścia, co najczęściej czynimy na czas otwierania zbioru - tuż po otwarciu kontrola jest ponownie przywracana dyrektywą {$I+}.
Jeżeli otwarcie zbioru wykonywane jest w zasięgu dyrektywy {$I}, to stan operacji można odczytać wywołując bezparametrową funkcję IOResult, której wartość niezerowa oznacza wystąpienie jakiegoś błędu - wartość zero można przyjąć za poprawnie wykonane otwarcia zbioru.
Funkcja IOResult zwraca stan wykonania ostatniej operacji wejścia-wyjścia i może być wywołana tylko raz. Drugie i każde następne wywołanie tej funkcji zawsze zwróci wartość zero. Dlatego też, jeżeli kod błędu ma być później wykorzystany w jakiś sposób, to należy przechować go w zmiennej pomocniczej.
Funkcje pomocnicze w przetwarzaniu zbiorów zdefiniowanych
Standardowa funkcja
Eof(Zmienna plikowa)
zwraca wartość prawdy, gdy wskaźnik pliku znajduje się w pozycji końca pliku lub za jego końcem (można go tam bowiem ustawić procedurą Seek), w przeciwnym wypadku wartością funkcji jest False.
Plik zdefiniowany najczęściej przetwarzany jest sekwencyjnie element po elemencie za pomocą pętli:
While Not Eof(Plik) Do
Begin
Read(Plik, Zmienna);
...
End;
Pierwszą linię tej pętli można nawet intuicyjnie odczytać jako warunek "Dopóki nie koniec pliku...".
Wykonanie procedury
Truncate(Zmienna plikowa)
powoduje obcięcie pliku w miejscu aktualnej pozycji wskaźnika pliku. Po wykonaniu procedury z pliku są usuwane wszystkie elementy poczynając od aktualnej pozycji wskaźnika, również element na który wskazuje wskaźnik pliku.
Usunięcie z pliku wszystkich elementów można wykonać za pomocą instrukcji:
Seek(Plik, 0);
Truncate(Plik);
W podobny sposób można usunąć z pliku ostatni element:
If FileSize(Plik)>0 Then
Begin
Seek(Plik, FileSize(Plik)-1);
Truncate(Plik);
End;
Załóżmy jednak, że plik zawiera N (N>0) elementów i konieczne jest usunięcie elementu o numerze K (0<=K<=N-1). Zadanie to można wykonać za pomocą następującego algorytmu:
Seek(Plik, FileSize(Plik)-1);
Read(Plik, Zmienna);
Seek(Plik, K);
Write(Plik, Zmienna);
Seek(Plik, FileSize(Plik)-1);
Truncate(Plik);
Usunięcie elementu o numerze K polega na przeniesieniu ostatniego elementu pliku na miejsce niepotrzebnego elementu K a następnie na obcięciu pliku w miejscu ostatniego elementu.
Ten sposób postepowania zmienia układ elementów w pliku, niemniej jednak powoduje usunięcie z pliku wybranego elementu. Algotytm, który usunąłby z pliku wybrany element K nie zmieniając przy tym układu pozostałych elementów musiałby mieć postać:
Odczytaj element K+1;
Zapisz go na miejscu elementu K;
Odczytaj element K+2;
Zapisz go na miejscu elementu K+1;
Odczytaj element K+3;
Zapisz go na miejscu elementu K+2;
...
Odczytaj element FileSize(Plik)-1;
Zapisz go na miejscu FileSize(Plik)-2;
Ustaw wskaźnik na miejscu FileSize(Plik)-1;
Obetnij plik w tym miejscu;
Przykład 2
Dana jest nazwa pliku z rozszerzeniem. Napisz program, który utworzy dwa nowe pliki o takiej samej nazwie i rozszerzeniach odpowiednio f01 i f02, zawierające po połowie znaków podanego pliku - jeżeli dany plik zawiera 311 znaków, to plik z rozszerzeniem f01 powinien zawierać 156 pierwszych znaków, plik z rozszerzeniem f02 155 pozostałych. Jeżeli plik zawiera mniej niż 2 znaki, to plik z rozszerzeniem f02 powinien być pusty.
Program DzieleniePlikow;
Uses Crt;
Procedure Stop(Kom : String);
Begin
WriteLn;
WriteLn(Kom);
WriteLn('Naciśnij jakiś klawisz...');
While KeyPressed Do ReadKey;
ReadKey;
While KeyPressed Do ReadKey;
Halt;
End;
Var
Wejscie, Wyjscie1, Wyjscie2 : File Of Char;
I, Ilosc : LongInt;
Nazwa, Nazwa1, Nazwa2 : String;
Blad : Integer;
Znak : Char;
Begin
ClrScr;
Write('Nazwa pliku = ');
ReadLn(Nazwa);
If Pos('.', Nazwa)=0 Then Stop('Błąd w nazwie pliku.');
Nazwa1:=Copy(Nazwa, 1, Pos('.', Nazwa));
Nazwa2:=Nazwa1;
Nazwa1:=Nazwa1+'f01';
Nazwa2:=Nazwa2+'f02';
Assign(Wejscie, Nazwa);
{$I-}
Reset(Wejscie);
{$I+}
If IOResult<>0 Then Stop('Nie można otworzyć zbioru '+Nazwa);
Assign(Wyjscie1, Nazwa1);
Assign(Wyjscie2, Nazwa2);
{$I-}
Rewrite(Wyjscie1);
{$I+}
If IOResult<>0 Then Stop('Nie można utworzyć zbioru '+Nazwa1);
{$I-}
Rewrite(Wyjscie2);
{$I+}
If IOResult<>0 Then Stop('Nie można utworzyć zbioru '+Nazwa2);
Ilosc:=FileSize(Wejscie);
While FilePos(Wejscie)<(Ilosc+1) Div 2 Do
Begin
Read(Wejscie, Znak);
Write(Wyjscie1, Znak);
End;
Close(Wyjscie1);
While Not Eof(Wejscie) Do
Begin
Read(Wejscie, Znak);
Write(Wyjscie2, Znak);
End;
Close(Wyjscie2);
Close(Wejscie);
Stop('');
End.
Pliki zdefiniowane najczęściej wykorzystywane są do przetwarzania baz danych. Niech dana będzie definicja typu rekordowego Osoba:
Type
Osoba = Record
Numer : Word;
Nazwisko, Imie : String[25];
AdrUlica : String[30];
AdrNr : String[6];
AdrKod : String[6];
AdrPoczta : String[20];
DataWpisu : String[10];
IloscWypozyczen, IloscAktWypozyczen : Word;
End;
Typ opisuje czytelnika biblioteki. Pole Numer zawierać będzie niepowtarzalny numer użytkownika poprzez który będzie on identyfikowany. Pole IloscAktWypozyczen zawierać będzie ilość aktualnie wypożyczonych książek, zaś pole IloscWypozyczen ilość wszystkich dotychczas wypożyczonych książek.
Zadania do samodzielnego rozwiązania
Dana jest nazwa pliku tekstowego. Napisz program, który wypisze na ekranie ilość wystąpień wszystkich małych i dużych liter w tym pliku.
Dana jest nazwa pliku tekstowego. Napisz program, który wypisze na ekranie ilość wystąpień wszystkich znaków z zakresu #32..#255 w tym pliku.
Dana jest nazwa pliku tekstowego. Napisz program, który utworzy plik o takiej nazwie zawierający liczby z zakresu Ord('A')..Ord('z'). Następnie obejrzyj zawartość tego pliku w dowolnym programie.
Dana jest nazwa pliku tekstowego. Napisz program, który utworzy plik o takiej nazwie zawierający 100 losowo wygenerowanych liczb typu Word. Następnie obejrzyj zawartość tego pliku w dowolnym programie.
Dana jest nazwa pliku tekstowego. Napisz program, który wypisze na ekranie zawartość tego pliku. Program po zadrukowaniu całego ekranu powinien zatrzymać wyświetlanie i w ostatnim 25 wierszu wypisać komunikat 'Naciśnij jakiś klawisz...'.
Dana jest nazwa pliku tekstowego. Napisz program, który wszystkie naciskane na klawiaturze znaki będzie wyświetlał na ekranie i równoczeście umieszczał je w pliku o podanej nazwie.
Dana jest nazwa pliku z rozszerzeniem składającego się z bajtów. Napisz program, który podzieli ten plik na dwa pliki o takiej samej nazwie i rozszerzeniach odpowiednio 'f01' i 'f02'. Do pliku pierwszego powinny trafić wszystkie bajty o numerach parzystych, do pliku drugiego bajty pozostałe.
Napisz program, który będzie potrafił połączyć takie pliki w całość.
Dana jest nazwa pliku z rozszerzeniem składającego się z bajtów oraz liczba całkowita N typu LongInt. Napisz program, który podzieli ten plik na pliki o rozmiarze N bajtów, o takiej samej nazwie i rozszerzeniach 'f01, 'f02', itd. (plik ostatni może mieć mniejszy rozmiar).
Napisz program, który pozwoli połączyć takie pliki w całość.
Dana jest nazwa pliku tekstowego. Napisz program po wykonaniu którego plik ten zostanie w prosty sposób zaszyfrowany, tzn. każdy znak będący dużą literą zostanie zamieniony na dużą literę według schematu: literę 'A' zamieniamy na 'Z', literę 'Z' na literę 'A', literę 'B' zamieniamy na 'Y', literę 'Y' na literę 'B', itd. Analogicznie zamieniamy małe litery alfabetu.
Napisz program, który pozwoli odszyfrować tak przekształcony plik.
Dana jest nazwa pliku tekstowego. Napisz program który wczyta pewną ilość napisów a następnie umieści je w tym pliku w nastepujący sposób: kod pierwszego zapisanego znaku jest długością napisu pierwszego, następnie zapisane są znaki pierwszego napisu. Kod następnego znaku zawiera długość napisu drugiego, po czym zapisane są znaki napisu drugiego itd.
Napisz program, który pozwoli na odczytanie z pliku o takiej budowie dowolnego napisu o numerze N.
Dana jest nazwa pliku typu File Of Integer. Napisz program który wyznaczy najmniejszą i największą liczbę w tym pliku, średnią liczb tego pliku oraz liczbę która najmniej różni się od średniej liczb w tym pliku.
Dana jest nazwa pliku typu File Of Integer. Napisz program który ułoży liczby w tym pliku w taki sposób, aby tworzyły one ciąg rosnący.
Dana jest nazwa pliku typu File Of Word. Napisz program który ułoży liczby w tym pliku w taki sposób, aby tworzyły one ciąg niemalejący.
Dana jest nazwa pliku typu File Of Word. Napisz program który usunie z tego pliku wszystkie powtarzające się liczby - w pliku pozostawiamy tylko pierwsze wystąpienie danej liczby.