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