Pliki1MSoffice, Programowanie, Pascal


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.