You are on page 1of 39

Rozdzia 13.

Tablice i listy poczone


W poprzednich rozdziaach deklarowalimy pojedyncze obiekty typu int, char, i tym podobne.
Czsto chcemy jednak deklarowa zbiory obiektw, takie jak 20 wartoci typu int czy kilka
obiektw typu CAT.
Z tego rozdziau dowiesz si:

czym s tablice i jak si je deklaruje,

czym s acuchy i jak je tworzy za pomoc tablic znakw,

jaki jest zwizek pomidzy tablicami a wskanikami,

w jaki sposb posugiwa si arytmetyk na wskanikach odnoszcych si do tablic.

Czym jest tablica?


Tablica (ang. array) jest zbiorem miejsc przechowywania danych, w ktrym kade z tych miejsc
zawiera dane tego samego typu. Kade miejsce przechowywania jest nazywane elementem tablicy.
Tablic deklaruje si, zapisujc typ, nazw tablicy oraz jej rozmiar. Rozmiar tablicy jest
zapisywany jako ujta w nawiasy kwadratowe ilo elementw tablicy. Na przykad linia:
long LongArray[25];

deklaruje tablic skadajc si z dwudziestu piciu wartoci typu long, noszc nazw
LongArray. Gdy kompilator natrafi na t deklaracj, zarezerwuje miejsce do przechowania
wszystkich dwudziestu piciu elementw. Poniewa kada warto typu long wymaga czterech
bajtw pamici, ta deklaracja rezerwuje sto bajtw cigego obszaru pamici, tak jak pokazano na
rysunku 13.1.

Rys. 13.1. Deklarowanie tablicy

Elementy tablicy
Do kadego z elementw tablicy moemy si odwoa, podajc przesunicie (ang. offset)
wzgldem nazwy tablicy. Elementy tablicy s liczone od zera. Tak wic pierwszym elementem
tablicy jest arrayName[0]. W przykadzie z tablic LongArray, pierwszym elementem jest
LongArray[0], drugim LongArray[1], itd.
Moe to by nieco mylce. Tablica SomeArray[3] zawiera trzy elementy. S to: SomeArray[0],
SomeArray[1] oraz SomeArray[2]. Tablica SomeArray[n] zawiera n elementw
ponumerowanych od SomeArray[0] do SomeArray[n-1].
Elementy tablicy LongArray[25] s ponumerowane od LongArray[0] do LongArray[24].
Listing 13.1 przedstawia sposb zadeklarowania tablicy piciu wartoci cakowitych i
wypenieniau jej wartociami.
Listing 13.1. Uycie tablicy wartoci cakowitych
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:

//Listing 13.1 - Tablice


#include <iostream>
int main()
{
int myArray[5];
int i;
for ( i=0; i<5; i++) // 0-4
{
std::cout << "Wartosc elementu myArray[" << i << "]: ";
std::cin >> myArray[i];
}
for (i = 0; i<5; i++)
std::cout << i << ": " << myArray[i] << "\n";
return 0;
}

Wynik
Wartosc elementu myArray[0]: 3

Wartosc
Wartosc
Wartosc
Wartosc
0: 3
1: 6
2: 9
3: 12
4: 15

elementu
elementu
elementu
elementu

myArray[1]:
myArray[2]:
myArray[3]:
myArray[4]:

6
9
12
15

Analiza
W linii 5. jest deklarowana tablica o nazwie myArray, zawierajca pi zmiennych cakowitych.
W linii 7. rozpoczyna si ptla zliczajca od 0 do 4, czyli przeznaczona dla wszystkich elementw
picioelementowej tablicy. Uytkownik jest proszony o podanie kolejnych wartoci, ktre s
umieszczane w odpowiednich miejscach tablicy.
Pierwsza warto jest umieszczana w elemencie myArray[0], druga w elemencie myArray[1] , i
tak dalej. Druga ptla, for, suy do wypisania na ekranie wartoci kolejnych elementw tablicy.
UWAGA Elementy tablic liczy si od 0, a nie od 1. Z tego powodu pocztkujcy programici
jzyka C++ czsto popeniaj bdy. Zawsze, gdy korzystasz z tablicy, pamitaj, e tablica
zawierajca 10 elementw jest liczona od ArrayName[0] do ArrayName[9]. Element
ArrayName[10] nie jest uywany.

Zapisywanie poza koniec tablicy


Gdy zapisujesz warto do elementu tablicy, kompilator na podstawie rozmiaru elementu oraz jego
indeksu oblicza miejsce, w ktrym powinien j umieci. Przypumy, e chcesz zapisa warto
do elementu LongArray[5], ktry jest szstym elementem tablicy. Kompilator mnoy indeks (5)
przez rozmiar elementu, ktry w tym przypadku wynosi 4. Nastpnie przemieszcza si od
pocztku tablicy o otrzyman liczb bajtw (20) i zapisuje warto w tym miejscu.
Gdy poprosisz o zapisanie wartoci do pidziesitego elementu tablicy LongArray, kompilator
zignoruje fakt, i taki element nie istnieje. Obliczy miejsce wstawienia wartoci (200 bajtw od
pocztku tablicy) i zapisze j w otrzymanym miejscu pamici. Miejsce to moe zawiera
praktycznie dowolne dane i zapisanie tam nowej wartoci moe da zupenie nieoczekiwane
wyniki. Jeli masz szczcie, program natychmiast si zaamie. Jeli nie, dziwne wyniki pojawi
si duo pniej i bdziesz mia problem z ustaleniem, co jest ich przyczyn.
Kompilator przypomina lepca poruszajcego si mijajcego kolejne domy. Zaczyna od
pierwszego domu, MainStreet[0]. Gdy poprosisz go o przejcie do szstego domu na Main
Street, mwi sobie: Musz min jeszcze pi domw. Kady dom to cztery due kroki. Musz
wic przej jeszcze dwadziecia krokw. Gdy poprosisz go o przejcie do MainStreet[100],
mimo i przy Main Street stoi tylko 25 domw, przejdzie 400 krokw. Z pewnoci, zanim
przejdzie ten dystans, wpadnie pod ciarwk. Uwaaj wic, gdzie go wysyasz.
Listing 13.2 pokazuje, co si dzieje, gdy zapiszesz co za kocem tablicy.

OSTRZEENIE Nie uruchamiaj tego programu, moe on zaama system!

Listing 13.2. Zapis za kocem tablicy

0: //Listing 13.2
1: // Demonstruje, co si stanie, gdy zapiszesz
2: // warto za kocem tablicy
3:
4: #include <iostream>
5: using namespace std;
6:
7: int main()
8: {
9:
// wartownicy
10:
long sentinelOne[3];
11:
long TargetArray[25]; // tablica do wypenienia
12:
long sentinelTwo[3];
13:
int i;
14:
for (i=0; i<3; i++)
15:
sentinelOne[i] = sentinelTwo[i] = 0;
16:
17:
for (i=0; i<25; i++)
18:
TargetArray[i] = 0;
19:
20:
cout << "Test 1: \n"; // sprawdzamy biece wartoci
(powinny by 0)
21:
cout << "TargetArray[0]: " << TargetArray[0] << "\n";
22:
cout << "TargetArray[24]: " << TargetArray[24] << "\n\n";
23:
24:
for (i = 0; i<3; i++)
25:
{
26:
cout << "sentinelOne[" << i << "]: ";
27:
cout << sentinelOne[i] << "\n";
28:
cout << "sentinelTwo[" << i << "]: ";
29:
cout << sentinelTwo[i]<< "\n";
30:
}
31:
32:
cout << "\nPrzypisywanie...";
33:
for (i = 0; i<=25; i++)
34:
TargetArray[i] = 20;
35:
36:
cout << "\nTest 2: \n";
37:
cout << "TargetArray[0]: " << TargetArray[0] << "\n";
38:
cout << "TargetArray[24]: " << TargetArray[24] << "\n";
39:
cout << "TargetArray[25]: " << TargetArray[25] << "\n\n";
40:
for (i = 0; i<3; i++)
41:
{
42:
cout << "sentinelOne[" << i << "]: ";
43:
cout << sentinelOne[i]<< "\n";
44:
cout << "sentinelTwo[" << i << "]: ";
45:
cout << sentinelTwo[i]<< "\n";
46:
}
47:
48:
return 0;
49: }

Wynik
Test 1:
TargetArray[0]: 0
TargetArray[24]: 0

sentinelOne[0]:
sentinelTwo[0]:
sentinelOne[1]:
sentinelTwo[1]:
sentinelOne[2]:
sentinelTwo[2]:

0
0
0
0
0
0

Przypisywanie...
Test 2:
TargetArray[0]: 20
TargetArray[24]: 20
TargetArray[25]: 20
sentinelOne[0]:
sentinelTwo[0]:
sentinelOne[1]:
sentinelTwo[1]:
sentinelOne[2]:
sentinelTwo[2]:

20
0
0
0
0
0

Analiza
W liniach 10. i 12. deklarowane s dwie tablice, zawierajce po trzy zmienne cakowite, ktre
peni rol wartownikw (ang. sentinel) wok docelowej tablicy (TargetArray). Tablice
wartownikw s wypeniane zerami. Gdy dokonamy zapisu do pamici poza tablic
TargetArray, najprawdopodobniej zmodyfikujemy zawarto wartownikw. Niektre
kompilatory zliczaj pami do gry, inne w d. Z tego powodu umiecilimy wartownikw po
obu stronach tablicy.
Linie od 20. do 30. potwierdzaj wartoci wartownikw w tecie 1. W linii 34. elementy tablicy s
wypeniane wartoci 20, ale licznik zlicza a do elementu 25, ktry nie istnieje w tablicy
TargetArray.
Linie od 37. do 39. wypisuj wartoci elementw tablicy TargetArray w drugim tecie. Zwr
uwag, e element TargetArray[25] wypisuje warto 20. Jednak gdy wypisujemy wartoci
wartownikw sentinelOne i sentinelTwo, okazuje si, e warto elementu
sentinelOne[0] ulega zmianie. Stao si tak, poniewa pami pooona o 25 elementw dalej
od elementu TargetArray[0] zajmuje to samo miejsce, co element sentinelOne[0]. Gdy
odwoujemy si do nieistniejcego elementu TargetArray[0], w rzeczywistoci odwoujemy si
do elementu sentinelOne[0].
Taki bd moe by bardzo trudny do wykrycia, gdy warto elementu sentinelOne[0] zostaje
zmieniona w miejscu programu, ktre pozornie nie jest zwizane z zapisem wartoci do tej tablicy.
W tym programie uyto magicznych liczb, takich jak 3 dla rozmiarw tablic wartownikw i 25
dla rozmiaru tablicy TargetArray. Bezpieczniej jednak jest uywa staych tak, aby mona byo
zmienia te wartoci w jednym miejscu.
Pamitaj, e poniewa kompilatory rni si od siebie, otrzymany przez ciebie wynik moe by
nieco inny.

