Issuu on Google+


Tytuł oryginału: Think Like a Programmer: An Introduction to Creative Problem Solving Tłumaczenie: Jacek Janusz ISBN: 978-83-246-7284-4 Original edition copyright © 2012 by V. Anton Spraul. All rights reserved. Published by arrangement with No Starch Press, Inc. Polish edition copyright © 2013 by HELION SA. All rights reserved. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 32 231 22 19, 32 230 98 63 e-mail: helion@helion.pl WWW: http://helion.pl (księgarnia internetowa, katalog książek) Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/myprog Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Pliki z przykładami omawianymi w książce można znaleźć pod adresem: ftp://ftp.helion.pl/przyklady/myprog.zip Printed in Poland.

• Kup książkę • Poleć książkę • Oceń książkę

• Księgarnia internetowa • Lubię to! » Nasza społeczność


Spis treci

PODZIKOWANIA ..................................................................................... 7 WSTP ........................................................................................................ 9 O ksice ................................................................................................................................11 Wymagania wstpne ..........................................................................................................11 Wybrane zagadnienia .........................................................................................................12 Styl programowania ...........................................................................................................12 wiczenia ...........................................................................................................................12 Dlaczego C++? .................................................................................................................13 1 STRATEGIE ROZWIZYWANIA PROBLEMÓW ......................................... 15 Klasyczne amigówki .............................................................................................................17 Lis, g i kukurydza ............................................................................................................17 amigówki z przesuwanymi elementami ..........................................................................22 Sudoku ...............................................................................................................................26 Zamek Quarrasi .................................................................................................................30 Ogólne techniki rozwizywania problemów ..........................................................................32 Miej zawsze jaki plan ........................................................................................................32 Ponownie zaprezentuj problem .........................................................................................33 Podziel problem .................................................................................................................34 Rozpocznij z wiedz, któr posiadasz ................................................................................35 Upro problem .................................................................................................................36 Szukaj analogii ....................................................................................................................37 Eksperymentuj ...................................................................................................................38 Nie popadaj we frustracj ..................................................................................................38 wiczenia ...............................................................................................................................40

Kup książkę

Poleć książkę


2 PRAWDZIWE AMIGÓWKI ..................................................................... 41 Elementy jzyka C++ wykorzystywane w tym rozdziale .................................................... 42 Tworzenie wzorów na wyjciu ............................................................................................. 42 Przetwarzanie danych wejciowych ...................................................................................... 48 Analiza problemu ............................................................................................................... 49 czenie wszystkich elementów w cao ......................................................................... 58

ledzenie stanu ...................................................................................................................... 60 Podsumowanie ...................................................................................................................... 73 wiczenia .............................................................................................................................. 74 3 ROZWIZYWANIE PROBLEMÓW ZA POMOC TABLIC .......................... 77 Podstawowe informacje o tablicach ...................................................................................... 78 Przechowywanie danych ................................................................................................... 79 Kopiowanie ........................................................................................................................ 79 Odczytywanie i przeszukiwanie ........................................................................................ 80 Sortowanie ........................................................................................................................ 81 Obliczenia statystyczne ..................................................................................................... 84 Rozwizywanie problemów za pomoc tablic ....................................................................... 85 Optymalizacja .................................................................................................................... 89 Tablice ze staymi wartociami .............................................................................................. 91 Tablice z wartociami nieskalarnymi ..................................................................................... 94 Tablice wielowymiarowe ...................................................................................................... 96 Kiedy naley uywa tablic .................................................................................................... 99 wiczenia ............................................................................................................................ 104 4 ROZWIZYWANIE PROBLEMÓW ZA POMOC WSKANIKÓW I PAMICI DYNAMICZNEJ ....................... 107 Podstawowe informacje o wska nikach .............................................................................. 108 Korzyci z uywania wska ników ........................................................................................ 109 Struktury danych o wielkoci definiowanej w trakcie dziaania programu ...................... 109 Struktury danych o zmiennych rozmiarach ..................................................................... 110 Wspódzielenie pamici ................................................................................................... 110 Kiedy naley uywa wska ników? ...................................................................................... 111 Pami ma znaczenie ........................................................................................................... 112 Stos i sterta ...................................................................................................................... 113 Rozmiar pamici .............................................................................................................. 116 Czas ycia ........................................................................................................................ 118 Rozwizywanie problemów za pomoc wska ników ......................................................... 119 a cuchy o zmiennej dugoci ......................................................................................... 119 Listy powizane ............................................................................................................... 130 Wnioski i nastpne dziaania ................................................................................................ 139 wiczenia ............................................................................................................................ 139

4

Spis treci

Kup książkę

Poleć książkę


5 ROZWIZYWANIE PROBLEMÓW ZA POMOC KLAS ............................ 143 Przegld podstawowych informacji o klasach ......................................................................144 Cele uycia klas ....................................................................................................................146 Enkapsulacja .....................................................................................................................146 Ponowne uycie kodu ......................................................................................................147 Dzielenie problemu ..........................................................................................................147 Hermetyzacja ...................................................................................................................148 Czytelno ........................................................................................................................150 Wyrazisto ......................................................................................................................150 Tworzenie przykadowej klasy .............................................................................................151 Podstawowy schemat klasy ..............................................................................................152 Metody wspierajce .........................................................................................................156 Klasy z danymi dynamicznymi ..............................................................................................160 Dodawanie wza .............................................................................................................162 Reorganizacja listy ............................................................................................................165 Destruktor .......................................................................................................................169 Kopiowanie gbokie ........................................................................................................170 Obraz caoci dla klas z pamici dynamiczn .................................................................175 Bdy, jakich naley unika ...................................................................................................176 Klasa fikcyjna ....................................................................................................................176 Jednozadaniowce .............................................................................................................177 wiczenia .............................................................................................................................178 6 ROZWIZYWANIE PROBLEMÓW ZA POMOC REKURENCJI ................ 181 Przegld podstawowych informacji o rekurencji .................................................................182 Rekurencja nieogonowa i ogonowa .....................................................................................182 Wielki Pomys Rekurencyjny ................................................................................................191 Czsto popeniane bdy ......................................................................................................194 Zbyt wiele parametrów ...................................................................................................195 Zmienne globalne .............................................................................................................196 Uywanie rekurencji w dynamicznych strukturach danych .................................................198 Rekurencja i listy powizane ............................................................................................198 Rekurencja i drzewa binarne ............................................................................................201 Funkcje opakowujce ...........................................................................................................204 Kiedy naley wybiera rekurencj? ......................................................................................207 Argumenty przeciwko rekurencji ....................................................................................207 wiczenia .............................................................................................................................211 7 ROZWIZYWANIE PROBLEMÓW ZA POMOC PONOWNEGO WYKORZYSTANIA KODU ......................... 213 Poprawne i niewaciwe wykorzystanie kodu ......................................................................214 Przegld podstawowych informacji o komponentach .........................................................215

Spis treci

Kup książkę

5

Poleć książkę


Blok kodu ........................................................................................................................ 215 Algorytmy ........................................................................................................................ 216 Wzorce ............................................................................................................................ 216 Abstrakcyjne typy danych ................................................................................................ 217 Biblioteki .......................................................................................................................... 218 Zdobywanie wiedzy o komponentach ................................................................................ 219 Eksploracyjne zdobywanie wiedzy .................................................................................. 219 Zdobywanie wiedzy w razie potrzeby ............................................................................ 223 Wybór typu komponentu .................................................................................................... 232 Wybór komponentu w praktyce ..................................................................................... 234 Porównanie wyników ...................................................................................................... 238 wiczenia ............................................................................................................................ 239 8 MYLENIE JAK PROGRAMISTA ............................................................. 241 Tworzenie wasnego planu gównego ................................................................................. 242 Uwzgldnienie mocnych i sabych stron ......................................................................... 242 Budowanie planu gównego ............................................................................................. 248 Rozwizywanie kadego problemu ..................................................................................... 250 Opracowywanie metody oszukiwania ............................................................................. 252 Wymagane podzadania dla metody oszukiwania w grze wisielec ................................... 254 Wstpny projekt .............................................................................................................. 256 Kodowanie wstpne ........................................................................................................ 257 Analiza wstpnych wyników ............................................................................................ 266 Sztuka rozwizywania problemów .................................................................................. 267 Zdobywanie nowych umiejtnoci programistycznych ....................................................... 268 Nowe jzyki .................................................................................................................... 269 Zdobywanie nowych umiejtnoci w jzyku, który ju znasz ......................................... 271 Nowe biblioteki ............................................................................................................... 272 Bierz udzia w szkoleniach ............................................................................................... 273 Podsumowanie .................................................................................................................... 274 wiczenia ............................................................................................................................ 275

SKOROWIDZ .......................................................................................... 276

6

Spis treci

Kup książkę

Poleć książkę


4 Rozwi zywanie problemĂłw za pomoc wska nikĂłw i pamici dynamicznej W

TYM ROZDZIALE NAUCZYMY SI ROZWI ZYWANIA PROBLEMĂ“W ZA POMOC

WSKANIKÓW I PAMI CI DYNAMICZNEJ, CO POZWOLI NAM NA PISANIE UNIWERSALNYCH PROGRAMÓW ZARZ DZAJ CYCH DANYMI, KTÓRYCH ROZMIARY nie s znane przed uruchomieniem kodu. Wskaniki i dynamiczne przydzielanie pamici s programowaniem ekstremalnym. Gdy piszesz programy, które rezerwuj pami w locie, przypisuj j do przydatnych struktur, a nastpnie przed zako czeniem dziaania sprztaj po sobie w taki sposób, by nic nie pozostao, nie jeste po prostu kim, kto potrafi troch kodowa — jeste programist.

Poniewa wskaniki s trudnym zagadnieniem, a wiele popularnych jzykĂłw programowania, na przykad Java, obywa si bez nich, niektĂłrzy pocztkujcy programici staraj si przekona samych siebie, e ten obszar wiedzy mog zupenie pomin. Jest to bd. Wskaniki i niebezporedni dostp do pamici bd

Kup ksiÄ…ĹźkÄ™

Poleć ksiąşkę


zawsze uywane w mechanizmach jzykĂłw wysokiego poziomu. Aby wic rzeczywicie myle jak programista, musisz umie swobodnie porusza si wrĂłd wskanikĂłw oraz problemĂłw wymagajcych ich uycia. Zanim jednak zajmiemy si rozwizywaniem problemĂłw ze wskanikami, dokadnie przeanalizujemy wszystkie pierwszo- i drugoplanowe aspekty zwizane z ich dziaaniem. Dziki temu osigniemy dwie korzyci. Po pierwsze, zdobyta wiedza pozwoli wykorzysta wskaniki w najbardziej wydajny sposĂłb. Po drugie, poprzez ujawnienie tajemnic zwizanych ze wskanikami bdziemy mogli ich bezpiecznie uywa.

Podstawowe informacje o wska nikach Podobnie jak w przypadku zagadnie omawianych w poprzednich rozdziaach, równie tutaj zakadam, e masz ju pewn podstawow wiedz o wskanikach. Aby si jednak upewni, e „nadajemy na tej samej fali�, przedstawi poniej krótki przegld ich moliwoci. Wskaniki w jzyku C++ s reprezentowane przez znak gwiazdki (*). W zalenoci od kontekstu gwiazdka oznacza deklaracj wskanika lub pami, do której si on odwouje, a nie sam wskanik. Aby zadeklarowa wskanik, umieszczamy znak gwiazdki midzy nazw typu i identyfikatorem: int *intPointer;

Powyszy kod zawiera deklaracj zmiennej intPointer, bdcej wskanikiem do typu int. ZwrĂł uwag na to, e gwiazdka czy si z identyfikatorem, a nie typem. W poniszej deklaracji zmienna variable1 jest wskanikiem do typu int, a variable2 po prostu zmienn typu int: int *variable1, variable2;

Znak & przed zmienn dziaa jak operator adresu. Za pomoc poniszej instrukcji moglibymy wic przypisa adres zmiennej variable2 do variable1: variable1 = &variable2;

Moemy take bezporednio przypisa warto jednej zmiennej wskanikowej do drugiej: intPointer = variable1;

108

Rozdzia 4

Kup ksiÄ…ĹźkÄ™

Poleć ksiąşkę


By moe najwaniejsze jest to, e w trakcie dziaania programu moemy przydziela pami, ktĂłra jest dostpna jedynie poprzez wskanik. Jest to moliwe przy uyciu operatora new: double *doublePointer = new double;

Dostp do pamici wskazywanej przez wskanik jest zwany wyuskiwaniem (dereferencj). Uzyskuje si go za pomoc gwiazdki umieszczonej po lewej stronie identyfikatora wskanika. Jak ju wspomniaem, jest to ten sam sposób umieszczania znaku gwiazdki, jaki stosuje si podczas deklaracji wskanika, jednake inny kontekst sprawia, e obie operacje róni si od siebie. Oto przykad: ™ *doublePointer = 35.4; š double localDouble = *doublePointer;

Do obszaru pamici typu double przydzielonego we wczeniejszym przykadzie przypisujemy warto ™, a nastpnie kopiujemy j do zmiennej localDouble š. Aby zwolni niepotrzebn pami przydzielon za pomoc operatora new, stosujemy sowo kluczowe delete: delete doublePointer;

Sposób dziaania tego procesu zosta dokadnie przedstawiony w dalszej czci rozdziau, w podrozdziale „Pami ma znaczenie�.

Korzyci z uywania wska ników Wskaniki daj moliwoci niedostpne przy uyciu statycznego przydzielania pamici, a take pozwalaj na jej efektywne wykorzystanie. Trzy gówne korzyci wynikajce z uywania wskaników s nastpujce: „ Struktury danych o wielkoci definiowanej w trakcie dziaania programu. „ Struktury danych o zmiennych rozmiarach. „ Wspódzielenie pamici. Przyjrzyjmy si kadej z nich bardziej szczegóowo.

Struktury danych o wielkoci definiowanej w trakcie dziaania programu Dziki uyciu wskanikĂłw moemy utworzy tablic o rozmiarze ustalonym w trakcie dziaania programu, a nie definiowa go jeszcze przed kompilacj aplikacji. Dziki temu unikamy sytuacji ewentualnego braku miejsca w tablicy

