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