Bd supka w pocie
Zapisanie wartoci o jedn pozycj za kocem tablicy jest tak czsto popenianym bdem, e
otrzyma on nawet swoj wasn nazw. Nazywany jest bdem supka w pocie. Nazwa ta
nawizuje do problemu, jaki wiele osb ma z obliczeniem iloci supkw potrzebnych do
utrzymania dziesiciometrowego potu, z supkami rozmieszonymi co metr. Wikszo osb
odpowie, e potrzeba dziesiciu supkw, ale oczywicie potrzebnych jest ich jedenacie. Wyjania
to rysunek 13.2.
Rys. 13.2. Bd supka w pocie

Typ zliczania o jeden wicej moe by pocztkowo udrk programisty. Jednak z czasem
przywykniesz do tego, e elementy w dwudziestopicioelementowej tablicy zliczane s tylko do
elementu numer dwadziecia cztery, oraz do tego, e wszystko liczone jest poczwszy od zera.
UWAGA Niektrzy programici okrelaj element ArrayName[0] jako element zerowy. Nie
naley si na to zgadza, gdy jeli ArrayName[0] jest elementem zerowym, to czym jest
ArrayName[1]? Pierwszym? Jeli tak, to czy bdziesz pamita, gdy zobaczysz
ArrayName[24], e nie jest to element dwudziesty czwarty, ale dwudziesty pity? Lepiej jest
powiedzie, e element ArrayName[0] ma zerowy offset i jest pierwszym elementem.

Inicjalizowanie tablic
Deklarujc po raz pierwszy prost tablic wbudowanych typw, takich jak int czy char, moesz
j zainicjalizowa. Po nazwie tablicy umie znak rwnoci (=) oraz ujt w nawiasy klamrowe
list rozdzielonych przecinkami wartoci. Na przykad
int IntegerArray[5] = { 10, 20, 30, 40, 50 };

deklaruje IntegerArray jako tablic piciu wartoci cakowitych. Przypisuje elementowi


IntegerArray[0] warto 10, elementowi IntegerArray[1] warto 20, i tak dalej.
Gdy pominiesz rozmiar tablicy, zostanie stworzona tablica na tyle dua, by moga pomieci
inicjalizujce je elementy. Jeli napiszesz:
int IntegerArray[] = { 10, 20, 30, 40, 50 };

stworzysz tak sam tablic, jak w poprzednim przykadzie.


Jeli chcesz zna rozmiar tablicy, moesz poprosi kompilator, aby go dla ciebie obliczy. Na
przykad
const USHORT IntegerArrayLength = sizeof(IntegerArray) /
sizeof(IntegerArray[0]);

przypisuje staej IntegerArrayLength typu USHORT wynik dzielenia rozmiaru caej tablicy
przez rozmiar pojedynczego jej elementu. Wynik ten odpowiada iloci elementw w tablicy.
Nie mona inicjalizowa wicej elementw ni wynosi rozmiar tablicy. Tak wic zapis
int IntegerArray[5] = { 10, 20, 30, 40, 50, 60 };

spowoduje bd kompilacji, gdy zostaa zadeklarowana tablica picioelementowa, a my


prbujemy zainicjalizowa sze elementw. Mona natomiast napisa
int IntegerArray = {10, 20};

TAK

NIE

Pozwl, by kompilator sam okrela rozmiar


inicjalizowanych tablic.

Nie dokonuj zapisw za kocem tablicy.

Nadawaj tablicom znaczce nazwy, tak jak


wszystkim innym zmiennym.
Pamitaj, e pierwszy element tablicy ma offset
(przesunicie) wynoszcy zero.

Deklarowanie tablic
Tablica moe mie dowoln nazw, zgodn z zasadami nazywania zmiennych, ale nie moe mie
takiej samej nazwy jak zmienna lub inna tablica wewntrz danego zakresu. Dlatego nie mona
mie jednoczenie tablicy o nazwie myCats[5] oraz zmiennej myCats.
Rozmiar tablicy mona okreli, uywajc staej lub wyliczenia. Ilustruje to listing 13.3.
Listing 13.3. Uycie staej i wyliczenia jako rozmiaru tablicy
0:
1:
2:
3:
4:

// Listing 13.3
// Uycie staej i wyliczenia jako rozmiaru tablicy
#include <iostream>
int main()

5:
6:
7:
8:
9:
10:
11:
12:

{
enum WeekDays { Sun, Mon, Tue,
Wed, Thu, Fri, Sat, DaysInWeek };
int ArrayWeek[DaysInWeek] = { 10, 20, 30, 40, 50, 60, 70 };
std::cout << "Wartoscia Wtorku jest: " << ArrayWeek[Tue];
return 0;
}

Wynik
Wartoscia Wtorku jest: 30

Analiza
Linia 6. tworzy typ wyliczeniowye o nazwie WeekDays (dni tygodnia). Zawiera ono osiem
skadowych. Staej Sun (od sunday niedziela) odpowiada warto 0, za staej DaysInWeek
(dni w tygodniu) odpowiada warto 7.
W linii 10. wyliczeniowa staa Tue (od tuesday wtorek) peni rol offsetu tablicy. Poniewa
staa Tue odpowiada wartoci dwa, w linii 10. zwracany i wypisywany jest trzeci element tablicy,
ArrayWeek[2].
Tablice

Aby zadeklarowa tablic, zapisz typ przechowywanego w niej obiektu, nazw tablicy oraz jej
rozmiar, okrelajcy ilo obiektw, ktre powinny by przechowane w tej tablicy.

Przykad 1
int MyIntegerArray[90];

Przykad 2
long * ArrayOfPointersToLong[8];

Aby odwoa si do elementw tablicy, uyj operatora indeksu.

Przykad 1
int theNinethInteger = MyIntegerArray[8];

Przykad 2
long * pLong = ArrayOfPointersToLongs[8];

Elementy tablic s liczone od zera. Tablica n elementw zawiera elementy liczone od zera do
n1.

Tablice obiektw
W tablicach mona przechowywa dowolne obiekty, zarwno wbudowane, jak i te zdefiniowane
przez uytkownika. Deklarujc tablic, informujesz kompilator o typie przechowywanych
obiektw oraz iloci obiektw, dla jakiej ma zosta zaalokowane miejsce. Kompilator, na
podstawie deklaracji klasy, zna ilo miejsca zajmowanego przez kady z obiektw. Klasa musi
posiada domylny konstruktor nie posiadajcy argumentw (aby obiekty mogy zosta stworzone
podczas definiowania tablicy).
Na proces dostpu do danych skadowych w tablicy obiektw skadaj si dwa kroki. Waciwy
element tablicy jest wskazywany za pomoc operatora indeksu ([]), po czym dodawany jest
operator skadowej (.), wydzielajcy okrelon zmienn skadow obiektu. Listing 13.4
demonstruje sposb tworzenia i wykorzystania tablicy piciu obiektw typu CAT.
Listing 13.4. Tworzenie tablicy obiektw
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:

// Listing 13.4 - Tablica obiektw


#include <iostream>
using namespace std;
class CAT
{
public:
CAT() { itsAge = 1; itsWeight=5; }
~CAT() {}
int GetAge() const { return itsAge; }
int GetWeight() const { return itsWeight; }
void SetAge(int age) { itsAge = age; }
private:
int itsAge;
int itsWeight;
};
int main()
{
CAT Litter[5];
int i;
for (i = 0; i < 5; i++)
Litter[i].SetAge(2*i +1);
for (i = 0; i < 5; i++)
{
cout << "Kot nr " << i+1<< ": ";
cout << Litter[i].GetAge() << endl;
}
return 0;

32:

Wynik
Kot
Kot
Kot
Kot
Kot

nr
nr
nr
nr
nr

1:
2:
3:
4:
5:

1
3
5
7
9

Analiza
Linie od 5. do 17. deklaruj klas CAT. Klasa CAT musi posiada domylny konstruktor, aby w
tablicy mogy by tworzone jej obiekty. Pamitaj, e jeli stworzysz jakikolwiek inny konstruktor,
domylny konstruktor nie zostanie dostarczany przez kompilator; bdziesz musia stworzy go
sam.
Pierwsza ptla for (linie 23. i 24.) ustawia wiek dla kadego z piciu obiektw CAT w tablicy.
Druga ptla for (linie od 26. do 30.) odwouje si do kadego z obiektw i wywouje jego funkcj
skadow GetAge().
Metoda GetAge() kadego z poszczeglnych obiektw jest wywoywana poprzez okrelenie
elementu tablicy, Litter[i], po ktrym nastpuje operator kropki (.) oraz nazwa funkcji
skadowej.

Tablice wielowymiarowe
Tablice mog mie wicej ni jeden wymiar. Kady wymiar jest reprezentowany przez oddzielny
indeks tablicy. Na przykad, tablica dwuwymiarowa posiada dwa indeksy; tablica trjwymiarowa
posiada trzy indeksy, i tak dalej. Tablice mog mie dowoln ilo wymiarw, cho
najprawdopodobniej wikszo tworzonych przez ciebie tablic bdzie miaa tylko jeden lub dwa
wymiary.
Dobrym przykadem tablicy dwuwymiarowej jest szachownica. Jeden wymiar reprezentuje osiem
rzdw, za drugi wymiar reprezentuje osiem kolumn. Ilustruje to rysunek 13.3.
Rys. 13.3. Szachownica oraz tablica dwuwymiarowa

Przypumy e mamy klas o nazwie SQUARE (kwadrat). Deklaracja tablicy o nazwie Board
(plansza), ktra j reprezentuje, mogaby wic mie posta:
SQUARE Board[8][8];

Te same dane moglibymy przechowa w jednowymiarowej, 64-elementowej tablicy. Na


przykad:
SQUARE Board[64];

Nie odpowiadaoby to jednak rzeczywistej, dwuwymiarowej planszy. Na pocztku gry krl


umieszczany jest na czwartej pozycji w pierwszym rzdzie; tej pozycji odpowiada:
Board[0][3];

przy zaoeniu, e pierwszy wymiar odnosi si do rzdw, a drugi do kolumn.

Inicjalizowanie tablic wielowymiarowych


Tablice wielowymiarowe take mog by inicjalizowane. Kolejnym elementom tablicy
przypisywane s wartoci, przy czym gdy wczeniejsze wymiary pozostaj stay,zmianie
eniapodlegaj sidane w ostatnim wymiarze tablicy (przy ustaleniu wszystkich poprzednich). Tak
wic, gdy mamy tablic:
int theArray[5][3];

pierwsze trzy elementy trafiaj do theArray[0], nastpne trzy do theArray[1], i tak dalej.
Tablic t moemy zainicjalizowa, piszc:
int theArray[5][3] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };

W celu uzyskania lepszej przejrzystoci, moesz pogrupowa wartoci, uywajc nawiasw


klamrowych. Na przykad:
int theArray[5][3] = { { 1, 2, 3},
{ 4, 5, 6},
{ 7, 8, 9},
{10,11,12},
{13,14,15} };

Kompilator ignoruje wewntrzne nawiasy klamrowe (cho uatwiaj one uytkownikowi


zrozumienie sposobu uoenia wartoci).
Kada warto musi by oddzielona przecinkiem, bez wzgldu na stosowanie nawiasw
klamrowych. Cay zestaw inicjalizacyjny musi by ujty w nawiasy klamrowe i koczy si
rednikiem.
Listing 13.5 tworzy tablic dwuwymiarow. Pierwszym wymiarem (czyli pierwsz kolumn) jest
zestaw liczb od zera do cztery. Drugi wymiar (czyli drug kolumn) zawiera podwojon stanowi
liczby o wartociach dwukrotnie wikszych ni wartoci w kolumnie pierwszej.
kadej z wartoci w pierwszym wymiarze.
Listing 13.5. Tworzenie tablicy wielowymiarowej
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:

// Listing 13.5 - Tworzenie tablicy wielowymiarowej


#include <iostream>
using namespace std;
int main()
{
int SomeArray[5][2] = { {0,0}, {1,2}, {2,4}, {3,6}, {4,8}};
for (int i = 0; i<5; i++)
for (int j=0; j<2; j++)
{
cout << "SomeArray[" << i << "][" << j << "]: ";
cout << SomeArray[i][j]<< endl;
}
return 0;
}

Wynik
SomeArray[0][0]:
SomeArray[0][1]:
SomeArray[1][0]:
SomeArray[1][1]:
SomeArray[2][0]:
SomeArray[2][1]:
SomeArray[3][0]:
SomeArray[3][1]:
SomeArray[4][0]:
SomeArray[4][1]:

0
0
1
2
2
4
3
6
4
8

Analiza
Linia 7. deklaruje SomeArray jako tablic dwuwymiarow. Pierwszy wymiar skada si z piciu
liczb cakowitych, za drugi z dwch liczb cakowitych. Powstaje wic siatka o rozmiarach 52,
co ilustruje rysunek 13.4.
Rys. 13.4. Tablica 5 x 2

Wartoci s inicjalizowane w parach, cho rwnie dobrze mogyby zosta obliczone. Linie 8. i 9.
tworz zagniedon ptl for. Ptla zewntrzna przechodzi przez kady element pierwszego
wymiaru. Odpowiednio, dla kadego elementu w tym wymiarze, wewntrzna ptla przechodzi
przez kady element drugiego wymiaru. Jest to zgodne z wydrukiem. Po elemencie
SomeArray[0][0] nastpuje element SomeArray[0][1]. Pierwszy wymiar jest
inkrementowany tylko wtedy, gdy drugi wymiar zostanie wczeniej zwikszony o jeden. Wtedy
zaczyna si ponowne zliczanie drugiego wymiaru.

Kilka sw na temat pamici


Gdy deklarujesz tablic, dokadnie informujesz kompilator o tym, ile obiektw chcesz w niej
przechowa. Kompilator rezerwuje pami dla wszystkich tych obiektw, nawet jeli nigdy z nich
nie skorzystasz. Nie stanowi to problemu w przypadku tych tablic, w ktrych dokadnie znasz
ilo potrzebnych ci elementw. Na przykad, szachownica zawiera 64 pola, za koty rodz od
jednego do dziesiciu kocit. Jeli jednak nie masz pojcia, ilu obiektw potrzebujesz, musisz
skorzysta z bardziej zaawansowanych struktur danych.
W tej ksice przedstawimy tablice wskanikw, tablice tworzone na stercie oraz rne inne
kolekcje. Poznamy te kilka zaawansowanych struktur danych, jednak wicej informacji na ten
temat moesz znale w mojej ksice C++ Unleashed, wydanej przez Sams Publishing. Dwie
najlepsze rzeczy w programowaniu to moliwo cigego uczenia si i to, e zawsze pojawiaj si
kolejne ksiki, z ktrych mona si uczy.

Tablice wskanikw
W przedstawianych dotd tablicach wszystkie ich elementy byy skadowane na stosie. Zwykle
pami stosu jest do ograniczona, podczas gdy pami na stercie jest duo bardziej obszerna.
Istnieje moliwo zadeklarowania wszystkich obiektw na stercie i przechowania w tablicy tylko
wskanika do kadego z obiektw. Powoduje to znaczne zmniejszenie iloci pamici stosu
zajmowanej przez tablic. Listing 13.6 zawiera zmodyfikowan wersj listingu 13.4, w ktrej
wszystkie obiekty przechowywane s na stercie. W celu podkrelenia faktu, e umoliwia ona

lepsze wykorzystanie pamici, rozmiar tablicy zosta zwikszony z piciu do piciuset elementw,
za jej nazwa zostaa zmieniona z Litter (miot) na Family (rodzina).
Listing 13.6. Tablica wskanikw do obiektw
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:

// Listing 13.6 - Tablica wskanikw do obiektw


#include <iostream>
using namespace std;
class CAT
{
public:
CAT() { itsAge = 1; itsWeight=5; }
~CAT() {}
// destruktor
int GetAge() const { return itsAge; }
int GetWeight() const { return itsWeight; }
void SetAge(int age) { itsAge = age; }
private:
int itsAge;
int itsWeight;
};
int main()
{
CAT * Family[500];
int i;
CAT * pCat;
for (i = 0; i < 500; i++)
{
pCat = new CAT;
pCat->SetAge(2*i +1);
Family[i] = pCat;
}
for (i = 0; i < 500; i++)
{
cout << "Kot nr " << i+1 << ": ";
cout << Family[i]->GetAge() << endl;
}
return 0;
}

Wynik
Kot
Kot
Kot
...
Kot
Kot

nr 1: 1
nr 2: 3
nr 3: 5
nr 499: 997
nr 500: 999

Analiza
Obiekt CAT zadeklarowany w liniach od 5. do 17. jest identyczny z obiektem CAT
zadeklarowanym na listingu 13.4. Tym razem jednak tablica zadeklarowana w linii 21. ma nazw
Family i zawiera 500 wskanikw do obiektw typu CAT.

W pocztkowej ptli for (linie od 24. do 29.) na stercie tworzonych jest piset nowych obiektw
typu CAT; dla kadego z nich wiek jest ustawiany jako podwojony indeks plus jeden. Tak wic
pierwszy CAT ma wiek 1, drugi ma wiek 3, trzeci ma 5, i tak dalej. Na zakoczenie, w tablicy
umieszczany jest wskanik kolejnego stworzonego obiektu.
Poniewa tablica zostaa zadeklarowana jako zawierajca wskaniki, dodawany jest do niej
wskanik a nie obiekt wyuskany zspod tego wskanika.
Druga ptla for (linie od 31. do 35.) wypisuje kad z wartoci. Dostp do wskanika uzyskuje
si przez uycie indeksu elementu, Family[i]. Otrzymany adres umoliwia dostp do metody
GetAge().
W tym przykadzie tablica Family i wszystkie zawarte w niej wskaniki s przechowywane na
stosie, ale piset stworzonych przedtem obiektw CAT znajduje si na stercie.

Deklarowane tablic na stercie


Istnieje moliwo umieszczenia na stercie caej tablicy. W tym celu naley wywoa new z
operatorem indeksu. W rezultacie otrzymujemy wskanik do obszaru sterty zawierajcego nowo
utworzon tablic. Na przykad:
CAT *Family = new CAT[500];

deklaruje zmienn Family jako wskanik do pierwszego elementu w piciusetelementowej tablicy


obiektw typu CAT. Innymi sowy, Family wskazuje na czyli zawiera adres element
Family[0].
Zalet uycia zmiennej Family w ten sposb jest to, e moemy na wskanikach wykona
dziaania arytmetyczne odwoujc si do elementw tablicy. Na przykad, moemy napisa:
CAT *Family = new CAT[500];
CAT *pCat = Family;
pCat->SetAge(10);
pCat++;
pCat->SetAge(20);

//
//
//
//

pCat wskazuje na Family[0]


ustawia Family[0] na 10
przechodzi do Family[1]
ustawia Family[1] na 20

Deklarujemy tu now tablic piciuset obiektw typu CAT oraz wskanik wskazujcy pocztek tej
tablicy. Uywajc tego wskanika, wywoujemy funkcj SetAge() pierwszego obiektu,
przekazujc jej warto 10. Nastpnie wskanik jest inkrementowany i automatycznie wskazuje
nastpny obiekt w tablicy, po czym wywoywana jest metoda SetAge()nastpnego obiektu.

Wskanik do tablicy a tablica wskanikw


Przyjrzyjmy si trzem poniszym deklaracjom:
1: CAT
FamilyOne[500];
2: CAT * FamilyTwo[500];
3: CAT * FamilyThree = new CAT[500];

FamilyOne jest tablic piciuset obiektw typu CAT. FamilyTwo jest tablic piciuset
wskanikw do obiektw typu CAT. FamilyThree jest wskanikiem do tablicy piciuset
obiektw typu CAT.