Rozwizywanie problemĂłw za pomoc wskanikĂłw i pamici dynamicznej

Kup ksiÄ…ĹźkÄ™

109

Poleć ksiąşkę


oraz definiowania jej zbyt duego rozmiaru, a przez to najczciej niewykorzystania wikszoci przydzielonej pamici. Z dynamicznym ustalaniem rozmiaru struktury danych mielimy ju do czynienia w rozdziale 3., w podrozdziale „Kiedy naley uywa tablic�. Ta idea zostanie równie wykorzystana w dalszej czci niniejszego rozdziau, w podrozdziale „ a cuchy o zmiennej dugoci�.

Struktury danych o zmiennych rozmiarach Moemy take stworzy wskanikowe struktury danych, które w razie koniecznoci powikszaj si lub zmniejszaj w trakcie dziaania programu. Najprostsz struktur danych o zmiennych rozmiarach jest lista powizana, o której ju wspominalimy. Do jej danych mona uzyska dostp jedynie w sposób sekwencyjny. Rezerwuje ona jednak tylko tyle miejsca, ile wynosi rozmiar samych danych, bez adnego marnowania pamici. Jak zobaczysz w dalszej czci ksiki, inne, bardziej zoone wskanikowe struktury danych zawieraj opcje porzdkowania i „profile�, które umoliwiaj lepsze zarzdzanie powizaniami midzy zapamitanymi danymi, ni ma to miejsce w przypadku tablic. Z tego powodu, mimo e tablica umoliwia uzyskanie penego dostpu swobodnego, operacja wyszukiwania (podczas której w strukturze wyszukujemy element najlepiej speniajcy okrelone kryterium) moe by szybciej zrealizowana w strukturze opartej na wskanikach. Z tej waciwoci skorzystamy w dalszej czci tego rozdziau, aby stworzy struktur danych do przechowywania informacji o studentach, która powiksza si w razie koniecznoci.

