Wykład dwudziesty piąty: Kolekcje obiektów
Transkrypt
Wykład dwudziesty piąty: Kolekcje obiektów
Podstawy Programowania – semestr drugi Wykład dwudziesty trzeci 1. Kolekcje obiektów Na poprzednim wykładzie poznaliśmy pierwszą z obiektowych struktur danych, nieuporządkowaną listę liniową obiektów. Przykład pokazujący wykorzystanie takiej struktury budował listę obiektów korzystając z techniki dziedziczenia. Na tym wykładzie zostanie zaprezentowana inna obiektowa struktura danych do której budowania zostanie wykorzystana technika kompozycji. Ta struktura jest nazwana kolekcją obiektów. Koncepcja kolekcji obiektów jest zbliżona do koncepcji obiektów kontenerów, znanych z takich języków programowania obiektowego, jak C++ i Java. Kolekcja jest obiektem potrafiącym przechowywać wskaźniki do innych obiektów. Wskaźniki te tworzą tablicę. W zaprezentowanym na niniejszym wykładzie rozwiązaniu będą to wskaźniki typu pointer. Alternatywnie mógłby być zastosowany 1 wskaźnik klasy, po której dziedziczyłyby klasy wszystkich obiektów, które byłyby przechowywane przez kolekcję . Zastosowanie wskaźników bez określonego typu jest rozwiązaniem elastycznym, gdyż wskaźniki te mogą występować po obu stronach instrukcji przypisania i nie powodować błędu czasu kompilacji, nawet jeśli po przeciwnej stronie tej instrukcji znajduje się wskaźnik określonego typu. Podobna sytuacja ma również miejsce, gdy przekazujemy taki wskaźnik przez wartość do podprogramu. Niestety te zalety są równocześnie wadami – użycie wskaźników typu pointer powoduje „zagubienie” prawdziwej klasy wskazywanego obiektu, jak również osłabienie mechanizmu sprawdzania typów, co może zaowocować błędami czasu wykonania. Aby uniknąć takich sytuacji należy użyć wcześniej omawianej funkcji TypeOf. Poniżej jest przedstawiony kod programu, który przechowuje w kolekcji wskaźniki do obiektów zawierających liczby całkowite. Kodu modułu „KO”, gdzie zdefiniowana jest klasa obiektu – kolekcji pochodzi ze skryptu Zofii Kruczkiewicz, pt.: „Metody programowania obiektowego”. Omówienie tego programu zaczniemy od modułu „KO”. W wierszu 7 tego modułu jest definiowany typ tablicowy, którego elementy są typu pointer. Ilość tych elementów 1 program Zbior_liczb; 2 uses crt,zbior,lk; 5 pom:PLiczba; jest określona stałą MaxKolekcji. Jej wartość jest tak dobrana, aby zmieścić w tablicy maksymalną ilość elementów nie powodując przy tym błędu czasu kompilacji. W module definiowany jest również wskaźnik na taką tablicę, oraz wskaźnik klasy Kolekcja. Ta klasa 6 mem:longint; jest tutaj również definiowana. Posiada ona trzy pola. Pole Tablica jest wskaźnikiem na tablicę wskaźników typu 3 var 4 zbiorLiczb:Zb; pointer, pole Ilosc określa maksymalną ilość obiektów, 7 begin 8 clrscr; których adresy mogą być zapamiętane w kolekcji. Pole Ostatni zawiera z kolei indeks wskaźnika ostatniego 9 mem:=memavail; obiektu, który został umieszczony w kolekcji. Konstruktor klasy Kolekcja jest konstruktorem z parametrem. Poprzez parametr przekazywana jest przez ilość elementów, które będą przechowywane w kolekcji. Konstruktor najpierw sprawdza, czy jest dostępna wystarczająca ilość wolnej pamięci na stercie, aby móc ją przeznaczyć na pole będące tablicą wskaźników. Jeśli tak, przypisuje polu Ilosc wartość przekazaną przez parametr, a następnie sprawdza, za pomocą metody zakres, która będzie opisana 10 zbiorLiczb.inicjujKolekcje; 11 zbiorLiczb.wydrukujElementy; 12 readln; 13 clrscr; 14 new(pom,inicjuj(30)); w dalej, czy ta wartość nie przekracza wartości stałej MaxKolekcji. Jeśli i ten wymóg jest spełniony, to 15 if zbiorLiczb.dodajElement(pom) then przydzielana jest pamięć na tablicę (warto zauważyć, że nie jest przydzielana pamięć na wszystkie elementy tablicy, tylko na tyle, ile w danej chwili potrzeba) przy pomocy procedury getmem. Jeśli któreś ze wcześniejszych 16 writeln('Dodano nowy element',#10#13); 17 zbiorLiczb.wydrukujElementy; 18 readln; 19 clrscr; 20 if zbiorLiczb.usunElement(10) then writeln('Usunięto 10 element',#10#13); ma wartość różną od nil, jeśli tak, to wywołuje metodę usunKolekcje, która zwalnia za pomocą procedury fremem 21 zbiorLiczb.wydrukujElementy; 22 zbiorLiczb.usunKolekcje; pamięć przeznaczoną na elementy tablicy wskaźników. Procedura fremem musi zostać wywołana dokładnie z 23 if mem-memavail<>0 then writeln('Błąd zarządzania pamięcią.'); 24 readln; sprawdzeń się nie powiedzie, to program jest natychmiast zatrzymywany. Ostatnią czynnością jaka jest wykonywana w konstruktorze, jest nadanie polu Ostatni wartości zero. Destruktor usun sprawdza, czy pole Tablica takimi samymi parametrami, z jakimi została wywołana procedura getmem w konstruktorze. Metoda zakres pełni rolę pomocniczą. Jej zadaniem jest określenie, czy wartość podana jej przez parametr, która będzie użyta jako indeks w tablicy wskaźników jest wartością poprawną, czyli czy nie jest większa od wartości stałej MaxKolekcji, co 25 end. oznaczałoby, że próbujemy sięgnąć poza tę tablice, czy nie jest większa od wartości pola Ilosc, co oznaczałoby, że 1 unit lk; sięgamy poza ostatni element tablicy na jaki została przydzielona pamięć i czy nie jest mniejsza lub równa zero, gdyż przedział wartości indeksów zaczyna się od wartości jeden. Metoda podajLiczbeElementow zwraca 2 3 interface 4 zawartość pola Ostatni, czyli wartość indeksu elementu w tablic, który wskazuje na ostatnio dodany obiekt. Jest to również ostatni element tej tablicy, który zawiera prawidłowy adres. Metoda ustawLiczbeElementow służy 5 type 6 PLiczba=^Liczba; 7 Liczba = object 8 do bezpośredniego manipulowania polem Ostatni, tzn. pozwala ustalić wartość tego pola na taką, jaka została jej przekazana przez parametr nowaliczba. Metoda dodaj private służy do dodawania nowych elementów do kolekcji. 1 Takie rozwiązanie jest stosowane w kontenerach języka Java. Takie obiekty przechowują referencje (które można zdefiniować jako rodzaj wskaźnika) do innych obiektów. Referencje te są klasy Object, po której dziedziczą (niejawnie) wszystkie klasy definiowane w Javie. 1 Podstawy Programowania – semestr drugi 9 Wskaźnik do dodawanego obiektu jest jej przekazywany przez parametr nastepny. Metoda zwraca wartość true jeśli uda się dodać obiekt do kolekcji lub false jeśli taka operacja się nie wartosc:integer; 10 public powiedzie. Aby obiekt został dodany do kolekcji musi istnieć dla niego miejsce w tablicy. Ten warunek jest sprawdzany w 63 wierszu modułu, należącym do tej metody. Jeśli jest spełniony, to zwiększana jest wartość pola Ostatni, po czym to pole jest 11 constructor inicjuj(w:integer); 12 destructor usun; virtual; 13 procedure ustawWartosc(w:integer); wykorzystywane jako indeks w tablicy Tablica. Do elementu 14 function podajWartosc:integer; 15 procedure wydrukElementu; virtual; tablicy wskazywanego przez to pole wpisywany jest adres obiektu, który ma być przechowany w kolekcji. Zadaniem metody usunElement jest usunięcie pojedynczego elementu z 16 kolekcji, którego indeks jest do niej przekazany przez parametr numer. W wierszu 74 modułu, należącym do tej metody end; sprawdzane jest, czy podana wartość indeksu określa prawidłowy element tablicy. Jeśli ten warunek jest spełniony, to w przypadku kiedy usuwany element nie jest ostatnim w tablicy, to wszystkie elementy znajdujące się za nim są przesuwane o jedno miejsce w lewo. Dokonywane jest to za pomocą procedury move, która jest standardową procedurą języka Pascal. Jako pierwszy argument ta procedura przyjmuje 17 18 implementation 19 20 constructor Liczba.inicjuj(w:integer); 21 begin 22 23 żródłowe miejsce w tablicy, czyli to od którego należy zacząć kopiować, jako drugi miejsce docelowe, czyli to do którego trzeba kopiować, ostatni argument określa ilość bajtów jaką należy skopiować. Ostatnią czynnością wykonywaną w tej metodzie jest zmniejszenie zawartości pola Ostatni o jeden. Metoda wartosc:=w; end; podajElement zwraca wskaźnik do elementu przechowywanego 24 w kolekcji, którego numer został jej przekazany przez parametr 25 destructor Liczba.usun; o nazwie numerelementu. Jeśli parametr ten określał miejsce 26 begin 27 end; module lk zdefiniowana jest klasa obiektów, które będą przechowywane w kolekcji. Klasa ta nazywa się Liczba i poza tablicą, to zwracana jest w tym przypadku wartość nil. W zawiera pole o nazwie wartosc, które będzie przechowywać 28 29 procedure Liczba.ustawWartosc(w:integer); 30 begin 31 32 liczbę całkowitą. Konstruktor jest konstruktorem z parametrem. Przez ten parametr przekazywana jest liczba, która następnie jest przypisywana polu wartosc. Destruktor usun jest metodą pustą, gdyż klasa nie posiada pól wskaźnikowych (nie musi być również metodą wirtualną, ale została tak zadeklarowana na potrzeby rozbudowy programu). Metoda ustawWartosc ustala wartosc:=w; wartość pola wartosc niezależnie od konstruktora. Odczytać end; zawartość tego pola możemy poprzez zastosowanie metody podajWartosc. Metoda wydrukElementu wypisuje na ekran zawartość pola wartosc. Moduł Zbior zawiera klasę Zb 33 34 function Liczba.podajWartosc:integer; 35 begin 36 37 okerślającą obiekt „opakowujący” obiekt – kolekcję. Ten ostatni jest tworzony jako składowa obiektu klasy Zb. Klasa ta zawiera również procedurę inicjujKolekcje, która tworzy obiekt kolekcji mogący przechować jedenaście elementów, a następnie umieszcza w nim obiekty klasy Liczba, w których polu wartsc podajWartosc:=wartosc; end; zapisuje losowe liczby. Metoda ta nie jest konstruktorem, gdyż wszystkie elementy klasy Zb będą tworzone statycznie. Metoda 38 39 procedure Liczba.wydrukElementu; usunKolekcje iteruje po wszystkich elementach znajdujących się w kolekcji i zwalnia pamięć na nie przydzieloną, a następnie zwalnia również sam obiekt – kolekcję. Metoda dodajElement 40 begin jest zwykłą nakładką na metodę dodaj klasy Kolekcja. Metoda 41 42 usunElement nie tylko usuwa z kolekcji wskaźnik na określony writeln('Wartość: ',podajWartosc); element, ale również zwalnia pamięć przeznaczoną na ten element. end; zawartości 43 end. uses lk,ko; type Zb = object 5 private 6 7 liczby:PKolekcja; public 8 procedure inicjujKolekcje; 9 procedure usunKolekcje; 10 wydrukujElementy wartosc drukuje poszczególnych na ekran obiektów tych obiektów jest wypisywana na ekran. Kolejnymi czynnościami wykonywanymi w programie jest dodanie nowego obiektu do kolekcji, ponowne wypisanie zawartości kolekcji na ekran i usunięcie dziesiątego elementu z kolekcji, a następnie ponowne wypisanie kolekcji na ekran. Na zakończenie działania programu kolekcja jest usuwana z pamięci. W programie nie jest prezentowana opcja, w której kolekcja przechowuje elementy różnych klas. 2 interface 4 pól przechowywanych w kolekcji. W programie głównym jest tworzony obiekty klasy Zb i tworzona jest kolekcja zawierająca dziesięć obiektów klasy Liczba. Następnie zawartość pól wartosc 1 unit Zbior; 3 Metoda procedure wydrukujElementy; 2 Podstawy Programowania – semestr drugi 11 function dodajElement(licz:PLiczba):boolean; 12 function usunElement(i:integer):boolean; 13 end; 14 15 implementation 16 17 procedure Zb.inicjujKolekcje; 18 var 19 i:integer; 20 element:PLiczba; 21 begin 22 randomize; 23 new(liczby,inicjuj(11)); 24 for i:=1 to 10 do 25 begin 26 new(element,inicjuj(random(20))); 27 liczby^.dodaj(element); 28 29 end; end; 30 31 procedure Zb.usunKolekcje; 32 var 33 34 35 36 37 i:integer; pom:PLiczba; begin for i:=1 to liczby^.podajLiczbeElementow do begin 38 pom:=liczby^.podajElement(i); 39 dispose(pom,usun); 40 41 42 end; dispose(liczby,usun); end; 43 44 function Zb.dodajElement(licz:PLiczba):boolean; 45 begin 46 dodajElement:=liczby^.dodaj(licz); 47 end; 48 49 function Zb.usunElement(i:integer):boolean; 50 var 51 52 l:PLiczba; begin 53 l:=liczby^.podajElement(i); 54 if l<> nil then dispose(l,usun); 55 usunElement:=Liczby^.usunElement(i); 3 Podstawy Programowania – semestr drugi 56 end; 57 58 procedure Zb.wydrukujElementy; 59 var 60 i:integer; 61 pom:PLiczba; 62 begin 63 for i:=1 to Liczby^.podajLiczbeElementow do 64 begin 65 pom:=Liczby^.podajElement(i); 66 pom^.wydrukElementu; 67 end; 68 end; 69 end. 1 unit KO; 2 interface 3 const 4 MaxKolekcji = 65519 div SizeOf(Pointer); 5 type 6 PTabKolekcji = ^TabKolekcji; 7 TabKolekcji = array [1..MaxKolekcji] of Pointer; 8 PKolekcja = ^Kolekcja; 9 Kolekcja = object 10 private 11 Tablica:PTabKolekcji; 12 Ilosc:integer; 13 Ostatni:integer; 14 public 15 constructor inicjuj(nilosc:integer); 16 destructor usun; 17 18 function podajLiczbeElementow:integer; 19 procedure ustawLiczbeElementow(nowaliczba:integer); 20 function dodaj(nastepny:pointer):boolean; 21 function usunElement(numer:integer):boolean; 22 function zakres(wartosc:integer):boolean; 23 procedure usunKolekcje; 24 25 function podajElement(numerelementu:integer):pointer; end; 26 27 implementation 28 29 constructor Kolekcja.inicjuj(nilosc:integer); 30 begin 31 if maxavail < nilosc*sizeof(pointer) then halt; 4 Podstawy Programowania – semestr drugi 32 Ilosc:=nilosc; 1 unit KO; 33 if zakres(Ilosc) then halt; 34 getmem(Tablica, nilosc*sizeof(pointer)); 2 interface 3 const 35 4 36 Ostatni:=0; MaxKolekcji = 65519 div SizeOf(Pointer); end; 5 type 37 38 6 destructor Kolekcja.usun; 39 begin 7 40 8 41 9 42 PTabKolekcji = ^TabKolekcji; TabKolekcji = array [1..MaxKolekcji] of Pointer; if Tablica <> nil then usunKolekcje; PKolekcja = ^Kolekcja; end; Kolekcja = object 43 10 function Kolekcja.zakres(wartosc:integer):boolean; private 44 11 45 12 46 13 var Tablica:PTabKolekcji; log:boolean; Ilosc:integer; begin Ostatni:integer; 47 log:=(wartosc > MaxKolekcji) or (wartosc>Ilosc) or (wartosc<=0); 14 48 public zakres:=log; 49 15 end; constructor inicjuj(nilosc:integer); 50 destructor usun; 16 51 function Kolekcja.podajLiczbeElementow:integer; 17 52 18 53 begin function podajLiczbeElementow:integer; podajLiczbeElementow:=Ostatni; 19 end; 54 procedure ustawLiczbeElementow (nowaliczba:integer); 55 20 procedure Kolekcja.ustawLiczbeElementow(nowaliczba:integer); function dodaj(nastepny:pointer):boolean; 56 57 21 begin 58 22 59 function usunElement(numer:integer):boolean; Ostatni:=nowaliczba; function zakres(wartosc:integer):boolean; end; 23 procedure usunKolekcje; 60 24 61 function podajElement(numerelementu:integer): function Kolekcja.dodaj(nastepny:pointer):boolean; pointer; 62 25 63 26 64 65 27 66 28 67 begin end; if Ostatni < Ilosc then begin inc(Ostatni); implementation Tablica^[Ostatni]:=nastepny; dodaj:=true; 29 constructor Kolekcja.inicjuj(nilosc:integer); 68 end 30 begin else dodaj:=false; 69 31 end;if 70 71 32 maxavail < nilosc*sizeof(pointer) then halt; Ilosc:=nilosc; 72 function Kolekcja.usunElement(numer:integer):boolean; 73 begin 74 75 76 if zakres(numer) or (numer>Ostatni) then usunElement:=False else 5 Podstawy Programowania – semestr drugi 77 78 begin usunElement:=True; 79 if numer <> Ostatni then 80 move(Tablica^[numer+1],Tablica^[numer],(Ostatni-Numer)*sizeof(Pointer)); 81 dec(Ostatni); 82 83 end; end; 84 85 procedure Kolekcja.usunKolekcje; 86 begin 87 freemem(Tablica,Ilosc*sizeof(pointer)); 88 Tablica:=nil; 89 end; 90 91 function Kolekcja.podajElement(numerelementu:integer):Pointer; 92 begin 93 if zakres(numerelementu) or (numerelementu > Ostatni) then 94 95 podajElement:=nil else 96 97 podajElement:=Tablica^[numerelementu]; end; 98 end. 6