Rnice pomidzy tymi trzema liniami kodu zasadniczo wpywaj na dziaanie tych tablic. Jeszcze
bardziej dziwi fakt, i FamilyThree jest po prostu wariantem deklaracji FamilyOne (i bardzo si
rni od FamilyTwo).
Mamy tu do czynienia ze zoonym zagadnieniem powiza tablic ze wskanikami. W trzecim
przypadku, FamilyThree jest wskanikiem do tablicy, czyli adres w zmiennej FamilyThree jest
adresem pierwszego elementu w tej tablicy, co dokadnie odpowiada przypadkowi ze zmienn
FamilyOne.

Wskaniki a nazwy tablic


W C++ nazwa tablicy jest staym wskanikiem do pierwszego elementu tablicy. Tak wic, w
deklaracji
CAT Family[50];

Family jest wskanikiem do &Family[0], ktre jest adresem pierwszego elementu w tablicy
Family.

Uywanie nazw tablic jako staych wskanikw (i odwrotnie) jest dozwolone. Tak wic Family +
4 jest poprawnym sposobem odwoania si do danych w elemencie Family[4].
Podczas dodawania, inkrementowania lub dekrementowania wskanikw wszystkie dziaania
arytmetyczne wykonuje kompilator. Adres, do ktrego odwoujemy si, piszc Family + 4, nie
jest adresem pooonym o cztery bajty od adresu wskazywanego przez Family, lecz adresem
pooonym o cztery obiekty dalej. Gdyby kady z obiektw zajmowa cztery bajty, wtedy Family
+ 4 odnosioby si do miejsca pooonego o szesnacie bajtw za pocztkiem tablicy. Gdyby
kady obiekt typu CAT zawiera cztery skadowe typu long, zajmujce po cztery bajty kada, oraz
dwie skadowe typu short, po dwa bajty kada, wtedy kady obiekt tego typu zajmowaby
dwadziecia bajtw, za Family + 4 odnosioby si do adresu pooonego o osiemdziesit
bajtw od pocztku tablicy.
Deklarowanie i wykorzystanie tablicy zadeklarowanej na stercie ilustruje listing 13.7.

Listing 13.7. Tworzenie tablicy za pomoc operatora new


0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:

// Listing 13.7 - Tablica utworzona na stercie


#include <iostream>
class CAT
{
public:
CAT() { itsAge = 1; itsWeight=5; }
~CAT();
int GetAge() const { return itsAge; }
int GetWeight() const { return itsWeight; }
void SetAge(int age) { itsAge = age; }
private:
int itsAge;
int itsWeight;
};
CAT :: ~CAT()
{
// cout << "Wywolano destruktor!\n";
}
int main()
{
CAT * Family = new CAT[500];
int i;
for (i = 0; i < 500; i++)
{
Family[i].SetAge(2*i +1);
}
for (i = 0; i < 500; i++)
{
std::cout << "Kot nr " << i+1 << ": ";
std::cout << Family[i].GetAge() << std::endl;
}
delete [] Family;
return 0;
}

Wynik
Kot
Kot
Kot
...
Kot
Kot

nr 1: 1
nr 2: 3
nr 3: 5
nr 499: 997
nr 500: 999

Analiza
Linia 25. deklaruje tablic Family zawierajc piset obiektw typu CAT. Caa tablica jest
tworzona na stercie, za pomoc wywoania new CAT[500].

Usuwanie tablic ze sterty


Co si stanie z pamici zaalokowan dla tych obiektw CAT, gdy tablica zostanie zniszczona?
Czy istnieje moliwo wycieku pamici? Usunicie tablicy Family automatycznie zwrci ca
pami przydzielon tablicy wtedy, gdy uyjesz operatora delete[], pamitajc o nawiasach
kwadratowych. Kompilator potrafi wtedy zniszczy kady z obiektw w tablicy i odpowiednio
zwolni pami sterty.
Aby to sprawdzi, zmie rozmiar tablicy z 500 na 10 w liniach 25., 28. oraz 36. Nastpnie usu
znak komentarza przy instrukcji cout w linii 20. Gdy program dojedzie do linii 39., w ktrej
tablica jest niszczona, zostanie wywoany destruktor kadego z obiektw CAT.
Gdy tworzysz element na stercie za pomoc operatora new, zawsze powiniene usuwa go i
zwalnia jego pami za pomoc operatora delete. Gdy tworzysz tablic, uywajc operatora
new <klasa>[rozmiar], do usunicia tej tablicy i zwolnienia jej pamici powiniene uy
operatora delete[]. Nawiasy kwadratowe sygnalizuj kompilatorowi, e chodzi o usunicie
tablicy.
Gdy pominiesz nawiasy kwadratowe, zostanie usunity tylko pierwszy element w tablicy. Moesz
to sprawdzi sam, usuwajc nawiasy kwadratowe w linii 39. Jeli zmodyfikowae lini 20. tak, by
destruktor wypisywa komunikat, powiniene zobaczy na ekranie, e niszczony jest tylko jeden
obiekt CAT. Gratulacje! Wanie stworzye wyciek pamici!
TAK

NIE

Pamitaj, e tablica n elementw zawiera


elementy liczone od zera do n1.

Nie dokonuj zapisw ani odczytw poza


kocem tablicy.

W przypadku wskanikw wskazujcych


tablice, uywaj indeksowania tablic.

Nie myl tablicy wskanikw ze wskanikiem do


tablicy.

Tablice znakw
acuch w jzyku C jest tablic znakw, zakoczon znakiem null. Jedyne acuchy w stylu C, z
jakimi mielimy dotd do czynienia, to nienazwane stae acuchowe, uywane w instrukcjach
cout, takie jak:
cout << "hello world.\n";

acuchy w stylu C moesz deklarowa i inicjalizowa tak samo, jak wszystkie inne tablice. Na
przykad:
char Greeting[] =

