Podstawy Programowania Wyk ad dziesi ty: ł ą Pliki

Transkrypt

Podstawy Programowania Wyk ad dziesi ty: ł ą Pliki
Podstawy Programowania
Wykład dziesiąty:
Pliki
1. Pliki – podstawowe wiadomości
Jeśli dane, które przetwarza lub produkuje program komputerowy mają być
dostępne po zakończeniu jego pracy, to muszą one zostać przechowane
w pamięci masowej komputera. Pamięć tego typu jest urządzeniem
pozwalającym zachować zapisane w niej informacje w sposób trwały. Takimi
urządzeniami są np.: dyski twarde, płyty CD, DVD i pamięci typu Flash. Każde
z tych urządzeń przechowuje dane w inny sposób. Aby ujednolicić sposób
korzystania ze zgromadzonych na nich informacji wprowadzono specjalną
strukturę danych nazywaną plikiem. Pliki gwarantują ten sam sposób
korzystania z informacji niezależnie od tego, czy znajdują się one na dysku
twardym, w pamięci operacyjnej, czy innym nośniku. W przypadku większości
współczesnych systemów komputerowych urządzenia zewnętrzne, takie jak
klawiatura, ekran, dysk twardy, napęd CD, drukarki są „widziane” przez
programy komputerowe właśnie jako pliki. Język Turbo Pascal pozwala
korzystać z trzech rodzajów plików: plików tekstowych, plików binarnych
zdefiniowanych1 i plików binarnych niezdefiniowanych (amorficznych). Pliki,
niezależnie do którego rodzaju należą, składają się zawsze z pewnych
elementów. W przypadku plików tekstowych tymi elementami są wiersze, czyli
ciągi znaków zakończone znakami końca wiersza (w przypadku Turbo Pascala
są to znaki #13#10). Wynika stąd, że wiersze te mogą mieć różny rozmiar.
Elementami plików binarnych zdefiniowanych są wartości określonego przez
nas typu. Mogą to być wartości typu byte, integer, real, a nawet typu
rekordowego. W plikach amorficznych (binarnych niezdefiniowanych) wartości,
które są przechowywane nie mają określonego typu. Cały plik jest podzielony
na na porcje o ustalonej wielkości, zwane blokami. Dla wszystkich typów
plików możemy stosować dostęp sekwencyjny, tzn. taki, w którym aby
przeczytać lub zapisać element znajdujący się w dowolnym miejscu pliku
musimy przeczytać wszystkie elementy znajdujące się przed nim. W przypadku
plików binarnych możemy mieć również swobodny dostęp do elementów
plików2. Pliki binarne możemy zatem potraktować jako tablice, których rozmiar
jest zmienny.
2. Typy plików
W Turbo Pascalu jedynym typem plików, który powinniśmy zdefiniować jest typ
pliku binarnego zdefiniowanego. Typ pliku binarnego zdefiniowanego określamy
w sekcji type programu lub podprogramu według wzorca:
nazwa_typu_pliku = file of nazwa_typu_elementu_pliku;
1 Nazywanych krótko plikami binarnymi.
2 Tzn. możemy wskazać dowolny element, który chcemy przeczytać/ zmodyfikować, bez
konieczności przeglądania elementów je poprzedzających.
2
Przykładowo, plik binarny, którego elementy byłyby typu integer miałby następujący typ:
type
plik = file of integer;
Pliki amorficzne, jak również tekstowe mają już zdefiniowane typy, które są
częścią języka i nie musimy im nadawać nowej nazwy (choć istnieje taka możliwość). Typem pliku binarnego niezdefiniowanego jest po prostu file, a typem
pliku tekstowego jest text.
3. Zmienne plikowe
Podobnie jak zmienne innych typów zmienne plikowe możemy deklarować
w sekcji var programu lub podprogramu. W przypadku plików binarnych
zdefiniowanych należy wcześniej zdefiniować typ takiej zmiennej według zamieszczonego wyżej wzorca, gdyż zmiennych plikowych tego rodzaju o anonimowych typach nie będziemy mogli przekazać przez parametr. Nie wymagają
tego zmienne plikowe związane z plikami tekstowymi lub amorficznymi. Nie
możemy deklarować zmiennych plikowych zainicjalizowanych. Jedynym sposobem, w jaki możemy przekazać zmienną plikową do procedury lub funkcji za
pomocą parametru jest przekazanie przez zmienną (adres).
4. Operacje na plikach
Zanim będziemy mogli zapisać lub odczytać informacje z pliku musimy wykonać dodatkowe czynności. Schemat korzystania z pliku może być przedstawiony następująco:
1. skojarzenie zmiennej z plikiem,
2. otwarcie pliku,
3. przetwarzanie informacji zawartych w pliku,
4. zamknięcie pliku.
Należy zaznaczyć, że przy wykonywaniu prawie każdej z tych operacji mogą
pojawić się wyjątki3, które należy obsłużyć. Powyższe operacje są realizowane
za pomocą różnych funkcji i procedur. Nazwy tych podprogramów mogą się
3 Inaczej nazywane błędami, choć nie jest to nazwa w pełni adekwatna.
3
różnić w zależności od typu pliku, na którym zostaną użyte. Zostały one
zebrane w tabelkę:
Nazwa operacji
Skojarzenie
Otwarcie
Odczyt
Zapis
Zamknięcie
read
write
readln
writeln
read
write
close
blockread
blockwrite
close
Typ pliku
rewrite
Plik tekstowy
assign
reset
append
Plik binarny
assign
rewrite
close
reset
Plik amorficzny
assign
rewrite
reset
Procedura assign działa w ten sam sposób dla wszystkich typów plików. Wiąże
ona zmienną plikową z nazwą pliku. Pierwszym parametrem jej wywołania jest
nazwa zmiennej plikowej, a drugim ciąg znaków będący nazwą pliku. W Turbo
Pascalu4 nazwa pliku składa się maksymalnie z jedenastu liter. Pierwszych
osiem liter jest nazwą właściwą, a trzy ostatnie stanowią tak zwane
rozszerzenie, które określa zawartość pliku. Obie te części nazwy pliku są
rozdzielone kropką. Istnieją też pliki o specjalnych nazwach, które związane są
z urządzeniami. Takimi plikami są LPR1 lub PTR związane z drukarką, CON
związany z konsolą (klawiatura + ekran monitora), COM1 związany z portem
szeregowym. Po skojarzeniu zmiennej plikowej z plikiem możemy go otworzyć.
W tym wypadku zachowanie poszczególnych procedur otwierających różni się
w zależności od typu otwieranego pliku. Dla plików tekstowych procedura
rewrite tworzy nowy plik, który można wyłącznie zapisywać. Procedura reset
otwiera istniejący plik w trybie tylko do odczytu, natomiast procedura append
otwiera istniejący plik w trybie do dopisywania, co oznacza, że nowe informacje
będą w tym pliku zapisywane za już istniejącymi. Wszystkie te procedury, jako
parametr wywołania przyjmują zmienną plikową skojarzoną z plikiem, który
ma zostać otwarty. W przypadku plików binarnych zdefiniowanych procedura
rewrite5 tworzy nowy plik, natomiast procedura reset otwiera plik, który już
istnieje. W obu przypadkach możliwe jest odczytywanie i zapisywanie do pliku.
Podobnie, jak poprzednio jedynym parametrem wywołania tych procedur są
zmienne plikowe, na których operacja ma zostać wykonana. W przypadku
plików amorficznych, procedury rewrite i reset mają takie samo działanie, jak
dla plików binarnych zdefiniowanych, ale mogą przyjmować dodatkowy
parametr wywołania, będący liczbą określającą wielkość (w bajtach) bloku, jaki
4 Dokładniej w systemie DOS, dla którego kompilator Turbo Pascala jest przeznaczony.
5 Jeśli procedura rewrite zostanie użyta dla istniejącego pliku, to skasuje jego zawartość,
niezależnie od jego typu.
4
można jednorazowo zapisać lub odczytać z takiego pliku. Jeśli sami nie
określimy rozmiaru bloku, to zostanie przyjęty rozmiar domyślny równy 128
bajtów. Do odczytu plików tekstowych możemy zastosować znane już nam
procedury read i readln. Domyślnie te procedury związane są z plikiem
o nazwie Input, czyli po prostu z klawiaturą6. Jeśli jednak, jako pierwszy
parametr ich wywołania podamy nazwę zmiennej plikowej, to odczyt zamiast
z klawiatury będzie odbywał się z pliku. Po nazwie zmiennej plikowej występuje
lista składająca się co najmniej z jednej zmiennej do której będą wczytane
informacje z pliku. Różnica między readln i read polega na tym, że pierwsza
odczytuje cały wiersz znaków, zakończony znakami końca wiersza, natomiast
druga czyta tyle informacji, ile wynika z liczby i typu zmiennych do których one
będą zapisywane. W przypadku plików binarnych zdefiniowanych odczyt
możliwy jest wyłącznie za pomocą procedury read. Pierwszym parametrem
wywołania tej procedury jest zmienna plikowa, natomiast następnymi zmienne
(w liczbie co najmniej jeden), do których informacje będą zapisane. Typy tych
zmiennych muszą być takie same lub zgodne z typem elementów pliku.
W przypadku plików amorficznych odczyt jest możliwy tylko za pośrednictwem
procedury blockread. Ta procedura może przyjmować trzy lub cztery parametry
wywołania. Pierwszym jest zmienna plikowa, drugim zmienna do której ma
zostać zapisana informacja, a trzecim zmienna, wartość lub wyrażanie typu
word, określające ile bloków chcemy z pliku odczytać. Czwarty parametr
wywołania jest opcjonalny i jest nim zmienna typu word, w której jest
zapisywana liczba faktycznie przeczytanych bloków. Jeśli nie umieścimy tego
argumentu na liście parametrów wywołania procedury blockread, to
w przypadku, kiedy zostanie odczytana mniejsza niż chcieliśmy liczba bloków
z pliku program zakończy się natychmiast sygnalizując błąd wykonania. Jeśli
przekażemy czwarty parametr, to możemy obsłużyć ten wyjątek. Zapis do pliku
również odbywa się w różny sposób, w zależności od rodzaju pliku. Do plików
tekstowych możemy zapisywać informacje za pomocą write i writeln. Podobnie
jak w przypadku procedury read i readln domyślnie są one skojarzone
z plikiem Output, czyli ekranem monitora7. Jeśli jako pierwszy parametr
wywołania tej procedury umieścimy zmienną plikową, to zapis informacji
nastąpi do pliku z nią związanego. Za tym parametrem, mogą występować inne
będące zmiennymi, wyrażeniami lub wprost - wartościami, które mają zostać
zapisane do pliku. Procedura writeln zapisuje do pliku wiersze zakończone
znakami końca wiersza. Natomiast procedura write nie umieszcza w pliku tych
znaków samodzielnie. Do zapisu plików binarnych zdefiniowanych używana
jest wyłącznie procedura write. Sposób wywołania tej procedury jest taki sam,
jak w przypadku plików tekstowych. Pliki amorficzne mogą być zapisywane
6 Plik skojarzony z klawiaturą nazywany jest również standardowym wejściem.
7 Plik ten nazywany jest również standardowym wyjściem.
5
przy pomocy procedury blockwrite. Jej parametry wywołania są takie same, jak
w przypadku procedury blockread, z tym że odnoszą się one do zapisu, a nie
odczytu. Operacja zamykania pliku jest przeprowadzana w ten sam sposób dla
wszystkich rodzajów plików – przy pomocy procedury close. Jedynym
parametrem wywołania tej procedury jest zmienna plikowa.
5. Inne operacje związane z plikami
W języku Trubo Pascal zdefiniowano szereg procedur i funkcji, które wykonują
inne pomocne operacje na plikach, niż te, które opisano wyżej. Oto niektóre
z nich wraz, z krótkim omówieniem:
funkcja eof zwraca wartość true, jeśli został osiągnięty koniec pliku.
Działa dla wszystkich typów plików. Jej parametrem wywołania jest
nazwa zmiennej plikowej, jeśli zostanie ona pominięta, to wywołanie to
będzie się odnosiło do standardowego wejścia,
funkcja seekeof działa podobnie, ale tylko dla plików tekstowych
i ignoruje znaki białe (spacja, powrót karetki, itp.),
funkcja eoln działa jedynie dla plików tekstowych i zwraca true, jeśli
został osiągnięty koniec wiersza,
funkcja seekeoln działa podobnie, ale ignoruje znaki spacji i tabulacji,
jest przeznaczona wyłącznie dla plików tekstowych,
funkcja filesize zwraca liczbę elementów pliku binarnego lub liczbę
bloków pliku amorficznego,
funkcja filepos działa dla plików binarnych i amorficznych, zwraca
wartość typu longint, która oznacza pozycję wskaźnika pliku,
procedura seek ustawia pozycję wskaźnika pliku8. Pierwszym
argumentem jej wywołania jest nazwa zmiennej plikowej, drugim wartość
nowej pozycji wskaźnika pliku, wartość ta jest typu longint, procedura ta
jest przeznaczona dla plików binarnych i amorficznych,
procedura truncate „ucina” plik począwszy od bieżącej pozycji wskaźnika
pliku, działa dla plików binarnych i amorficznych,
8 O wskaźniku pliku możemy myśleć jak o indeksie tablicy. Jest on przesuwany automatycznie
po wykonaniu każdego odczytu lub zapisu zawartości pliku.
6
funkcja ioresult zwraca kod wykonania operacji na pliku, jeśli operacja
przebiegła prawidłowo, to kod ten jest równy zero, w przeciwnym
przypadku jest to wartość różna od zera, aby ta funkcja działała
prawidłowo należy wyłączyć dyrektywę $I kompilatora.
Opisana wcześniej funkcja eof jest dosyć często stosowana, kiedy
konstruujemy pętlę, w której umieszczamy instrukcje odczytujące z pliku. Jeśli
jest to pętla while, to warunek zakończenia ma postać: not eof(zp), natomiast
jeśli jest to pętla repeat ... until, to warunkiem zakończenia jest eof(zp).
W przypadku plików binarnych i amorficznych możliwe jest użycie do odczytu
pętli for. Poniżej znajdują się przykłady programów operujących na
odpowiednio: plikach tekstowych, binarnych i amorficznych.
program pliki_tekstowe;
uses crt;
var T:text;
procedure zapisz(var F:text);
{Zapisuje do pliku tekstowego 10 wierszy pobranych od użytkownika.}
var
a:string[80];
i:byte;
begin
rewrite(F);
for i:=1 to 10 do
begin
readln(a);
writeln(F,a);
end;
close(f);
end;
procedure odczytaj(var F:text);
{Odczytuje z pliku tekstowego 10 wierszy.}
var
7
a:string[80];
begin
reset(F);
repeat
readln(f,a);
writeln(a);
until eof(f);
close(f);
end;
begin
clrscr;
assign(T,'dane.txt');
zapisz(T);
clrscr;
odczytaj(T);
readln;
end.
Zmienna plikowa, typu text jest kojarzona z plikiem o nazwie dane.txt w bloku
głównym programu. Plik ten tworzony jest w procedurze zapisz, w wyniku
wywołania procedury rewrite. W tej też procedurze do pliku zapisywanych jest
dziesięć wierszy (pętla for) zawierających ciągi znaków podane przez
użytkownika (o wielkości maksymalnie 80 znaków). Procedura odczyt odczytuje
te dane z tego samego pliku. Odczytanie i wyświetlenie na ekran odbywa się
wewnątrz pętli repeat. W obydwu procedurach ostatnią wykonywaną instrukcją
jest zamknięcie pliku.
program plik_binarny;
uses
crt;
type
rec = record
x,y:integer;
end;
pl_rec = file of rec;
8
var
plik:pl_rec;
procedure zapisz(var f:pl_rec);
var
r:rec;
i:byte;
begin
{$I-}rewrite(f);{$I+}
if ioresult <> 0 then exit;
for i:=1 to 5 do
begin
with r do
begin
writeln('Podaj x');
readln(x);
writeln('Podaj y');
readln(y);
end;
write(f,r);
end;
close(f);
end;
procedure append_bin(var f:pl_rec);
begin
{$I-}reset(f);{$I+}
if ioresult <> 0 then halt(1);
seek(f,filesize(f));
end;
9
procedure dopisz(var f:pl_rec);
var
r:rec;
begin
writeln('Podaj x');
readln(r.x);
writeln('Podaj y');
readln(r.y);
append_bin(f);
write(f,r);
close(f);
end;
procedure wypisz(var f:pl_rec);
var
r:rec;
begin
{$I-}reset(f);{$I+}
if IOResult <> 0 then exit;
while not eof(f) do
begin
read(f,r);
write(r.x,' ');
write(r.y);
writeln;
end;
close(f);
end;
begin
clrscr;
assign(plik,'dane.dat');
zapisz(plik);
writeln('Naciśnij Enter.');
readln;
10
clrscr;
wypisz(plik);
writeln('Naciśnij Enter.');
readln;
clrscr;
dopisz(plik);
wirteln('Naciśnij Enter.');
readln;
wypisz(plik);
writeln('Naciśnij Enter.');
readln;
end.
Powyższy program przetwarza informacje zawarte w pliku binarnym
zdefiniowanym, którego elementami są rekordy. Taki rodzaj pliku nazywa się
krótko plikiem rekordowym. Każdy rekord zawiera dwa pola x i y, które
przechowują liczby całkowite. Zadaniem programu jest utworzenie pliku
o nazwie dane.dat, zapisanie do niego pięciu rekordów zawierających dane
pobrane od użytkownika, zamknięcie go, następnie ponowne otwarcie,
dopisanie jednego rekordu na końcu tego pliku i wypisanie zawartości
wszystkich rekordów z pliku na ekran. Warto zwrócić uwagę na definicję pliku
rekordowego, jak również na procedurę append_bin. Ponieważ procedura
append działa tylko dla plików tekstowych, w programie została zdefiniowana
osobna procedura dla plików binarnych zdefiniowanych. Otwiera ona taki plik
przy pomocy reset i ustawia jego wskaźnik za pomocą procedury seek na
ostatnim elemencie pliku. Jego położenie jest ustalane za pomocą funkcji
filesize – elementy pliku są numerowane od zera, więc pozycja ostatniego
elementu ma wartość równą liczbie elementów w pliku minus jeden. Warto
również zauważyć, że wyjątki jakie mogą spowodować procedury otwierania
plików są obsługiwane za pomocą funkcji ioresult. Aby nie były one
obsługiwane domyślnie przez kompilator należy wyłączyć przed ich
wykonaniem dyrektywę $I kompilatora ({$I-}), a następnie włączyć ją po
wykonaniu tej operacji ({$I+}). Podobnie można obsługiwać wyjątki pochodzące
od innych operacji na pliku, ale w ich przypadku w programie została
zostawiona obsługa domyślna. Funkcja ioresult może odczytać kod błędu tych
operacji tylko przy wyłączonej dyrektywie $I.
11
program plik_amorficzny_i_tablica;
uses
crt;
type
tablica_losowa = array [1..1000] of word;
var
tl:tablica_losowa;
pl:file;
procedure wypelnij_i_zapisz(var F:file; var x:tablica_losowa);
{Wypełnia i zapisuje do pliku amorficznego tablicę 1000 elementów typu word.}
var
l,i:word;
begin
randomize;
for i:=1 to 1000 do x[i] := random(100);
for i:=1 to 1000 do write(x[i]:4);
{$I-}rewrite(F,sizeof(x));{$I+}
if IOResult <> 0 then exit;
blockwrite(F,x,1,l);
if l<>1 then exit;
close(F);
end;
procedure odczytaj_i_pokaz(var F:file; var x:tablica_losowa);
{Wypełnia tablicę 1000 elementów typu word danymi z pliku.}
var
l,i:word;
begin
{$I-}reset(F,sizeof(x));{$I+}
if IOResult <> 0 then exit;
blockread(F,x,1,l);
if l<>1 then exit;
for i:=1 to 1000 do write(x[i]:4);
12
close(F);
end;
begin
assign(pl,'tablica.dat');
clrscr;
wypelnij_i_zapisz(pl,tl);
writeln('Naciśnij Enter.' );
readln;
clrscr;
odczytaj_i_pokaz(pl,tl);
writeln('Naciśnij Enter.' );
readln;
end.
Ten program wypełnia tablice o 1000 elementach typu word i zapisuje ją,
a następnie odczytuje z pliku amorficznego. Warto zwrócić uwagę na sposób
użycia procedur blockread i blockwirte.
13