Wykład 24: Kolekcje obiektów

Transkrypt

Wykład 24: Kolekcje obiektów
Podstawy Programowania – semestr drugi
Wykład dwudziesty czwarty
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
wskaźnik klasy, po której dziedziczyłyby klasy wszystkich obiektów, które byłyby przechowywane przez kolekcję1. Zastosowanie wskaźników bez określonego typu jest
rozwiązaniem elastycznym, gdyż mogą one 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. Liczba 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ą liczbę
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ą liczbę 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 liczba 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));
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
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
z 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
5 type
w tablic, który wskazuje na ostatni obiekt w kolekcji. Jest
to również ostatni element tej tablicy, który zawiera
prawidłowy adres. Metoda ustawLiczbeElementow służy
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 zapisywany jest adres
obiektu, który ma być przechowywany w kolekcji. Zadaniem
metody usunElement jest usunięcie pojedynczego elementu
16
z kolekcji, którego indeks jest do niej przekazany przez
parametr numer. W wierszu 74 modułu, należącym do tej
end;
metody 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, 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 liczbę 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;
zdefiniowana jest klasa obiektów, które będą przechowywane
w kolekcji. Klasa ta nazywa się Liczba i zawiera pole o nazwie
wartosc, które będzie przechowywać liczbę całkowitą.
poza tablicą, to zwracana jest wartość nil. W module lk
28
29
procedure Liczba.ustawWartosc(w:integer);
30
begin
31
32
Konstruktor jest konstruktorem z parametrem. Przez ten
parametr przekazywana jest wartość, 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
ewentualnej
rozbudowy
programu).
Metoda
ustawWartosc ustala wartość pola wartosc niezależnie od
wartosc:=w;
end;
konstruktora. Odczytać zawartość tego pola możemy poprzez
zastosowanie metody podajWartosc. Metoda wydrukElementu
33
wypisuje na ekran zawartość pola wartosc. Moduł Zbior zawiera
34
function Liczba.podajWartosc:integer;
35
begin
36
37
klasę Zb okreś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,
podajWartosc:=wartosc;
end;
w których polu wartsc zapisuje losowe liczby. Metoda ta nie jest
39
procedure Liczba.wydrukElementu;
40
begin
statycznie. Metoda 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 jest zwykłą nakładką na metodę
konstruktorem, gdyż wszystkie obiekty klasy Zb będą tworzone
38
41
42
dodaj klasy Kolekcja. Metoda usunElement nie tylko usuwa
writeln('Wartość: ',podajWartosc);
z kolekcji wskaźnik na określony element, ale również zwalnia
pamięć
przeznaczoną
na
ten
element.
Metoda
wydrukujElementy drukuje na ekran zawartości pól wartosc
end;
43 end.
poszczególnych obiektów przechowywanych w kolekcji.
W programie głównym jest tworzony obiekty klasy Zb
1 unit Zbior;
i tworzona jest kolekcja zawierająca dziesięć obiektów klasy
Liczba. Następnie zawartość pól wartosc tych obiektów jest
2 interface
3
uses lk,ko;
4
type Zb = object
5
private
6
7
liczby:PKolekcja;
public
8
procedure inicjujKolekcje;
9
procedure usunKolekcje;
10
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.
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