{ 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\0' };

Ostatni znak, '\0', jest znakiem null, ktry jest rozpoznawany przez wiele funkcji C++ jako znak
koczcy acuch w stylu C. Cho inicjalizowanie znak po znaku przynosi efekty, jest jednak
do mudne i daje wiele okazji do bdw. C++ pozwala wic na uywanie dla poprzedniej
deklaracji formy skrtowej:
char Greeting[] = "Hello World";

Powiniene zwrci uwag na dwa elementy w tej konstrukcji:

zamiast pojedynczych znakw ujtych w apostrofy, oddzielonych przecinkami i ujtych w


nawiasy klamrowe, wpisuje si ujty w cudzysowy acuch w stylu C, bez przecinkw i bez
nawiasw klamrowych,

nie ma potrzeby doczania znaku null, gdy kompilator docza go automatycznie.

acuch Hello World, przechowywany w stylu C, ma dwanacie znakw. Sowo Hello zajmuje
pi bajtw, spacja jeden bajt, sowo World pi bajtw, za koczcy znak null zajmuje jeden
bajt.
Moesz take tworzy nie zainicjalizowane tablice znakw. Tak jak w przypadku wszystkich
tablic, naley upewni si, czy w buforze nie zostanie umieszczone wicej znakw, ni jest
miejsca.
Listing 13.8 demonstruje uycie nie zainicjalizowanego bufora.
Listing 13.8. Wypenianie tablicy
0: //Listing 13.8 bufory znakowe
1:
2: #include <iostream>
3:
4: int main()
5: {
6:
char buffer[80];
7:
std::cout << "Wpisz lancuch: ";
8:
std::cin >> buffer;
9:
std::cout << "Oto zawartosc bufora:
std::endl;
10:
return 0;
11: }

" << buffer <<

Wynik
Wpisz lancuch: Hello World
Oto zawartosc bufora: Hello

Analiza
W linii 6. deklarowany jest bufor, mogcy pomieci osiemdziesit znakw. Wystarcza to do
przechowania 79znakowego acucha w stylu C oraz koczcego znaku null.

W linii 7. uytkownik jest proszony o wpisanie acucha w stylu C, ktry jestw linii 8. jest
wprowadzany do bufora. Obiekt cin automatycznie dopisuje do acucha w buforze znak
koczcy null.
W przypadku programu z listingu 13.8 pojawiaj si dwa problemy. Po pierwsze, jeli uytkownik
wpisze wicej ni 79 znakw, cin dokona zapisu poza kocem bufora. Po drugie, gdy uytkownik
wpisze spacj, cin potraktuje j jako koniec acucha i przestanie zapisywa reszt znakw do
bufora.
Aby rozwiza te problemy, musisz wywoa specjaln metod obiektu cin: metod get().
Metoda cin.get() posiada trzy parametry:

bufor do wypenienia,

maksymaln ilo znakw do pobrania,

znak koczcy wprowadzany acuch.

Domylnym znakiem koczcym jest znak nowej linii. Uycie tej metody ilustruje listing 13.9.
Listing 13.9. Wypenianie tablicy
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
znaku
10:
11:
12:

//Listing 13.9 uycie cin.get()


#include <iostream>
using namespace std;
int main()
{
char buffer[80];
cout << "Wpisz lancuch: ";
cin.get(buffer, 79);
// pobiera do 79 znakw lub do
nowej linii
cout << "Oto zawartosc bufora: " << buffer << endl;
return 0;
}

Wynik
Wpisz lancuch: Hello World
Oto zawartosc bufora: Hello World

Analiza
Linia 9. wywouje metod get() obiektu cin. Bufor zadeklarowany w linii 7. jest przekazywany
jako jej pierwszy argument. Drugim argumentem jest maksymalna ilo znakw do pobrania, w
tym przypadku musi ni by 79 (tak, aby bufor mg pomieci take koczcy znak null). Nie ma
potrzeby podawania take dodawania znaku koczcego wpis, gdy robi to warto
wystarczydomylna ejdla przejcia do nowej linii.

strcpy() oraz strncpy()


C++ odziedziczyo od jzyka C bibliotek funkcji operujcych na acuchach w stylu C. Wrd
wielu funkcji tego typu znajdziemy dwie funkcje suce do kopiowania jednego acucha do

drugiego: strcpy() oraz strncpy(). Funkcja strcpy() kopiuje ca zawarto jednego


acucha do wskazanego bufora. Jej uycie ilustruje listing 13.10.
Listing 13.10. Uycie strcpy()
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:

//Listing 13.10 Uycie strcpy()


#include <iostream>
#include <string.h>
using namespace std;
int main()
{
char String1[] = "Zaden czlowiek nie jest samoistna wyspa";
char String2[80];
strcpy(String2,String1);
cout << "String1: " << String1 << endl;
cout << "String2: " << String2 << endl;
return 0;
}

Wynik
String1: Zaden czlowiek nie jest samoistna wyspa
String2: Zaden czlowiek nie jest samoistna wyspa

Analiza
W linii 3. jest doczany plik nagwkowy string.h. Ten plik zawiera prototyp funkcji strcpy().
Funkcja otrzymuje dwie tablice znakw docelow oraz rdow. Gdyby tablica rdowa bya
wiksza ni tablica docelowa, funkcja strcpy() dokonaaby zapisu poza koniec bufora.
Aby nas przed tym zabezpieczy, biblioteka standardowa zawiera take funkcj strncpy(). Ta
wersja funkcji posiada dodatkowy argument, okrelajcy maksymaln ilo znakw do
skopiowania. Funkcja strncpy() kopiuje znaki, a do napotkania pierwszego znaku null albo do
osignicia dozwolonej maksymalnej iloci znakw.
Listing 13.11 ilustruje uycie funkcji strncpy().
Listing 13.11. Uycie funkcji strncpy()
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:

//Listing 13.11 Uycie strncpy()


#include <iostream>
#include <string.h>
int main()
{
const int MaxLength = 80;
char String1[] = "Zaden czlowiek nie jest samoistna wyspa";
char String2[MaxLength+1];
strncpy(String2,String1,MaxLength);
std::cout << "String1: " << String1 << std::endl;

15:
16:
17:

std::cout << "String2: " << String2 << std::endl;


return 0;
}

Wynik
String1: Zaden czlowiek nie jest samoistna wyspa
String2: Zaden czlowiek nie jest samoistna wyspa

Analiza
W linii 12. wywoanie funkcji strcpy() zostao zmienione na wywoanie funkcji strncpy().
Funkcja ta posiada take trzeci parametr, okrelajcy maksymaln ilo znakw do skopiowania.
Bufor String2 ma dugo MaxLength+1 (maksymalna dugo + 1) znakw. Dodatkowy znak
jest przeznaczony do przechowania znaku null, ktry jest automatycznie umieszczany na kocu
acucha zarwno przez funkcj strcpy(), jak i strncpy().

Klasy acuchw
C++ przejo z jzyka C zakoczone znakiem null acuchy oraz bibliotek funkcji, zawierajc
take funkcj strcpy(), ale funkcje tej biblioteki nie s zintegrowane z bibliotekami
zorientowanymi obiektowo. Biblioteka standardowa zawiera klas String, obejmujc zestaw
danych i funkcji przeznaczonych do manipulowania acuchami, oraz zestaw akcesorw, dziki
ktrym same dane s ukryte przed klientami tej klasy.
W ramach wiczenia potwierdzajcego waciwe zrozumienie omawianych zagadnie,
sprbujemy teraz stworzy wasn klas String. Nasza klasa String powinna likwidowa
podstawowe ograniczenia tablic znakw. Podobnie jak wszystkie tablice, tablice znakw s
statyczne. Sami definiujemy, jaki maj rozmiar. Tablice te zawsze zajmuj okrelon ilo pamici,
bez wzgldu na to, czy jest to naprawd potrzebne. Z kolei dokonanie zapisu za kocem tablicy
moe mie katastrofalne skutki.
UWAGA Przedstawiona tu klasa String jest bardzo ograniczona i w adnym przypadku nie
moe by uwaana za nadajc si do powanych zastosowa. Wystarczy jednak na potrzeby
naszego wiczenia, gdy biblioteka standardowa zawiera pen i stabiln klas String.

Dobra klasa String alokuje tylko tyle pamici, ile potrzebuje (aby zawsze wystarczao na
przechowanie tego, co powinna zawiera). Jeli nie moe zaalokowa wystarczajcej iloci
pamici, powinna poprawnie to zgosi.
Pierwsz prb utworzenia naszej klasy String przedstawia listing 13.12.
Listing 13.12. Uycie klasy String
0:
1:
2:
3:
4:
5:
6:

//Listing 13.12 Uycie klasy String


#include <iostream>
#include <string.h>
using namespace std;
// zasadnicza klasa acucha

7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:

class String
{
public:
// konstruktory
String();
String(const char *const);
String(const String &);
~String();
// przecione operatory
char & operator[](unsigned short offset);
char operator[](unsigned short offset) const;
String operator+(const String&);
void operator+=(const String&);
String & operator= (const String &);
// oglne akcesory
unsigned short GetLen()const { return itsLen; }
const char * GetString() const { return itsString; }
private:
String (unsigned short);
char * itsString;
unsigned short itsLen;
};

// prywatny konstruktor

// domylny konstruktor tworzy acuch o dugoci zera bajtw


String::String()
{
itsString = new char[1];
itsString[0] = '\0';
itsLen=0;
}
// prywatny (pomocniczy) konstruktor, uywany tylko przez
// metody klasy, do tworzenia nowego acucha o
// wymaganym rozmiarze, wypenionym bajtami zerowymi
String::String(unsigned short len)
{
itsString = new char[len+1];
for (unsigned short i = 0; i<=len; i++)
itsString[i] = '\0';
itsLen=len;
}
// zamienia tablic znakw na String
String::String(const char * const cString)
{
itsLen = strlen(cString);
itsString = new char[itsLen+1];
for (unsigned short i = 0; i<itsLen; i++)
itsString[i] = cString[i];
itsString[itsLen]='\0';
}
// konstruktor kopiujcyi
String::String (const String & rhs)
{
itsLen=rhs.GetLen();
itsString = new char[itsLen+1];
for (unsigned short i = 0; i<itsLen;i++)

68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:

itsString[i] = rhs[i];
itsString[itsLen] = '\0';
}
// destruktor, zwalnia zaalokowan pami
String::~String ()
{
delete [] itsString;
itsLen = 0;
}
// operator rwnoci, zwalnia istniejc pami,
// po czym kopiuje acuch i rozmiar
String& String::operator=(const String & rhs)
{
if (this == &rhs)
return *this;
delete [] itsString;
itsLen=rhs.GetLen();
itsString = new char[itsLen+1];
for (unsigned short i = 0; i<itsLen;i++)
itsString[i] = rhs[i];
itsString[itsLen] = '\0';
return *this;
}
// nie stay operator indeksu, zwraca
// referencj do znaku, dziki czemu moe on
// by zmieniony!
char & String::operator[](unsigned short offset)
{
if (offset > itsLen)
return itsString[itsLen-1];
else
return itsString[offset];
}
// stay operator offsetu doindeksu dla uycia dla
// staych obiektw (patrz konstruktor kopiujcyi!)
char String::operator[](unsigned short offset) const
{
if (offset > itsLen)
return itsString[itsLen-1];
else
return itsString[offset];
}
// tworzy nowy acuch przez dodanie biecego
// acucha do rhs
String String::operator+(const String& rhs)
{
unsigned short totalLen = itsLen + rhs.GetLen();
String temp(totalLen);
unsigned short i;
for ( i= 0; i<itsLen; i++)
temp[i] = itsString[i];
for (unsigned short j = 0; j<rhs.GetLen(); j++, i++)
temp[i] = rhs[j];
temp[totalLen]='\0';
return temp;
}

129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:

// zmienia biecy acuch, nic nie zwraca


void String::operator+=(const String& rhs)
{
unsigned short rhsLen = rhs.GetLen();
unsigned short totalLen = itsLen + rhsLen;
String temp(totalLen);
unsigned short i;
for (i = 0; i<itsLen; i++)
temp[i] = itsString[i];
for (unsigned short j = 0; j<rhs.GetLen(); j++, i++)
temp[i] = rhs[i-itsLen];
temp[totalLen]='\0';
*this = temp;
}
int main()
{
String s1("poczatkowy test");
cout << "S1:\t" << s1.GetString() << endl;
char * temp = "Hello World";
s1 = temp;
cout << "S1:\t" << s1.GetString() << endl;
char tempTwo[20];
strcpy(tempTwo,"; milo tu byc!");
s1 += tempTwo;
cout << "tempTwo:\t" << tempTwo << endl;
cout << "S1:\t" << s1.GetString() << endl;
cout << "S1[4]:\t" << s1[4] << endl;
s1[4]='x';
cout << "S1:\t" << s1.GetString() << endl;
cout << "S1[999]:\t" << s1[999] << endl;
String s2(" Inny lancuch");
String s3;
s3 = s1+s2;
cout << "S3:\t" << s3.GetString() << endl;
String s4;
s4 = "Dlaczego to dziala?";
cout << "S4:\t" << s4.GetString() << endl;
return 0;
}

Wynik
S1:
poczatkowy test
S1:
Hello World
tempTwo:
; milo tu byc!
S1:
Hello World; milo tu byc!
S1[4]: o
S1:
Hellx World; milo tu byc!
S1[999]:
!
S3:
Hellx World; milo tu byc! Inny lancuch
S4:
Dlaczego to dziala?

Analiza
Linie od 7. do 31. zawieraj deklaracj prostej klasy String. Linie od 11. do 13. zawieraj trzy
konstruktory: konstruktor domylny, konstruktor kopiujcyi oraz konstruktor otrzymujcy
istniejcy acuch, zakoczony znakiem null (w stylu C).
Klasa String przecia operator indeksu ([]), operator plus (+) oraz operator plus-rwna si
(+=). Operator indeksu jest przeciony dwukrotnie: raz jako funkcja const zwracajca znak;
drugi raz jako funkcja nie const zwracajca referencj do znaku. Wersja nie const jest uywana
w wyraeniach takich, jak:
SomeString[4] = 'x';

tak, jak wida zielimyw linii 161. Dziki temu mamy bezporedni dostp do kadego znaku w
acuchu. Zwracana jest referencja do znaku, dziki czemu funkcja wywoujca moe nim
manipulowa.
Wersja const jest uywana w przypadkach, gdy nastpuje odwoanie do staego obiektu typu
String, na przykad w implementacji konstruktora kopiujcegoi (linia 63.). Zauwa, e nastpuje
odwoanie do rhs[i],; jednake cho rhs jest zadeklarowane jako const String &. Dostp do
tego obiektu za pomoc funkcji skadowej, nie bdcej funkcj const, jest zabroniony. Dlatego
operator indeksu musi by przeciony za pomoc akcesora const.
Jeli zwracane obiekty byyby bardzo due, mgby zechcie zadeklarowa zwracan warto
jako referencj const. Jednak poniewa znak zajmuje tylko jeden bajt, nie ma takiej potrzeby.
Domylny konstruktor jest zaimplementowany w liniach od 33. do 39. Tworzy on acuch, ktrego
dugo wynosi zero znakw. Zgodnie z konwencj, klasa String zwraca dugo acucha bez
kocowego znaku null. Tworzony acuch domylny zawiera wic jedynie kocowy znak null.
Konstruktor kopiujcyi zosta zaimplementowany w liniach od 63. do 70. Ustawia on dugo
nowego acucha zgodnie z dugoci acucha istniejcego, plus jeden bajt dla kocowego znaku
null. Kopiuje kady znak z istniejcego acucha do nowego acucha, po czym koczy nowy
acuch znakiem null.
W liniach od 53. do 60. znajduje si implementacja konstruktora otrzymujcego istniejcy acuch
w stylu C. Ten konstruktor jest podobny do konstruktora kopiujcegoi. Dugo istniejcego
acucha wyznaczana jest w wyniku wywoania standardowej funkcji bibliotecznej strlen().
W linii 28. zosta zadeklarowany jeszcze jeden konstruktor, String(unsigned short),
stanowicy prywatn funkcj skadow. Odpowiada on zamierzeniom projektanta, zgodnie z
ktrymi, aden z klientw klasy nigdy nie powinien tworzy acuchw typu String o okrelonej
dugoci. Ten konstruktor istnieje tylko po to, by pomc przy wewntrznym tworzeniu
egzemplarzy acuchw, na przykad w funkcji operator+= w linii 130. Opiszemy to dokadniej
przy okazji omawiania dziaania funkcji operator+=.
Konstruktor String(unsigned short) wypenia kady element swojej tablicy wartoci NULL.
Dlatego w ptli dokonujemy sprawdzenia i <= len, a nie i < len.

Destruktor, zaimplementowany w liniach od 73. do 77., usuwa acuch znakw przechowywany


przez klas. Musimy pamita o uyciu nawiasw kwadratowych w wywoaniu operatora delete
(aby usunite zostay wszystkie elementy tablicy, a nie tylko pierwszy).
Operator przypisania najpierw sprawdza, czy prawa strona przypisania jest taka sama, jak lewa
strona. Jeli tak nie jest, biecy acuch jest usuwany, po czym w jego miejsce tworzony jest i
kopiowany nowy acuch. W celu umoliwienia przypisa w rodzaju:
String1 = String2 = String3;

zwracana jest referencja. Operator indeksu jest przeciany dwukrotnie. W obu przypadkach
przeprowadzane jest podstawowe sprawdzanie zakresw. Jeli uytkownik prbuje odwoa si do
znaku pooonego poza kocem tablicy, zwracany jest ostatni znak znak (znajdujcy si na pozycji
len-1).
Linie od 117. do 127. implementuj operator plus (+) jako operator konkatenacji (czenia
acuchw). Dziki temu moemy napisa:
String3 = String1 + String2;

przypisujc acuchowi String3 poczenie dwch pozostaych acuchw. W tym celu funkcja
operatora plus oblicza poczon dugo obu acuchw i tworzy tymczasowy acuch temp. To
powoduje wywoanie prywatnego konstruktora, ktry otrzymuje warto cakowit i tworzy
acuch wypeniony znakami null. Nastpnie znaki null s zastpowane przez zawarto obu
acuchw. acuch po lewej stronie (*this) jest kopiowany jako pierwszy; acuch po prawej
stronie (rhs) kopiowany jest pniej.
Pierwsza ptla for przechodzi przez acuch po lewej stronie i przenosi kady jego znak do
nowego acucha. Druga ptla for przetwarza acuch po prawej stronie. Zauwa, e zmienna i
cay czas wskazuje miejsce w acuchu docelowym, nawet wtedy, gdy zmienna j zlicza znaki
acucha rhs.
Operator plus zwraca acuch tymczasowy poprzez warto, ktra jest przypisywana acuchowi
po lewej stronie przypisania (string1). Operator += dziaa na istniejcym acuchu tj. na
acuchu po lewej stronie instrukcji string1 += string2. Dziaa on tak samo, jak operator
plus, z tym, e warto tymczasowa jest przypisywana do biecego acucha (*this = temp w
linii 142.).
Funkcja main() (w liniach od 145. do 175.) suy jako program testujcy t klas. Linia 147.
tworzy obiekt String za pomoc konstruktora, otrzymujcego acuch w stylu C zakoczony
znakiem null. Linia 148. wypisuje jego zawarto za pomoc funkcji akcesora GetString().
Linia 150. tworzy kolejny acuch w stylu C. Linia 151. sprawdza operator przypisania, za linia
152. wypisuje wyniki.
Linia 154. tworzy trzeci acuch w stylu C, tempTwo. Linia 155. wywouje funkcj strcpy() (w
celu wypenienia bufora znakami ; milo tu byc!). Linia 156. wywouje operator += i docza
tempTwo do istniejcego acucha s1. Linia 158. wypisuje wyniki.

W linii 160. odczytywany jest i wypisywany pity znak acucha s1. W linii 161. jest mu
przypisywana nowa warto. Powoduje to wywoanie operatora indeksu (operatora []; w wersji
nie const). Linia 162. wypisuje wynik, ktry pokazuje, e warto znaku rzeczywicie ulega
zmianie.
Linia 164. prbuje odwoa si do znaku za kocem tablicy. Ostatni znak w tablicy jest zwracany,
zgodnie z projektem klasy.
Linie 166. i 167. tworz kolejne dwa obiekty String, za linia 168. wywouje operator
dodawania. Wynik jest wypisywany w linii 169.
Linia 171. tworzy nowy obiekt String o nazwie s4. Linia 172. wywouje operator przypisania.
Linia 173. wypisuje wyniki. By moe zastanawiasz si: Skoro operator przypisania jest
zdefiniowany w linii 21. jako otrzymujcy sta referencj do obiektu String, ale to dlaczego w
tym miejscu program przekazuje acuch w stylu C. Jak to moliwe?
A oto odpowied na to pytanie: kompilator oczekuje obiektu String, lecz otrzymuje tablic
znakw. W zwizku z tym sprawdza, czy moe stworzy obiekt String z tego, co otrzyma. W
linii 12. zadeklarowalimy konstruktor, ktry tworzy obiekt String z tablicy znakw. Kompilator
tworzy tymczasowy obiekt String z tablicy znakw i przekazuje go do operatora przypisania.
Proces ten nazywa si rzutowaniem niejawnym lub promocj. Gdyby nie zosta zadeklarowany i
zaimplementowany konstruktor przyjmujcy tablic znakw, to takie przypisanie spowodowaoby
bd kompilacji.

Listy poczone i inne struktury


Tablice przypominaj kontenery suce do przeprowadzek. S one bardzo przydatnymi
pojemnikami, ale maj okrelony rozmiar. Jeli wybierzesz zbyt duy pojemnik, niepotrzebnie
zmarnujesz miejsce. Jeli wybierze pojemnik zbyt may, jego zawarto wysypie si i powstanie
baagan.
Jednym ze sposobw rozwizania tego problemu jest uycie listy poczonej. Lista poczona jest
to struktura danych skadajca si z maych pojemnikw, przystosowanych do czenia si ze sob
w miar potrzeb. Naszym celem jest napisanie klasy zawierajcej pojedynczy obiekt naszych
danych na przykad jeden obiekt CAT lub jeden obiekt Rectangle ktra moe wskazywa
na nastpny pojemnik. Tworzymy jeden pojemnik dla kadego obiektu, ktry chcemy przechowa
i w miar potrzeb czymy je ze sob.
Takie pojemniki s nazywane wzami (ang. node). Pierwszy wze listy jest nazywany gow
(ang. head), za ostatni ogonem (ang. tail).
Listy wystpuj w trzech podstawowych odmianach. W kolejnoci od najmniej do najbardziej
zoonej, s to

lista poczona pojedynczo,

lista poczona podwjnie,

drzewo.

W licie poczonej pojedynczo kady wze wskazuje na wze nastpny (nie wskazuje
poprzedniego). Aby odszuka okrelony wze, musimy zacz od pocztku listy, tak jak w
zabawie w poszukiwanie skarbw (Nastpny wze jest pod fotelem). Lista poczona
podwjnie umoliwia poruszenie si wzdu acucha wzw do przodu i do tyu. Drzewo jest
zoon struktur zbudowan z wzw. Kady wze moe wskazywa w dwch lub wicej
kierunkach. Te trzy podstawowe struktury przedstawia rysunek 13.5.
Rys. 13.5. Listy poczone

Analiza listy poczonej


W tym podrozdziale omwimy dziaanie listy poczonej. Lista ta posuy nam nie tylko jako
przykad tworzenia zoonych struktur, ale przede wszystkim jako przykad uycia dziedziczenia,
polimorfizmu i kapsukowania w celu zarzdzania wikszymi projektami.

Przeniesienie odpowiedzialnoci
Podstawowym celem programowania zorientowanego obiektowo jest to, by kady obiekt
wykonywa dobrze jedn rzecz, za wszystkie inne czynnoci przekazywa innym obiektom.
Doskonaym przykadem zastosowania tej idei w praktyce jest samochd: zadaniem silnika jest
dostarczanie siy. Jej dystrybucja nie jest ju zadaniem silnika, ale ukadu napdowego. Skrcanie
nie jest zadaniem ani silnika, ani ukadu napdowego, tylko k.
Dobrze zaprojektowana maszyna skada si z mnstwa maych, dobrze okrelonychprzemylanych
czci, z ktrych kada wykonuje swoje zadanie i wsppracuje z innymi czciami w celu
osignicia wsplnego celu. Dobrze zaprojektowany program dziaa bardzo podobnie: kada klasa
wykonuje swoje niewielkie operacje, ale w poczeniu z innymi moe wykona naprawd
skomplikowane zadanie.

Czci skadowe
Lista poczona skada si z wzw. Pojcie klasy wza bdzie abstrakcyjne; do wykonania
zadania uyjemy trzech podtypw. Lista Bbdzie teo zawieraa wze gowyczoowy, ktrego
zadaniem bdzie zarzdzanie gow listy, wze ogona (domyl si, do czego posuy!) oraz zero
lub wicej wzw wewntrznych. Wzy wewntrzne bd odpowiedzialne za dane
przechowywane wewntrz listy.
Zauwa, e dane i lista s od siebie zupenie niezalene. Teoretycznie, moesz przechowywa w
licie dane dowolnego rodzaju, poniewa to nie dane s ze sob poczone, ale wzy, ktre je
przechowuj.
Program sterujcy nie wie niczego o wzach, poniewa operuje na licie. Jednak sama lista
wykonuje niewiele pracy, delegujc wikszo zada na wzy.
Kod programu przedstawia listing 13.13; za moment omwimy jego szczegy.
Listing 13.13. Lista poczona
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:

// ***********************************************
//
PLIK:
Listing 13.13
//
//
PRZEZNACZENIE: Demonstruje list poczon
//
UWAGI:
//
// COPYRIGHT: Copyright (C) 1998 Liberty Associates, Inc.
//
All Rights Reserved
//
// Demonstruje obiektowo zorientowane podejcie do list
// poczonych. Lista deleguje prac na wzy.

11: // Wze jest abstrakcyjnym typem danych. Uywane s trzy


12: // typy wzw: wzy gowy, wzy ogona oraz wzy
13: // wewntrzne. Tylko wzy wewntrzne przechowuj dane.
14: //
15: // Klasa data zostaa stworzona jako obiekt danych
16: // przechowywany na licie poczonej.
17: //
18: // ***********************************************
19:
20:
21: #include <iostream>
22: using namespace std;
23:
24: enum { kIsSmaller, kIsLarger, kIsSame};
25:
26: // Klasa danych do umieszczania w licie poczonej.
27: // Kada klasa w tej poczonej licie musi posiada dwie
metody:
28: // Show (wywietla warto) a oraz
29: // Compare (zwraca wzgldn pozycj)
30: class Data
31: {
32: public:
33:
Data(int val):myValue(val){}
34:
~Data(){}
35:
int Compare(const Data &);
36:
void Show() { cout << myValue << endl; }
37: private:
38:
int myValue;
39: };
40:
41: // Compare jest uywane do podjcia decyzji, w ktrym
42: // miejscu listy powinien znale si dany obiekt.
43: int Data::Compare(const Data & theOtherData)
44: {
45:
if (myValue < theOtherData.myValue)
46:
return kIsSmaller;
47:
if (myValue > theOtherData.myValue)
48:
return kIsLarger;
49:
else
50:
return kIsSame;
51: }
52:
53: // wstpne deklaracje
54: class Node;
55: class HeadNode;
56: class TailNode;
57: class InternalNode;
58:
59: // ADT reprezentuje obiekt wza listy
60: // Kada klasa potomna musi przesoni metody Insert i Show
61: class Node
62: {
63: public:
64:
Node(){}
65:
virtual ~Node(){}
66:
virtual Node * Insert(Data * theData)=0;
67:
virtual void Show() = 0;
68: private:
69: };
70:

71: // To jest wze przechowujcy rzeczywisty obiekt.


72: // W tym przypadku obiekt jest typu Data.
73: // Gdy poznamy szablony, dowiemy si, jak mona
74: // to uoglni.
75: class InternalNode: public Node
76: {
77: public:
78:
InternalNode(Data * theData, Node * next);
79:
~InternalNode(){ delete myNext; delete myData; }
80:
virtual Node * Insert(Data * theData);
81:
// delegujemy!
82:
virtual void Show() { myData->Show(); myNext->Show(); }
83:
84: private:
85:
Data * myData; // dane jako takie
86:
Node * myNext;
// wskazuje nastpny wze w licie
poczonej
87: };
88:
89: // Konstruktor dokonuje jedynie inicjalizacji
90: InternalNode::InternalNode(Data * theData, Node * next):
91: myData(theData),myNext(next)
92: {
93: }
94:
95: // Esencja listy
96: // Gdy umiecisz nowy obiekt na licie, jest on
97: // przekazywany do wza, ktry stwierdza, gdzie
98: // powinien on zosta umieszczony i wstawia go do listy
99: Node * InternalNode::Insert(Data * theData)
100: {
101:
102:
// czy nowy element jest wikszy czy mniejszy ni ja?
103:
int result = myData->Compare(*theData);
104:
105:
106:
switch(result)
107:
{
108:
// konwencja: gdy jest taki sam jak ja, wstawiamy wczeniej
109:
case kIsSame:
// przechodzimy dalej
110:
case kIsLarger:
// nowe dane trafiaj przede mnie
111:
{
112:
InternalNode * dataNode = new InternalNode(theData,
this);
113:
return dataNode;
114:
}
115:
116:
// gdy jest wikszy ni ja, przekazuj go do nastpnego wza
117:
// i niech ON si tym zajmie.
118:
case kIsSmaller:
119:
myNext = myNext->Insert(theData);
120:
return this;
121:
}
122:
return this;
123: }
124:
125:
126: // Wze ogona jest tylko wartownikiem.
127:
128: class TailNode : public Node
129: {

130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:

public:
TailNode(){}
~TailNode(){}
virtual Node * Insert(Data * theData);
virtual void Show() { }
private:
};
// Gdy dane trafiaj do mnie, musz by wstawione wczeniej,
// gdy jestem ogonem i za mn NIC nie ma.
Node * TailNode::Insert(Data * theData)
{
InternalNode * dataNode = new InternalNode(theData, this);
return dataNode;
}
// Wze gowy nie zawiera danych; wskazuje jedynie
// na sam pocztek listy.
class HeadNode : public Node
{
public:
HeadNode();
~HeadNode() { delete myNext; }
virtual Node * Insert(Data * theData);
virtual void Show() { myNext->Show(); }
private:
Node * myNext;
};
// Gdy tylko gowa zostanie stworzona,
// natychmiast tworzy ogon.
HeadNode::HeadNode()
{
myNext = new TailNode;
}
// Przed gow nic nie wstawiamy nic, zatem
// po prostu przekazujemy dane do nastpnego wza.
Node * HeadNode::Insert(Data * theData)
{
myNext = myNext->Insert(theData);
return this;
}
// Odbieram sowa uznania, a sama nic nie robi.
class LinkedList
{
public:
LinkedList();
~LinkedList() { delete myHead; }
void Insert(Data * theData);
void ShowAll() { myHead->Show(); }
private:
HeadNode * myHead;
};
// Przy narodzinach tworz wze gowy.
// Tworzy on wze ogona.
// Tak wic pusta lista wskazuje na gow, ktra

191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:

// wskazuje na ogon; pomidzy nimi nie ma nic innego.


LinkedList::LinkedList()
{
myHead = new HeadNode;
}

Jaka
Jaka
Jaka
Jaka
Jaka
Jaka
Jaka
2
3
5
8
9
10

wartosc?
wartosc?
wartosc?
wartosc?
wartosc?
wartosc?
wartosc?

// Delegujemy, delegujemy, delegujemy


void LinkedList::Insert(Data * pData)
{
myHead->Insert(pData);
}
// testowy program sterujcy
int main()
{
Data * pData;
int val;
LinkedList ll;
// prosimy uytkownika o wpisanie kilku wartoci
// i umieszczamy je na licie
for (;;)
{
cout << "Jaka wartosc? (0 aby zakonczyc): ";
cin >> val;
if (!val)
break;
pData = new Data(val);
ll.Insert(pData);
}
// teraz przechodzimy list i wywietlamy wartoci
ll.ShowAll();
return 0; // ll wychodzi poza zakres i zostaje zniszczone!
}

Wynik
(0
(0
(0
(0
(0
(0
(0

aby
aby
aby
aby
aby
aby
aby

zakonczyc):
zakonczyc):
zakonczyc):
zakonczyc):
zakonczyc):
zakonczyc):
zakonczyc):

5
8
3
9
2
10
0

Analiza
Pierwsz rzecz, jak naley zauway, jest instrukcja wyliczeniowae definiujca zawierajce trzy
stae: kIsSmaller (jest mniejsze), kIsLarger (jest wiksze) oraz kIsSame (jest takie same).
Kady obiekt, ktry moe by przechowywany na tej licie poczonej, musi obsugiwa metod
Compare(). Te stae s zwracane wanie przez t metod.

Dla przykadu, w liniach od 30. do 39. zostaa stworzona klasa Data (dane), za jej metoda
Compare() zostaa zaimplementowana w liniach od 41. do 51. Obiekt typu Data przechowuje
warto i moe porwnywa si z innymi obiektami Data. Oprcz tego obsuguje metod
Show(), ktra wywietla warto obiektu Data.
Najprostszym sposobem na zrozumienie dziaania listy poczonej jest przeanalizowanie
ilustrujcego j przykadu. W linii 203. rozpoczyna si testowy program sterujcy; w linii 206.
deklarowany jest wskanik do obiektu Data, za w linii 208. definiowana jest lokalna lista
poczona.
Gdy tworzona jest lista poczona, wywoywany jest jej konstruktor, zaczynajcy si w linii 192.
Jedyn prac wykonywan w tym konstruktorze jest zaalokowanie obiektu HeadNode (wze
gowy) i przypisanie mujego adresu do wskanika przechowywanego w poczonej licie w linii
185.
Tworzenie obiektu HeadNode powoduje wywoanie konstruktora klasy HeadNode zawartego w
liniach od 163. do 166. To z kolei powoduje zaalokowanie obiektu TailNode (wze gowy) i
przypisanie jego adresu do wskanika myNext (mj nastpny) w wle gowy. Tworzenie obiektu
TailNode powoduje wywoanie konstruktora tej klasy zdefiniowanego w linii 131., ktry jest
funkcj inline i nie robi nic.
Dziki zwykemu zaalokowaniu listy poczonej na stosie, tworzona jest sama lista, wzy gowy i
ogona oraz ustanawiane s powizania pomidzy nimi. Ilustruje to rysunek 13.6.
Rys. 13.6. Lista poczona po jej utworzeniu

Linia 212. rozpoczyna ptl nieskoczon. Uytkownik jest proszony o wpisanie wartoci
dodawanych do listy poczonej. Moe on doda dowoln ilo wartoci tyle wartoci, ile chce;
wpisanie wartoci 0 powoduje zakoczenie pobierania danych. Kod w linii 216. sprawdza
wprowadzan warto; po natrafianiu na warto 0 powoduje on wyjcie z ptli.
Jeli warto jest rna od zera, w linii 218. tworzony jest nowy obiekt typu Data, ktry w linii
219. jest wstawiany do listy. Dla zilustrowania tego przykadu zamy, e uytkownik
wprowadzi warto 15. Powoduje to wywoanie metody Insert() (wstaw) w linii 198.
Lista poczona (klasa LinkedList) natychmiast przenosi odpowiedzialno za wstawienie
obiektu na swj wze gowy. To wywouje metod Insert() z linii 170. Z kolei Wwze gowy
natychmiast przekazuje odpowiedzialno za wstawienie obiektu do wza wskazywanego przez
skadow myNext (mj nastpny). W tym (pierwszym) przypadku, skadowa ta, wskazuje ona na
wze ogona (pamitajmy, e podczas tworzenia wza gowy zostao stworzone cze do wza
ogona). Tak wic zostaje wywoana metoda Insert() z linii 142.

Metoda TailNode::Insert() wie, e obiekt, ktry otrzymaa, musi by wstawiony


bezporednio przed jej obiektem tj. nowy obiekt znajdzie si na licie tu przed wzem ogona.
Zatem, w linii 144. tworzy ona nowy obiekt InternalNode (wze wewntrzny), przekazujc mu
dane oraz wskanik do siebie. To powoduje wywoanie konstruktora klasy InternalNode,
zdefiniowanego w linii 90.
Konstruktor klasy InternalNode inicjalizuje tylko swj wskanik Data za pomoc adresu
otrzymanego obiektu Data oraz inicjalizuje swj wskanik myNext za pomoc otrzymanego
adresu wza. W tym przypadku nowy wze wewntrzny bdzie wskazywa na wze ogona
(pamitajmy, e wze ogona przekaza mu swj wskanik this).
Gdy utworzony zostanie nowy wze InternalNode, w linii 144. jego adres jest przypisywany
do wskanika dataNode i wanie ten adres jest zwracany z metody TailNode::Insert().
Wracamy wic do metody HeadNode::Insert(), w ktrej adres wza InternalNode jest
przypisywany do wskanika myNext wza gowy (w linii 172.). Na zakoczenie, adres wza
gowy jest zwracany do listy poczonej, gdzie w linii 200. jest odrzucany (nie robimy z nim nic,
poniewa lista poczona znaa adres swojego wza gowy ju wczeniej).
Dlaczego zawracamy sobie gow zwracaniem adresu, ktry nie jest uywany? Metoda Insert
jest zadeklarowana w klasie bazowej, Node. Zwracana warto jest potrzebna w innych
implementacjach. Gdy zmienisz zwracan warto metody HeadNode::Insert(), spowodujesz
bd kompilacji; prociej jest wic po prostu zwrci adres wza gowy i pozwoli, by lista
poczona go zignorowaa.
Co si wic stao? Dane zostay wstawione do listy. Lista przekazaa je do gowy. Gowa, na
lepo, przekazaa dane do pierwszego wskazywanego przez siebie elementu. W tym (pierwszym)
przypadku, gowa wskazywaa na ogon. Ogon natychmiast stworzy nowy wze wewntrzny,
inicjalizujc go tak, by wskazywa na ogon. Nastpnie ogon zwrci gowie adres nowego wza,
ktra zmodyfikowaa swj wskanik myNext tak, aby wskazywa na nowy wze. Gotowe! Dane
na licie znajduj si we waciwym miejscu, co ilustruje rysunek 13.7.
Rys. 13.7. Lista poczona po wstawieniu pierwszego wza

Po wstawieniu pierwszego wza, sterowanie programu powraca do linii 214., gdzie wprowadzane
s kolejne dane. Dla przykadu zamy, e uytkownik wpisa warto 3. Powoduje to stworzenie
w linii 218. nowego obiektu typu Data, ktry jest wstawiany do listy w linii 219.
W linii 200. lista ponownie przekazuje dane do swojego wza gowy. Z kolei metoda
HeadNode::Insert() przekazuje now warto do wza wskazywanego przez swj wskanik
myNext. Jak wiemy, w tym momencie wskanik ten wskazuje wze zawierajcy obiekt Data o
wartoci 15. Powoduje to wywoanie metody InternalNode::Insert() z linii 99.
W linii 103. obiekt InternalNode uywa wskanika myData, aby dla wasnego obiektu Data
(tego o wartoci 15) wywoa za pomoc otrzymanego nowego obiektu Data (tego o wartoci 3)
metod Compare(). To powoduje wywoanie metody InternalNode::Compare()
zdefiniowanej w linii 43.
Obie wartoci zostaj porwnane, a poniewa myValue ma warto 15, za
theOtherData.myValue ma warto 3, zwrcon wartoci jest kIsLarger. To powoduje, e
program przechodzi do linii 112.
Dla nowego obiektu Data tworzony jest nowy wze InternalNode. Nowy wze bdzie
wskazywa na biecy obiekt InternalNode, a metoda InternalNode::Insert() zwrci do
obiektu HeadNode adres nowego wza. Zatem nowy wze, ktrego warto obiektu danych jest
mniejsza od wartoci obiektu danych wza biecego, zostanie wstawiony do listy przed wzem
biecym. Caa lista wyglda w tym momencie tak, jak na rysunku 13.8.
Rys. 13.8. Lista poczona po wstawieniu drugiego wza

W trakcie trzeciego wykonania ptli uytkownik wpisa warto 8. Jest ona wiksza od 3, ale
mniejsza od 15, wic powinna by wstawiona pomidzy dwa istniejce ju wzy. Dziaanie
programu bdzie podobne do przedstawionego w poprzednim przykadzie, z t rnic, e gdy
dojdzie do porwnania wartoci danych z wartoci 3, zamiast zwrci sta kIsLarger, funkcja
zwrci warto kIsSmaller (co oznacza, e obiekt o wartoci 3 jest mniejszy od nowego
obiektu, ktrego warto wynosi 8).

To spowoduje, e metoda InternalNode::Insert() przejdzie do linii 119. Zamiast tworzy


nowy wze i wstawia go, obiekt InternalNode po prostu przekae nowe dane do metody
Insert tego wza, na ktry wskazuje akurat jego zmienna myNext. W tym przypadku zostanie
wic wywoana metoda Insert() tego obiektu InternalNode, ktrego obiektem danych jest
warto 15.
Ponownie odbywa si porwnanie i tworzony jest nowy obiekt InternalNode. Bdzie
wskazywa on na wze, ktrego wartoci danych jest 15, za jego adres zostanie przekazany
wstecz do wza, ktrego wartoci danych jest 3 (w linii 119.).
Spowoduje to, e nowy wze zostanie wstawiony we waciwe miejsce na licie.
Jeli to moliwe, powiniene przeledzi w swoim debuggerze proces wstawiania kolejnych
wzw. Powiniene zobaczy, jak te metody wzajemnie si wywouj i odpowiednio dostosowuj
wskaniki.

Czego si nauczya, Dorotko?


Jeli kiedykolwiek pjd za gosem serca, nie wyjd poza swoje podwrko. Nie ma to jak w
domu i nie ma to jak programowanie proceduralne. W programowaniu proceduralnym metoda
kontrolujca sprawdza dane i wywouje funkcje.
W metodzie obiektowej kady obiekt ma swoje cile okrelone zadanie. Lista poczona
odpowiada za zarzdzanie wzem gowy. Wze gowy natychmiast przekazuje nowe dane do
nastpnego wskazywanego przez siebie wza, bez wzgldu na to, czym jest ten wze.
Wze ogona tworzy nowy wze i wstawia go do listy za kadym razem, gdy otrzyma dane.
Potrafi tylko jedno: jeli co do niego dotrze, wstawia to tu przed sob.
Wzy wewntrzne s nieco bardziej skomplikowane; prosz swj istniejcy obiekt o porwnanie
si z nowym obiektem. W zalenoci od wyniku tego porwnania, wstawiaj go przed sob lub po
prostu przekazuj do nastpnego wza na licie.
Zauwa, e wze InternalNode nie ma pojcia o sposobie przeprowadzenia porwnania;
naley to wycznie do obiektu danych. InternalNode wie jedynie, e powinien poprosi obiekt
danych o dokonanie porwnania w celu otrzymania jednej z trzech odpowiedzi. Na podstawie
otrzymanej odpowiedzi wstawia obiekt do listy; w przeciwnym razie przekazuje go dalej, nie
dbajc o to, gdzie w kocu dotrze.
Kto wic tu rzdzi? W dobrze zaprojektowanym programie zorientowanym obiektowo nikt nie
rzdzi. Kady obiekt wykonuje wasn, ograniczon prac, za w oglnym efekcie otrzymujemy
sprawnie dziaajc maszyn.

Klasy tablic
Napisanie wasnej klasy tablicowejy ma wiele zalet w porwnaniem z korzystaniem z tablic
wbudowanych. Jako pocztkujcy programista, moesz zabezpieczy program przed

przepenieniem tablicy. Moesz take wzi pod uwag stworzenie wasnej klasy tablicowej,y
dynamicznie zmieniajcej rozmiar: tu po utworzeniu mogaby ona zawiera tylko jeden element i
zwiksza rozmiar w miar potrzeb, podczas dziaania programu.
W przypadku, gdy zechcesz posortowa lub uporzdkowa elementy tablicy w jaki inny sposb,
moesz wykorzysta kilka rnych przydatnych odmian tablic. Do najpopularniejszych z nich
nale:

zbir uporzdkowany (ordered collection): kady element jest uoony w odpowiedniej


kolejnoci,

zestaw (set): kady element wystpuje tylko raz,

sownik (dictionary): wykorzystuje on dopasowane do siebie pary, w ktrych jedna warto


peni rol klucza sucego do pobierania drugiej wartoci,

rzadka tablica (sparse array): umoliwia uywanie bardzo szerokiego zakresu indeksw, ale
pami zajmuj tylko te elementy, ktre rzeczywicie zostay dodane do tablicy. Moesz
poprosi o element SparseArray[5] lub SparseArray[200], a mimo to pami zostanie
zaalokowana tylko dla niewielkiej iloci elementw,

torba (bag): nieuporzdkowany zbir, ktrego elementy s dodawane i zwracane w


przypadkowej kolejnoci.

Przeciajc operator indeksu ([]), moesz zamieni list poczon w zbir uporzdkowany.
Odrzucajc duplikaty, moesz zamieni zbir w zestaw. Jeli kady obiekt na licie posiada par
dopasowanych wartoci, moesz uy listy poczonej do zbudowania sownika lub rzadkiej
tablicy.

You might also like