WspĂłdzielenie pamici Wskaniki mog poprawi wydajno programu poprzez umoliwienie wspĂłdzielenia blokĂłw pamici. Na przykad jeli wywoujemy funkcj, za pomoc parametrĂłw referencji moemy przekaza tylko wskanik do obszaru pamici, zamiast kopiowa cay blok. Najprawdopodobniej widziae ju tak operacj w praktyce. Referencje maj umieszczony znak & midzy typem i nazw na licie parametrĂłw formalnych: void refParamFunction (int Â&#x2122; & x) { Â&#x161; x = 10; } int number = 5; refParamFunction(Â&#x203A; number); cout << Â&#x153; number << "\n"; UWAGA

110

Spacje przed i po znaku & nie s wymagane. Umieciem je wycznie ze wzgldĂłw estetycznych. W kodach innych programistĂłw bdziesz mĂłg zauway takie zapisy, jak int& x, int &x, a by moe nawet int&x.

Rozdzia 4

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


W powyszym kodzie formalny parametr x ย™ nie jest kopi argumentu number ย›, lecz odwoaniem do miejsca w pamici, w ktรณrym przechowywana jest zmienna number. Dlatego te po zmianie x ยš pami zajmowana przez zmienn number zostaje zmodyfikowana, a w wyniku tego program wywietla warto 10 ยœ. Jak przedstawiono w powyszym przykadzie, parametry referencyjne mog by uywane w mechanizmach umoliwiajcych zwracanie wartoci z funkcji. Mรณwic ogรณlniej, pozwalaj one wspรณdzieli ten sam obszar pamici zarรณwno przez funkcj wywoywan, jak i wywoujc, a przez to zmniejsza narzut. Jeli zmienna bdca parametrem zajmuje 1 kilobajt pamici, przekazanie jej jako referencji wymaga skopiowania tylko 32- lub 64-bitowego wskanika zamiast caego kilobajta. Poprzez zastosowanie sowa kluczowego const moemy poinformowa, e uywamy parametru referencyjnego w celu poprawy wydajnoci, a nie zwracania wartoci: int anotherFunction(const int & x);

Dziki umieszczeniu sowa const przed deklaracj parametru referencyjnego x funkcja anotherFunction otrzyma referencj do przekazanego argumentu, lecz nie bdzie moga go zmodyfikowa, podobnie jak dzieje si w przypadku kadego innego parametru zdefiniowanego jako const. Mรณwic ogรณlnie, moemy uywa wskanikรณw w powyszy sposรณb, aby pozwoli rรณnym czciom programu lub strukturom znajdujcym si w nim na dostp do tego samego obszaru danych bez potrzeby jego kopiowania.

Kiedy naley uywa wska nikรณw? Podobnie jak tablice, rรณwnie wskaniki maj potencjalne wady i powinny by uywane jedynie w odpowiednich sytuacjach. W jaki sposรณb moemy wiedzie, kiedy uycie wskanika jest odpowiednie? Znajc ju zalety stosowania wskanikรณw, moemy stwierdzi, e powinny by one uywane jedynie wรณwczas, gdy naley wzi pod uwag co najmniej jedn z korzyci przez nie oferowanych. Jeli Twรณj program wymaga zoonej struktury w celu przechowywania danych, lecz przed jego uruchomieniem nie moesz dokadnie oszacowa, ile miejsca ona zajmie; jeli potrzebujesz struktury, ktรณra moe si powiksza i zmniejsza w trakcie dziaania aplikacji; jeli obsugujesz due obiekty lub inne bloki danych, ktรณre s przekazywane w rรณnych miejscach programu โ€” wรณwczas powiniene zastosowa wskaniki. W przypadku gdy nie masz do czynienia z ktรณr z wymienionych sytuacji, naley jednak unika uycia wskanikรณw i dynamicznego przydzielania pamici. Znajc z saw wskanikรณw, uwaanych za jedn z najtrudniejszych cech jzyka C++, mรณgby stwierdzi, e aden z programistรณw nie sprรณbuje nawet ich uy, gdy nie bdzie to niezbdne. Byem jednak wielokrotnie zaskoczony sytuacj zupenie odwrotn. Czasem programici po prostu oszukuj samych

Rozwizywanie problemรณw za pomoc wskanikรณw i pamici dynamicznej

Kup ksiฤ…ลผkฤ™

111

Poleฤ‡ ksiฤ…ลผkฤ™


siebie, starajc si udowodni, e uycie wskanikĂłw jest konieczne. Wyobra sobie, e wywoujesz funkcj napisan przez kogo innego, na przykad znajdujc si w bibliotece lub interfejsie programowania aplikacji. Uywasz w tym celu nastpujcego prototypu: void compute(int input, int *output);

Moglibymy zaoy, e ta funkcja zostaa napisana w jzyku C, a nie C++, dlatego uywa wskanika zamiast referencji (&), by zdefiniowa parametr wyjciowy. Nierozwany programista mĂłgby wywoa t funkcj w poniszy sposĂłb: int num1 = 10; int *num2 = new int; compute(num1, num2);

Powyszy kod jest niewydajny pamiciowo, poniewa tworzy wskanik, mimo e nie jest to wymagane. OprĂłcz dwĂłch zmiennych cakowitych rezerwuje on miejsce na dodatkowy wskanik. Kod jest take niewydajny czasowo, poniewa zbdna alokacja pamici zajmuje czas procesora (jak wyjaniono w nastpnym podrozdziale). Tego wszystkiego mona unikn poprzez zastosowanie innego aspektu operatora &, ktĂłry pozwoli na uzyskanie adresu dla statycznie zadeklarowanej zmiennej, na przykad: int num1 = 10; int num2; compute(num1, &num2);

MĂłwic dokadniej, mimo e w kolejnej wersji naszego kodu wci stosujemy wskanik, uywamy go jednak w sposĂłb domylny, nie tworzc nowej zmiennej wskanikowej ani nie deklarujc dynamicznej pamici.

Pami ma znaczenie Aby zrozumie, w jaki sposĂłb dynamiczne przydzielanie pamici pozwala nam na powikszanie i zmniejszanie zasobĂłw w czasie dziaania programu, powinnimy dowiedzie si wicej o tym, jak ogĂłlnie dziaa alokacja pamici. Wedug mnie jest to jedno z zagadnie , z ktĂłrych mog skorzysta pocztkujcy programici uczcy si jzyka C++. Wszyscy programici musz w ko cu zrozumie, jak dziaaj systemy pamici w nowoczesnym komputerze, a jzyk C++ po prostu zmusza Ci do stanicia twarz w twarz z tym zagadnieniem. Inne jzyki tak dokadnie ukrywaj szczegĂły zarzdzania pamici, e nowicjusze przekonuj samych siebie, i ta wiedza ich nie dotyczy, co jednak nie jest prawd.

112

Rozdzia 4

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


Szczegóy moe nie s wane, dopóki wszystko dziaa prawidowo. Gdy jednak tylko pojawiaj si kopoty, ignorowanie niskopoziomowych modeli pamici powoduje powstanie pomidzy programist i rozwizaniem problemu przeszkód nie do pokonania.

Stos i sterta Jzyk C++ alokuje pami w dwóch miejscach: na stosie (ang. stack) i na stercie (ang. heap). Jak wynika z samych nazw, stos jest uporzdkowany i schludny, a sterta chaotyczna i niechlujna. Nazwa „stos” jest szczególnie opisowa, poniewa pozwala wyobrazi sobie metod zwartego przydzielania pamici. Zaómy, e masz do czynienia ze stosem skrzy przedstawionym na rysunku 4.1 (a). Gdy musisz przechowa now skrzyni w magazynie, umieszczasz j na samej górze stosu. Aby usun okrelon skrzyni, zdejmujesz najpierw te, które s nad ni. W praktycznej terminologii programowania oznacza to, e po przydzieleniu bloku pamici (skrzyni) na stosie nie mona zmieni jego rozmiaru, poniewa w dowolnym momencie mog istnie inne obszary, znajdujce si bezporednio za nim (inne skrzynie nad ni).

Rysunek 4.1. Stos skrzy i stos wywo a funkcji W jzyku C++ moesz jawnie stworzy wasny stos, by uy go w okrelonym algorytmie, lecz bez wzgldu na to w systemie istnieje jeden stos, który zawsze bdzie uywany przez Twój program. Jest on zwany stosem wywoa (ang. runtime stack). Za kadym razem gdy wywoana zostaje funkcja (oznacza to take funkcj main), na szczycie stosu wywoa zarezerwowany zostaje blok pamici. Jest on zwany rekordem aktywacji (ang. activation record). Pena analiza jego

Rozwizywanie problemów za pomoc wskaników i pamici dynamicznej

Kup książkę

113

Poleć książkę


zawartoci wykracza poza ramy tej ksiki, lecz bdc osob zajmujc si rozwizywaniem problemĂłw, powiniene wiedzie, e podstawowym zadaniem rekordu aktywacji jest udostpnianie miejsca do przechowywania zmiennych. Jest w nim przydzielana pami dla wszystkich zmiennych lokalnych, wcznie z parametrami funkcji. Przyjrzyjmy si nastpujcemu przykadowi: int functionB(int inputValue) { Â&#x2122; return inputValue - 10; } int functionA(int num) { int localVariable = functionB(num * 10); return localVariable; } int main() { int x = 12; int y = functionA(x); return 0; }

W powyszym kodzie funkcja main wywouje functionA, ktĂłra z kolei wywouje functionB. Na rysunku 4.1 (b) przedstawiono uproszczon wersj stanu stosu wywoa zaraz przed wykonaniem instrukcji powrotu z funkcji functionB Â&#x2122;. Rekordy aktywacji dla wszystkich trzech funkcji zostay umieszczone na stosie zwartej pamici, przy czym funkcja main znajduje si na jego samym dole (aby jeszcze bardziej skomplikowa zagadnienie, chciabym doda, e moliwe jest, by â&#x20AC;&#x201D; przeciwnie do naturalnego zachowania â&#x20AC;&#x201D; stos zaczyna si od najwyszego moliwego adresu w pamici, a nastpnie rĂłs w kierunku niszych adresĂłw. Nic nie stoi jednak na przeszkodzie, by zignorowa t moliwo). Logicznie rozumujc, rekord aktywacji funkcji main znajduje si na dole stosu, rekord aktywacji funkcji functionA nad nim, a rekord aktywacji funkcji functionB na samej gĂłrze stosu. aden z dwĂłch niszych rekordĂłw aktywacji nie moe zosta usunity, zanim nie zlikwidujemy rekordu aktywacji dla funkcji functionB. Stos jest dokadnie uporzdkowany, lecz w przeciwie stwie do niego sterta jest do chaotyczna. Wyobra sobie, e znĂłw musisz przechowywa skrzynie, lecz tym razem s one delikatne i nie moesz ukada jednej na drugiej. Na pocztku dysponujesz duym, pustym pomieszczeniem i moesz w nim ukada skrzynie w dowolnym miejscu na pododze. S one jednak cikie, wic po ich uoeniu na pododze pozostaj tam a do momentu, gdy trzeba si bdzie ich pozby. W porĂłwnaniu ze stosem, taki system ma pewne zalety i wady. Z jednej strony jest uniwersalny i pozwala w dowolnym momencie uzyska dostp do zawartoci kadej ze skrzy . Z drugiej strony jednak w pomieszczeniu szybko zapanuje nieporzdek. Jeli skrzynie maj rĂłne rozmiary, szczegĂłlnie trudno bdzie wykorzysta kad woln przestrze dostpn na pododze. W ko cu

114

Rozdzia 4

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


midzy skrzyniami bdzie istnie mnรณstwo wolnych miejsc, ktรณre bd jednak miay zbyt ma powierzchni, by umieci na nich kolejny element. Poniewa skrzynie nie mog by w prosty sposรณb przemieszczane, usuwanie niektรณrych z nich spowoduje, e pojawi si raczej kolejne, trudne do zlikwidowania wolne przestrzenie, a nie obszar, ktรณry byby uporzdkowany i zbliony wygldem do naszej pocztkowej pustej podogi. W praktycznej terminologii programowania nasza sterta jest jak podoga w opisanym wanie pomieszczeniu. Blok pamici jest zwartym cigiem adresรณw. W trakcie dziaania programu, ktรณry wykonuje wiele rezerwacji i zwolnie pamici, pojawia si mnรณstwo wolnych przestrzeni midzy zaalokowanymi obszarami. Problem ten jest znany jako fragmentacja pamici (ang. memory fragmentation). Kady program ma wasn stert, w ktรณrej mona dynamicznie przydziela pami. W jzyku C++ oznacza to zazwyczaj uycie sowa kluczowego new, lecz moesz take stosowa standardowe funkcje jzyka C suce do alokowania pamici, takie jak malloc. Kade wywoanie new (lub malloc) rezerwuje odpowiedni obszar na stercie i zwraca wskanik do niego, natomiast kade wywoanie delete (lub free w przypadku, gdy do alokacji uyto malloc) powoduje, e jest on zwalniany do puli dostpnej pamici. Z powodu fragmentacji nie wszystkie obszary pamici na stercie s rรณwnie uyteczne. Jeli nasz program rozpoczyna swoje dziaanie od przydzielenia pamici na stercie dla zmiennych A, B i C, moemy si spodziewa, e te trzy bloki bd zwarte. Jeli jednak zwolnimy pami dla zmiennej B, pojawi si wolne miejsce, ktรณre moe zosta uyte jedynie przez danie wymagajce rezerwacji obszaru o rozmiarze B lub mniejszym, chyba e zostanie rรณwnie zwolniona pami dla zmiennej A lub C. Omawian sytuacj wyjaniono na rysunku 4.2. W czci (a) widzimy podog naszego pomieszczenia z chaotycznie umieszczonymi skrzyniami. W pewnym momencie pomieszczenie byo prawdopodobnie uporzdkowane, lecz z upywem czasu skrzynie byy umieszczane w sposรณb przypadkowy. Obecnie mamy niewielk skrzyni (b), ktรณra jednak nie moe zmieci si na adnym wolnym miejscu na pododze, mimo e cakowita nieuywana powierzchnia jest duo wiksza od powierzchni samej skrzyni. W czci (c) przedstawilimy niewielk stert. Linie przerywane oznaczaj najmniejsze (niepodzielne) fragmenty pamici, ktรณre w zalenoci od rodzaju menedera sterty mogyby by pojedynczym bajtem, sowem lub jeszcze czym wikszym. Obszary zacienione reprezentuj rezerwacje zwartej pamici. Jeden z nich w celu lepszej klarownoci zawiera pewne fragmenty, ktรณre zostay ponumerowane. Podobnie jak nieuporzdkowana podoga, rรณwnie stos z fragmentacj zawiera wiele oddzielnych fragmentรณw nieprzydzielonej pamici, co zmniejsza ich przydatno. Mamy 85 nieuywanych fragmentรณw pamici, lecz najwikszy zwarty obszar, wskazywany przez symbol strzaki, skada si tylko z 17 elementรณw. Inaczej mรณwic, jeli kady z fragmentรณw byby bajtem, ta sterta nie mogaby pozwoli na wykonanie polecenia new przydzielajcego obszar pamici wikszy od 17 bajtรณw, mimo e cakowita ilo wolnego miejsca wynosi 85 bajtรณw.

Rozwizywanie problemรณw za pomoc wskanikรณw i pamici dynamicznej

Kup ksiฤ…ลผkฤ™

115

Poleฤ‡ ksiฤ…ลผkฤ™


Rysunek 4.2. Nieuporzdkowana pod oga, skrzynia, której nie mona na niej umieci, a take pami z fragmentacj

Rozmiar pamici Pierwszym praktycznym zagadnieniem zwizanym z pamici jest ograniczanie jej uycia tylko do tego, co niezbdne. Nowoczesne systemy komputerowe maj tak duo pamici, e atwo mona zaoy, i jej pojemno jest niesko czona, lecz w rzeczywistoci kady z programów ma dostp do jej ograniczonej iloci. Programy równie musz uywa pamici w sposób efektywny, aby unikn spowolnienia dziaania caego systemu. W wielozadaniowym systemie operacyjnym (co oznacza praktycznie kady nowoczesny system) kady bajt pamici zmarnowany przez dany program zwiksza prawdopodobie stwo pojawienia si sytuacji, w której zestaw dziaajcych aplikacji nie ma ju wystarczajcej iloci pamici do poprawnej pracy. W tym momencie system operacyjny zaczyna w sposób cigy kopiowa fragmenty pamici z jednego programu do drugiego, powodujc znaczce spowolnienie dziaania aplikacji. Taka sytuacja zwana jest szamotaniem (ang. thrashing). Zauwa, e poza koniecznoci utrzymania jak najmniejszego rozmiaru dla caego programu naley równie dy do tego, by stos i sterta byy jak najwiksze. Aby to udowodni, zacznijmy rezerwowa pami na stercie po jednym kilobajcie za kadym razem, a do momentu gdy co przestanie dziaa: const int intsPerKilobyte = 1024 / sizeof(int); while (true) { int *oneKilobyteArray = new int[intsPerKilobyte]; }

Chciabym zaznaczy, e jest to okropny kod, napisany wycznie w celu zademonstrowania problemu. Jeli chciaby sprawdzi go w swoim systemie, sugeruj, aby wczeniej — po prostu dla bezpiecze stwa — zapisa wszystkie dane, na których pracujesz. Program zawiesi si, a Twój system operacyjny poinformuje, e kod wywoa wyjtek bad_alloc, lecz nie móg go obsuy. Jest on 116

Rozdzia 4

Kup książkę

Poleć książkę


zgaszany przez instrukcj new, gdy aden blok nieprzydzielonej pamici na stercie nie jest na tyle duy, by mona byo poprawnie obsuy danie. Brak miejsca na stercie jest zwany przepenieniem sterty (ang. heap overflow). W niektĂłrych systemach taka sytuacja moe si zdarza czsto, a w innych bdne dziaanie programu moe spowodowa pojawienie si dugotrwaego stanu szamotania, zanim zostanie zgoszony wyjtek bad_alloc (w moim systemie uycie instrukcji new nie powiodo si dopiero wĂłwczas, gdy za pomoc wczeniejszych wywoa zaalokowaem 2 gigabajty pamici). Podobna sytuacja zdarza si w przypadku stosu wywoa . Kade wywoanie funkcji rezerwuje miejsce na stosie, a oprĂłcz tego dla kadego rekordu aktywacji istnieje stay narzut, nawet w przypadku funkcji nieposiadajcej parametrĂłw lub lokalnych zmiennych. Najprostszym sposobem zademonstrowania takiej sytuacji jest niekontrolowane wywoanie funkcji rekurencyjnej: Â&#x2122; int count = 0; void stackOverflow() { Â&#x161; count++; Â&#x203A; stackOverflow(); } int main() { Â&#x153; stackOverflow(); return 0; }

Powyszy kod zawiera zmienn globaln Â&#x2122;, co w wikszoci przypadkĂłw wiadczy o zym stylu programowania. W tej sytuacji potrzebujmy jednak zmiennej, ktĂłra bdzie istnie podczas wszystkich wywoa rekurencyjnych. Poniewa jest ona zadeklarowana poza funkcj, nie ma potrzeby rezerwowania dla niej miejsca w rekordzie aktywacji. Nie istniej rĂłwnie adne inne lokalne zmienne ani parametry. Funkcja tylko zwiksza licznik Â&#x161;, a nastpnie wykonuje wywoanie rekurencyjne Â&#x203A;. Rekurencja zostanie dokadnie omĂłwiona w rozdziale 6., lecz w tym przykadzie jest ona uywana tylko w celu stworzenia moliwie najduszego a cucha wywoa funkcji. Rekord aktywacji funkcji pozostaje na stosie do momentu jej zako czenia. Gdy wic z funkcji main zostaje po raz pierwszy wywoana funkcja stackOverflow Â&#x153;, rekord aktywacji jest umieszczany na stosie wywoa i nie moe zosta usunity, dopĂłki nie zako czy si jej dziaanie. Nie zdarzy si to jednak nigdy, poniewa funkcja wykonuje kolejne wywoanie stackOverflow, umieszczajc nastpny rekord aktywacji na stosie, nastpnie tworzy trzecie wywoanie itd. Liczba rekordĂłw aktywacji ronie na stosie a do momentu, gdy zaczyna brakowa na nim miejsca. W moim systemie warto count wynosia okoo 4900, gdy program przesta dziaa. rodowisko projektowe Visual Studio, w ktĂłrym pracuj, domylnie przydziela programowi 1 MB pamici na stosie, co oznacza, e kade wywoanie funkcji, nawet takiej, ktĂłra nie ma lokalnych zmiennych ani parametrĂłw, tworzy rekord aktywacji o wielkoci ponad 200 bajtĂłw.

Rozwizywanie problemĂłw za pomoc wskanikĂłw i pamici dynamicznej

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

117

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


Czas ycia Czas ycia (ang. lifetime) zmiennej to okres midzy jej alokacj a zwolnieniem pamici. W przypadku zmiennej korzystajcej ze stosu, czyli zmiennej lokalnej lub parametru, czas ycia jest zarzdzany w sposĂłb domylny. Zmienna zostaje zaalokowana podczas wywoania funkcji, a usunita po jej zako czeniu. W przypadku zmiennej korzystajcej ze sterty, czyli dynamicznie zaalokowanej przy uyciu instrukcji new, czas jej ycia jest w naszych rkach. Zarzdzanie czasem ycia dynamicznie alokowanych zmiennych jest zmor kadego programisty C++. Najczciej znanym problemem jest grony wyciek pamici, czyli sytuacja, w ktĂłrej pami zostaje przydzielona na stercie, lecz nigdy nie jest zwolniona ani wykorzystana przez aden wskanik. Oto prosty przykad: Â&#x2122; int *intPtr = new int; Â&#x161; intPtr = NULL;

W powyszym kodzie deklarujemy wskanik do liczby cakowitej Â&#x2122;, inicjalizujc go poprzez przydzielenie odpowiedniego miejsca na stercie. Nastpnie w drugim wierszu do tego wskanika przypisujemy warto NULL Â&#x161; (ktĂłra jest po prostu innym identyfikatorem liczby zero). Warto cakowita, ktĂłr zaalokowalimy za pomoc instrukcji new, wci jednak istnieje. Samotna i porzucona znajduje si na swoim miejscu na stercie, czekajc na operacj zwolnienia pamici, ktĂłra nigdy nie wystpi. Nie moemy zwolni pamici dla zaalokowanej zmiennej cakowitej, poniewa taka operacja wymaga uycia sowa kluczowego delete z parametrem bdcym wskanikiem do usuwanego bloku, a my ju nim nie dysponujemy. Jeli chcielibymy jednak wykona polecenie delete intPtr, zako czyoby si ono bdem, poniewa warto intPrt jest rĂłwna zeru. Czasem zamiast problemu z pamici, ktĂłrej nie mona zwolni, mamy do czynienia z czym odwrotnym. Jest to prĂłba zwolnienia tej samej pamici dwukrotnie, co powoduje powstanie bdu czasu wykonania. Wydaje si, e takiej sytuacji mona atwo unikn: wystarczy nie wywoywa dwukrotnie instrukcji delete dla tej samej zmiennej. Jest to jednak bardziej skomplikowane, poniewa moemy mie wiele zmiennych wskazujcych na ten sam obszar pamici. Jeli wiele zmiennych wskazuje na ten sam blok pamici i zostanie wykonane polecenie delete dla jednej z nich, wĂłwczas w rzeczywistoci zwolnimy pami dla wszystkich. Jeli nie przypiszemy im jawnie wartoci NULL, wĂłwczas bdziemy mieli do czynienia ze wskanikami zawieszonymi, dla ktĂłrych wywoanie delete bdzie powodowa pojawienie si bdu czasu wykonania.

118

Rozdzia 4

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


Rozwizywanie problemĂłw za pomoc wska nikĂłw W tym momencie jeste ju gotowy do rozwizywania problemĂłw, dlatego przyjrzyjmy si kilku z nich i sprĂłbujmy uy wskanikĂłw oraz dynamicznego przydzielania pamici, by je rozwiza. Najpierw zajmiemy si dynamicznie alokowanymi tablicami, co pozwoli na zaprezentowanie metody kontrolowania pamici sterty podczas ich uywania. Nastpnie zanurzymy si w prawdziwie dynamicznej strukturze.

a cuchy o zmiennej dugoci W pierwszym problemie zamierzamy stworzy funkcje do obsugi a cuchĂłw tekstowych. Ten termin jest tu uyty w najbardziej ogĂłlnym znaczeniu â&#x20AC;&#x201D; jako sekwencja znakĂłw, bez wzgldu na sposĂłb ich przechowywania. ZaĂłmy, e nasz typ a cuchowy powinien by wspierany przez trzy funkcje. PROBLEM: OBSUGA ACUCHĂ&#x201C;W O ZMIENNEJ DUGOCI StwĂłrz implementacj opart na stercie dla trzech funkcji obsugujcych a cuchy: Â? append â&#x20AC;&#x201D; funkcja wymaga podania a cucha i znaku, a w wyniku

swojego dziaania docza ten znak do ko ca a cucha. Â? concatenate â&#x20AC;&#x201D; funkcja uywa dwĂłch a cuchĂłw i docza znaki

z drugiego do pierwszego. Â? characterAt â&#x20AC;&#x201D; funkcja wymaga uycia a cucha oraz odpowiedniej

liczby, zwracajc znak znajdujcy si na wskazywanej przez ni pozycji w a cuchu (pierwszy znak a cucha ma indeks rĂłwny zeru). Napisz kod, zakadajc, e funkcja characterAt bdzie wywoywana czsto, a dwie pozostae stosunkowo rzadko. Wzgldna wydajno operacji powinna odzwierciedla czstotliwo ich stosowania.

W tym przypadku powinnimy zaprojektowa tak reprezentacj a cucha, ktĂłra pozwoli na szybkie wykonywanie funkcji characterAt, co oznacza, e musimy wymyle szybki sposĂłb na uzyskanie dostpu do dowolnego znaku. Jak prawdopodobnie pamitasz z poprzedniego rozdziau, to jest wanie najlepsza zaleta tablic â&#x20AC;&#x201D; dostp swobodny. Rozwimy wic nasz problem przy uyciu tablic typu char. Funkcje append i concatenate zmieniaj rozmiar a cucha, co oznacza, e napotkamy tu wszystkie rodzaje problemĂłw dotyczcych tablic, o ktĂłrych ju wspominalimy. Poniewa w problemie nie zdefiniowano ograniczenia dotyczcego wielkoci a cucha, nie moemy okreli pocztkowego rozmiaru dla naszych tablic i mie nastpnie nadziej, e wszystko bdzie dziaa. Zamiast tego musimy zmienia go w trakcie dziaania programu.

Rozwizywanie problemĂłw za pomoc wskanikĂłw i pamici dynamicznej

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

119

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


Aby rozpocz, zdefiniujmy nazw typu dla naszego a cucha za pomoc sowa kluczowego typedef. Wiemy, e tablice bdziemy tworzy w sposĂłb dynamiczny, wic musimy zaprojektowa nasz typ a cuchowy jako wskanik do char: typedef char * arrayString;

Majc gotowy typ, zajmijmy si funkcjami. Wykorzystujc zasad rozpoczynania od tego, co ju potrafimy zrobi, napiszmy szybko funkcj characterAt: char characterAt(arrayString s, int position) { Â&#x2122; return s[position]; }

Jak pamitasz z rozdziau 3., gdy wskanikowi zosta przypisany adres tablicy, moemy uzyska dostp do jej elementĂłw, wykorzystujc w tym celu zwyk notacj tablicow Â&#x2122;. Zauwa jednak, e w przypadku gdy warto position nie bdzie poprawnym indeksem tablicy s, mog pojawi si problemy. Dodatkowo powyszy kod przerzuca odpowiedzialno za kontrolowanie poprawnoci drugiego parametru na funkcj wywoujc. Inne rozwizania tego zagadnienia zostan przedstawione w wiczeniach. W chwili obecnej zajmijmy si funkcj append. Moemy sobie ogĂłlnie wyobrazi, co powinna ona wykonywa, lecz aby pozna szczegĂły, powinnimy rozway uycie przykadu. Jest to technika, ktĂłr nazywam rozwizywaniem poprzez przykad. Rozpocznij od zdefiniowania nietrywialnego zestawu danych wejciowych dla funkcji lub programu. Zapisz wszystkie szczegĂły zwizane z danymi wejciowymi, a take te, ktĂłre dotycz wynikĂłw. Gdy bdziesz tworzy kod, zajmiesz si ogĂłlnym przypadkiem, a take dokadnie sprawdzisz, w jaki sposĂłb na kadym etapie dziaania programu zmodyfikowane zostaj dane wejciowe, aby upewni si, e osigasz wymagany stan wyjciowy. Ta metoda jest szczegĂłlnie przydatna podczas pracy ze wskanikami i dynamicznie przydzielan pamici, poniewa w takich przypadkach program dziaa w wikszoci poza bezporedni kontrol. ledzenie stanu na papierze zmusza Ci do obserwowania wszystkich zmieniajcych si wartoci w pamici â&#x20AC;&#x201D; nie tylko tych, ktĂłre s bezporednio reprezentowane przez zmienne, lecz take samej sterty. ZaĂłmy, e rozpoczniemy od a cucha test, co oznacza, e na stercie mamy tablic zawierajc znaki t, e, s i t â&#x20AC;&#x201D; w tej wanie kolejnoci, a przy uyciu funkcji append chcielibymy doda do niej znak wykrzyknika. Na rysunku 4.3 zaprezentowano stan pamici przed (a) i po (b) wykonaniu tej operacji. To, co znajduje si po lewej stronie pionowej, kropkowanej linii, jest pamici stosu (lokalnymi zmiennymi lub parametrami). Wszystko po jej prawej stronie dotyczy pamici na stercie, dynamicznie przydzielonej za pomoc sowa kluczowego new.

120

Rozdzia 4

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


Rysunek 4.3. Proponowane stany pamici przed i po wywo aniu funkcji append Spogldajc na rysunek, od razu widz ewentualny problem, ktรณry moe si pojawi w naszej funkcji. Wykorzystujc zdefiniowan przez nas implementacj a cuchรณw tekstowych, funkcja zamierza stworzy now tablic, majc liczb elementรณw wiksz o jeden od pierwotnej, a nastpnie skopiowa wszystkie znaki z pierwszej tablicy do drugiej. Lecz w jaki sposรณb bdziemy wiedzie, jaki rozmiar ma pierwsza tablica? Z poprzedniego rozdziau wiemy, e powinnimy sami ledzi wielko naszych tablic. Tak wic czego tu brakuje. Jeli mamy jakie dowiadczenie w pracy z a cuchami ze standardowej biblioteki jzykรณw C i C++, wiemy ju, czym jest brakujcy element. Jeli nie, moemy go szybko znale. Pamitaj, e jedn z naszych technik rozwizywania problemรณw jest poszukiwanie analogii. By moe powinnimy si zastanowi nad innymi problemami, w ktรณrych wiedza o wielkoci jakiego skadnika jest nieznana. W rozdziale 2. podczas rozwizywania problemu walidacji sumy kontrolnej Luhna przetwarzalimy numery identyfikacyjne o dowolnej liczbie cyfr. Nie wiedzielimy wรณwczas, ile cyfr wprowadzi uytkownik, jednak napisalimy ptl while dziaajc a do momentu, w ktรณrym nie zosta wczytany ostatni znak, czyli koniec wiersza. Niestety, na ko cu naszych tablic nie ma znaku ko ca wiersza. Ale dlaczego nie moglibymy umieci tego znaku jako ostatniego elementu we wszystkich naszych tablicach znakowych? Wรณwczas bdziemy mogli wyznaczy dugo a cucha w taki sam sposรณb, jak ustalalimy liczb cyfr w numerze identyfikacyjnym. Jedyn wad tej metody jest to, e w naszych a cuchach, z wyjtkiem ich zako czenia, nie moglibymy ju uywa znakรณw ko ca wiersza. Nie jest to zbyt wielkie ograniczenie i zaley od sytuacji, w ktรณrej wykorzystamy nasze a cuchy. Dla uzyskania maksymalnej uniwersalnoci powinnimy jednak wybra tak warto, ktรณra nie bdzie kolidowa z adnym znakiem moliwym do zastosowania przez uytkownika. Do oznaczania ko ca naszych a cuchรณw wybierzemy wic zero, poniewa reprezentuje ono warto pust w ASCII i innych systemach kodowania znakรณw. Jest to dokadnie taka sama metoda, ktรณr zastosowano w standardowej bibliotece dla jzykรณw C i C++. Po rozwizaniu powyszego problemu moemy si bardziej skoncentrowa na tym, co powinna robi funkcja append z naszymi przykadowymi danymi. Wiemy, e bdzie ona miaa dwa parametry: pierwszy typu arrayString ma by wskanikiem do a cucha znakรณw na stercie, a drugi typu char powinien by znakiem doczanym do niego. Aby zbytnio nie komplikowa, wemy si do pracy i stwรณrzmy szkic funkcji append, a take kod, ktรณry powinien j testowa:

Rozwizywanie problemรณw za pomoc wskanikรณw i pamici dynamicznej

Kup ksiฤ…ลผkฤ™

121

Poleฤ‡ ksiฤ…ลผkฤ™


void append(Â&#x2122; arrayString& s, char c) { } void appendTester() { Â&#x161; arrayString a = new char[5]; Â&#x203A; a[0] = 't'; a[1] = 'e'; a[2] = 's'; a[3] = 't'; a[4] = Â&#x153; append(a, '!') Â? cout << a << "\n"; }

0;

Funkcja appendTester przydziela pami na stercie dla naszego a cucha Â&#x161;. ZwrĂł uwag na to, e rozmiar tablicy wynosi 5, co jest niezbdne, poniewa musimy przypisa cztery litery sowa test oraz ko czcy a cuch znak pusty Â&#x203A;. Nastpnie wywoujemy funkcj append Â&#x153;, ktĂłra w tym momencie nie zawiera adnej logiki. Podczas pisania nagĂłwka funkcji zdaem sobie spraw, e parametr typu arrayString powinien by referencj (&) Â&#x2122;, poniewa funkcja zamierza utworzy na stercie now tablic. Wynika std, e warto zmiennej a, ktĂłra jest przekazywana do funkcji append, zostanie zmieniona po jej zako czeniu, gdy musi wskazywa na nowy a cuch. Zauwa, e ze wzgldu na to, i nasze tablice uywaj ko czcego znaku pustego stosowanego w bibliotekach standardowych, moemy przekaza tablic poprzez wskanik bezporednio do strumienia wyjciowego, aby sprawdzi jej warto Â?. Na rysunku 4.4 pokazano analiz tego, co powinna zrobi funkcja z danymi testowymi. Znaki ko czce tablic znajduj si we waciwych miejscach; dla wikszej czytelnoci oznaczono je jako NULL. W stanie (b) oczywiste jest, e zmienna s wskazuje na nowy blok pamici. Poprzednia tablica jest obecnie przedstawiona w kolorze szarym. W tego typu rysunkach stosuj taki kolor w celu poinformowania, e dany obszar pamici zosta zwolniony. Umieszczanie pamici przydzielonej w naszych diagramach pozwala nie zapomnie o jej zwalnianiu.

Rysunek 4.4. Zaktualizowane i uzupe nione stany pamici przed (a) i po (b) wywo aniu funkcji append

122

Rozdzia 4

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


Majc wszystko waciwie narysowane, moemy napisa sam funkcj: void append(arrayString& s, char c) { int oldLength = 0; ™ while (s[oldLength] != 0) { oldLength++; } š arrayString newS = new char[oldLength + 2]; › for (int i = 0; i < oldLength; i++) { newS[i] = s[i]; } œ newS[oldLength] = c;  newS[oldLength + 1] = 0; ž delete[] s; Ÿ s = newS; }

W kodzie dzieje si wiele rzeczy, wic przeanalizujmy go krok po kroku. Na pocztku funkcji mamy ptl, która odszukuje znak NULL ko czcy tablic ™. Po jej zako czeniu zmienna oldLength bdzie zawiera liczb waciwych znaków w naszej tablicy (to znaczy bez ko czcego j znaku pustego). Na stercie przydzielamy miejsce dla nowej tablicy, która bdzie miaa rozmiar oldLength + 2 š. Jest to jeden ze szczegóów, którego nie mona atwo zrozumie, gdy starasz si to zrobi tylko w swoim umyle, lecz staje si to proste po przedstawieniu na rysunku. Podajc za kodem i korzystajc z przykadu przedstawionego na rysunku 4.5, moemy zauway, e warto oldLength powinna by równa 4. Wiemy, e tak ma by, poniewa sowo test skada si z czterech znaków. Nowa tablica, przedstawiona w czci (b), wymaga alokacji szeciu znaków, gdy musimy mie dodatkowe miejsce na doczany wykrzyknik oraz ko czcy znak pusty.

Rysunek 4.5. Zalenoci midzy zmienn lokaln, parametrami i przydzielon pamici przed i po wykonaniu funkcji append

Rozwizywanie problemów za pomoc wskaników i pamici dynamicznej

Kup książkę

123

Poleć książkę


Majc ju przydzielon pami, kopiujemy wszystkie znaki waciwe ze starej tablicy do nowej ›, a nastpnie w odpowiednich miejscach umieszczamy doczany œ oraz ko czcy znak NULL . Przypomn jeszcze raz, e stosowanie diagramu pozwala na zachowanie porzdku. Aby wszystko wygldao jeszcze prociej, na rysunku 4.5 zaprezentowano sposób wyznaczania wartoci oldLength, a take pozycj, któr wskazuje ta zmienna w nowej tablicy. Dziki tej metodzie mona atwo okreli poprawne indeksy dla obu instrukcji przypisania. Ostatnie trzy wiersze w funkcji append dotycz szarego prostokta znajdujcego si na rysunku w czci (b). Aby unikn wycieku pamici, musimy zwolni pami na stercie dla tablicy, na któr pierwotnie wskazywa parametr s ž. Nastpnie przypisujemy mu adres nowej, wikszej tablicy Ÿ. Niestety, jednym z powodów tego, e sytuacje wycieku pamici s tak czsto spotykane podczas programowania w jzyku C++, jest to, i dopóki cakowita wielko takiej pamici nie jest dua, system nie zgasza adnych problemów. Wynika std, e wyciek pamici moe zosta zupenie niezauwaony przez programist podczas przeprowadzania testów programu. Jako programici musimy wic by staranni i zawsze bra pod uwag czas ycia bloków pamici przydzielonych na stercie. Za kadym razem gdy uywasz sowa kluczowego new, pomyl o tym, gdzie i kiedy pojawi si odpowiednie sowo delete. Zauwa, e wszystko, co zawarlimy w funkcji, wynika bezporednio z naszych diagramów. Skomplikowane programowanie staje si duo prostsze przy uyciu dobrze zaprojektowanych rysunków. Chciabym, aby pocztkujcy programici powicali troch czasu na ich stworzenie przed rozpoczynaniem waciwego kodowania. Jest to zwizane z nasz najwaniejsz zasad rozwizywania problemów: zawsze naley mie jaki plan. Dobrze zdefiniowany diagram dla przykadowego problemu jest czym w rodzaju dokadnie zaplanowanej trasy prowadzcej do punktu docelowego przed rozpoczciem wyjazdu na wakacje. Na pocztku wymagane jest powicenie dodatkowego czasu, by na ko cu unikn niepotrzebnego wysiku i frustracji. TWORZENIE DIAGRAMÓW Wszystko, czego potrzebujesz do narysowania diagramu, to oówek i papier. Jeli jednak masz wicej czasu, sugeruj uycie odpowiedniego programu graficznego. Zawiera on narzdzia do rysowania oraz szablony dedykowane specjalnie dla potrzeb rozwizywania problemów programistycznych. Jednake kady program obsugujcy grafik wektorow bdzie odpowiedni (uyty tu termin wektor oznacza, e aplikacja pozwala na tworzenie linii prostych i krzywych, a nie przetwarza grafiki rastrowej, tak jak Photoshop). Ilustracje do tej ksiki wykonaem za pomoc darmowego programu Inkscape. Tworzenie diagramów w komputerze pozwala na ich uporzdkowanie i przechowywanie w tym samym miejscu, w którym znajduje si odpowiedni kod. S one zazwyczaj take bardziej staranne, dziki czemu mona je atwiej zrozumie, gdy po jakim czasie do nich wrócisz. Wreszcie diagram stworzony na komputerze mona atwo skopiowa i zmodyfikowa, podobnie jak zrobiem to w przypadku tworzenia rysunku 4.5 na podstawie poprzedniego. Jeli chcesz umieci na nim jak tymczasow uwag, moesz to atwo zrobi na wydrukowanej kopii.

124

Rozdzia 4

Kup książkę

Poleć książkę


Wracajc do naszej funkcji append — kod wyglda na godny zaufania, lecz pamitaj, e stworzylimy go na podstawie okrelonego przykadu. Nie moemy wic by zbyt pewni siebie i zakada, e bdzie dziaa w kadym przypadku. W szczególnoci naley przetestowa przypadki specjalne. Przypadkiem specjalnym w programowaniu nazywana jest sytuacja, w której poprawne dane spowoduj typowe dziaanie kodu dajcego bdne wyniki. Zauwa, e nasz problem nie dotyczy niepoprawnych danych, takich jak wartoci spoza zakresu. W przypadku kodu zawartego w tej ksice zaoylimy, e programy i funkcje bd otrzymywa poprawne dane. Na przykad jeli program oczekuje podania szeregu liczb cakowitych oddzielonych przecinkami, zakadamy, e je otrzyma, a nie pojawi si adne dodatkowe znaki, wartoci nieliczbowe itp. Takie zaoenie jest niezbdne w celu uzyskania programu o sensownej dugoci, a take uniknicia powtarzania tego samego kodu sprawdzajcego poprawno danych. W wiecie rzeczywistym powinnimy jednak stosowa odpowiednie zabezpieczenia przeciwko bdnym danym wejciowym. Taka waciwo programu jest zwana odpornoci. Odporna aplikacja dziaa poprawnie nawet w przypadku zych danych wejciowych. Na przykad tego typu program mógby wywietli komunikat bdu, zamiast spowodowa awari dziaania.

Testowanie przypadków specjalnych Przyjrzyjmy si ponownie funkcji append, sprawdzajc j pod wzgldem przypadków specjalnych; inaczej mówic, upewniajc si, e dla poprawnych wartoci wejciowych nie pojawi si adne dziwne zachowania programu. Najlepszymi przypadkami specjalnymi s sytuacje ekstremalne, takie jak minimalna i maksymalna warto wejciowa. Wprawdzie w przypadku funkcji append nie istnieje maksymalny rozmiar tablicy znakowej, jednake mamy okrelone minimum. Jeli a cuch nie ma waciwych znaków, wówczas odpowiada tablicy jednoelementowej (jedynym elementem jest ko cowy znak NULL). Podobnie jak poprzednio, stwórzmy teraz diagram, aby nawietli sytuacj. Zaómy, e doczylimy znak wykrzyknika do pustego a cucha, tak jak pokazano na rysunku 4.6.

Rysunek 4.6. Testowanie przypadku najmniejszej wartoci dla funkcji append

Rozwizywanie problemów za pomoc wskaników i pamici dynamicznej

Kup książkę

125

Poleć książkę


Rysunek wyglda sensownie, lecz powinnimy analizowany przypadek sprawdzi jeszcze w naszej funkcji. Dodajmy poniszy kod do funkcji testujcej appendTester: arrayString b = new char[1]; b[0] = 0; append(b, '!'); cout << b << "\n";

Kod dziaa poprawnie. Czy funkcja append podoba si nam, gdy upewnilimy si, e bdzie dziaa poprawnie? Kod wydaje si prosty i nie wyczuwam adnych „zych zapachów”, lecz jest chyba zbyt dugi dla tak prostej operacji. Gdy myl o przyszej funkcji concatenate, stwierdzam, e podobnie jak append, bdzie ona musiaa ustala dugo jednej lub dwóch tablic znakowych. Poniewa obie operacje wymagaj ptli, która znajduje znak pusty ko czcy a cuch, moglibymy stworzy oddzieln funkcj dla tego kodu, która w razie potrzeby byaby wywoywana z append i concatenate. Zróbmy to i odpowiednio zmodyfikujmy funkcj append: int length(arrayString s) { ™ int count = 0; while (s[count] != 0) { count++; } return count; } void append(arrayString& s, char c) { š int oldLength = length(s); arrayString newS = new char[oldLength + 2]; for (int i = 0; i < oldLength; i++) { newS[i] = s[i]; } newS[oldLength] = c; newS[oldLength + 1] = 0; delete[] s; s = newS; }

Kod funkcji length ™ zosta w zasadzie skopiowany z pocztku funkcji append. W niej samej umiecilimy w tym miejscu wywoanie funkcji length š. Funkcja length jest zwana funkcj pomocnicz (ang. helper function). Jej zadaniem jest realizowanie operacji wspólnych dla kilku innych funkcji. Eliminacja nadmiarowego kodu oznacza oprócz zmniejszenia jego dugoci take to, e staje si on bardziej niezawodny i prostszy do modyfikacji. Pozwala take w rozwizaniu problemu, poniewa funkcje pomocnicze dziel kod na mniejsze kawaki, pozwalajc nam atwiej odkry moliwoci jego ponownego wykorzystania.

126

Rozdzia 4

Kup książkę

Poleć książkę


Kopiowanie dynamicznie przydzielonych a cuchów Nadszed czas na zajcie si funkcj concatenate. Zastosujemy to samo podejcie co w przypadku funkcji append. Najpierw stworzymy pust wersj funkcji concatenate, aby zdefiniowa jej parametry i ich typy danych. Nastpnie narysujemy diagram dla przypadku testowego, a wreszcie napiszemy odpowiadajcy mu kod. Oto szablon funkcji razem z dodatkowym kodem testujcym: void concatenate(™ arrayString& s1, š arrayString s2) { } void concatenateTester() { arrayString a = new char[5]; a[0] = 't'; a[1] = 'e'; a[2] = 's'; a[3] = 't'; a[4] = arrayString b = new char[4]; b[0] = 'o'; b[1] = 'w'; b[2] = 'y'; b[3] = 0; concatenate(a, b); }

0;

Pamitaj, e opis funkcji mówi, i znaki drugiego a cucha (drugiego parametru) powinny zosta doczone na ko cu pierwszego. Pierwszy parametr funkcji concatentate bdzie wic referencj ™, podobnie jak byo w przypadku pierwszego parametru funkcji append. Drugi parametr š nie powinien jednak zosta zmieniony przez funkcj, dlatego bdzie wartoci. W naszym testowym przypadku poczymy a cuchy test i owy. Sytuacja przed i po wykonaniu tej operacji zostaa przedstawiona na rysunku 4.7.

Rysunek 4.7. Stan przed (a) i po (b) wykonaniu metody concatenate Szczegóy diagramu powinny by Ci ju znane z analizy funkcji append. W obecnym przypadku rozpoczynamy od dwóch tablic, dynamicznie przydzielonych na stosie i wskazywanych przez nasze dwa parametry s1 oraz s2. Po zako czeniu

Rozwizywanie problemów za pomoc wskaników i pamici dynamicznej

Kup książkę

127

Poleć książkę


dziaania funkcji parametr s1 powinien wskazywa na now tablic na stosie, ktĂłra zawiera 9 znakĂłw. Ta, ktĂłra bya wskazywana poprzednio, powinna zosta usunita. Parametr s2 i jego tablica pozostaj niezmienione. Mimo e umieszczenie parametru s2 i tablicy owy na diagramie moe wydawa si bezcelowe, w celu uniknicia bdĂłw kodowania ledzenie niezmienianych elementĂłw jest rĂłwnie wane jak tych, ktĂłre ulegaj modyfikacji. Ponumerowaem rĂłwnie elementy starej i nowej tablicy, poniewa przydao si to w przypadku funkcji append. Wszystko znajduje si na swoim miejscu, moemy wic zaj si pisaniem funkcji: void concatenate(arrayString& s1, arrayString s2) { Â&#x2122; int s1_OldLength = length(s1); int s2_Length = length(s2); int s1_NewLength = s1_OldLength + s2_Length; Â&#x161; arrayString newS = new char[s1_NewLength + 1]; Â&#x203A; for(int i = 0; i < s1_OldLength; i++) { newS[i] = s1[i]; } for(int i = 0; i < s2_Length; i++) { newS[Â&#x153; s1_OldLength + i] = s2[i]; } Â? newS[s1_NewLength] = 0; Â&#x17E; delete[] s1; Â&#x; s1 = newS; }

Na pocztku obliczamy dugoci obu a cuchĂłw, ktĂłre powinny zosta poczone Â&#x2122;, a nastpnie sumujemy je, aby wyznaczy dugo docelowego a cucha. Pamitaj, e te wszystkie wartoci dotycz liczby waciwych znakĂłw, bez uwzgldniania znaku zako czenia NULL. Gdy tworzymy tablic na stercie w celu przechowania nowego a cucha Â&#x161;, przydzielamy wic jej o jeden znak wicej, ni wynika to z sumy dugoci, poniewa musimy take zapamita ko czcy znak pusty. Nastpnie kopiujemy znaki z dwĂłch rĂłdowych a cuchĂłw do docelowego Â&#x203A;. Pierwsza ptla jest prosta, zwrĂł jednak uwag na wyznaczanie indeksu w drugiej ptli Â&#x153;. Kopiujemy tam dane z tablicy s2 w rodek tablicy newS. Jest to kolejny przykad translacji jednego zakresu wartoci na inny, co robimy ju od rozdziau 2. Dziki przyjrzeniu si numeracji elementĂłw na diagramie mona sprawdzi, jakie zmienne powinno si poczy ze sob, by wyznaczy poprawny indeks w tablicy docelowej. W pozostaej czci funkcji umieszczamy znak NULL na ko cu nowego a cucha Â?. Podobnie jak w przypadku append, zwalniamy ze sterty pierwotny blok pamici, wskazywany przez pierwszy parametr Â&#x17E;, a nastpnie przypisujemy mu nowo utworzony a cuch Â&#x;. Kod wyglda na dziaajcy, lecz podobnie jak przedtem, chcemy si upewni, e nieumylnie nie stworzylimy funkcji, ktĂłra dziaa poprawnie w naszym przykadzie, lecz nie we wszystkich przypadkach. Najbardziej prawdopodobne sytuacje sprawiajce kopoty mog polega na uyciu jednego lub dwĂłch parametrĂłw o dugoci zero (zawierajcych sam znak zako czenia a cucha). Powinnimy

128

Rozdzia 4

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


jawnie przetestowa oba przypadki przed dalszym dziaaniem. Pamitaj, e podczas sprawdzania poprawnoci kodu, ktĂłry uywa wskanikĂłw, powiniene zwraca uwag na nie same, a nie na wartoci na stercie, ktĂłre s przez nie wskazywane. Oto jeden przypadek testowy: arrayString a = new char[5]; a[0] = 't'; a[1] = 'e'; a[2] = 's'; a[3] = 't'; a[4] = arrayString c = new char[1]; c[0] = 0; concatenate(c, a); cout << a << "\n" << c << "\n"; Â&#x2122; cout << (void *) a << "\n" << (void *) c << "\n";

0;

Chciaem si upewni, e wywoanie funkcji concatenate spowoduje, i wskaniki a i c bd wskazywa na a cuch test, to znaczy na tablice o takiej samej zawartoci. RĂłwnie wane jest jednak to, e powinny one wskazywa na rĂłne a cuchy, jak przedstawiono na rysunku 4.8 (a). Ten przypadek jest sprawdzany w drugiej instrukcji wyjciowej poprzez zmian typĂłw zmiennych na void *. Powoduje to, e strumie wyjciowy wywietla waciw warto wskanika Â&#x2122;. Jeli wskaniki miayby tak sam warto, oznaczaoby to, e zostay poczone krzyowo (ang. cross-linked), jak pokazano na rysunku 4.8 (b). Gdy wskaniki zostan z nieznanych powodĂłw poczone ze sob krzyowo, mog pojawi si dziwne problemy, poniewa zmiana zawartoci jednej zmiennej na stosie powoduje w sposĂłb tajemniczy modyfikacj innej (w rzeczywistoci tej samej) zmiennej. W przypadku zoonej aplikacji takie zjawisko moe by jednak trudne do zaobserwowania. Pamitaj take, e jeli dwa wskaniki s poczone krzyowo, wĂłwczas po zwolnieniu pamici za pomoc instrukcji delete dla jednego z nich drugi staje si wskanikiem zawieszonym. Wynika std, e musimy starannie analizowa nasz kod i zawsze sprawdza go pod ktem wystpienia potencjalnych pocze krzyowych.

Rysunek 4.8. Funkcja concatenate powinna tworzy dwa rĂłne a cuchy (a), a nie dwa wskaniki po czone ze sob krzyowo (b)

Rozwizywanie problemĂłw za pomoc wskanikĂłw i pamici dynamicznej

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

129

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


Po zaimplementowaniu wszystkich trzech funkcji: characterAt, append i concatenate moemy stwierdzi, e problem zosta rozwizany.

Listy powizane A teraz sprĂłbujemy czego trudniejszego. Obsuga wskanikĂłw bdzie bardziej skomplikowana, lecz bdziemy prezentowa wszystko w prosty sposĂłb, poniewa wiemy, jak naley tworzy diagramy. PROBLEM: OBSUGA REJESTRU STUDENTĂ&#x201C;W O NIEZNANEJ LICZBIE ELEMENTĂ&#x201C;W Dla tego problemu napiszesz funkcje przechowujce i modyfikujce kolekcj rejestrĂłw studentĂłw. Rejestr studenta zawiera numer studenta i jego ocen; dane te s liczbami cakowitymi. Powinny zosta zaimplementowane nastpujce funkcje: Â? addRecord â&#x20AC;&#x201D; funkcja uywa wskanika do kolekcji rejestrĂłw studentĂłw,

zawierajcych numery studentĂłw i ich oceny, aby doda do niej nowy rejestr z wypenionymi danymi. Â? averageRecord â&#x20AC;&#x201D; funkcja wymaga podania wskanika do kolekcji

rejestrĂłw studentĂłw i zwraca redni ocen studentĂłw w kolekcji jako liczb typu double. Kolekcja moe mie dowolny rozmiar. Operacja addRecord bdzie wykonywana czsto, wic powinna zosta zaimplementowana w wydajny sposĂłb.

Istnieje szereg sposobĂłw rozwizania powyszego zagadnienia, ktĂłre speniaj przedstawion specyfikacj. My jednak wybierzemy taki, ktĂłry pozwoli nam przewiczy metody rozwizywania problemĂłw w oparciu o wskaniki, czyli listy powizane. By moe syszae ju wczeniej o tej strukturze. Jeli nie, powiniene wiedzie, e wprowadzenie list powizanych do naszych rozwaa spowoduje przejcie na zupenie inny poziom dyskusji. Zdolny programista by moe potrafiby rozwiza wczeniejsze problemy, majc do dyspozycji wystarczajc ilo czasu i umiejtno przeprowadzania starannej analizy. Wikszo programistĂłw nie wymyliaby jednak list powizanych bez dodatkowej pomocy. Gdy tylko je poznasz i opanujesz podstawy ich uycia, bdziesz mĂłg tworzy inne struktury w swoim umyle, a wtedy wszystko szybko zacznie poprawnie dziaa. Lista powizana jest prawdziw struktur dynamiczn. Nasze tablice a cuchĂłw przechowywalimy w dynamicznie przydzielonej pamici, lecz po stworzeniu byy one statycznymi strukturami bez moliwoci ich powikszania lub zmniejszania i mogy tylko by zamieniane na inne. W przeciwie stwie do tego lista powizana jest rosnc w czasie kolekcj elementĂłw â&#x20AC;&#x201D; czym w rodzaju a cucha.

130

Rozdzia 4

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


Tworzenie listy wzĂłw StwĂłrzmy przykadow list powizan rejestrĂłw studentĂłw. Aby to wykona, potrzebujesz struktury, ktĂłra przechowuje dane, jakie powinny si znale w kolekcji, a oprĂłcz tego zawiera wskanik do niej samej. W naszym przypadku w strukturze bdzie si znajdowa numer studenta i jego ocena. struct Â&#x2122; listNode { Â&#x161; int studentNum; int grade; Â&#x203A; listNode * next; }; Â&#x153; typedef listNode * studentCollection;

Nasza struktura nazywa si listNode Â&#x2122;. Struktura uywana do tworzenia listy powizanej jest zawsze nazywana wzem (ang. node). Prawdopodobnie nazwa ta powstaa jako analogia do terminu botanicznego oznaczajcego miejsce na pniu, z ktĂłrego wyrasta nowa ga. Wze skada si z numeru studenta Â&#x161; i oceny, ktĂłre razem tworz jego waciw tre. Zawiera on jednak take wskanik do tego samego typu struktury, ktĂłry definiujemy Â&#x203A;. Taki zapis dla wikszoci programistĂłw widzcych go po raz pierwszy jest dezorientujcy, a nawet wydaje si niemoliwy pod wzgldem skadniowym. W jaki sposĂłb mona stworzy struktur, korzystajc podczas jej definiowania z niej samej? Jest to jednak prawidowa operacja, a jej sens zostanie niebawem wyjaniony. ZwrĂł uwag na to, e wskanik odwoujcy si w wle do samego siebie ma zazwyczaj nazw next, nextPtr1 itd. Wreszcie kod zawiera deklaracj typedef dla typu wskanika do naszego wza Â&#x153;. A teraz stwĂłrzmy nasz przykadow list powizan przy uyciu zadeklarowanych typĂłw: Â&#x2122; studentCollection sc; Â&#x161; listNode * node1 = new listNode; Â&#x203A; node1->studentNum = 1001; node1->grade = 78; listNode * node2 = new listNode; node2->studentNum = 1012; node2->grade = 93; listNode * node3 = new listNode; Â&#x153; node3->studentNum = 1076; node3->grade = 85; Â? sc = node1; Â&#x17E; node1->next = node2; Â&#x; node2->next = node3;   node3->next = NULL; ÂĄ node1 = node2 = node3 = NULL;

1

W jzyku angielskim oznacza ona â&#x20AC;&#x17E;nastpnyâ&#x20AC;? lub â&#x20AC;&#x17E;nastpny wskanikâ&#x20AC;? â&#x20AC;&#x201D; przyp. tum.

Rozwizywanie problemĂłw za pomoc wskanikĂłw i pamici dynamicznej

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

131

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


Rozpoczynamy od zadeklarowania zmiennej sc typu studentCollection Â&#x2122;, ktĂłra ostatecznie bdzie nazw naszej listy powizanej. Nastpnie deklarujemy zmienn node1 Â&#x161;, ktĂłra jest wskanikiem do struktury listNode. Jak ju wspomniano, nazwa studentCollection jest synonimem listNode *, jednake w celu lepszej czytelnoci bd uywa typu studentCollection tylko dla zmiennych, ktĂłre odwouj si do caej struktury listy. Po zadeklarowaniu zmiennej node1 i przypisaniu jej wskanika typu listNode do nowo przydzielonego miejsca na stercie Â&#x161; inicjalizujemy wartoci pĂłl studentNum i grade Â&#x203A;. W tym momencie pole next nie jest jeszcze zainicjalizowane. W tej ksice nie omawiamy skadni jzyka C++, lecz jeli nie widziae wczeniej notacji ->, chciabym wyjani, e jest ona uywana do okrelania pola w strukturze (lub klasie), do ktĂłrej odwoujemy si za pomoc wskanika. Tak wic zapis node1->studentNum oznacza: â&#x20AC;&#x17E;Pole studentNum w strukturze wskazywanej przez node1â&#x20AC;? i jest rĂłwnowany wyraeniu (*node1).studentNum. Nastpnie powtarzamy t sam czynno dla node2 i node3. Po przypisaniu wartoci pĂłl dla ostatniego wza stan pamici przedstawia si zgodnie z rysunkiem 4.9. W poniszych diagramach uyem metody â&#x20AC;&#x17E;podzielonych kontenerĂłwâ&#x20AC;?, ktĂłr wczeniej zastosowaem w przypadku tablic znakowych.

Rysunek 4.9. W po owie drogi do stworzenia przyk adowej listy powizanej Gdy mamy ju wszystkie wzy, moemy je poczy ze sob, by stworzy list powizan. Tego wanie dotyczy pozostaa cz kodu. Po pierwsze, wskazujemy poprzez nasz zmienn studentCollection pierwszy wze Â?, nastpnie przypisujemy polu next z pierwszego wza adres drugiego wza Â&#x17E;, a wreszcie polu next z drugiego wza adres trzeciego Â&#x;. W dalszej kolejnoci przypisujemy warto NULL (jak ju wspomniaem, jest to po prostu synonim zera) dla pola next w ostatnim wle (8.). Wykonujemy t czynno z tego samego powodu, z jakiego w poprzednim problemie umieszczalimy pusty znak na ko cu naszych tablic: aby ograniczy struktur. Tak jak potrzebowalimy specjalnego znaku wskazujcego koniec tablicy, analogicznie musimy uy wartoci rĂłwnej zeru w polu next ostatniego wza naszej listy powizanej, aby upewni si, e jest on rzeczywicie ostatni. Wreszcie, aby uporzdkowa kod i unikn pojawienia si ewentualnych problemĂłw pocze krzyowych, przypisujemy warto NULL do kadego z poszczegĂłlnych wskanikĂłw do wzĂłw ÂĄ. Ko cowy stan pamici zosta przedstawiony na rysunku 4.10.

132

Rozdzia 4

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


Rysunek 4.10. Uko czona przyk adowa lista powizana Majc rysunek przed oczami, moemy atwo zrozumie, dlaczego nasza struktura jest zwana list powizan: kady jej wze jest poczony z nastpnym. Listy powizane s czsto przedstawiane graficznie w postaci liniowej, lecz ja wol widok prezentujcy je w postaci rozproszonej w pamici, poniewa uwypukla on to, e wzy nie maj ze sob adnego zwizku z wyjtkiem pocze â&#x20AC;&#x201D; kady z nich mĂłgby znajdowa si w dowolnym miejscu na stercie. Upewnij si, e przeanalizowae kod tak dokadnie, i zrozumiae, e jest on zgodny z diagramem. ZwrĂł uwag na to, e w docelowej konfiguracji jest uywany tylko jeden wskanik wykorzystujcy pami stosu. Jest nim zmienna sc typu studentCollection, ktĂłra wskazuje pierwszy wze. Wskanik spoza listy (czyli taki, ktĂłry nie jest polem next jednego z jej wzĂłw) wskazujcy jej pierwszy wze jest zwany wskanikiem gowy (ang. head pointer). Na poziomie symbolicznym reprezentuje on ca list, lecz oczywicie w rzeczywistoci wskazuje bezporednio tylko jej pierwszy wze. Aby przej do drugiego wza, musimy po drodze min pierwszy, by doj do trzeciego, musimy przej przez dwa pierwsze itd. Oznacza to, e listy powizane pozwalaj jedynie na dostp sekwencyjny â&#x20AC;&#x201D; w przeciwie stwie do dostpu swobodnego, oferowanego przez tablice. Dostp sekwencyjny jest wad struktur wykorzystujcych listy powizane. Jak wczeniej wspomnielimy, ich zalet jest moliwo powikszania i pomniejszania rozmiaru caej listy poprzez dodawanie i usuwanie wzĂłw bez potrzeby tworzenia nowej struktury i kopiowania do niej danych, tak jak robilimy w przypadku tablic.

Dodawanie wzĂłw do listy Obecnie zajmiemy si implementacj funkcji addRecord. Jej zadaniem jest tworzenie nowego wza i doczanie go do istniejcej listy powizanej. Uyjemy tej samej techniki, ktĂłr wykorzystalimy w poprzednim problemie. Na pocztku stworzymy nagĂłwek pustej funkcji i jej przykadowe wywoanie. Do celĂłw testowych dodamy w taki sposĂłb odpowiedni kod do poprzedniego listingu, by zmienna sc bya ju wskanikiem gowy dla listy skadajcej si z trzech wzĂłw. void addRecord(studentCollection& sc, int stuNum, int gr) { } Â&#x2122; addRecord(sc, 1274, 91);

Rozwizywanie problemĂłw za pomoc wskanikĂłw i pamici dynamicznej

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

133

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


Wywoanie Â&#x2122; powinno si pojawi na ko cu poprzedniego listingu. Majc szkic funkcji wraz z jej parametrami, moemy przedstawi na diagramie stan pamici przed jej wywoaniem, co zaprezentowano na rysunku 4.11.

Rysunek 4.11. Stan pamici przed wywo aniem funkcji addRecord Jeli chodzi o stan po wywoaniu funkcji, mamy jednak moliwo wyboru. Moemy przypuszcza, e utworzymy nowy wze na stercie, a nastpnie skopiujemy wartoci parametrĂłw stuNum i gr do odpowiednich jego pĂłl studentNum i grade. Pytanie brzmi: w ktĂłrym logicznym miejscu listy umiecimy ten wze? Najbardziej oczywistym wyborem byoby umieszczenie go na jej ko cu: pole next zawiera warto NULL, idealnie nadajc si do wskazywania na nowy wze. Takie rozwizanie przedstawiono na rysunku 4.12.

Rysunek 4.12. Propozycja stanu pamici po wywo aniu funkcji addRecord Jeli jednak zaoymy, e kolejno rejestrĂłw nie ma znaczenia (czyli nie musimy przechowywa ich w takiej samej kolejnoci, w jakiej zostay dodane do kolekcji), wĂłwczas nasza decyzja nie bdzie poprawna. Aby to zrozumie, rozwa kolekcj skadajc si nie z trzech, lecz trzech tysicy rejestrĂłw. By uzyska dostp do ostatniego elementu naszej listy powizanej w celu zmodyfikowania pola next, musielibymy przej przez wszystkie 3000 wzĂłw. Taka operacja jest szczegĂłlnie nieefektywna, poniewa moemy umieci nowy wze na licie bez potrzeby â&#x20AC;&#x17E;podrĂłowaniaâ&#x20AC;? przez wszystkie istniejce elementy. Rozwizanie przedstawiono na rysunku 4.13. Po utworzeniu nowego wza zostaje on doczony na pocztku listy, a nie na jej ko cu. Po tej operacji nasz wskanik gowy sc wskazuje nowy wze, a jego pole next wskazuje element, ktĂłry wczeniej by pierwszym wzem na licie, czyli struktur zawierajc numer studenta 1001. Zauwa, e podczas przypisywania wartoci do pola next

134

Rozdzia 4

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


w nowym wle jedynym wskanikiem, ktĂłry ulega modyfikacji, jest sc, a adne inne wartoci dla istniejcych wzĂłw nie zmieniaj si ani nawet nie s odczytywane. Na podstawie diagramu moemy wic stworzy kod: void addRecord(studentCollection& sc, int stuNum, int gr) { Â&#x2122; listNode * newNode = new listNode; Â&#x161; newNode->studentNum = stuNum; newNode->grade = gr; Â&#x203A; newNode->next = sc; Â&#x153; sc = newNode; }

Rysunek 4.13. Akceptowany stan po wykonaniu funkcji addRecord. Strza ka z linii kropkowanej wskazuje poprzedni warto wskanika przechowywanego w zmiennej sc Jak ju wspominaem, zamiana diagramu na kod jest duo prostsza ni analizowanie wszystkiego wycznie w swoim umyle. Kod moe zosta napisany bezporednio na podstawie rysunku. Tworzymy nowy wze Â&#x2122;, a nastpnie przypisujemy polom struktury numer studenta i ocen, pobrane z parametrĂłw Â&#x161;. W dalszej kolejnoci przyczamy nowy wze do listy, wskazujc za pomoc jego pola next wczeniejszy pierwszy wze (przypisujc mu warto sc) Â&#x203A;, a nastpnie wskazujc za pomoc zmiennej sc ten nowy element Â&#x153;. Zauwa, e ostatnie dwa kroki powinny by wykonane wanie w takiej kolejnoci: musimy uy pierwotnej wartoci zmiennej sc, zanim zostanie ona zmieniona. We rĂłwnie pod uwag to, e ze wzgldu na modyfikacj zmiennej sc musi by ona parametrem referencyjnym. Jak zwykle podczas tworzenia kodu na podstawie przykadu powinnimy przetestowa ewentualne przypadki specjalne. W naszej sytuacji oznacza to sprawdzenie, czy funkcja dziaa z pust list. W przypadku tablic znakowych pusty a cuch by poprawnym wskanikiem, poniewa mielimy tablic, na ktĂłr mona wskazywa â&#x20AC;&#x201D; tablic zawierajc tylko ko czcy znak NULL. Tu liczba wzĂłw jest rĂłwna liczbie rejestrĂłw, a pusta lista oznacza wskanik gowy wskazujcy warto NULL. Czy wic nasz kod zadziaa, jeli na licie, ktĂłrej wskanik gowy jest rĂłwny NULL, sprĂłbujemy umieci przykadowe dane? Na rysunku 4.14 prezentujemy stany przed i po wykonaniu tej operacji.

Rozwizywanie problemĂłw za pomoc wskanikĂłw i pamici dynamicznej

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

135

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


Rysunek 4.14. Stany przed i po wywo aniu funkcji addRecord dla pustej listy Analizujc ten przykad na podstawie kodu, moemy stwierdzi, e dziaa on prawidowo. Nowy wze jest tworzony tak jak poprzednio. Poniewa zmienna sc przed operacj jest równa NULL, poprawnym dziaaniem jest jej skopiowanie › do pola next naszego nowego wza, poniewa dziki temu lista jednoelementowa zostaje poprawnie zako czona. Zauwa, e w sytuacji, w której uywalibymy innej metody implementacyjnej, polegajcej na umieszczeniu nowego wza na ko cu listy zamiast na jej pocztku, wstpnie pusta lista mogaby by przypadkiem szczególnym, poniewa tylko wtedy zmienna sc byaby modyfikowana.

Przegldanie listy Nadszed czas na opracowanie funkcji averageRecord. Jak poprzednio, rozpoczniemy od stworzenia nagówka pustej funkcji i diagramu. Odpowiedni kod zosta przedstawiony poniej. Zakadamy, e wywoanie ™ wystpi po stworzeniu naszej pierwotnej listy przykadowej, jak zaprezentowano na rysunku 4.10. double averageRecord(studentCollection sc) { } ™ int avg = averageRecord(sc);

Jak widzisz, postanowiem wyznacza warto redni jako liczb cakowit, podobnie jak czynilimy w przypadku tablic z poprzedniego rozdziau. W zalenoci od problemu moe si jednak okaza, e lepiej bdzie j wylicza jako warto zmiennoprzecinkow. Potrzebujemy jeszcze diagramu, lecz mamy przecie dostpny stan przed operacj, zaprezentowany na rysunku 4.9. Dla przedstawienia stanu po wywoaniu funkcji nie wymagamy diagramu, poniewa nie zmienia ona dynamicznej struktury listy, a jedynie tworzy na jej podstawie raport. Powinnimy po prostu zna oczekiwany wynik, który w naszym przypadku wynosi okoo 85,3333.

136

Rozdzia 4

Kup książkę

Poleć książkę


Jak wic bdziemy wylicza redni? Mamy ju ogĂłlny pomys oparty na naszych dowiadczeniach z wyznaczania redniej ze wszystkich elementĂłw tablicy. Musimy dodawa kad warto z kolekcji, a nastpnie podzieli sum przez liczb elementĂłw. W przypadku kodu stosowanego dla tablic odczytywalimy dane w ptli for, poczynajc od 0 a do wartoci o 1 mniejszej od rozmiaru tablicy, przy czym licznik ptli by zarazem indeksem. W obecnej sytuacji nie moemy uy ptli for, poniewa nie wiemy z gĂłry, ile liczb jest zapisanych w licie powizanej. Zamiast tego powinnimy kontynuowa dziaania a do osignicia wartoci NULL w polu next, ktĂłra oznacza koniec listy. Sugeruje to uycie ptli while, czyli rozwizania, ktĂłre wykorzystalimy ju wczeniej w tym rozdziale do przetwarzania tablic o nieznanej dugoci. Tego typu przetwarzanie jest zwane przegldaniem listy. Jest to jedna z podstawowych operacji, jakie mona wykonywa na listach powizanych. Zaimplementujmy wic nasz ide przegldania listy, by moga pomĂłc nam rozwiza problem: double averageRecord(studentCollection sc) { Â&#x2122; int count = 0; Â&#x161; double sum = 0; Â&#x203A; listNode * loopPtr = sc; Â&#x153; while (loopPtr != NULL) { Â? sum += loopPtr->grade; Â&#x17E; count++; Â&#x; loopPtr = loopPtr->next; }   double average = sum / count; return average; }

Rozpoczynamy od zadeklarowania zmiennej count, ktĂłra bdzie przechowywa liczb wzĂłw napotkanych na licie Â&#x2122;. Bdzie to rĂłwnie liczba wartoci wystpujcych w kolekcji, ktĂłrej uyjemy do wyznaczenia redniej. Nastpnie deklarujemy zmienn sum przeznaczon do przechowywania sumy czciowej wartoci ocen na licie Â&#x161;. OprĂłcz tego tworzymy deklaracj wskanika do struktury listNode, nazwanego loopPtr, ktĂłry wykorzystamy do przegldania listy Â&#x203A;. Jest to odpowiednik naszej zmiennej typu cakowitego, ktĂłrej uywalimy w ptli for podczas przetwarzania tablic. Pamita ona biece pooenie na licie powizanej nie za pomoc licznika, lecz przy wykorzystaniu wskanika do wza, ktĂłry wanie przetwarzamy. W tym miejscu kodu rozpoczyna si waciwe przegldanie listy. Ptla przegldania trwa a do momentu, gdy nasz wskanik uzyska warto NULL Â&#x153;. Wewntrz ptli dodajemy warto pola grade biecego wza do zmiennej sum Â?. Zwikszamy licznik count Â&#x17E;, a nastpnie przypisujemy pole next biecego wza do naszego wskanika sterujcego ptl Â&#x;. Jest to trudniejsza cz kodu, dlatego upewnij si, e dobrze j rozumiesz. Na rysunku 4.15 zaprezentowano, w jaki sposĂłb zmienna wza ulega modyfikacji w trakcie dziaania programu. Symbole od (a) do (d) oznaczaj rĂłne momenty ycia zmiennej loopPtr oraz

Rozwizywanie problemĂłw za pomoc wskanikĂłw i pamici dynamicznej

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

137

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


miejsca, które ona wskazuje. Punkt (a) oznacza po prostu pocztek ptli: zmienna loopPtr zostaa wanie zainicjalizowana wartoci sc, dlatego, podobnie jak ona, wskazuje na pierwszy wze na licie. Podczas pierwszej iteracji ptli do zmiennej sum zostaje dodana warto oceny równa 78, pobrana z pierwszego wza. Warto pola next zostaje skopiowana do loopPtr, wic obecnie zmienna ta wskazuje drugi wze na licie — jest to punkt (b). Podczas drugiej iteracji dodajemy warto 93 do zmiennej sum oraz kopiujemy pole next drugiego wza do zmiennej loopPtr — jest to punkt (c). Wreszcie podczas trzeciej i ostatniej iteracji ptli dodajemy 85 do zmiennej sum i przypisujemy warto NULL pobran z pola next trzeciego wza do zmiennej loopPtr — jest to punkt (d). Gdy ponownie osigamy pocztek ptli while, od razu z niej wychodzimy, poniewa loopPtr jest równa NULL. W kadej iteracji zwikszalimy licznik count, którego warto wynosi 3.

Rysunek 4.15. Zmiany lokalnej zmiennej loopPtr podczas iteracji w ptli dla funkcji averageRecord Po zako czeniu ptli po prostu dzielimy warto sum przez count i zwracamy wynik  . Kod dziaa poprawnie dla naszych testowych danych, lecz jak zwykle powinnimy sprawdzi ewentualne przypadki specjalne. Jak ju wspominaem, najbardziej oczywistym przypadkiem specjalnym dla naszej struktury jest lista pusta. Co si stanie z naszym kodem, gdy funkcja rozpocznie swoje dziaanie, a warto sc bdzie równa NULL? Czy potrafisz zgadn? Kod wykona niepoprawn operacj (musiaem sprawi, by jeden z przypadków specjalnych spowodowa pojawienie si powanego problemu, poniewa w przeciwnym razie nie traktowaby moich sów powanie). Sama ptla przetwarzajca list powizan dziaa poprawnie. Jeli sc wynosi NULL, wówczas zmienna loopPtr zostaje równie zainicjalizowana t wartoci, a ptla ko czy si zaraz po jej rozpoczciu. Warto zmiennej sum jest wówczas równa 0, co wydaje si sensowne. Problem pojawia si w momencie, gdy chcemy wykona dzielenie w celu wyznaczenia redniej  . Warto count jest równie równa 0, co oznacza, e dzielimy przez zero, a w wyniku tego program

138

Rozdzia 4

Kup książkę

Poleć książkę


przestaje dziaa lub otrzymujemy bezsensowny wynik. Aby unikn tego problemu, powinnimy sprawdza pod koniec funkcji, czy zmienna count jest rรณwna 0. Dlaczego jednak nie moglibymy obsugiwa tej sytuacji na pocztku i sprawdza sc? Dodajmy nastpujcy wiersz na samej gรณrze funkcji averageRecord: if (sc == NULL) return 0;

Jak wida na powyszym przykadzie, obsuga przypadkรณw specjalnych jest zazwyczaj cakiem prosta. Musimy mie po prostu czas, by mรณc je zidentyfikowa.

Wnioski i nastpne dziaania W tym rozdziale tylko powierzchownie przeanalizowalimy rozwizywanie problemรณw za pomoc wskanikรณw i pamici dynamicznej. Tego typu zagadnienia pojawi si take w dalszej czci ksiki. Na przykad obiektowo zorientowane techniki programowania, ktรณre omรณwimy w rozdziale 5., s szczegรณlnie przydatne podczas pracy ze wskanikami. Dziki nim mona obudowywa wskaniki w taki sposรณb, by nie martwi si o wycieki pamici, wskaniki zawieszone i inne zwizane z nimi niebezpiecze stwa. Mimo e do zdobycia jest jeszcze duo wiedzy zwizanej z rozwizywaniem problemรณw z tego obszaru, bdziesz mรณg rozwija swoje umiejtnoci przy wykorzystaniu coraz bardziej zoonych struktur opartych na wskanikach, jeli zastosujesz si do gรณwnych zasad omรณwionych w tym rozdziale. Po pierwsze, stosuj ogรณlne reguy dotyczce rozwizywania problemรณw. Nastpnie wykorzystuj okrelone zasady dotyczce wskanikรณw, a take uywaj diagramรณw lub innych narzdzi pozwalajcych na zwizualizowanie kadego z rozwiza przed rozpoczciem kodowania.

wiczenia Nie artuj, wspominajc o wiczeniach. Mam nadziej, e nie czytasz samego rozdziau i zaraz potem nie przechodzisz do nastpnego. 4.1. Samodzielnie zdefiniuj wasne zadanie. We pod uwag problem, ktรณry jest ograniczony rozmiarem tablicy i dla ktรณrego znasz rozwizanie przy jej uyciu. Zmie kod w taki sposรณb, by usun ograniczenie i zastosowa tablic przydzielan dynamicznie. 4.2. Dla dynamicznie przydzielanych a cuchรณw stwรณrz funkcj substring z nastpujcymi trzema parametrami: zmienn typu arrayString, liczb cakowit oznaczajc pocztkow pozycj, liczb cakowit okrelajc dugo a cucha. Funkcja powinna zwrรณci wskanik do nowo przydzielonego bloku pamici zawierajcego tablic znakรณw. Musi ona zawiera znaki pochodzce z oryginalnego

Rozwizywanie problemรณw za pomoc wskanikรณw i pamici dynamicznej

Kup ksiฤ…ลผkฤ™

139

Poleฤ‡ ksiฤ…ลผkฤ™


a cucha, poczynajc od okrelonej pozycji i o podanej dugoci. Pierwotny a cuch nie powinien zosta zmodyfikowany. Jeli wic oryginalnym a cuchem by abcdefg, pozycj pocztkow 3, a dugoci 4, wรณwczas nowy a cuch powinien by rรณwny cdef. 4.3. Dla naszych dynamicznie przydzielanych a cuchรณw stwรณrz funkcj replaceString, ktรณra wykorzystuje trzy parametry, kady o typie arrayString: source, target i replaceText. Funkcja zamienia kade wystpienie a cucha target w a cuchu source a cuchem replaceText. Na przykad jeli source wskazuje tablic zawierajc cig znakรณw abcdabee, target wskazuje ab, a replaceText cig xyz, wรณwczas po zako czeniu funkcji parametr source powinien wskazywa tablic zawierajc a cuch xyzcdxyzee. 4.4. Zmie implementacj naszych a cuchรณw w taki sposรณb, aby element location[0] tablicy przechowywa jej rozmiar (czyli pozycja location[1] powinna zawiera pierwszy faktyczny znak a cucha), w przeciwie stwie do zastosowania ograniczajcego znaku pustego. Zaimplementuj kad z funkcji: append, concatenate i characterAt, wykorzystujc w miar moliwoci sposรณb zapamitania informacji o rozmiarze. Poniewa nie bdziemy ju wykorzystywa znaku pustego oczekiwanego przez standardowy strumie wyjciowy, musisz napisa wasn funkcj output, ktรณra bdzie przetwarza a cuch, wywietlajc jego elementy. 4.5. Napisz funkcj removeRecord, ktรณra uywa wskanika do struktury studentCollection oraz numeru studenta, a nastpnie usuwa z kolekcji rejestr z tym wanie numerem. 4.6. Stwรณrz implementacj a cuchรณw, ktรณra zamiast wykorzystywa dynamicznie alokowane tablice, uywa listy powizanej zawierajcej znaki. Tak wic bdziesz mie list powizan, w ktรณrej waciwymi danymi bd pojedyncze znaki. Pozwoli to na operacje powikszania bez potrzeby ponownego tworzenia caego a cucha od nowa. Rozpocznij od zaimplementowania funkcji append i characterAt. 4.7. Korzystajc z poprzedniego wiczenia, zaimplementuj funkcj concatenate. Pamitaj, e w przypadku wywoania concatenate(s1, s2), w ktรณrym oba parametry s wskanikami do pierwszych wzรณw odpowiednich list powizanych, funkcja powinna kopiowa kady z wzรณw s2 i docza go na koniec s1. Oznacza to, e funkcja nie powinna po prostu przypisa polu next ostatniego wza w licie s1 adresu pierwszego wza listy s2. 4.8. Dodaj funkcj removeChars do implementacji a cuchรณw wykorzystujcej list powizan. Funkcja powinna usuwa cz znakรณw z a cucha, korzystajc z parametrรณw okrelajcych pozycj i dugo. Na przykad wywoanie removeChars(s1, 5, 3) powinno usun trzy znaki, poczynajc od pitej pozycji w a cuchu. Upewnij si, e pami wykorzystywana przez usunite wzy zostanie prawidowo zwolniona. 4.9. Wyobra sobie list powizan, w ktรณrej zamiast znakรณw przechowujemy cyfry, czyli liczby cakowite z zakresu od 0 do 9. Dziki temu moglibymy reprezentowa dodatnie liczby o dowolnym rozmiarze. Na przykad liczba 149 byaby list powizan, w ktรณrej pierwszym wle przechowywalibymy cyfr 1, w drugim 4, a w trzecim i ostatnim 9. Napisz funkcj intToList, ktรณra wykorzystuje jako

140

Rozdzia 4

Kup ksiฤ…ลผkฤ™

Poleฤ‡ ksiฤ…ลผkฤ™


parametr liczb cakowit i tworzy tego typu list powizan. WskazĂłwka: atwiej bdzie stworzy list przechowujc cyfry w odwrotnej kolejnoci, czyli dla wartoci 149 w pierwszym wle byaby przechowywana cyfra 9. 4.10. Dla struktury z poprzedniego wiczenia napisz funkcj, ktĂłra uywa dwĂłch list jako parametrĂłw i w wyniku swojego dziaania tworzy now list, zawierajc sum liczb w nich zapisanych.

Rozwizywanie problemĂłw za pomoc wskanikĂłw i pamici dynamicznej

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

141

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


142

Rozdzia 4

Kup książkę

Poleć książkę


Skorowidz A abstrakcyjne typy danych, 149, 217, 232 algorytm, 216 Luhna, 50, 61 quicksort, 89 wyszukiwania interpolacyjnego, 239 wyszukiwania sekwencyjnego, 92 alokacja pamici, 112, 115 analiza kodu, 271 wyników, 266 API, 218 ASCII, 69

B biblioteka cstring, 218 DirectX, 218 biblioteki standardowe, 218 blok kodu, 215 bd semantyczny, 243

C cechy rekurencji wydajno, 207 wymagania pamiciowe, 207 zoono konceptualna, 207 czas ycia zmiennej, 118 czytelno kodu, 150, 197

Kup książkę

D definiowanie iteratora, 227 deklarowanie klasy, class declaration, 144 konstruktorów, 228 obiektu, 144 tablicy, 79 wektora, 102 wza struktury, 161 dekodowanie wiadomoci, 61 delegowanie pracy, 186 destruktor, 169 diagram, 124, 135, 165 dugo a cucha, 128 dodawanie wza, 162 wza do listy, 133 dokumentacje bibliotek, 272 funkcji, 273 dominanta zbioru, 85 dostp do pamici, 109 sekwencyjny, 78 swobodny, 78, 103, 119 drzewo binarne, 201, 204, 212 dynamiczne przydzielanie pamici, 107, 112, 170 struktury danych, 160, 198 ustalanie rozmiaru struktury, 110 dyspozytor, dispatcher, 192 dziedziczenie, 147 dzielenie przez zero, 245

Poleć książkę


E efektywno czasowa, 103 pamiciowa, 103, 112 enkapsulacja, encapsulation, 146, 148, 152

instrukcja if, 88, 97 return, 168 switch, 91 interfejs, 148 interfejs programowania aplikacji, API, 218 iterator, 226, 230

F fikcyjny rekord, 230 fragmentacja pamici, memory fragmentation, 115 funkcja addRecord, 130, 136 append, 119, 121, 125 appendTester, 122 averageRecord, 130, 138 characterAt, 119 compareFunc, 89 concatenate, 119, 127, 129 length, 126 malloc, 115 qsort, 82, 88, 220, 234, 238 recordWithNumber, 160 removeRecord, 160 strcmp, 218, 221 funkcje iteracyjne, 192, 194 opakowujce, wrapper functions, 196, 204 rekurencyjne, 117, 206 skadowe, 152

G gra wisielec, 254

H hermetyzacja, 148, 154 histogram, 89, 90

I implementacja metod, 154 indeks, 78 informacje o klasach, 144 o komponentach, 215 o rekurencji, 182 o tablicach, 78 o wskanikach, 108

J JDBC, 218 jzyk proceduralny, 13

K klasa, 143, 177 scIterator, 228, 230 stack, 208 studentCollection, 162, 169, 221, 226 wektorowa, 101 wordList, 266 klasy fikcyjne, 177 kontenerowe, 226 pochodne, subclass, 144 klient, 148 kod gry, 257â&#x20AC;&#x201C;266 znaku, 53 kodowanie, 242 komponenty, 215, 219, 232 niskopoziomowe, 239 wyszego poziomu, 239 kompozycja, composition, 161 konstruktor, 144 domylny, default constructor, 145, 155 kopiujcy, copy constructor, 174 z parametrami, 155 kopia projektu, 267 kopiowanie gbokie, deep copy, 172 listy, 172 a cuchĂłw, 127 pytkie, shallow copy, 171 tablicy, 79 korze , 201

Skorowidz

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

277

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


L licznik count, 137, 197 LIFO, 149 listy powizane, 130, 133, 198 listy powizane jednokierunkowe, 198 litera numeryczny, 244

 amigówka lis, g i kukurydza, 17 przesuwane elementy, 22 sudoku, 26 zamek Quarrasi, 30 a cuch tekstowy, 119

M metoda, method, 144 addRecord, 160 get, 153 push_back, 101 removeRecord, 165 set, 153 setFirstStudentPolicy, 221 metody chronione, 159 prywatne, 159 wspierajce, 156 modyfikowanie danych obiektu, 156 interfejsu, 148

N nieuczciwa strategia, 252 nowe umiejtnoci, 268, 271

O obiekt studentRecord, 230 obiekty, 143 obliczanie dugoci a cuchów, 128 kosztu licencji, 93 redniej, 84, 244 obsuga a cuchów tekstowych, 119 pamici dynamicznej, 176 wyjtków, 165

278

odczytywanie pliku tekstowego, 258 operator &, 112 ||, 164 ++, 230 adresu, 108 delete[], 100 modulo, 51, 55 new, 109 przypisania, 173, 243 równoci, 243 optymalizacja kodu, 89 oszukiwanie w grze wisielec, 254

P pakiety, 272 pami, 112 ptla for, 43 while, 56, 168 ptle zagniedone, 96 pisanie kodu gry, 257 plik nagówkowy iostream, 42 pole, data member, 144 polecenia wyjciowe, 42 polecenie delete intPtr, 118 ponowne wykorzystanie kodu, 63, 147, 218 porównywanie elementów tablicy, 220 wyników, 238 problem czytanie liczby, 64 dekodowanie wiadomoci, 60, 72 dyurny, 220, 223 ile papug, 183 kontrola poprawnoci, 49, 54 najlepszy klient, 186 obsuga a cuchów, 119 obsuga rejestru studentów, 130 odliczanie w dó, 44 oszukujca gra w wisielca, 251 poówka kwadratu, 42 porzdek wywietlania listy powizanej, 209 przeksztacenie cyfry w liczb, 52 sortowanie niektórych elementów, 234 szkolny dziennik, 151 trójkty poczone bokami, 46 wartoci dodatnie, 57 wydajne przetwarzanie, 224, 231

Skorowidz

Kup książkę

Poleć książkę


problem wyliczanie sumy liczb, 192 wyszukiwanie w drzewie binarnym, 202 wyznaczanie dominanty, 85 wyznaczanie liczby lici, 204 zarzdzanie nieznan liczb rejestrĂłw, 160 zliczanie liczb w licie, 200 programista mocne strony, 247 sabe strony, 245 programowanie proceduralne, 144 sterowane testami, 246 zorientowane obiektowo, 13, 144, 176 projekt wstpny gry, 256 projektowanie, 242 przecianie operatora, operator overloading, 173 przegldanie listy, 136 przepenienie sterty, heap overflow, 117 przerost funkcjonalnoci, 248 przeszukiwanie tablicy, 80 przydzielanie pamici, 113, 124 przypisanie wielokrotne, 174 punkt przywracania, 267

R redundancja danych, 157 referencja, 112 referencja do elementu, 229 rekord aktywacji, activation record, 113 rekurencja, 117, 181 bezporednia, 182 nieogonowa, head recursion, 185, 191, 198 ogonowa, tail recursion, 184, 189 porednia, 182 rekurencyjne przetwarzanie listy, 199 rozmiar pamici, 116 struktury, 110 tablicy, 100 wektora, 102 rozwizywanie problemĂłw, 9, 16, 250, 267 analogie, 37 dzielenie problemu, 34, 147 eksperymentowanie, 38 plan dziaania, 32 prezentowanie problemu, 33 upraszczanie problemu, 36 za pomoc klas, 143

za pomoc ponownego wykorzystania kodu, 213 za pomoc rekurencji, 181 za pomoc wskanikĂłw, 119

S schemat klasy, 152 skalowalno rozwizania, 91 skadowa chroniona, protected member, 144 publiczna, public member, 144 sabe strony kodowania, 243 projektowania, 245 sowo kluczowe const, 92 delete, 109, 118 get, 153 new, 115 operator, 173 this, 153 sortowanie, 81 przez wstawianie, 83, 216 rejestrĂłw, 237 tablicy obiektĂłw, 235 ze wstawianiem, 235 specyfikator dostpu, access specifier, 144 spowolnienie dziaania aplikacji, 116 sprawdzanie poprawnoci, 158 staa ARRAY_SIZE, 81 stan pamici, 121, 134 standardowe biblioteki, 272 sterta, heap, 113, 212 stos wywoa , runtime stack, 113 stos, stack, 113 stosowanie klas, 146 komponentĂłw, 227, 233 rekurencji, 198, 207 tablic, 99 wskanikĂłw, 109, 111 struktura, 145 agentStruct, 99 binaryTreeNode, 208 listNode, 131 student, 94, 152 treeNode, 201 struktury danych, 109, 110 dynamiczne, 130, 160

Skorowidz

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

279

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


strumie cout, 259 styl programowania, 12 suma kontrolna, 49, 57 szablony klas, 178 szamotanie, thrashing, 116

T tablica, array, 78 sortArray, 238 struktur, 94 typu char, 119 z wartociami nieskalarnymi, 94 ze staymi wartociami, 91 tablice dynamiczne, 100, 101 wielowymiarowe, 96 test dopasowania do wzorca, 261 testowanie, 125, 246 tryb dekodowania, 60 maych liter, 60 wielkich liter, 60 znakĂłw interpunkcyjnych, 60, 70 tworzenie diagramĂłw, 124 funkcji iteracyjnej, 194 iteratora, 227 klasy, 150, 156, 176 kodu produkcyjnego, 269 listy wzĂłw, 131 planu gĂłwnego, 242, 248 stosu, 113 typ cakowity, 52, 60 a cuchowy, 120 wyliczeniowy, 70

U uniwersalno komponentu, 232 usuwanie wza z listy, 165â&#x20AC;&#x201C;168

W walidacja, 158, 159 wartociowanie skrĂłcone, 164 warto NULL, 118, 163 wektor, 77, 101 wze, node, 131

280

waciwo, 153 WPR, 191 wskanik, 107, 119 do char, 120 do liczby cakowitej, 118 do struktury wza, 205 gowy, head pointer, 133 wskaniki poczone krzyowo, 129 zawieszone, 118 wspĂłdzielenie pamici, 110 wybĂłr komponentu, 232, 234 wyciek pamici, 124 wydajno, 224 wyjtek bad_alloc, 117 wykorzystanie kodu niewaciwe, 214 poprawne, 214 wyuskiwanie, 109 wyraenie, 44 wyraenie inicjalizujce, 79 wyszukiwanie komponentu, 225, 226 najwikszej wartoci, 98 okrelonej wartoci, 80 oparte na kryterium, 81 wzorce projektowe, 216 wzorzec iterator, 227

Z zapis node1->studentNum, 132 zdobywanie wiedzy eksploracyjne, 219 w praktyce, 219 w razie potrzeby, 223 zmienne globalne, 196 lokalne, 123, 198 obiektowe, 270 statyczne, 197 znak %, 51 &, 108, 110 [EOL], 68 gwiazdki, 108 ko ca wiersza, 121 tyldy, 170 zwalnianie pamici, 109

Skorowidz

Kup ksiÄ&#x2026;ĹźkÄ&#x2122;

PoleÄ&#x2021; ksiÄ&#x2026;ĹźkÄ&#x2122;


Kup książkę

Poleć książkę



My l jak programista. Techniki kreatywnego rozwi zywania problem w