You are on page 1of 733

"C++ dla kadego"

Cz I (19) Rozdzia 1. Zaczynamy (21) Rozdzia 2. Anatomia programu C++ (37) Rozdzia 3. Zmienne i stae (49) Rozdzia 4. Wyraenia i instrukcje (69) Rozdzia 5. Funkcje (95) Rozdzia 6. Programowanie zorientowane obiektowo (129) Rozdzia 7. Sterowanie przebiegiem dziaania programu (159) Cz II (191) Rozdzia 8. Wskaniki (193) Rozdzia 9. Referencje (223) Rozdzia 10. Funkcje zaawansowane (253) Rozdzia 11. Analiza i projektowanie zorientowane obiektowo (287) Rozdzia 12. Dziedziczenie (321) Rozdzia 13. Tablice i listy poczone (351) Rozdzia 14. Polimorfizm (391) Cz III (435) Rozdzia 15. Specjalne klasy i funkcje (437) Rozdzia 16. Dziedziczenie zaawansowane (463) Rozdzia 17. Strumienie (511) Rozdzia 18. Przestrzenie nazw (549) Rozdzia 19. Wzorce (565) Rozdzia 20. Wyjtki i obsuga bdw (609) Rozdzia 21. Co dalej (635)

Dodatki (683) Dodatek A Dwjkowo i szesnastkowo (685) Dodatek B Sowa kluczowe C++ (695) Dodatek C Kolejno operatorw (697)

Cz 1.

Rozdzia 1. Zaczynamy
Wprowadzenie
Witamy w C++ dla kadego. Ten rozdzia pomoe ci efektywnie programowa w C++. Dowiesz si z niego: dlaczego C++ jest standardowym jzykiem tworzenia oprogramowania Jakie kroki naley wykona przy opracowaniu programu w C++ w jaki sposb wpisa, skompilowa i zbudowa swj pierwszy, dziaajcy program w C++.

Krtka historia jzyka C++


Od czasu pierwszych komputerw elektronicznych, zbudowanych do wspomagania artyleryjskich oblicze trajektorii podczas drugiej wojny wiatowej, jzyki programowania przebyy dug drog. Na pocztku programici uywali najbardziej prymitywnych instrukcjami komputera: jzyka

maszynowego. Te instrukcje byy zapisywane jako dugie cigi zer i jedynek. Dlatego wymylono tzw. asemblery, zamieniajce instrukcje maszynowe na czytelne dla czowieka i atwiejsze do zapamitania mnemoniki, takie jak ADD czy MOV. Z czasem pojawiy si jzyki wyszego poziomu, takie jak BASIC czy COBOL. Te jzyki umoliwiay stosowanie zapisu przypominajcego sowa i zdania, np. LET I = 100. Te instrukcje byy tumaczone przez interpretery i kompilatory na jzyk maszynowy. Interpreter tumaczy odczytywany program, bezporednio zamieniajc jego instrukcje (czyli kod) na dziaania. Kompilator natomiast tumaczy kod na pewn form poredni. Ten proces jest nazywany kompilacj; w jej wyniku otrzymujemy plik obiektowy. Nastpnie kompilator wywouje program czcy (tzw. linker), ktry zamienia plik obiektowy na program wykonywalny. Poniewa interpretery odczytuj kod programu bezporednio i wykonuj go na bieco, s atwiejsze w uyciu dla programistw. Obecnie wikszo programw interpretowanych jest nazywanych skryptami, za sam interpreter nosi nazw Script Engine (w wolnym tumaczeniu: motor skryptu). Niektre jzyki, takie jak Visual Basic, nazywaj interpreter bibliotek czasu dziaania. Java nazywa swj interpreter maszyn wirtualn (VM, Virtual Machine), jednak w pewnych przypadkach taka maszyna wirtualna jest dostarczana przez przegldark WWW (tak jak Internet Explorer lub Netscape). Kompilatory wymagaj wprowadzenia dodatkowego kroku zwizanego z kompilowaniem kodu rdowego (czytelnego dla czowieka) na kod obiektowy (czytelny dla maszyny). Ten dodatkowy krok jest do niewygodny, ale dziki niemu kompilowane programy dziaaj bardzo szybko, gdy czasochonne zadanie przetumaczenia kodu rdowego na jzyk maszynowy jest wykonywane tylko raz (podczas kompilacji) i nie jest ju konieczne podczas dziaania programu. Kolejn zalet wielu jzykw kompilowanych (takich jak C++) jest posiadanie tylko programu wykonywalnego (bez koniecznoci posiadania interpretera). W przypadku jzyka interpretowanego, do uruchomienia programu konieczne jest posiadanie interpretera. Przez wiele lat gwnym celem programistw byo uzyskanie niewielkich fragmentw szybko dziaajcego kodu. Programy musiay by niewielkie, gdy pami bya droga; musiay by take szybkie, gdy droga bya rwnie moc obliczeniowa. Gdy komputery stay si mniejsze, tasze i szybsze, a take gdy spada cena pamici, te priorytety ulegy zmianie. Obecnie czas pracy programisty jest duo droszy ni koszty eksploatacji wikszoci komputerw wykorzystywanych w codziennej pracy. Teraz najwaniejszy jest dobrze napisany, atwy w konserwacji kod. atwo konserwacji oznacza, e gdy zmieni si wymagania wobec dziaania programu, program mona zmieni i rozbudowa, bez ponoszenia wikszych wydatkw.

UWAGA Sowo program jest uywane w dwch kontekstach: w odniesieniu do zestawu poszczeglnych instrukcji (kodu rdowego), tworzonego przez programist oraz w odniesieniu si do caego programu przyjmujacego posta pliku wykonywalnego. Moe to powodowa znaczne nieporozumienia, w zwizku z czym bdziemy stara si dokona rozrnienia pomidzy kodem rdowym a plikiem wykonywalnym.

Rozwizywanie problemw
Problemy, ktre obecnie rozwizuj programici, s zupenie inne ni problemy rozwizywane dwadziecia lat temu. W latach osiemdziesitych programy byy tworzone w celu zarzdzania duymi ilociami nie poddanych obrbce danych danych. Zarwno osoby piszce kod, jak i osoby korzystajce z programw, zajmoway si komputerami profesjonalnie. Obecnie z komputerw korzysta duo osb, wikszo z nich ma niewielkie pojcie o tym, jak dziaa program i komputer. Komputery s narzdziem uywanym przez ludzi do konkretnej pracy, a nie w celu dodatkowego zmagania si z samym komputerem. Mona uwaa za ironi, e wraz z pojawieniem si coraz atwiejszych do opanowania przez og uytkownikw programw, tworzymy programy, ktre same w sobie staj si coraz bardziej wymylne i skomplikowane. Miny ju czasy wpisywania przez uytkownika tajemniczych polece po znaku zachty, ktre powodoway wywietlenie strumienia nie przetworzonych danych. Obecne programy korzystaj z wymylnych przyjaznych interfejsw uytkownika, posiadajcych wiele okien, menu, okien dialogowych oraz innych elementw, ktre wszyscy dobrze znamy. Wraz z rozwojem sieci WWW, komputery wkroczyy w now er penetracji rynku; korzysta z nicj wicej osb ni kiedykolwiek, a ich oczekiwania s bardzo due. Przez kilka lat, jakie upyny od czasu pierwszego wydania tej ksiki, programy stay si bardziej zoone, w zwizku z czym powstao zapotrzebowanie na pomocne w ich opanowaniu techniki programistyczne. Wraz ze zmian wymaga dotyczcych oprogramowania, zmieniy si take same jzyki i technika pisania programw. Cho historia tych przemian jest fascynujca, w tej ksice skupimy si na transformacjach jakie nastpiy w trakcie przejcia od programowania proceduralnego do programowania obiektowego.

Programowanie proceduralne, strukturalne i obiektowe


Do niedawna program by traktowany jako seria procedur, dziaajcych na danych. Procedura (funkcja) jest zestawem specyficznych, wykonywanych jedna po drugiej instrukcji. Dane byy cakowicie odseparowane od procedur, za zadaniem programisty byo zapamitanie, ktra funkcja wywouje inne funkcje, oraz jakie dane byy w wyniku tego zmieniane. W celu uniknicia wielu potencjalnych bdw opracowane zostao programowanie strukturalne. Gwn ide programowania strukturalnego jest: dziel i rzd. Program komputerowy moe by uwaany za zestaw zada. Kade zadanie, ktre jest zbyt skomplikowane aby mona byo je atwo opisa, jest rozbijane na zestaw mniejszych zada skadowych, a do momentu gdy, wszystkie zadania s wystarczajco atwe do zrozumienia. Na przykad, obliczenie przecitnej pensji przecitnego pracownika przedsibiorstwa jest do zoonym zadaniem. Mona je jednak podzieli na nastpujce podzadania: 1. Obliczenie, ile zarabiaj poszczeglne osoby. 2. Policzenie iloci pracownikw.

3. Zsumowanie wszystkich pensji. 4. Podzielenie tej sumy przez ilo pracownikw. Sumowanie pensji mona podzieli na nastpujce kroki: 1. Odczytanie danych dotyczcych kadego pracownika. 2. Odwoanie si do danych dotyczcych pensji. 3. Dodanie pensji do naliczanej sumy. 4. Przejcie do danych dotyczcych nastpnego pracownika. Z kolei, uzyskanie danych na temat pracownika mona rozbi na: 1. Otwarcie pliku pracownikw. 2. Przejcie do waciwych danych. 3. Odczyt danych z dysku. Programowanie strukturalne stanowi niezwykle efektywny sposb rozwizywania zoonych problemw. Jednak pod koniec lat osiemdziesitych ograniczenia takiej metody programowania objawiy si a nazbyt jasno. Po pierwsze, w trakcie tworzenia oprogramowania naturalnym deniem jest traktowanie danych (na przykad danych pracownika) oraz tego, co mona z nimi zrobi (sortowa, modyfikowa, itd.), jako pojedynczej caoci. Niestety, w programowaniu strukturalnym struktury danych s oddzielone od manipulujcych nimi funkcji, a w programie strukturalnym nie istnieje naturalny sposb ich poczenia. Programowanie strukturalne jest czsto nazywane programowaniem proceduralnym, gdy skupia si na procedurach (a nie na obiektach). Po drugie, programici zmuszeni s wci wymyla nowe rozwizania starych problemw. Czasem nazywa si to wymylaniem koa; stanowi to przeciwiestwo ponownego wykorzystania. Idea ponownego wykorzystania oznacza tworzenie komponentw, posiadajcych znane wczeniej waciwoci, ktre mog by w miar potrzeb doczane do programu. Pomys zosta zapoyczony z rozwiza sprztowych gdy inynier potrzebuje nowego tranzystora, zwykle nie musi go wymyla przeglda due pudo z tranzystorami i wybiera ten, ktry spenia dane wymagania, ewentualnie tylko nieco go modyfikujc. Inynier oprogramowania nie mia podobnej moliwoci. Na to zapotrzebowanie prbuje odpowiedzie programowanie zorientowane obiektowo, dostarcza ono technik zarzdzania zoonymi elementami, umoliwia ponowne wykorzystanie komponentw i czy w logiczn cao dane oraz manipulujce nimi funkcje. Zadaniem programowania zorientowanego obiektowo jest modelowanie obiektw (tzn. rzeczy), a nie danych. Modelowanymi obiektami mog by zarwno elementy na ekranie, takie jak

przyciski czy pola list, jak i obiekty wiata rzeczywistego, np. motocykle, samoloty, koty czy woda. Obiekty posiadaj charakterystyki (szybki, obszerny, czarny, mokry) oraz moliwoci (przyspieszanie, latanie, mruczenie, bulgotanie). Zadaniem programowania zorientowanego obiektowo jest reprezentacja tych obiektw w jzyku programowania.

C++ i programowanie zorientowane obiektowo


Jzyk C++ wspiera programowanie zorientowane obiektowo, obejmuje swym dziaaniem trzy podstawy takiego stylu programowania: kapsukowanie, dziedziczenie oraz polimorfizm.

Kapsukowanie
Gdy inynier chce doda do tworzonego urzdzenia rezystor, zwykle nie buduje go samodzielnie od pocztku podchodzi do pojemnika z rezystorami, sprawdza kolorowe paski, oznaczajce waciwoci, i wybiera potrzebny element. Z punktu widzenia inyniera rezystor jest czarn skrzynk niewany jest sposb w jaki dziaa (o ile tylko zachowuje si zgodnie ze swoj specyfikacj). Inynier nie musi zastanawia si nad wntrzem rezystora, aby uy go w swoim projekcie. Waciwo samozawierania si jest nazywana kapsukowaniem. W kapsukowaniu moemy zakada opcj ukrywania danych. Ukrywanie danych jest moliwoci, dziki ktrej obiekt moe by uywany przez osob nie posiadajc wiedzy o tym, w jaki sposb dziaa. Skoro moemy korzysta z lodwki bez znajomoci zasad dziaania kompresora, moemy te uy dobrze zaprojektowanego obiektu nie znajc jego wewntrznych danych skadowych. Sytuacja wyglda podobnie, gdy z rezystora korzysta inynier: nie musi wiedzie niczego o jego wewntrznym stanie. Wszystkie waciwoci rezystora s zakapsukowane w obiekcie rezystora (nie s rozrzucone po caym ukadzie elektronicznym). Do efektywnego korzystania z rezystora nie jest potrzebna wiedza o sposobie jego dziaania. Mona powiedzie, e jego dane s ukryte wewntrz obudowy. C++ wspiera kapsukowanie poprzez tworzenie typw zdefiniowanych przez uytkownika, zwanych klasami. O tym, jak tworzy klasy, dowiesz si z rozdziau szstego, Programowanie zorientowane obiektowo. Po stworzeniu, dobrze zdefiniowana klasa dziaa jako spjna cao jest uywana jako jednostka. Wewntrzne dziaanie klasy powinno by ukryte. Uytkownicy dobrze zdefiniowanych klas nie musz wiedzie, w jaki sposb one dziaaj; musz jedynie wiedzie, jak z nich korzysta.

Dziedziczenie i ponowne wykorzystanie


Gdy inynierowie z Acme Motors chc zbudowa nowy samochd, maj do wyboru dwie moliwoci: mog zacz od pocztku lub zmodyfikowa istniejcy ju model. By moe ich model, Gwiazda, jest prawie doskonay, ale chc do niego doda turbodoadowanie i szeciobiegow skrzyni biegw. Gwny inynier nie chciaby zaczyna od pocztku, zamiast tego wolaby zbudowa nowy, podobny model, z tym dodatkowym wyposaeniem. Nowy model

ma nosi nazw Kwazar. Kwazar jest rodzajem Gwiazdy, wyposaonym w nowe elementy (wedug NASA, kwazary s bardzo jasnymi ciaami, wydzielajcymi ogromne iloci energii). C++ wspiera dziedziczenie. Mona dziki niemu deklarowa nowe typy, bdce rozszerzeniem istniejcych ju typw. Mwi si, e nowa podklasa jest wyprowadzona z istniejcego typu i czasem nazywa si j typem wyprowadzonym (pochodnym). Kwazar jest wyprowadzony z Gwiazdy i jako taki dziedziczy jej moliwoci, ale w razie potrzeby moe je uzupeni lub zmodyfikowa. Dziedziczenie i jego zastosowania w C++ zostan omwione w rozdziale dwunastym, Dziedziczenie oraz szesnastym, Zaawansowane dziedziczenie.

Polimorfizm
Nowy Kwazar moe reagowa na nacinicie pedau gazu inaczej ni Gwiazda. Kwazar moe korzysta z wtrysku paliwa i turbodoadowania, natomiast w Gwiedzie benzyna po prostu wpywa do ganika. Uytkownik jednak nie musi wiedzie o tych rnicach, po prostu naciska na peda gazu i samochd robi to, co do niego naley, bez wzgldu na to, jakim jest pojazdem. C++ sprawia e rne obiekty robi odpowiednie rzeczy poprzez mechanizm zwany polimorfizmem funkcji i polimorfizmem klas. Poli oznacza wiele, za morfizm oznacza w tym przypadku form. Pojcie polimorfizm oznacza, e ta sama nazwa moe przybiera wiele form, zostanie ono szerzej omwione w rozdziale dziesitym, Funkcje zaawansowane oraz czternastym, Polimorfizm.

Jak ewoluowao C++


Gdy powszechnie znane stay si analiza, projektowanie i programowanie zorientowane obiektowo, Bjarne Stroustrup sign do najpopularniejszego jzyka przenaczonego do tworzenia komercyjnego oprogramowania, C, i rozszerzy go, uzupeniajc o elementy umoliwiajce programowanie zorientowane obiektowo. Cho C++ stanowi nadzbir jzyka C, a wszystkie poprawne programy C s take poprawnymi programami C++, rnica pomidzy C a C++ jest bardzo znaczca. C++ przez wiele lat czerpao korzyci ze swego pokrewiestwa z C, gdy programici mogli atwo przej z C do tego nowego jzyka. Aby w peni skorzysta z jego zalet, wielu programistw musia pozby si swoich przyzwyczaje i nauczy nowego sposobu formuowania i rozwizywania problemw programistycznych.

Czy naley najpierw pozna C?


Natychmiast nasuwa si wic pytanie: skoro C++ jest nadzbiorem C, to czy powinienem najpierw nauczy si C? Stroustrup i wikszo innych programistw C++ nie tylko zgadza si, e wczeniejsze poznanie jzyka C nie jest konieczne, ale take e brak jego znajomoci moe stanowi pewn zalet.

Programowanie w C opiera si na programowaniu strukturalnym; natomiast C++ jest oparte na programowaniu zorientowanym obiektowo. Poznawanie jzyka C tylko po to, by oduczy si niepodanych nawykw nabytych podczas pracy z C, jest bdem. Nie zakadamy e masz jakiekolwiek dowiadczenie programistyczne. Jeli jednak jeste programist C, kilka pierwszych rozdziaw tej ksiki bdzie stanowi dla ciebie powtrzenie posiadanych ju wiadomoci. Prawdziw prac nad tworzeniem obiektowo zorientowanego oprogramowania zaczniemy dopiero od rozdziau szstego.

C++ a Java i C#
C++ jest obecnie dominujcym jzykiem oprogramowania komercyjnego. W ostatnich latach pojawia si dla niego silna konkurencja w postaci Javy, jednak wahado wrcio i wielu programistw, ktrzy porzucili C++ dla Javy, zaczyna do niego powraca. Jzyki te s tak podobne, e opanowanie jednego jest rwnoznaczne z opanowaniem dziewidziesiciu procent drugiego. C# jest nowym jzykiem, opracowanym przez Microsoft dla platformy .Net. C# stanowi w zasadzie podzbir C++, i cho oba jzyki rni si w kilku zasadniczych sprawach, poznanie C++ oznacza poznanie okoo dziewidziesiciu procent C#. Upynie jeszcze wiele lat, zanim przekonamy si, czy C# bdzie powanym konkurentem dla C++; jednak nawet, gdy tak si stanie, praca woona w poznanie C++ z pewnoci okae si doskona inwestycj.

Standard ANSI
Midzynarodowy standard jzyka C++ zosta stworzony przez komitet ASC (Accredited Standards Committee), dziaajcy w ramach ANSI (American National Standards Institute). Standard C++ jest nazywany standardem ISO (International Standards Organization), standardem NCITS (National Committee for Information Technology Standards), standardem X3 (starsza nazwa NCITS) oraz standardem ANSI/ISO. W tej ksice bdziemy odwoywali si do standardu ANSI, gdy to okrelenie jest najbardziej popularne. Standard ANSI prbuje zapewni przenono C++ zapewnia na przykad to, e kod zgodny ze standardem ANSI napisany dla kompilatora Microsoftu skompiluje si bez bdw w kompilatorze innego producenta. Poniewa kod w tej ksice jest zgodny ze standardem ANSI, powinien kompilowa si bez bdw na Macintoshu, w Windows lub w komputerze z procesorem Alpha. Dla wikszoci osb uczcych si jzyka C++, standard ANSI bdzie niewidoczny. Ten standard istnieje ju od duszego czasu i obsuguje go wikszo gwnych producentw. Woylimy wiele trudu, by zapewni e cay kod w tej ksice jest zgodny z ANSI.

Przygotowanie do programowania
C++, bardziej ni inne jzyki, wymaga od programisty zaprojektowania programu przed jego napisaniem. Banalne problemy, takie jak te przedstawiane w kilku pierwszych rozdziaach ksiki, nie wymagaj projektowania. Jednak zoone problemy, z ktrymi profesjonalni programici zmagaj si kadego dnia, wymagaj projektowania; za im dokadniejszy i peniejszy projekt, tym wiksze prawdopodobiestwo, e program rozwie problemy w zaplanowanym czasie, nie przekraczajc budetu. Dobry projekt sprawia take, e program jest w duym stopniu pozbawiony bdw i atwy w konserwacji. Obliczono, e poczony koszt debuggowania i konserwacji stanowi co najmniej dziewidziesit procent kosztw tworzenia oprogramowania. Poniewa dobry projekt moe te koszty zredukowa, staje si wanym czynnikiem wpywajcym na ostateczne wydatki zwizane z tworzenia programu. Pierwszym pytaniem, jakie powinnimy zada, przygotowujc si do projektowania programu jest: jaki problem ma zosta rozwizany? Kady program powinien ustanawia jasny, dobrze okrelony celem przekonasz si, e w tej ksice nawet najprostszy program spenia ten postulat. Drugie pytanie, stawiane przez kadego dobrego programist, to: czy mona to osign bez koniecznoci pisania wasnego oprogramowania? Ponowne wykorzystanie starego programu, uycie pira i papieru lub zakup istniejcego oprogramowania, jest czsto lepszym rozwizaniem problemu ni pisanie nowego programu. Programista znajdujcy takie alternatywy nigdy nie bdzie narzeka na brak pracy; znajdowanie najtaszych rozwiza dzisiejszych problemw otwiera nowe moliwoci na przyszo. Zakadajc, e rozumiesz problem, i e wymaga on napisania nowego programu, jeste gotw do rozpoczcia projektowania. Proces penego zrozumienia problemu (analiza) i znajdowania jego rozwizania (projekt) stanowi niezbdn podstaw dla pisania komercyjnych aplikacji na najwyszym, profesjonalnym poziomie.

Twoje rodowisko programowania


Zakadamy, e twj kompilator posiada tryb, w ktrym moe wypisywa tekst bezporednio na ekranie, bez koniecznoci wykorzystania rodowiska graficznego, na przykad Windows czy Macintosh. Poszukaj opcji takiej, jak console, console wizard czy easy window lub przejrzyj dokumentacj kompilatora. Kompilator moe posiada wasny, wbudowany edytor tekstw, lub do tworzenia plikw programw moesz uy komercyjnego edytora lub procesora tekstw. Wane jest, by bez wzgldu na to, jaki program stosujemy, mia on moliwo zapisywania zwykych plikw tekstowych, nie zawierajcych osadzonych w tekcie kodw i polece formatowania. Bezpiecznymi pod tym wzgldem edytorami s na przykad Notatnik w Windows, program Edit w DOS-ie, Brief, Epsilon, Emacs i vi. Wiele komercyjnych procesorw tekstu, takich jak WordPerfect, Word czy tuziny innych, take oferuje moliwo zapisywania zwykych plikw tekstowych.

Pliki tworzone za pomoc edytora tekstw s nazywane plikami rdowymi, w przypadku jzyka C++ zwykle posiadaj nazwy z rozszerzeniem .cpp, .cp lub .c. W tej ksice wszystkie pliki rdowe posiadaj rozszerzenie .cpp, ale sprawd w swoim kompilatorze, jakich plikw oczekuje.
UWAGA Wikszo kompilatorw C++ nie zwraca uwagi na rozszerzenia nadawane nazwom plikw rdowych, ale jeli nie okrelisz tych plikw, wiele z nich domylnie korzysta z rozszerzenia .cpp. Naley jednak zachowa ostrono, gdy niektre kompilatory traktuj pliki .c jako pliki jzyka C, za pliki .cpp jako pliki jzyka C++. Sprawd koniecznie dokumentacj kompilatora.

Tak Do tworzenia plikw rdowych uywaj prostego edytora tekstw lub skorzystaj z edytora wbudowanego w kompilator. Zapisuj pliki, nadajc im rozszerzenie .c, .cp lub .cpp. Sprawd w dokumentacji kompilatora i linkera, w jaki sposb naley kompilowa i budowa programy.

Nie Nie uywaj procesora tekstw zapisujcego wraz z tekstem specjalne znaki formatujce. Jeli korzystasz z takiego procesora, zapisuj pliki jako tekst ASCII.

Tworzenie programu
Cho kod rdowy w pliku wyglda na niezrozumiay i kady, kto nie zna C++, bdzie mia trudnoci ze zrozumieniem jego przeznaczenia, kod ten przyjmuje czyteln dla czowieka posta. Plik kodu rdowego nie jest programem i, w odrnieniu od pliku wykonywalnego, nie moe zosta wykonany (uruchomiony).

Tworzenie pliku obiektowego za pomoc kompilatora


Do zamiany kodu rdowego w program uywamy kompilatora. Sposb uruchomienia go i wskazania mu plikw rdowych zaley od konkretnego kompilatora; sprawd w tym celu posiadan przez ciebie dokumentacj.

Gdy kod rdowy zostanie skompilowany, tworzony jest plik obiektowy. Ten plik ma czsto rozszerzenie .obj 1 jednak w dalszym cigu nie jest to program wykonywalny. Aby zmieni go w program wykonywalny, naley uy tzw. linkera, czyli programu czcego.

Tworzenie pliku wykonywalnego za pomoc linkera


Programy C++ zwykle powstaj w wyniku czenia jednego lub wicej plikw .obj z jedn lub wicej bibliotekami. Biblioteka (ang. library) jest zbiorem poczonych plikw, dostarczanym wraz z kompilatorem. Moe te zosta nabyta osobno lub stworzona i skompilowana samodzielnie. Wszystkie kompilatory C++ s dostarczane wraz z bibliotekami uytecznych funkcji (lub procedur) oraz klas, ktre mona zastosowa w programie. O klasach i funkcjach porozmawiamy szczegowo w nastpnych rozdziaach. Kroki konieczne do stworzenia pliku wykonywalnego to: 1. Stworzenie pliku kodu rdowego z rozszerzeniem .cpp. 2. Skompilowanie kodu rdowego do pliku z rozszerzeniem .obj. 3. Poczenie pliku .obj z wymaganymi bibliotekami w celu stworzenia programu wykonywalnego.

Cykl tworzenia programu


Gdyby kady program zadziaa ju przy pierwszej prbie uruchomienia, wtedy peny cykl tworzenia wygldaby nastpujco: pisanie programu, kompilowanie kodu rdowego, czenie plikw .obj, uruchomienie programu wykonywalnego. Niestety, prawie kady program (nawet najbardziej trywialny) moe zawiera bdy, czsto nazywane pluskwami. Niektre bdy uniemoliwiaj kompilacj, inne uniemoliwiaj czenie, za jeszcze inne objawiaj si dopiero podczas dziaania programu. Bez wzgldu na rodzaj bdu, naley go poprawi oznacza to edycj kodu rdowego, ponown kompilacja i czenie, oraz ponowne uruchomienie programu. Cay ten cykl zosta przedstawiony na rysunku 1.1, schematycznie obrazuje on kolejne kroki w cyklu tworzenia programu wykonywalnego. Rys. 1.1. Kroki wykonywane podczas tworzenia programu w jzyku C++
C

Komentarz [D1]: Do boxu Edycja kodu rdowego powinny prowadzi strzaki zwrotne od rbw: Bdw kompilacji, Bdw czenia oraz bdw uruchomienia. Absolutnie nie mog dosta si do tego rysunku w miom Wordzie, zatem czynno t pozostawiam Redakcji.

1 Plik .obj jest kodem wynikowym programu (ang. object code). Stanowi translacj (przekad) tekstu rdowego na jzyk zrozumiay dla komputera. Kod wynikowy jest zawsze wczytywany przez linker (konsolidator) przyp.tum.

10

11

HELLO.cpp twj pierwszy program w C++


Tradycyjne ksiki o programowaniu zaczynaj od wypisania na ekranie sw Witaj wiecie 2 lub od innej wariacji na ten temat. Ta uwiecona tradycj formua zostanie zachowana take i tu. Wpisz swj pierwszy program bezporednio do edytora, dokadnie przepisujc jego tre. Gdy bdziesz pewien, e zosta wpisany poprawnie, zapisz go do pliku, skompiluj, pocz i uruchom. Program wypisze na ekranie sowa Witaj wiecie. Nie martw si na razie tym, jak dziaa; teraz powiniene jedynie pozna cykl tworzenia programu. Kady element programu zostanie omwiony w kilku nastpnych rozdziaach.
OSTRZEENIE Na przedstawionym poniej listingu po lewej stronie umieszczone zostay numery linii. Te numery su jedynie jako punkty odniesienia dla opisu w tekcie. Nie naley ich wpisywa do kodu programu. Na przykad, w linii 1. listingu 1.1 naley wpisa:
#include <iostream>

Listing 1.1. HELLO.cpp, program Witaj wiecie.


0: 1: 2: 3: 4: 5: 6: #include <iostream> int main() { std::cout << "Witaj Swiecie!\n"; return 0; }

Upewnij si, czy wpisae kod dokadnie tak, jak na listingu. Zwr szczegln uwag na znaki przestankowe. Znaki << w linii 4. s symbolem przekierowania, ktry na wikszoci klawiatur uzyskuje si wciskajc klawisz Shift, po czym dwukrotnie naciskajc klawisz przecinka. Pomidzy sowami std i cout w linii 4. wystpuj dwa dwukropki (:). Linie 4. i 5. kocz si rednikiem (;). Upewnij si take, czy postpujesz zgodnie z zaleceniami kompilatora. Wikszo kompilatorw potrafi poczy (zbudowa) program wykonywalny automatycznie, ale sprawd to w dokumentacji. Jeli pojawi si bdy, dokadnie przejrzyj kod i sprawd, czym rni si od kodu z listingu. Gdy zauwaysz bd w pierwszej linii, na przykad cannot find file iostream (nie mona znale pliku iostream), sprawd w dokumentacji kompilatora, w jaki sposb naley ustawi ciek do doczanych plikw lub zmienne rodowiskowe. Gdy otrzymasz bd informujcy o braku prototypu dla main, tu przed lini 2. dopisz lini int main();. W takim przypadku musisz dopisa t lini przed pocztkiem funkcji main w kadym programie

2 Jak zwykle w takich przypadkach, pojawia si problem polskich znakw diakrytycznych. Wpisanie w kodzie programu sw Witaj wiecie w dosownym brzmieniu, spowodowaoby pojawianie si na ekranie dziwnego znaczka (w miejscu litery ). W zwizku z tym w treci listingw, w tekstach wypisywanych przez program, zrezygnowaem ze stosowania polskich znakw diakrytycznych, zastpujc je odpowiednikami aciskimi. przyp.tum.

12

pojawiajcym si w tej ksice. Wikszo kompilatorw tego nie wymaga, ale istnieje kilka wyjtkw. Peny program bdzie wyglda nastpujco:
1: 2: 3: 4: 5: 6: 7: 8: #include <iostream> int main(); // wikszo kompilatorw nie wymaga tej linii int main() { std::cout << "Witaj Swiecie!\n"; return 0; }

UWAGA Trudno jest czyta program samemu, nie wiedzc jak s wymawiane specjalne znaki i sowa kluczowe. Pierwsz lini odczytujemy jako : hasz-inklad ajoustrim. Linia 6. to es-ti-di-siaut Witaj wiecie.

Sprbuj uruchomi plik HELLO.exe; program powinien wypisa:


Witaj Swiecie!

bezporednio na ekranie. Jeli tak si stao, gratulacje! Wanie wpisae, skompilowae i uruchomie swj pierwszy program w C++. By moe nie wyglda to efektownie, ale kady profesjonalny programista C++ zaczyna dokadnie od tego wanie programu.

Korzystanie z bibliotek standardowych

Jeli masz bardzo stary kompilator, przedstawiony powyej program nie bdzie dziaa nie zostan odnalezione nowe biblioteki standardu ANSI. W takim przypadku zmie kod programu na:

0: 1: 2: 3: 4: 5: 6:

#include <iostream.h> int main() { cout << "Witaj Swiecie!\n"; return 0; }

13

Zwr uwag, e tym razem nazwa biblioteki koczy si na .h (kropka-h) i e nie korzystamy ju z std:: na pocztku linii 4. Jest to stary, poprzedzajcy ANSI styl plikw nagwkowych. Jeli twj kompilator zadziaa z tym programem, lecz nie poradzi sobie z wersj przedstawion wczeniej, oznacza to, e jest prawdziwym antykiem. Nadaje si jedynie do wykorzystania w trakcie czytania kilku pierwszych rozdziaw, ale gdy przejdziemy do wzorcw i wyjtkw, taki kompilator ju nie wystarczy.

Zaczynamy prac z kompilatorem


Ta ksika nie jest zwizana z okrelonym kompilatorem. Oznacza to, e zawarte w niej programy powinny dziaa z kadym zgodnym z ANSI kompilatorem C++, na kadej dostpnej platformie (Windows, Mac, UNIX, Linux, itd.). Wikszo programistw pracuje jednak w Windows, za wikszo profesjonalnych programistw uywa kompilatorw Microsoftu. Nie jestem w stanie opisa szczegw kompilowania i czenia za pomoc kadego istniejcego kompilatora, ale mog jedynie pokaza od czego zacz w kompilatorze Visual C++ 6. Inne kompilatory dziaaj podobne, zatem bdziesz wiedzia, od czego rozpocz. Kompilatory mimo wszystko rni si od siebie, wic pamitaj o przejrzeniu dokumentacji 3 .
Komentarz [D2]: Nie wiem, czy tego rodzaju samocytowanie jest dopuszczalne (ale jest to chyba jedyna obecnie na rynku nowsza ksika opisujca to zjawisko), jednak trzeba pamita, e w odrnieniu od USA, gdzie by moe (osobicie nie jestem przekonany) dominuje Visual C++ Microsoftu to w Europie jednak chyba wikszymi wzgldami wrd programistw ciesz si kompilatory Borlanda. Cytowana ksika zawiera kompletny opis zagadnienia. Wbrew temu, co twierdzi autor proces budowania projektu w tych dwch kompilatorach wcale nie jest tak bardzo podobny.

Budowanie projektu Hello World


Aby stworzy i przetestowa program Hello World, wykonaj nastpujce kroki: 1. 2. 3. 4. 5. 6. 7. 8. 9. Uruchom kompilator. W menu File (plik) wybierz polecenie New (nowy). Wybierz pozycj Win32 Console Application (aplikacja konsoli Win32) i w polu Project name wpisz nazw projektu, tak jak Przyklad 1. Nastpnie kliknij na przycisku OK. W oknie dialogowym wybierz opcj An Empty Project (pusty projekt) i kliknij na przycisku OK. W menu File wybierz polecenie New. Wybierz pozycj C++ Source File (plik rdowy C++) i nadaj jej nazw prz1. Wpisz kod programu, w sposb opisany nieco wczeniej. W menu Build (buduj) wybierz polecenie Build Przyklad1.exe. Sprawd, czy nie pojawiy si bdy kompilacji lub czenia.

10. Nacinij Ctrl+F5, aby uruchomi program.

3 Szczegowy opis tworzenia projektu za pomoc kompilatorw Borlanda mona znale w ksice Andrzeja Daniluka C++Builder 5. wiczenia praktyczne, Helion 2001. przyp.redakcji.

14

11. Nacinij spacj, aby zakoczy program.

Czsto zadawane pytanie

Mog uruchomi program, ale znika on tak szybko, e nie mog odczyta wypisywanego tekstu. Co si dzieje?

Odpowied

Sprawd w dokumentacji kompilatora; powinna ona zawiera informacje na temat sposobu zachowania na ekranie wynikw dziaania programu. W przypadku kompilatorw Microsoftu najprociej jest uy kombinacji Ctrl+F5.

W przypadku starszych kompilatorw Borlanda naley klikn prawym przyciskiem myszy w oknie edycji kodu, klikn na poleceniu Target Export, zmieni opcj Platform na Win 3.1 (16), po czym ponownie przekompilowa i uruchomi program. Okno wynikw pozostanie otwarte do momentu, w ktrym sam je zamkniesz.

Na zkoczenie, w kadym kompilatorze, bezporednio przed instrukcj return (tj. pomidzy liniami 4. i 5. na listingu 1.1), moesz doda przedstawione poniej linie:

int x; std::cin >> x;

Spowoduj one wstrzymanie dziaania programu i oczekiwanie na wprowadzenie jakiej wartoci. Aby zakoczy dziaanie programu, wpisz liczb (na przykad 1), po czym nacinij klawisz Enter.

Znaczenie std::cin i std::cout zostanie omwione w nastpnych rozdziaach. Na razie uznaj je za swego rodzaju magiczne zaklcia.

Prawdopodobnie bardzo wielu czytelnikw posiada kompilator Borlanda (np. C++Builder). Piszc programy dla Windows w rodowisku Buildera, naley zwrci uwag na pewne charakterystyczne dla tego rodowiska cechy.

15

1. Dobrym zwyczajem jest poinformowanie kompilatora o zakoczeniu listy plikw nagwkowych, tj. plikw zapisanych w ostrych nawiasach (absolutnie nie dotyczy to tzw. moduw z rozszerzeniem .h). Dokonujemy tego, korzystajc z dyrektywy prekompilatora #pragma hdrstop (ang. header stop). Zapis ten znacznie przypieszy proces konsolidacji projektu. 2. Jeeli tworzymy aplikacje konsolowe za pomoc Borland C++Buildera w celu przytrzymania ekranu (w tym wypadku normalnego tekstowego okienka DOS), zawsze moemy uy funkcji getch()przynalenej do prototypu conio.h. Naley jednak pamita, e funkcja ta podtrzymywana jest obecnie jedynie w Win32 i nie naley ju do szerokiego standardu ANSI C/C++. 3. Przestrze strumieni wejcia-wyjcia w C++Builder jest dostatecznie dobrze zdefiniowana, dlatego w tym wypadku nie jest konieczne jawne wskazywanie kompilatorowi miejsca ich pochodzenia. Poniszy przykad ilustruje te cechy.
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: #include <iostream.h> #include <conio> #pragma hdrstop int main() { cout << "Witaj Swiecie "<< endl; cout << "Nacisnij klawisz..."; getch(); return 0; }

Naley zwrci uwag, i przy nastpujcym zapisie, wykorzystujcym jawne wskazanie przestrzeni strumieni wejcia-wyjcia, dziaanie programu bdzie rwnie poprawne:
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: #include <iostream> #include <conio> #pragma hdrstop int main() { std::cout << "Witaj Swiecie "<< std::endl; std::cout << "Nacisnij klawisz..."; getch(); return 0; }

Bdy kompilacji
Bdy kompilacji mog pojawi si z wielu powodw. Zwykle s rezultatem pomyki przy wpisywaniu lub innych, mniej istotnych przyczyn. Dobry kompilator nie tylko poinformuje, co

16

jest nie tak, ale take wskae dokadnie miejsce kodu, w ktrym zosta popeniony bd. Najlepsze kompilatory sugeruj nawet, co naley z tym zrobi! Moesz zobaczy, co si stanie gdy celowo umiecimy w programie bd. Jeli program HELLO.cpp dziaa poprawnie, zmodyfikuj go teraz i usu zamykajcy nawias klamrowy z linii 6. Teraz program bdzie wyglda tak, jak na listingu 1.2. Listing 1.2. Demonstracja bdu kompilacji.
0: 1: 2: 3: 4: 5: #include <iostream> int main() { std::cout << "Witaj Swiecie!\n"; return 0;

Ponownie skompiluj program; powiniene zauway bd podobny do tego:


Hello.cpp(7) : fatal error C1004: unexpected end of file found 4

Ten bd informuje o nazwie pliku i numerze linii, w ktrej wystpi problem, oraz o przyczynie pojawienia si problemu (przyznam jednak, e ten komunikat jest nieco tajemniczy). Czasem bdy informuj jedynie o oglnej przyczynie problemu. Gdyby kompilator mg idealnie zidentyfikowa kady z problemw, mgby poprawia kod samodzielnie.

4 W kompilatorach najnowszej generacji firmy Borland komunikat o ww. bdzie jest wywietlony w formie nie wymagajcej gbszego zastanowiania si nad jego znaczeniem:

[C++ Error] Hello.cpp(6): E2134 Compound statement missing } przyp.redakcji.

17

Rozdzia 2. Anatomia programu C++


Programy C++ skadaj si z obiektw, funkcji, zmiennych i innych elementw. Wikszo tej ksiki stanowi obszerny opis tych elementw, jednake w celu zrozumienia zasad ich wspdziaania, musisz najpierw pozna cay dziaajcy program. W tym rozdziale: poznasz elementy programu C++, dowiesz si, jak te elementy ze sob wsppracuj, dowiesz si, czym jest funkcja i do czego suy.
Usunito: skadowych Usunito: jest powicona dogbnemu Usunito: owi Usunito: ale by Usunito: Usunito: jak one do siebie pasuj, Usunito: peny Usunito: P

Prosty program
Nawet prosty program HELLO.cpp z rozdziau pierwszego, Zaczynamy, mia wiele interesujcych elementw. W tym podrozdziale omwimy go bardziej szczegowo. Listing 2.1 przypomina tre programu HELLO.cpp z poprzedniego rozdziau. Listing 2.1. HELLO.cpp demonstruje elementy programu C++
0: 1: 2: 3: 4: 5: 6: #include <iostream> int main() { std::cout << "Witaj Swiecie!\n"; return 0; }
1

Usunito: . Usunito: D Usunito: . Usunito: D Usunito: zawiera Usunito: n Usunito: enie Usunito: ci Usunito: . Usunito: :

Wynik dziaania
Witaj Swiecie!

W rodowisku programowania takim, jak np. Visual, pod napisem Witaj Swiecie! pojawi si dodatkowo napis: Press any key to continue. Nacinicie jakiegokolwiek klawisza zamknie dziaanie programu HELLO.exe i usunie z ekranu jego okno (ramk). przyp.redakcji.

Usunito: , Usunito: jeszcze

Analiza: W linii 0. do biecego pliku jest doczany plik iostream. Oto sposb jego dziaania: pierwszy znak jest symbolem #, ktry stanowi sygna dla preprocesora. Za kadym razem gdy uruchamiasz kompilacj, uruchamiany jest preprocesor. Preprocesor odczytuje kod rdowy, wyszukujc linii zaczynajcych si od znaku # (hasz) i operuje na nich jeszcze przed uruchomieniem waciwego kompilatora. Preprocesor zostanie szczegowo opisany w rozdziale 21., Co dalej. Polecenie #include jest instrukcj preprocesora, mwic mu: Po mnie nastpuje nazwa pliku. Znajd ten plik i wstaw go w to miejsce. Nawiasy ktowe dookoa nazwy pliku informuj preprocesor, by szuka pliku w standardowych miejscach dla tego typu plikw. Jeli twj kompilator jest odpowiednio skonfigurowany, nawiasy ktowe powoduj, e preprocesor szuka pliku iostream w kartotece zawierajcej wszystkie pliki nagwkowe dostarczane wraz z kompilatorem. Plik iostream (Input Output Stream strumie wejcia-wyjcia) jest uywany przez obiekt cout, asystujcy przy wypisywaniu tekstu na ekranie. Efektem dziaania linii 0. jest wstawienie zawartoci pliku iostream do kodu programu, tak jakby zosta on wpisany przez ciebie. Preprocesor dziaa przed kadym rozpoczciem kompilacji, poprzedzajc jej waciw faz. Ponadto zamienia wszystkie linie rozpoczynajce si od znaku hasz (#) na specjalne polecenia, przygotowujc ostateczny kod rdowy dla kompilatora. Linia 2. rozpoczyna rzeczywisty program od funkcji o nazwie main(). Funkcj t posiada kady program C++. Funkcja jest blokiem kodu wykonujcym jedn lub wicej operacji. Zwykle funkcje s wywoywane przez inne funkcje, lecz funkcja main()pod tym wzgldem odbiega od standardu. Gdy program rozpoczyna dziaanie, jest ona wywoywana automatycznie. Funkcja main (), podobnie jak inne funkcje, musi okreli rodzaj zwracanej przez siebie wartoci. Typem zwracanej przez ni w programie HELLO.cpp wartoci jest typ int, to oznacza e po zakoczeniu dziaania funkcja ta zwraca systemowi operacyjnemu warto cakowit (ang. integer). W tym przypadku zwracan wartoci jest 0, tak jak to widzimy w linii 5. Zwrcenie wartoci systemowi operacyjnemu jest stosunkowo mao wan i rzadko wykorzystywan moliwoci, ale standard C++ wymaga, by funkcja main() zostaa zadeklarowana tak jak pokazano.
Usunito: jak to

Usunito: omwiony

Usunito: w Usunito: Preprocesor tumaczy

Usunito: jest Usunito: specjalna Usunito: funkcja main() Usunito: zwracanej przez funkcj main() w programie HELLO.cpp Usunito: c Usunito: ta Usunito: bya Usunito:

UWAGA Niektre kompilatory pozwalaj na deklaracj main(), jeli funkcja main ma zwraca typ void. Nie jest to zgodne ze standardem C++ i nie powiniene si do tego przyzwyczaja. Niech funkcja main() zwraca warto typu int, za w ostatniej linii tej funkcji po prostu zwracaj warto 0.

UWAGA Niektre systemy operacyjne umoliwiaj sprawdzanie (testowanie), jaka warto zostaa zwrcona przez program. Zgodnie z konwencj, zwrcenie wartoci 0 oznacza, e program zakoczy dziaanie normalnie.

Usunito: normalnie

Wszystkie funkcje rozpoczynaj si od nawiasu otwierajcego ({) i kocz nawiasem zamykajcym (}). Nawiasy dla funkcji main() znajduj si w liniach 3. i 6. Wszystko, co znajduje si pomidzy nawiasem otwierajcym a zamykajcym, jest uwaane za tre funkcji. Prawdziwa tre programu znajduje si w linii 4. Obiekt cout jest uywany do wypisywania komunikatw na ekranie. Obiektami zajmiemy si w rozdziale 6., Programowanie zorientowane obiektowo, za obiekt cout i powizany z nim obiekt cin omwimy szczegowo w rozdziale 17., Strumienie. Te dwa obiekty, cin i cout, s w C++ uywane, odpowiednio: do obsugi wejcia (na przykad z klawiatury) oraz wyjcia (na przykad na ekran). Obiekt cout jest dostarczany przez bibliotek standardow. Biblioteka jest kolekcj klas. Standardowa biblioteka jest standardow kolekcj dostarczan wraz z kadym kompilatorem zgodnym z ANSI. Uywajc specyfikatora przestrzeni nazw, std, informujemy kompilator, e obiekt cout jest czci biblioteki standardowej. Poniewa moesz mie kilka, pochodzcych od rnych dostawcw, obiektw o tych samych nazwach, C++ dzieli wiat na przestrzenie nazw. Przestrze nazw jest sposobem na powiedzenie, e: gdy mwi cout, mam na myli to, e cout jest czci standardowej przestrzeni nazw, a nie jakiej innej przestrzeni nazw. Mwimy to kompilatorowi poprzez umieszczenie przed nazw cout znakw sdt i podwjnego dwukropka. Wicej na temat rnych przestrzeni nazw powiemy w nastpnych rozdziaach. Oto sposb uycia obiektu cout: wpisz sowo cout, a po nim operator przekierowania wyjcia (<<). To, co nastpuje po operatorze przekierowania wyjcia, zostanie wypisane na ekranie. Jeli chcesz, by zosta wypisany acuch znakw, pamitaj o ujciu go w cudzysowy (tak jak widzimy w linii 4.) acuch tekstowy jest seri znakw drukowalnych. Dwa ostatnie znaki, \n, informuj obiekt cout, by po sowach Witaj wiecie! umieci now lini. Ten specjalny kod zostanie opisany szczegowo podczas omawiania obiektu cout w rozdziale 18., Przestrzenie nazw. Funkcja main() koczy si w linii 6. nawiasem zamykajcym.
Usunito: zawarto Usunito: oglnie Usunito: , Usunito: bibliotek

Usunito: . Usunito:

Usunito: sobie Usunito: jak Usunito: wany Usunito: jest Usunito: , Usunito: Usunito: znakw Usunito: Usunito: wyjaniony

Rzut oka na klas cout


W rozdziale 17. zobaczysz, w jaki sposb uywa si obiektu cout do wypisywania danych na ekranie. Na razie moesz z niego korzysta, nie wiedzc, jak dziaa. Aby wypisa warto na ekranie, napisz sowo cout, po nim operator wstawiania (<<), uzyskiwany w wyniku dwukrotnego wpisania znaku mniejszoci (<). Cho w rzeczywistoci s to dwa znaki, C++ traktuje je jako pojedynczy symbol. Po znaku wstawiania wpisz przeznaczone do wypisania dane. Listing 2.2 ilustruje sposb uycia tego obiektu. Wpisz w tym przykadzie dokadnie to, co pokazano na listingu, z tym, e zamiast nazwiska Jesse Liberty wpisz swoje wasne (chyba, e rzeczywicie nazywasz si Jesse Liberty). Listing 2.2. Uycie cout
Usunito: waciwie

Usunito: dane Usunito: wyjtkiem tego Usunito: .

0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21:

// Listing 2.2 uycie std::cout #include <iostream> int main() { std::cout << "Hej tam.\n"; std::cout << "To jest 5: " << 5 << "\n"; std::cout << "Manipulator std::endl "; std::cout << "wypisuje nowa linie na ekranie."; std::cout << std::endl; std::cout << "To jest bardzo duza liczba:\t" << 70000; std::cout << std::endl; std::cout << "To jest suma 8 i 5:\t"; std::cout << 8+5 << std::endl; std::cout << "To jest ulamek:\t\t"; std::cout << (float) 5/8 << std::endl; std::cout << "I bardzo, bardzo duza liczba:\t"; std::cout << (double) 7000 * 7000 << std::endl; std::cout << "Nie zapomnij zamienic Jesse Liberty "; std::cout << "na swoje nazwisko...\n"; std::cout << "Jesse Liberty jest programista C++!\n"; return 0; } Usunito: :

Wynik dziaania
Hej tam. To jest 5: 5 Manipulator std::endl wypisuje nowa linie na ekranie. To jest bardzo duza liczba: 70000 To jest suma 8 i 5: 13 To jest ulamek: 0.625 I bardzo, bardzo duza liczba: 4.9e+007 Nie zapomnij zamienic Jesse Liberty na swoje nazwisko... Jesse Liberty jest programista C++!

Usunito: ,

UWAGA Niektre kompilatory zawieraj bd; przed przekazaniem sumy do obiektu cout naley umieci j w nawiasach. Tak wic linia 12. powinna by zmieniona na:
12 std::cout << (8+5) << std::endl;

Usunito: wymagajcy by

Analiza: W linii 1., instrukcja #include <iostream> powoduje wczenie zawartoci pliku iostream do kodu rdowego. Jest ona wymagana, jeli uywasz obiektu cout i powizanych z nim funkcji. W linii 4. znajduje si najprostsze zastosowanie obiektu cout, do wypisania cigu znakw. Symbol \n jest specjalnym znakiem formatujcym. Informuje on cout by wypisa na ekranie znak nowej linii (tzn. aby dalsze wypisywanie rozpocz od nastpnej linii ekranu). W linii 5. przekazujemy do cout trzy wartoci, oddzielone operatorem wstawiania. Pierwsz z tych wartoci jest acuch "To jest 5: ". Zwr uwag na odstp po dwukropku. Ten odstp (spacja) jest czci acucha. Nastpnie do operatora wstawiania jest przekazywana warto 5 oraz znak nowej linii (zawsze w cudzysowach lub apostrofach), co powoduje wypisanie na ekranie linii
Usunito: zawartoci pliku iostream

Usunito: . To

To jest 5: 5

Poniewa po pierwszym acuchu nie wystpuje znak nowej linii, nastpna warto jest wypisywana tu za nim. Nazywa si to konkatenacj (czeniem) dwch wartoci. W linii 6. wypisywany jest komunikat informacyjny, po czym (w linii 8.) uyty zostaje manipulator endl. Przeznaczeniem endl jest wypisanie nowej linii na ekranie. (Inne zastosowania dla endl zostan omwione w rozdziale 16.). Zwr uwag, e endl take pochodzi z biblioteki standardowej.
Usunito: informacyjny Usunito: nastpuje Usunito: cie Usunito: a

UWAGA endl pochodzi od sw end line (zakocz lini) i w rzeczywistoci jest to end-L, a nie end-jeden.

W linii 9. zosta wprowadzony nowy znak formatujcy, \t. Powoduje on wstawienie znaku tabulacji i jest uywany w celu wyrwnywania wydrukw wynikw w liniach od 9. do 15. Linia 9. pokazuje, e wypisywane mog by nie tylko liczby cakowite, ale take dugie liczby cakowite. Linia 12. demonstruje, e cout potrafi wykona proste dodawanie. Do obiektu jest przekazywana warto 8+5, lecz wypisywana jest suma 13. W linii 14. do cout jest wstawiana warto 5/8. Symbol (float) informuje cout, e chcemy aby ta warto zostaa obliczona jako rozwinicie dziesitne, wic wypisywany jest uamek. W linii 16. cout otrzymuje warto 7000 * 7000, za symbol (double) suy do poinformowania cout, e jest to warto zmiennoprzecinkowa. Wszystko to zostanie wyjanione w rozdziale 3., Zmienne i stae, przy okazji omawiania typw danych. W linii 16. podstawie swoje nazwisko, za wynik potwierdza, e naprawd jeste programist C++. Musi by to prawda, skoro tak uwaa komputer!

Usunito: w celu wyrwnywania wydrukw wynikw Usunito: wypisywane Usunito:

Usunito:

Usunito: tak uwaa

Uywanie przestrzeni nazw standardowych


Z pewnoci zauwaye, e przed kadym cout i endl wystpuje std::, co po jakim czasie moe by irytujce. Cho korzystanie z odnonika do przestrzeni nazw jest poprawne, jednak okazuje si dosy mudne przy wpisywaniu. Standard ANSI oferuje dwa rozwizania tego niewielkiego problemu. Pierwszym z nich jest poinformowanie kompilatora (na pocztku listingu kodu) e bdziemy uywa cout i endl z biblioteki standardowej, tak jak pokazano na listingu 2.3. Listing 2.3. Uycie sowa kluczowego using
0: 1: 2: 3: // Listing 2.3 - uycie sowa kluczowego "using" #include <iostream> int main() {

Usunito: form Usunito: Usunito: jest Usunito: j Usunito: j Usunito: niedogodnoci Usunito: , Usunito: Usunito: , Usunito: .

4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24:

using std::cout; using std::endl; cout << "Hej tam.\n"; cout << "To jest 5: " << 5 << "\n"; cout << "Manipulator endl "; cout << "wypisuje nowa linie na ekranie."; cout << endl; cout << "To jest bardzo duza liczba:\t" << 70000; cout << endl; cout << "To jest suma 8 i 5:\t"; cout << 8+5 << endl; cout << "To jest ulamek:\t\t"; cout << (float) 5/8 << endl; cout << "I bardzo, bardzo duza liczba:\t"; cout << (double) 7000 * 7000 << endl; cout << "Nie zapomnij zamienic Jesse Liberty "; cout << "na swoje nazwisko...\n"; cout << "Jesse Liberty jest programista C++!\n"; return 0; } Usunito: :

Wynik dziaania
Hej tam. To jest 5: 5 Manipulator endl wypisuje nowa linie na ekranie. To jest bardzo duza liczba: 70000 To jest suma 8 i 5: 13 To jest ulamek: 0.625 I bardzo, bardzo duza liczba: 4.9e+007 Nie zapomnij zamienic Jesse Liberty na swoje nazwisko... Jesse Liberty jest programista C++!

Analiza Zauwa, e wynik jest identyczny. Jedyn rnic pomidzy listingiem 2.3 a 2.2 jest to, e w liniach 4. i 5. informujemy kompilator, e bdziemy uywa dwch obiektw ze standardowej biblioteki. Uywamy do tego sowa kluczowego using. Gdy to zrobimy, nie musimy ju kwalifikowa obiektw cout i endl. Drugim sposobem uniknicia pisania std:: przed cout i endl jest po prostu poinformowanie kompilatora, e bdziemy uywa caej przestrzeni nazw standardowych, tj, e kady obiekt, ktry nie zostanie oznaczony, z zaoenia bdzie pochodzi z przestrzeni nazw standardowych. W tym przypadku, zamiast pisa using std::cout; napiszemy po prostu using namespace std;, tak jak pokazano na listingu 2.4. Listing 2.4. Uycie sowa kluczowego namespace
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: // Listing 2.3 - uycie przestrzeni nazw standardowych #include <iostream> int main() { using namespace std; cout cout cout cout cout << << << << << "Hej tam.\n"; "To jest 5: " << 5 << "\n"; "Manipulator endl "; "wypisuje nowa linie na ekranie."; endl;

Usunito: : Usunito: i

Usunito: niedogodnoci Usunito: ; Usunito: .

Usunito: .

11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:

cout << "To jest bardzo duza liczba:\t" << 70000; cout << endl; cout << "To jest suma 8 i 5:\t"; cout << 8+5 << endl; cout << "To jest ulamek:\t\t"; cout << (float) 5/8 << endl; cout << "I bardzo, bardzo duza liczba:\t"; cout << (double) 7000 * 7000 << endl; cout << "Nie zapomnij zamienic Jesse Liberty ", cout << "na swoje nazwisko...\n", cou << "Jesse Liberty jest programista C++!\n"; return 0; } Usunito: : Usunito: mi Usunito: mi Usunito: to, e Usunito: ujemy Usunito: e Usunito: i n Usunito: samej Usunito: sp

Analiza Take tym razem wynik jest identyczny z wynikami uzyskiwanymi we wczeniejszych wersjach programu. Zalet zapisu using namespace std; jest to, e nie musimy okrela obiektw, z ktrych chcemy korzysta (na przykad cout oraz endl). Wad jest ryzyko niezamierzonego uycia obiektw z niewaciwej biblioteki. Puryci preferuj zapisywanie std:: przed kadym wystpieniem cout i endl. Osoby bardziej leniwe wol uywa using namespace std; Na tym zakoczmy temat. W tej ksice w wikszoci przypadkw bdziemy pisa, z jakich obiektw korzystamy, ale od czasu do czasu, dla odmiany, wyprbujemy take pozostae style.

Komentarze
Gdy piszesz program, to, co chcesz osign, zawsze jest jasne i oczywiste. Jednak miesic gdy do niego wracasz pniej, kod moe okaza si cakiem niezrozumiay. Nie jestem w stanie przewidzie, co moe by niezrozumiaego w twoim programie, ale zdarza si to zawsze. Aby sobie z tym poradzi, a take, by pomc innym w zrozumieniu twojego kodu, powiniene uywa komentarzy. Komentarze s tekstem cakowicie ignorowanym przez kompilator, mog natomiast informowa czytajcego o tym, co robisz w danym punkcie programu.

Usunito: ych Usunito: stylw Usunito: to, co chcesz osign Usunito: co zabawne, Usunito: gdy do niego wracasz, k Usunito: tak jest Usunito: lecz

Rodzaje komentarzy
Komentarze w C++ wystpuj w dwch odmianach: jako komentarze podwjnego ukonika (//) oraz jako komentarze ukonika i gwiazdki (/*). Komentarz podwjnego ukonika, nazywany komentarzem w stylu C++, informuje kompilator, by zignorowa wszystko, co po nim nastpuje, a do koca linii. Komentarz ukonika i gwiazdki informuje kompilator, by zignorowa wszystko to, co jest zawarte pomidzy znakami /* oraz */. Te znaki s nazywane komentarzami w stylu C. Kademu znakowi /* musi odpowiada zamykajcy komentarz znak */. Jak mona si domyla, komentarze w stylu C s uywane take w programach C; jednake komentarze C++ nie s czci oficjalnej definicji jzyka C.
Usunito: , lecz

Wikszo programistw uywa przewanie komentarzy w stylu C++, rezerwujc komentarze w stylu C do wyczania z kompilacji wikszych blokw kodu. Komentarze w stylu C++ mog wystpowa w blokach kodu skomentowanych komentarzami w stylu C. Ignorowana jest zawarto caego skomentowanego bloku, cznie z komentarzami w stylu C++.

Usunito: w wikszoci przypadkw Usunito: rezerwujc dla Usunito: wy Usunito: wy Usunito: w Usunito: , Usunito: Usunito: co ta Usunito: robi Usunito: . Usunito: gdy Usunito: je zaktualizowa Usunito: mie takie Usunito: by Usunito: ich Usunito: byo Usunito: , Usunito: wymwk Usunito: oczywicie by nigdy Usunito: Zamiast tego Usunito: poprawy jego Usunito: , Usunito: c Usunito: an Usunito: .

Uywanie komentarzy
Niektrzy programici zalecaj stosowanie komentarzy przed kad funkcj (w celu wyjanienia, jakie czynnoci funkcja wykonuje i jakie wartoci zwraca) Osobicie nie zgadzam si z tym, uwaam, e komentarze w nagwkach funkcji zwykle s nieaktualne, bo prawie nikt nie pamita o tym, by zaktualizowa je po modyfikacji kodu. Funkcje powinny przyjmowa takie nazwy, na podstawie ktrych mona jasno okreli, do czego su. Z kolei niejasne i skomplikowane fragmenty kodu powinny zosta przeprojektowane i przepisane tak, aby same si objaniay. Do czsto zdarza si, e komentarze stanowi dla leniwego programisty pretekst dla niedbaoci. Nie sugeruj eby w ogle nie korzysta z komentarzy, cho oczywicie nie powinny suy do wyjaniania niejasnego kodu. W takim przypadku naley poprawi sam kod. Mwic krtko, pisz swoje programy dobrze, za komentarzy uywaj w celu zwikszenia ich zrozumiaoci. Listing 2.5 demonstruje uycie komentarzy i pokazuje, e nie wpywaj one na dziaanie programu i na otrzymywane wyniki. Listing 2.5. HELP.cpp demonstruje komentarze
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: same 14: 15: 16: #include <iostream> int main() { using std::cout; /* to jest komentarz w stylu C i rozciga si on a do zamykajcego znaku gwiazdki i ukonika */ cout << "Witaj Swiecie!\n"; // ten komentarz koczy si wraz z kocem tej linii cout << "Ten komentarz sie zakonczyl!\n"; // komentarze podwjnego ukonika mog wystpowa w linii /* podobnie jak komentarze ukonika i gwiazdki */ return 0; }

Wynik
Witaj Swiecie! Ten komentarz sie zakonczyl!

Usunito: :

Analiza Komentarze w liniach od 6. do 8. s cakowicie ignorowane przez kompilator, podobnie jak komentarze w liniach 10., 13. oraz 14. Komentarz w linii 10. koczy si wraz z kocem linii, lecz komentarze w liniach 6. i 14. wymagaj uycia zamykajcego znaku komentarza.

Usunito: :

Jeszcze jedna uwaga na temat komentarzy


Komentarze, ktre informuj o czym oczywistym, s bezuyteczne. Mog by wrcz szkodliwe, np. gdy kod ulegnie zmianie, a programista zapomni o aktualizacji komentarza. Jednak to, co jest oczywiste dla jednej osoby, moe by niezrozumiae dla innej, zatem musisz samodzielnie oceni uyteczno komentarza. Oglnie rzecz biorc, komentarze powinny informowa nie o tym, co si dzieje, ale o tym, dlaczego tak si dzieje.

Usunito: I j Usunito: W rzeczywistoci, m Usunito: gdy Usunito: moe Usunito: c Usunito: lecz Usunito: moe Usunito: e

Funkcje
Funkcja main() nie jest zwyk funkcj. Normalnie funkcja musi by wywoana w czasie dziaania programu. Funkcja main() jest wywoywana przez system operacyjny. Program jest wykonywany linia po linii w kolejnoci, w jakiej wystpuj w kodzie rdowym, a do napotkania wywoania funkcji. Wtedy dziaanie programu rozgazia si w celu wykonania funkcji. Gdy funkcja zakoczy dziaanie, zwraca sterowanie do linii kodu nastpujcej bezporednio po linii, w ktrej funkcja zostaa wywoana. Dobrym przykdem jest ostrzenie owka. Jeli rysujesz obrazek a w owku zamie si grafit, przestajesz rysowa, idziesz naostrzy owek, po czym wracasz do tego miejsca rysunku, w ktrym przerwae rysowanie. Gdy program wymaga wykonania usugi, moe w tym celu wywoa funkcj, po czym po zakoczeniu jej dziaanie podj dziaanie w tym miejscu, w ktrym je przerwa. Przebieg tego procesu demonstruje listing 2.6. Listing 2.6. Przykad wywoania funkcji
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: #include <iostream> // funkcja DemonstrationFunction // wypisuje informacyjny komunikat void DemonstrationFunction() { std::cout << "Wewnatrz funkcji DemonstrationFunction\n"; } // funkcja main - wypisuje komunikat, nastpnie // wywouje funkcj DemonstrationFunction, po czym wypisuje // drugi komunikat. int main() { std::cout << "Wewnatrz funkcji main\n" ; DemonstrationFunction(); std::cout << "Ponownie w funkcji main\n"; return 0; }

Usunito: wic Usunito: to osdzi Usunito: Mwic o Usunito: Cho Usunito: , jednak jest to funkcja niezwyka Usunito: Aby by uyteczn, Usunito: ich Usunito: owania Usunito: si Usunito: Usunito: analogi jest Usunito: i Usunito: zakoczeniu Usunito: dziaanie Usunito: T ide Usunito: Demonstracja Usunito: .

Wynik
Wewnatrz funkcji main Wewnatrz funkcji DemonstrationFunction Ponownie w funkcji main

Usunito: :

Analiza

Usunito: :

Funkcja DemonstrationFunction() jest zdefiniowana w liniach od 5. do 7. Gdy zostanie wywoana, wypisuje na ekranie komunikat, po czym wraca. Linia 12. stanowi pocztek rzeczywistego programu. W linii 14. funkcja main() wypisuje komunikat informujcy, e program znajduje si wewntrz funkcji main(). Po wypisaniu tego komunikatu, wywouje funkcj DemonstrateFunction() w linii 15. To wywoanie powoduje e wykonane zostaj instrukcje zawarte wewntrz funkcji DemonstrationFunction (). W tym przypadku, caa funkcja skada si z kodu w linii 6., kod ten wypisuje kolejny komunikat. Gdy funkcja DemonstrateFunction() koczy dziaanie (linia 7.), program powraca do linii, z ktrej ta funkcja zostaa wywoana. W tym przypadku program wraca do linii 16., w ktrej funkcja main() wypisuje ostatni lini komunikatu.
UWAGA Zwr uwag, e nie ma sensu stosowa instrukcji using w funkcji DemonstrationFunction, gdy z obiektu cout korzystamy tylko raz, w zwizku z czym w zupenoci wystarczy zastosowanie zapisu std::cout. Mgbym zdecydowa si na skorzystanie z instrukcji using w funkcji main(), ale take tym razem po prostu uyem obiektu wraz z nazw przestrzeni nazw, tak jak pokazano w liniach 14. i 16.

Usunito: w Usunito: wywouje funkcj DemonstrateFunction() Usunito: ktry

Usunito: w funkcji DemonstrationFunction Usunito: , Usunito: W funkcji main() m Usunito: ,

Korzystanie z funkcji
Funkcje zwracaj albo warto, albo typ void, ktry oznacza, e nie zwracaj niczego. Funkcja dodajca dwie liczby cakowite moe zwraca ich sum, funkcja taka bdzie zdefiniowana jako zwracajca warto cakowit. Funkcja, ktra jedynie wypisuje komunikat, nie musi niczego zwraca i moe zosta zadeklarowana jako zwracajca typ void. Funkcja skada si z nagwka oraz ciaa. Nagwek skada si ze zwracanego typu, nazwy funkcji oraz parametrw funkcji. Parametry funkcji umoliwiaj przekazywanie wartoci do funkcji. Zatem, jeli funkcja ma dodawa dwie liczby, bd one parametrami funkcji. Oto typowy nagwek funkcji:
int Sum(int a, int b) Usunito: sama Usunito: techniczne Usunito: braku lub Usunito: jednoczenie Usunito: nie umiecisz instrukcji return Usunito: na koniec Usunito: albo Usunito: zwracaj Usunito: i jako Usunito: ca Usunito: jako taka Usunito: Z kolei n Usunito: Tak wic

Parametr jest deklaracj typu wartoci, jaka zostanie przekazana funkcji; warto przekazywana w wywoaniu funkcji jest nazywana argumentem. Wielu programistw uywa okrele parametr i argument jako synonimw. Inni zwracaj uwag na to rozrnienie. W tej ksice obu terminw bdziemy uywa zamiennie. Ciao funkcji skada si z otwierajcego nawiasu klamrowego, pewnej liczby instrukcji lub ich braku, oraz klamrowego nawiasu zamykajcego. Instrukcje okrelaj dziaanie funkcji. Funkcja moe zwraca warto, uywajc instrukcji return. Ta instrukcja powoduje rwnie wyjcie z funkcji. Jeli nie umiecisz instrukcji return wewntrz funkcji, funkcja automatycznie zwrci warto typu void. Zwracana warto musi mie typ zgodny z typem zadeklarowanym w nagwku funkcji.
UWAGA Funkcje zostan omwione bardziej szczegowo w rozdziale 5., Funkcje. Typy, jakie mog by przez funkcje zwracane, zostan dokadniej omwione w rozdziale 3., Zmienne i

Usunito: przez funkcje

stae. Informacje zamieszczone w tym rozdziale maj na celu jedynie oglne zaprezentowanie funkcji, gdy s one uywane w praktycznie wszystkich programach C++.

Usunito: podane Usunito: pobiene

Listing 2.7 przedstawia funkcj, ktra otrzymuje dwa parametry cakowite i zwraca warto cakowit. Nie martw si na razie o skadni i sposb posugiwania si wartociami cakowitymi (na przykad int x); zostan one dokadnie omwione w rozdziale 3. Listing 2.7. FUNC.cpp demonstruje prost funkcj
0: #include <iostream> 1: int Add (int x, int y) 2: { 3: std::cout << "Funkcja Add() otrzymala " << x << " oraz " << y << "\n"; 4: return (x+y); 5: } 6: 7: int main() 8: { 9: using std::cout; 10: using std::cin; 11: 12: 13: cout << "Jestem w funkcji main()!\n"; 14: int a, b, c; 15: cout << "Wpisz dwie liczby: "; 16: cin >> a; 17: cin >> b; 18: cout << "\nWywoluje funkcje Add()\n"; 19: c=Add(a,b); 20: cout << "\nPonownie w funkcji main().\n"; 21: cout << "c zostalo ustawione na " << c; 22: cout << "\nOpuszczam program...\n\n"; 23: return 0; 24: }

Usunito: lub Usunito: ie Usunito: to Usunito: .

Wynik
Jestem w funkcji main()! Wpisz dwie liczby: 3 5 Wywoluje funkcje Add() Funkcja Add() otrzymala 3 oraz 5 Ponownie w funkcji main(). c zostalo ustawione na 8 Opuszczam program...

Usunito: :

Analiza Funkcja Add() jest zdefiniowana w linii 1. Otrzymuje dwa parametry w postaci liczb cakowitych i zwraca warto cakowit. Sam program zaczyna si w linii 7. Program prosi uytkownika o dwie liczby (linie od 15. do 17.). Uytkownik wpisuje liczby, oddzielajc je spacj, po czym naciska klawisz Enter. Funkcja main() w linii 19. przekazuje funkcji Add()wartoci wpisane przez uytkownika. Przetwarzanie przechodzi do funkcji Add(), ktra rozpoczyna si od linii 1. Parametry a i b s wypisywane, po czym sumowane. Rezultat sumowania jest zwracany w linii 4., po czym nastpuje wyjcie z funkcji.

Usunito: :

Usunito: wciska

Znajdujcy si w liniach 16. i 17. obiekt cin suy do uzyskania liczb dla zmiennych a i b, za obiekt cout jest uywany do wypisania tych wartoci na ekranie. Zmienne i inne aspekty tego programu zostan szerzej omwione w kilku nastpnych rozdziaach.

Usunito: W

Usunito: dogbnie

Usunito: uywanych przez siebie Usunito: Z Usunito: e Usunito: e

Rozdzia 3. Zmienne i stae


Program musi mie moliwo przechowywania danych, z ktrych korzysta. Dziki zmiennym i staym mamy moliwo reprezentowania, przechowywania i manipulowania tymi danymi. Z tego rozdziau dowiesz si jak deklarowa i definiowa zmienne oraz stae, jak przypisywa wartoci zmiennym oraz jak nimi manipulowa, jak wypisywa warto zmiennej na ekranie.

Usunito: oferuj rne sposoby Usunito: W tym rozdziale dowiesz si Usunito: J Usunito: . Usunito: J Usunito: tymi wartociami. Usunito: J Usunito: jest miejscem do Usunito: . Usunito: Zmienna Usunito: jest Usunito: m Usunito: jedynie

Czym jest zmienna?


W C++ zmienna suy do przechowywania informacji jest to miejsce w pamici komputera, w ktrym moesz umieci warto, i z ktrego moesz j pniej odczyta. Zwr uwag, e jest to tymczasowe miejsce przechowywania. Gdy wyczysz komputer, wszystkie zmienne zostaj utracone. Przechowywanie trwae przebiega zupenie inaczej. Zwykle zmienne s przechowywane trwale dziki umieszczeniu ich w bazie danych lub w pliku na dysku. Przechowywanie w pliku na dysku zostanie omwione w rozdziale 16., Zaawansowane dziedziczenie.

Usunito: Trwae p Usunito: jest zupenie innym zagadnieniem Usunito: w wyniku Usunito: a Usunito: jest Usunito: a Usunito: a Usunito: . Kady pojemnik jest jednym z bardzo wielu takich samych pojemnikw Usunito: jest Usunito: Usunito: , Usunito: ktrej Usunito: bez znajomoci Usunito: yczn reprezentacj tej idei Usunito: na nim

Dane s przechowywane w pamici


Pami komputera mona traktowa jako szereg pojemnikw, uoonych jeden za drugim. Kady pojemnik czyli miejsce w pamici jest oznaczony kolejnym numerem. Te numery s nazywane adresami pamici. Zmienna rezerwuje jeden lub wicej pojemnikw, w ktrych moe przechowywa warto. Nazwa zmiennej (na przykad myVariable) stanowi etykietk jednego z tych pojemnikw; dziki niej mona go atwo zlokalizowa nie znajc rzeczywistego adresu pamici. Rysunek 3.1 przedstawia schemat przebiegu tego procesu. Jak wida, zmienna myVariable rozpoczyna si od

adresu 103. W zalenoci od rozmiaru tej zmiennej, moe ona zaj w pamici jeden lub wicej adresw. Rysunek 3.1. Schematyczna reprezentacja pamici

Usunito: w pamici

UWAGA Skrt RAM oznacza Random Access Memory (pami o dostpie swobodnym). Gdy uruchamiasz program, jest on adowany z pliku na dysku do pamici RAM. W pamici RAM tworzone s wszystkie zmienne. Gdy programici uywaj terminu pami, zwykle maj na myli pami RAM, do ktrej si odwouj.

Usunito: to Usunito: z pliku na dysku Usunito: s take Usunito: mwi o Usunito: ci Usunito: tej zmiennej

Przydzielanie pamici
Gdy definiujesz zmienn w C++, musisz poinformowa kompilator o jej rodzaju: czy jest to liczba cakowita, znak, czy co innego. Ta informacja mwi kompilatorowi, ile miejsca ma zarezerwowa dla zmiennej oraz jaki rodzaj wartoci bdzie w niej przechowywany. Kady pojemnik ma rozmiar jednego bajtu. Jeli tworzona zmienna ma rozmiar czterech bajtw, to wymaga czterech bajtw pamici, czyli czterech pojemnikw. Typ zmiennej (na przykad liczba cakowita) mwi kompilatorowi, ile pamici (pojemnikw) ma przygotowa dla zmiennej. Swojego czasu programici musieli zna si na bitach i bajtach, gdy stanowi one podstawowe jednostki przechowywania wszelkiego rodzaju danych. Programy komputerowe pozwalaj na ucieczk od tych szczegw, ale jest w dalszym cigu pomocna wiedza o przechowywaniu danych. Krtki przegld podstaw matematyki dwjkowej moesz znale w dodatku A, Binarnie i szesnastkowo.
UWAGA Jeli przeraa ci matematyka, nie przejmuj si dodatkiem A; tak naprawd nie jest ci potrzebny. Programici nie musz ju by rwnoczenie matematykami, cho umiejtno logicznego i racjonalnego mylenia jest zawsze podana.

Usunito: tej zmiennej Usunito: ile Usunito: koniecznie Usunito: dla Usunito: uzyskanie lepszej abstrakcji Usunito: jest Usunito: tym, jak dane s Usunito: e Usunito: . Usunito: Szybki Usunito: koncepcji stanowicych Usunito: Usunito: sprawia e z krzykiem wybiegasz z pokoju Usunito: wtedy Usunito: rawd jest, e p Usunito: zawsze podana jest

Rozmiar liczb cakowitych


W danym komputerze kady typ zmiennych zajmuje sta ilo miejsca. Oznacza to, e liczba cakowita moe mie w jednym komputerze dwa bajty, w innym cztery, lecz w danym komputerze ma zawsze ten sam, niezmienny rozmiar. Zmienna typu char (uywana do przechowywania znakw) ma najczciej rozmiar jednego bajtu. Krtka liczba cakowita (short) ma w wikszoci komputerw rozmiar dwch bajtw, za duga liczba cakowita (long) ma zwykle cztery bajty. Natomiast liczba cakowita (bez sowa kluczowego short lub long) moe mie dwa lub cztery bajty. Mona przypuszcza, e jzyk powinien to okrela precyzyjnie, ale tak nie jest. Ustalono jedynie, e typ short musi mie rozmiar mniejszy lub rwny typowi int (integer, liczba cakowita), ktry z kolei musi mie rozmiar mniejszy lub rwny typowi long. Najprawdopodobniej jednak pracujesz z komputerem, w ktrym typ short ma dwa bajty, za typy int i long maj po cztery bajty. Rozmiar liczb cakowitych jest wyznaczany przez procesor (16 lub 32 bity) oraz kompilator. W nowoczesnych, 32-bitowych procesorach Pentium z najnowszymi kompilatorami (na przykad Visual C++4 lub nowsze), liczby cakowite maj cztery bajty. W tej ksice zakadamy, e liczby cakowite (typ int) maj cztery bajty, cho w twoim przypadku moe by inaczej. Znak jest pojedyncz liter, cyfr lub symbolem i zajmuje pojedynczy bajt pamici. Skompiluj i uruchom w swoim komputerze listing 3.1; pokae on dokadny rozmiar kadego z tych typw. Listing 3.1. Sprawdzanie rozmiarw typw zmiennych istniejcych w twoim komputerze
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: #include <iostream> int main() { using std::cout; cout << "Rozmiar zmiennej typu int to:\t\t" << sizeof(int) << " bajty.\n"; cout << "Rozmiar zmiennej typu short int to:\t" << sizeof(short) << " bajty.\n"; cout << "Rozmiar zmiennej typu long int to:\t" << sizeof(long) << " bajty.\n"; cout << "Rozmiar zmiennej typu char to:\t\t" << sizeof(char) << " bajty.\n"; cout << "Rozmiar zmiennej typu float to:\t\t" << sizeof(float) << " bajty.\n"; cout << "Rozmiar zmiennej typu double to:\t" << sizeof(double) << " bajty.\n"; cout << "Rozmiar zmiennej typu bool to:\t" << sizeof(bool) << " bajty.\n"; return 0; } Usunito: : Usunito: . Usunito: W kadym komputerze kady typ zmiennych zajmuje Usunito: , niezmienn Usunito: To Usunito: y Usunito: za Usunito: ego Usunito: by sdzi Usunito: precyzyjnie Usunito: J Usunito: co musi zosta zapewnione, to to Usunito: ni Usunito: nowoczesnymi

Wynik
Rozmiar zmiennej typu int to: Rozmiar zmiennej typu short int to: Rozmiar zmiennej typu long int to: 4 bajty. 2 bajty. 4 bajty.

Rozmiar Rozmiar Rozmiar Rozmiar

zmiennej zmiennej zmiennej zmiennej

typu typu typu typu

char to: float to: double to: bool to:

1 4 8 1

bajty. bajty. bajty. bajty.

UWAGA W twoim komputerze rozmiary zmiennych mog by inne.

Wikszo listingu 3.1 powinna by ci znana. Podzieliem linie tak, aby mieciy si na caej stronie ksiki. W rzeczywistoci linie 6. i 7. powinny stanowi lini pojedyncz. Kompilator ignoruje tak zwane biae spacje (spacje, tabulatory, przejcia do nowej linii), wic traktuje linie 6. i 7. jak jedn cao. Nowym elementem w tym programie jest uycie w liniach od 6. do 19. operatora (funkcji) sizeof(). Ten operator jest dostarczany przez kompilator; informuje on o rozmiarze obiektu przekazywanego mu jako parametr. Na przykad w linii 7., do operatora sizeof() jest przekazywane sowo kluczowe int. Za pomoc tego operatora byem w stanie sprawdzi e w moim komputerze zmienne typu int maj ten sam rozmiar, co zmienne typu long, czyli cztery bajty.

Usunito: ju Usunito: znajoma Usunito: w szerokoci Usunito: strony Usunito: , tak w Usunito: ic w Usunito: lini Usunito: linie 6 i 7 Usunito: i Usunito: Uywajc

Zapis ze znakiem i bez znaku


Wszystkie typy cakowite wystpuj w dwch odmianach: signed (ze znakiem) oraz unsigned (bez znaku). Czasem potrzebna jest liczba ujemna, a czasem dodatnia. Liczby cakowite (krtkie i dugie) bez sowa kluczowego unsigned s traktowane jako liczby ze znakiem. Liczby cakowite signed s albo dodatnie albo ujemne, za liczby cakowite unsigned s zawsze dodatnie. Liczby ze znakiem i liczby bez znaku maj po tyle samo bajtw, wic najwiksza liczba, jak mona przechowa w zmiennej cakowitej bez znaku jest dwa razy wiksza ni najwiksza liczba dodatnia jak mona przechowa w zmiennej cakowitej ze znakiem. Zmienna typu unsigned short moe pomieci wartoci od 0 do 65 535. Poowa tych wartoci (reprezentowana przez zmienn typu signed short) jest ujemna, wic zmienna tego typu moe przechowywa jedynie wartoci od 32 768 do 32 767. Jeli wydaje ci si to skomplikowane, zajrzyj do dodatku A.

Podstawowe typy zmiennych


Jzyk C++ posiada jeszcze kilka innych wbudowanych typw zmiennych. Mona je wygodnie podzieli na typy cakowite, typy zmiennopozycyjne oraz typy znakowe. Zmienne zmiennoprzecinkowe zawieraj wartoci, ktre mona wyrazi w postaci uamkw dziesitnych stanowi obszerny podzbir liczb rzeczywistych. Zmienne znakowe maj rozmiar jednego bajtu i s uywane do przechowywania 256 znakw i symboli pochodzcych z zestaww znakw ASCII i rozszerzonego ASCII. Zestaw ASCII jest standardowym zestawem znakw uywanych w komputerach. ASCII stanowi skrt od American Standard Code for Information Interchange. Prawie kady komputerowy system operacyjny obsuguje zestaw ASCII, cho wiele systemw obsuguje take inne, midzynarodowe zestawy znakw.

W tabeli 3.1 przedstawione zostay typy zmiennych uywanych w programach C++. Tabela pokazuje typ zmiennej, jej rozmiar w pamici (zakadany w tej ksice) oraz rodzaj wartoci, jakie mog by przechowywane w zmiennej takiego typu. Zakres przechowywanych wartoci zaley od rozmiaru zmiennej, wic sprawd w swoim komputerze wynik dziaania programu z listingu 3.1. Tabela 3.1. Typy zmiennych Typ
bool unsigned short int short int unsigned long int long int int (16 bitw) int (32 bity) unsigned int (16 bitw) unsigned int (32 bity) char float double

Rozmiar 1 bajt 2 bajty 2 bajty 4 bajty 4 bajty 2 bajty 4 bajty 2 bajty 4 bajty 1 bajt 4 bajty 8 bajtw

Wartoci prawda lub fasz Od 0 do 65 535 Od 32 768 do 32 767 Od 0 do 4 294 967 295 Od 2 147 483 648 do 2 147 483 647 Od 32 768 do 32 767 Od 2 147 483 648 do 2 147 483 647 Od 0 do 65 535 Od 0 do 4 294 967 295 256 rnych znakw Od 1.2e-38 do 3.4e38 (dodatnich lub ujemnych) Od 2.2e-308 do 1.8e308 (dodatnich lub ujemnych)

UWAGA Rozmiary zmiennych mog si rni od pokazanych w tabeli 3.1 (w zalenoci od uywanego kompilatora i komputera). Jeli twj komputer da taki sam wynik, jaki pokazano pod listingiem 3.1, tabela 3.1 powinna by zgodna z twoim kompilatorem. Jeli wynik dziaania listingu 3.1 w twoim komputerze jest inny, powiniene sprawdzi w instrukcji kompilatora jaki zakres wartoci moe by przechowywany w zmiennych rnych typw.

Definiowanie zmiennej
Zmienn tworzy si lub definiuje poprzez okrelenie jej typu, po ktrym wpisuje si jedn lub wicej spacji, za po nich nazw zmiennej i rednik. Nazw zmiennej moe stanowi praktycznie dowolna kombinacja liter, lecz nie moe ona zawiera spacji. Poprawnymi nazwami zmiennych s na przykad: x, J23qrsnf czy myAge. Dobra nazwa zmiennej nie tylko informuje o tym, do czego

jest ona przeznaczona, ale take znacznie uatwia zrozumienie dziaania programu. Przedstawiona poniej instrukcja definiuje zmienn cakowit o nazwie myAge:
int myAge;

UWAGA Gdy deklarujesz zmienn, jest dla niej alokowana (przygotowywana i rezerwowana) pami. Warto zmiennej stanowi to, co w danej chwili znajduje si w tym miejscu pamici. Za chwil zobaczysz, jak mona przypisa nowej zmiennej okrelon warto.

W praktyce naley unika tak przeraajcych nazw, jak J23qrsnf oraz ograniczy uycie nazw jednoliterowych (takich jak x czy i) do zmiennych stosowanych jedynie pomocniczo. Postaraj si uywa nazw opisowych, takich jak myAge (mj wiek) czy howMany (jak duo). S one atwiejsze do zrozumienia trzy tygodnie po ich napisaniu i nie bdziesz ama sobie gowy nad tym, co chciae osign piszc, t lini kodu. Przeprowad taki eksperyment: w oparciu o znajmo kilku pierwszych linii kodu, sprbuj odgadn do, czego on suy: Przykad 1:
int main() { unsigned short x; unsigned short y; unsigned short z; z = x * y; return 0; };

Przykad 2:
int main() { unsigned short Szerokosc; unsigned short Dlugosc; unsigned short Obszar; Obszar = Szerokosc * Dlugosc; return 0; };

UWAGA Jeli skompilujesz ten program, kompilator ostrzee ci, e te wartoci nie zostay zainicjalizowane. Wkrtce dowiesz si, jak sobie poradzi z tym problemem.

Oczywicie, atwiejsze do odgadnicia jest przeznaczenie drugiego programu, a niedogodno polegajca wpisywaniu duszych nazw zmiennych zostaje z nawizk nagrodzona (przez atwo konserwacji drugiego programu).

Uwzgldnianie wielkoci liter


Jzyk C++ uwzgldnia wielko liter. Innymi sowy, odrnia mae i due litery. Zmienna o nazwie age rni si od zmiennej Age, ktra z kolei jest uwaana za rn od zmiennej AGE.

UWAGA Niektre kompilatory umoliwiaj wyczenie rozrniania duych i maych liter. Nie daj si jednak skusi twoje programy nie bd wtedy dziaa z innymi kompilatorami, za inni programici C++ bd mieli wiele problemw z twoim kodem.

Istniej rne konwencje nazywania zmiennych, i cho nie ma znaczenia, ktr z nich przyjmiesz, wane jest, by zachowa j w caym kodzie programu. Niespjne nazewnictwo moe znacznie utrudni zrozumienie twojego kodu przez innych programistw. Wielu programistw decyduje si na uywanie dla swoich zmiennych nazw skadajcych si wycznie z maych liter. Jeli nazwa wymaga uycia dwch sw (na przykad moje auto), mona zastosowa dwie popularne konwencje: moje_auto lub mojeAuto. Druga forma jest nazywana zapisem wielbda, gdy dua litera w jego rodku przypomina nieco garb tego zwierzcia. Niektrzy uwaaj, e znak podkrelenia (moje_auto) jest atwiejszy do odczytania, jednak inni wol go unika, gdy trudniej si go wpisuje. W tej ksice bdziemy stosowa zapis wielbda, w ktrym wszystkie kolejne sowa bd zaczyna si od wielkiej litery: myCar (mojeAuto), theQuickBrownFox (szybkiRudyLis), itd.
UWAGA Wielu zaawansowanych programistw korzysta ze stylu zapisu nazywanego notacj wgiersk. Polega ona na poprzedzaniu kadej nazwy zmiennej zestawem znakw opisujcym jej typ. Zmienne cakowite (integer) mog rozpoczyna si od maej litery i, a zmienne typu long mog zaczyna si od maej litery l. Inne litery oznaczaj stae, zmienne globalne, wskaniki, itd. Taki zapis ma duo wiksze znaczenie w przypadku jzyka C, dlatego nie bdzie stosowany w tej ksice.

Ten zapis jest nazywany notacj wgiersk, poniewa czowiek, ktry go wymyli, Charles Simonyi z Microsoftu, jest Wgrem. Jego monografi mona znale pod adresem http://www.strangecreations.com/library/c/naming.txt.

Microsoft ostatnio zrezygnowa z notacji wgierskiej, za zalecenia projektowe dla jzyka C# wyranie odradzaj jej wykorzystanie. Strategi t stosujemy rwnie w jzyku C++.

Sowa kluczowe
Niektre sowa s zarezerwowane przez C++ i nie mona uywa ich jako nazw zmiennych. S to sowa kluczowe, uywane do sterowania dziaaniem programu. Nale do nich if, while, for czy main. Dokumentacja kompilatora powinna zawiera pen list sw kluczowych, ale sowem kluczowym prawie na pewno nie jest kada sensowna nazwa zmiennej. Lista sw kluczowych jzyka C++ znajduje si w dodatku B. Tak Definiuj zmienn, zapisujc jej typ, a nastpnie jej nazw. Nie Nie uywaj sw kluczowych jzyka C++ jako nazw zmiennych.

Uywaj znaczcych nazw dla zmiennych. Pamitaj, e jzyk C++ uwzgldnia wielko znakw. Zapamitaj ilo bajtw, jak kady typ zmiennej zajmuje w pamici oraz jakie wartoci mona przechowywa w zmiennych danego typu.

Nie uywaj zmiennych bez znaku dla wartoci ujemnych.

Tworzenie kilku zmienych jednoczenie


W jednej instrukcji moesz tworzy kilka zmiennych tego samego typu; w tym celu powiniene zapisa typ, a po nim nazwy zmiennych, oddzielone przecinkami. Na przykad:
unsigned int myAge, myWeight; // dwie zmienne typu unsigned int long int area, width, length; // trzy zmienne typu long

Jak wida, myAge i myWeight s zadeklarowane jako zmienne typu unsigned int. Druga linia deklaruje trzy osobne zmienne typu long; ich nazwy to area (obszar), width (szeroko) oraz length (dugo). W obrbie jednej instrukcji nie mona deklarowa zmiennych o rnych typach.

Przypisywanie zmiennym wartoci


Do przypisywania zmiennej wartoci suy operator przypisania (=). Na przykad zmiennej Width przypisujemy warto 5, zapisujc:
unsigned short Width; Width = 5;

UWAGA Typ long jest skrconym zapisem dla long int, za short jest skrconym zapisem dla short int.

Moesz poczy te kroki i zainicjalizowa zmienn w chwili jej definiowania, zapisujc:


unsigned short Width = 5;

Inicjalizacja jest podobna do przypisania, a w przypadku zmiennych cakowitych rnica midzy nimi jest niewielka. Pniej, gdy poznasz stae, przekonasz si, e pewne wartoci musz by zainicjalizowane, gdy nie mona im niczego przypisywa. Zasadnicz rnic midzy inicjalizacj a przypisaniem jest to, e inicjalizacja odbywa si w chwili tworzenia zmiennej. Poniewa mona definiowa kilka zmienych jednoczenie, podczas tworzenia mona rwnie inicjalizowa wicej ni jedn zmienn. Na przykad:
// Tworzymy dwie zmienne typu long i inicjalizujemy je long width = 5, length = 7;

W tym przykadzie inicjalizujemy zmienn width typu long, nadajc jej warto 5 oraz zmienn length tego samego typu, nadajc jej warto 7. Mona take miesza definicje i inicjalizacje:
int myAge = 39, yourAge, hisAge = 40;

W tym przykadzie stworzylimy trzy zmienne typu int, inicjalizujc pierwsz i trzeci z nich. Listing 3.2 przedstawia peny, gotowy do kompilacji program, ktry oblicza obszar prostokta i wypisuje wynik na ekranie. Listing 3.2. Przykad uycia zmiennych
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: // Demonstracja zmiennych #include <iostream> int main() { using std::cout; using std::endl; unsigned short int Width = 5, Length; Length = 10; // tworzymy zmienn typu unsigned short i inicjalizujemy // j iloczynem szerokoci (Width) i dugoci (Length) unsigned short int Area = (Width * Length); cout << "Szerokosc:" << Width << "\n"; cout << "Dlugosc: " << Length << endl; cout << "Obszar: " << Area << endl; return 0; }

Wynik
Szerokosc:5 Dlugosc: 10 Obszar: 50

Analiza Linia 1. docza wymagany plik nagwkowy dla biblioteki iostream, dziki czemu moemy korzysta z obiektu cout. Linia 3. rozpoczyna program. Linie 5. i 6. definiuj cout i endl jako cz przestrzeni nazw standardowych (std).

W linii 8. zdefiniowana jest zmienna cakowita Width typu unsigned sort, ktra zostaje zainicjalizowana wartoci 5. Definiowana jest take inna zmienna typu unsigned short, zmienna Length, lecz nie jest ona inicjalizowana. W linii 9. zmiennej Length przypisywana jest warto 10. W linii 13. jest definiowana zmienna cakowita Area typu unsigned short, ktra jest inicjalizowana przez warto uzyskan w wyniku mnoenia wartoci zawartej w zmiennej Width przez warto zawart w zmiennej Length. W liniach od 15. do 17. wartoci zmiennych s wypisywane na ekranie. Zwr uwag, e sowo endl powoduje przejcie do nowej linii.

typedef
Cige wpisywanie unsigned short int moe by mudne, a co gorsza, moe spowodowa wystpienie bdu. C++ umoliwia uycie sowa kluczowego typedef (od type definition, definicja typu), dziki ktremu moesz stworzy skrcon form takiego zapisu. Dziki skrconemu zapisowi tworzysz synonim, lecz zwr uwag, e nie jest to nowy typ (bdziesz go tworzy w rozdziale 6., Programowanie zorientowane obiektowo). Przy zapisywaniu synonimu typu uywa si sowa kluczowego typedef, po ktrym wpisuje si istniejcy typ, za po nim now nazw typu. Cao koczy si rednikiem. Na przykad:
typedef unsigned short int USHORT;

tworzy now nazw typu, USHORT, ktrej mona uy wszdzie tam, gdzie mgby uy zapisu unsigned short int. Listing 3.3 jest powtrzeniem listingu 3.2, jednak zamiast typu unsigned short int zostaa w nim uyta definicja USHORT. Listing 3.3. Przykad uycia typedef
0: // ***************** 1: // Demonstruje uycie sowa kluczowego typedef 2: #include <iostream> 3: 4: typedef unsigned short int USHORT; //definiowane poprzez typedef 5: 6: int main() 7: { 8: 9: using std::cout; 10: using std::endl; 11: 12: USHORT Width = 5; 13: USHORT Length; 14: Length = 10; 15: USHORT Area = Width * Length; 16: cout << "Szerokosc:" << Width << "\n"; 17: cout << "Dlugosc: " << Length << endl; 18: cout << "Obszar: " << Area <<endl; 19: return 0; 20: }

Wynik
Szerokosc:5 Dlugosc: 10 Obszar: 50 UWAGA * oznacza mnoenie.

Analiza W linii 4. definiowany jest synonim USHORT dla typu unsigned short int. Poza tym program jest bardzo podobny do programu z listingu 3.2, a wyniki jego dziaania s takie same.

Kiedy uywa typu short, a kiedy typu long?


Jedn z przyczyn kopotw pocztkujcych programistw C++ jest konieczno wyboru pomidzy zadeklarowaniem zmiennej jako wartoci typu long lub jako wartoci typu short. Regua jest bardzo prosta: jeli istnieje moliwo, e jakakolwiek warto, ktra moe zosta umieszczona w zmiennej, przekroczy dozwolony zakres wartoci dla danego typu, naley uy typu o wikszym zakresie. Jak pokazano w tabeli 3.1, zmienne typu unsigned short (zakadajc e zajmuj dwa bajty) mog przechowywa wartoci z zakresu od 0 do 65 535, za zmienne cakowite signed short dziel ten zakres pomidzy liczby dodatnie a ujemne; std maksymalne wartoci stanowi w tym przypadku poow maksymalnej wartoci dla typu unsigned. Cho zmienne cakowite unsigned long mieszcz bardzo due liczby (4 294 967 295), w dalszym cigu s one znacznie ograniczone. Jeli potrzebujesz wikszej liczby, musisz uy typu float lub double, zmniejszajc jednak precyzj ich przechowywania. Zmienne typu float i double mog przechowywa naprawd bardzo due wartoci, ale w wikszoci komputerw ich precyzja ogranicza si do 7 lub 9 pierwszych cyfr. Oznacza to, e po tych kilku cyfrach liczba jest zaokrglana. Krtsze zmienne zajmuj mniej pamici. Obecnie pami jest tania, wic nie wahaj si uywa typu int, ktry w twoim komputerze zajmuje najprawdopodobniej cztery bajty.

Zawinicie liczby cakowitej bez znaku


Zmienne cakowite typu unsigned long mog pomieci due wartoci, ale co si stanie, gdy rzeczywicie zabraknie w nich miejsca? Gdy typ unsigned int osignie swoj maksymaln warto, przewija si i zaczyna od zera, podobnie jak licznik kilometrw w samochodzie. Listing 3.4 pokazuje, co si dzieje, gdy w krtkiej zmiennej cakowitej sprbujesz umieci zbyt du warto. Listing 3.4. Przykad umieszczenia zbyt duej wartoci w zmiennej cakowitej bez znaku
0: 1: 2: 3: #include <iostream> int main() {

4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:

using std::cout; using std::endl; unsigned short int smallNumber; smallNumber = 65535; cout << "krotka liczba:" << smallNumber << endl; smallNumber++; cout << "krotka liczba:" << smallNumber << endl; smallNumber++; cout << "krotka liczba:" << smallNumber << endl; return 0; }

Wynik
krotka liczba:65535 krotka liczba:0 krotka liczba:1

Analiza W linii 7. zmienna smallNumber deklarowana jest jako zmienna typu unsigned short int. W moim komputerze zmienne tego typu maj dwa bajty i mog pomieci wartoci od 0 do 65 535. W linii 8. zmiennej tej jest przypisywana maksymalna warto, ktra jest nastpnie wypisywana w linii 9. W linii 10. zmienna smallNumber jest inkrementowana, czyli zwikszana o 1. Symbolem inkrementacji jest podwjny znak plus (++) (tak jak w nazwie jzyka C++, co symbolizuje inkrementacj jzyka C). Tak wic wartoci zmiennej smallNumber powinno by teraz 65 536. Poniewa jednak zmienne typu unsigned short nie mog przechowywa wartoci wikszych od 65 535, warto ta jest przewijana do 0, ktre jest wypisywane w linii 11. W linii 12. zmienna smallNumber jest inkrementowana ponownie, po czym wypisywana jest jej nowa warto, czyli 1.

Zawinicie liczby cakowitej ze znakiem


Liczby cakowite ze znakiem rni si od liczb cakowitych bez znaku, gdy poowa wartoci, jakie mog reprezentowa, jest ujemna. Zamiast tradycyjnego samochodowego licznika kilometrw, moesz wyobrazi sobie zegar podobny do pokazanego na rysunku 3.2. Liczby na tym zegarze rosn zgodnie z ruchem wskazwek zegara i malej w kierunku przeciwnym. Spotykaj si na dole tarczy (czyli na godzinie szstej). Rys. 3.2. Gdyby zegary stosoway liczby ze znakiem...

W odlegoci jednej liczby od zera istnieje albo 1 (w kierunku zgodnym z ruchem wskazwek) albo 1 (w kierunku przeciwnym). Gdy skocz si liczby dodatnie, przejdziesz do najwikszej liczby ujemnej, a potem z powrotem do zera. Listing 3.5 pokazuje, co si stanie gdy do maksymalnej liczby dodatniej w zmiennej cakowitej typu short int dodasz 1. Listing 3.5. Przykad zwikszenia maksymalnej wartoci dodatniej w licznie cakowitej ze znakiem.
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: #include <iostream> int main() { short int smallNumber; smallNumber = 32767; std::cout << "krotka liczba:" << smallNumber << std::endl; smallNumber++; std::cout << "krotka liczba:" << smallNumber << std::endl; smallNumber++; std::cout << "krotka liczba:" << smallNumber << std::endl; return 0; }

Wynik
krotka liczba:32767 krotka liczba:-32768 krotka liczba:-32767

Analiza W linii 3. zmienna smallNumber deklarowana jest jako zmienna typu signed short int (jeli nie wskaemy jawnie, e zmienna jest unsigned, bez znaku, zakada si e jest signed, ze znakiem). Program dziaa bardzo podobnie do poprzedniego, jednak osigany przez niego wynik jest cakiem inny. Aby w peni zrozumie jego dziaanie, musisz wiedzie w jaki sposb liczby cakowite ze znakiem s reprezentowane bitowo w dwubajtowych zmiennych cakowitych. Podobnie jak w przypadku liczb cakowitych bez znaku, liczby cakowite ze znakiem po najwyszej wartoci dodatniej przewijaj si do najwyszej wartoci ujemnej.

Znaki
Zmienne znakowe (typu char) zwykle maj rozmiar jednego bajtu, co wystarczy do przechowania jednej z 256 wartoci (patrz dodatek C). Typ char moe by interpretowany jako maa liczba (od 0 do 255) lub jako element zestawu kodw ASCII. Skrt ASCII pochodzi od sw American Standard Code for Information Interchange. Zestaw znakw ASCII oraz jego odpowiednik ISO (International Standards Organization) su do kodowania wszystkich liter (alfabetu aciskiego), cyfr oraz znakw przestankowych.
UWAGA Komputery nie maj pojcia o literach, znakach przestankowych i zdaniach. Rozpoznaj tylko liczby. Zauwaaj tylko odpowiedni poziom napicia na okrelonym zczu przewodw. Jeli wystpuje napicie, jest ono symbolicznie oznaczane jako jedynka, za gdy nie wystpuje, jest oznaczane jako zero. Poprzez grupowanie zer i jedynek, komputer jest w stanie generowa wzory, ktre mog by interpretowane jako liczby, ktre z kolei mona przypisywa literom i znakom przestankowym.

W kodzie ASCII maa litera a ma przypisan warto 97. Wszystkie due i mae litery, wszystkie cyfry oraz wszystkie znaki przestankowe maj przypisane wartoci pomidzy 0 a 127. Dodatkowe 128 znakw i symboli jest zarezerwowanych dla wykorzystania przez producenta komputera, cho standard kodowania stosowany przez firm IBM sta si niejako obowizkowy.
UWAGA ASCII wymawia si jako eski.

Znaki i liczby
Gdy w zmiennej typu char umieszczasz znak, na przykad a, w rzeczywistoci jest on liczb pochodzc z zakresu od 0 do 255. Kompilator wie jednak, w jaki sposb odwzorowa znaki (umieszczone wewntrz apostrofw) na jedn z wartoci kodu ASCII. Odwzorowanie litery na liczb jest umowne; nie ma szczeglnego powodu, dla ktrego maa litera a ma warto 97. Dopki zgadzaj si na to klawiatura, kompilator i ekran, nie ma adnych problemw. Naley jednak zdawa sobie spraw z duej rnicy pomidzy wartoci 5 a znakiem 5. Ten ostatni ma w rzeczywistoci warto 53, podobnie jak litera a, ktra ma warto 97. Ilustruje to listing 3.6.

Listing 3.6. Wypisywanie znakw na podstawie ich kodw


0: 1: 2: 3: 4: 5: 6: #include <iostream> int main() { for (int i = 32; i<128; i++) std::cout << (char) i; return 0; }

Wynik
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEF GHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijkl mnopqrstuvwxyz{|}~_

Ten prosty program wypisuje znaki o wartociach od 32 do 127.

Znaki specjalne
Kompilator C++ rozpoznaje pewne specjalne znaki formatujce. Najpopularniejsze z nich przedstawia tabela 3.2. Kody te umieszcza si w kodzie programu, wpisujc znak odwrotnego ukonika, a po nim znak specjalny. Aby umieci w kodzie znak tabulacji, naley wpisa apostrof, odwrotny ukonik, liter t oraz zamykajcy apostrof:
char tabCharacter = '\t';

Ten przykad deklaruje zmienn typu char (o nazwie tabCharacter) oraz inicjalizuje j wartoci \t, ktra jest rozpoznawana jako tabulator. Specjalne znaki formatujce uywane s do wypisywania tekstu na ekranie, do zapisu tekstu do pliku lub do innego urzdzenia wyjciowego. Znak specjalny \ zmienia znaczenie znaku, ktry po nim nastpuje. Na przykad, normalnie znak n oznacza po prostu liter n, lecz gdy jest poprzedzony znakiem specjalnym \, oznacza przejcie do nowej linii. Tabela 3.2. Znaki specjalne Znak
\a \b \f \n \r \t \v \' \"

Oznacza Bell (dzwonek) Backspace (znak wstecz) Form feed (koniec strony) New line (nowa linia) Carriage return (powrt karetki; powrt na pocztek linii) Tab (tabulator) Vertical tab (tabulator pionowy) Single quote (apostrof) Double quote (cudzysw)

\? \\ \0oo \xhhh

Question mark (znak zapytania) Backslash (lewy ukonik) Zapis semkowy Zapis szesnastkowy

Stae
Do przechowywania danych su take stae. Jednak w odrnieniu od zmiennej, jak sama nazwa sugeruje, warto staej nie ulega zmianie. Podczas tworzenia staej trzeba j zainicjalizowa; pniej nie mona ju przypisywa jej innej wartoci.

Literay
C++ posiada dwa rodzaje staych: literay i stae symboliczne. Litera jest wartoci wpisywan bezporednio w danym miejscu programu. Na przykad:
int myAge = 39;

myAge jest zmienn typu int; z kolei 39 jest literaem. Literaowi 39 nie mona przypisa wartoci, za jego warto nie moe ulec zmianie.

Stae symboliczne
Staa symboliczna jest reprezentowana poprzez swoj nazw (podobnie jak w przypadku zmiennych). Jednak w odrnieniu od zmiennej, po zainicjalizowaniu staej, nie mona pniej zmienia jej wartoci. Jeli w programie wystpuje zmienna cakowita o nazwie students (studenci) oraz inna zmienna o nazwie classes (klasy), moemy obliczy ilo studentw znajc ilo klas (przy zakadajc e w kadej klasie jest pitnastu studentw):
students = classess * 15;

W tym przykadzie, 15 jest literaem. Kod bdzie jednak atwiejszy w czytaniu i w konserwacji, jeli zastpimy litera sta symboliczn:
students = classess * studentsPerClass;

Jeli zdecydujesz si zmieni ilo studentw przypadajc na kad z klas, moesz to zrobi w definicji staej studentsPerClass (studentw na klas), bez koniecznoci zmiany tej wartoci w kadym miejscu jej wystpienia. W jzyku C++ istniej dwa sposoby deklarowania staych symbolicznych. Starszy, tradycyjny (obecnie uwaany za przestarzay) polega na wykorzystaniu dyrektywy preprocesora, #define.

Definiowanie staych za pomoc #define


Aby zdefiniowa sta w tradycyjny sposb, moesz napisa:
#define studentsPerClass 15

Zwr uwag, e staa studentsPerClass nie ma okrelonego typu (int, char, itd.). Dyrektywa #define umoliwia jedynie proste podstawianie tekstu. Za kadym razem, gdy preprocesor natrafia na sowo studentsPerClass, zastpuje je napisem 15. Poniewa dziaanie preprocesora poprzedza dziaanie kompilatora, kompilator nigdy nie widzi takich staych; zamiast tego widzi po prostu liczb 15.

Definiowanie staych za pomoc const


Mimo, e dziaa instrukcja #define, do definiowania staych w C++ uywa si nowszego, lepszego sposobu:
const unsigned short int studentsPerClass = 15;

Ten przykad take deklaruje sta symboliczn o nazwie studentsPerClass, ale tym razem ta staa ma typ, ktrym jest unsigned short int. Metoda ta ma kilka zalet, dziki ktrym kod programu jest atwiejszy w konserwacji i jest bardziej odporny na bdy. Natomiast zdefiniowana w ten sposb staa posiada typ; dziki temu kompilator moe wymusi uycie jej zgodnie z tym typem.
UWAGA Stae nie mog by zmieniane podczas dziaania programu. Jeli chcesz na przykad zmieni warto staej studentsPerClass, musisz zmodyfikowa kod rdowy, po czym skompilowa program ponownie.

TAK

NIE

Sprawdzaj, czy liczby nie przekraczaj Nie uywaj sw kluczowych jako nazw maksymalnego rozmiaru dopuszczalnego dla zmiennych. zmiennych cakowitych i czy nie zawijaj si do niewaciwych wartoci. Nadawaj zmiennym nazwy znaczce, dobrze odzwierciedlajce ich zastosowanie.

Stae wyliczeniowe
Stae wyliczeniowe umoliwiaj tworzenie nowych typw, a nastpnie definiowanie ich zmiennych. Wartoci takich zmiennych ograniczaj si do wartoci okrelonych w definicji typu. Na przykad, moesz zadeklarowa typ COLOR (kolor) jako wyliczenie, dla ktrego moesz zdefiniowa pi wartoci: RED, BLUE, GREEN, WHITE oraz BLACK. Skadni definicji wyliczenia stanowi sowo kluczowe enum, nazwa typu, otwierajcy nawias klamrowy, lista wartoci oddzielonych przecinkami, zamykajcy nawias klamrowy oraz rednik. Oto przykad:
enum COLOR { RED, BLUE, GREEN, WHITE, BLACK };

Ta instrukcja wykonuje dwa zadania: 1. 2. Sprawia, e nowe wyliczenie otrzymuje nazw COLOR, tj. tworzony jest nowy typ. Powoduje, e RED (czerwony) jest sta symboliczn o wartoci 0, BLUE (niebieski) jest sta symboliczn o wartoci 1, GREEN (zielony) jest sta symboliczn o wartoci 2, itd.

Kada wyliczana staa posiada warto cakowit. Jeli tego nie okrelisz, zakada si e pierwsza staa ma warto 0, nastpna 1, itd. Kada ze staych moe zosta zainicjalizowana dowoln wartoci. Stae, ktre nie zostan zainicjalizowane, bd miay wartoci naliczane poczwszy od wartoci od jeden wikszej od wartoci staych zainicjalizowanych. Zatem, jeli napiszesz:
enum COLOR { RED=100, BLUE, GREEN=500, WHITE, BLACK=700 };

RED bdzie mie warto 100, BLUE bdzie mie warto 101; GREEN warto 500, WHITE (biay) warto 501, za BLACK (czarny) warto 700.

Moesz definiowa zmienne typu COLOR, ale mog one przyjmowa tylko ktr z wyliczonych wartoci (w tym przypadku RED, BLUE, GREEN, WHITE lub BLACK, albo 100, 101, 500, 501 lub 700). Zmiennej typu COLOR moesz przypisa dowoln warto koloru. W rzeczywistoci moesz przypisa jej dowoln warto cakowit, nawet jeli nie odpowiada ona dozwolonemu kolorowi; dobry kompilator powinien w takim przypadku wypisa ostrzeenie. Naley zdawa sobie spraw, e stae wyliczeniowe to w rzeczywistoci zmienne typu unsigned int oraz e te stae odpowiadaj zmiennym cakowitym. Moliwo nazywania wartoci okazuje si bardzo pomocna, na przykad podczas pracy z kolorami, dniami tygodnia czy podobnymi zestawami. Program uywajcy typu wyliczeniowego zosta przedstawiony na listingu 3.7. Listing 3.7. Przykad staych wyliczeniowych
0: 1: 2: 3: #include <iostream> int main() { enum Days { Sunday, Monday, Tuesday,

4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:

Wednesday, Thursday, Friday, Saturday }; Days today; today = Monday; if (today == Sunday || today == Saturday) std::cout << "\nUwielbiam weekendy!\n"; else std::cout << "\nWracaj do pracy.\n"; return 0; }

Wynik
Wracaj do pracy.

Analiza W linii 3. definiowana jest staa wyliczeniowa Days (dni), posiadajca siedem, odpowiadajcych dniom tygodnia, wartoci. Kada z tych wartoci jest wartoci cakowit, numerowan od 0 w gr (tak wic Monday poniedziaek ma warto 1) 1. Tworzymy te zmienn typu Days tj. zmienn, ktra bdzie przyjmowa warto z listy wyliczonych staych. W linii 7. przypisujemy jej warto wyliczeniow Monday, ktr nastpnie sprawdzamy w linii 9. Staa wyliczeniowa zawarta w linii 3. moe by zastpiona seri staych cakowitych, tak jak pokazano na listingu 3.8. Listing 3.8. Ten sam program wykorzystujcy stae cakowite
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: #include <iostream> int main() { const int Sunday = 0; const int Monday = 1; const int Tuesday = 2; const int Wednesday = 3; const int Thursday = 4; const int Friday = 5; const int Saturday = 6; int today; today = Monday; if (today == Sunday || today == Saturday) std::cout << "\nUwielbiam weekendy!\n"; else std::cout << "\nWracaj do pracy.\n"; return 0; }

Wynik
Wracaj do pracy.

Analiza

Amerykanie licz dni tygodnia zaczynajc od niedzieli. przyp.tum.

Wynik dziaania tego programu jest identyczny z wynikiem programu z listingu 3.7. W tym programie kada ze staych (Sunday, Monday, itd.) zostaa zdefiniowana jawnie i nie istnieje typ wyliczeniowy Days. Stae wyliczeniowe maj t zalet, e si same dokumentuj przeznaczenie typu wyliczeniowego Days jest oczywiste.

Rozdzia 4. Wyraenia i instrukcje


Program stanowi zestaw kolejno wykonywanych instrukcji. Jako dziaania programu zaley od moliwoci wykonywania okrelonego zestawu instrukcji w danych warunkach. Z tego rozdziau dowiesz si: czym s instrukcje, czym s bloki, czym s wyraenia, jak, w zalenoci od warunkw, kierowa wykonaniem programu, czym jest prawda i jak dziaa na jej podstawie.

Instrukcje
W C++ instrukcje kontroluj kolejno dziaania programu, obliczaj wyraenia lub nie robi nic (instrukcja pusta). Wszystkie instrukcje C++ kocz si rednikiem (nawet instrukcja pusta, ktra skada si wycznie ze rednika). Jedn z najczciej wystpujcych instrukcji jest instrukcja przypisania:
x = a + b;

W przeciwiestwie do znaczenia, jakie ma w algebrze, ta instrukcja nie oznacza tutaj, e x rwna si a+b. Naley j traktowa jako przypisz warto sumy a i b do x lub przypisz a+b do x lub niech x rwna si a+b. Cho ta instrukcja wykonuje dwie czynnoci, nadal jest pojedyncz instrukcj (std tylko jeden rednik). Operator przypisania przypisuje to, co znajduje si po prawej stronie znaku rwnoci elementowi znajdujcemu si po lewej stronie.

Biae spacje
Biae spacje (tabulatory, spacje i znaki nowej linii) s w instrukcjach ignorowane. Omawiana poprzednio instrukcja przypisania moe zosta zapisana jako:
x=a+b;

lub jako:
x + =a b ;

Cho ostatni zapis jest poprawny, jest rwnoczenie niemdry. Biae spacje mog by uywane w celu poprawienia czytelnoci programu lub stworzenia okropnego, niemoliwego do rozszyfrowania kodu. C++ daje do wyboru wiele moliwoci, ale ich rozwane uycie zaley od ciebie. Znaki biaych spacji nie s widoczne. Gdy zostan wydrukowane, na papierze bd widoczne jako odstpy.

Bloki i instrukcje zoone


Wszdzie tam, gdzie moe znale si instrukcja pojedyncza, moe znale si take instrukcja zoona, zwana take blokiem. Blok rozpoczyna si od otwierajcego nawiasu klamrowego ({) i koczy nawiasem zamykajcym (}). Cho kada instrukcja w bloku musi koczy si rednikiem, sam blok nie wymaga jego zastosowania (jak pokazano w poniszym przykadzie):
{ temp = a; a = b; b = temp; }

Ten blok kodu dziaa jak pojedyncza instrukcja i zamienia wartoci w zmiennych a i b. TAK Jeli uye otwierajcego nawiasu klamrowego, pamitaj take o nawiasie zamykajcym. Kocz instrukcje rednikiem. Uywaj rozwanie biaych spacji, tak aby kod by czytelny.

Wyraenia
Wszystko, co staje si wartoci, w C++ jest uwaane za wyraenie. Mwi si, e wyraenie zwraca warto. Skoro instrukcja 3+2; zwraca warto 5, wic jest wyraeniem. Wszystkie wyraenia s jednoczenie instrukcjami. Moesz zdziwi si, ile miejsc w kodzie kwalifikuje si jako wyraenia. Oto trzy przykady:
3.2 PI SecondsPerMinute // zwraca warto 3.2 // staa typu float zwracajca warto 3.14 // staa typu int zwracajca 60

Gdy zaoymy, e PI jest sta, ktr zainicjalizowaem wartoci 3.14 i e SecondsPerMinute (sekund na minut) jest sta wynoszc 60, wtedy wszystkie te trzy instrukcje s wyraeniami. Nieco bardziej skomplikowane wyraenie
x = a + b;

nie tylko dodaje do siebie a oraz b, a wynik umieszcza w x, ale take zwraca warto tego przypisania (now warto x). Zatem instrukcja przypisania take jest wyraeniem. Poniewa jest wyraeniem, moe wystpi po prawej stronie operatora przypisania:
y = x = a + b;

Ta linia jest przetwarzana w nastpujcym porzdku: Dodaj a do b. Przypisz wynik wyraenia a + b do x. Przypisz rezultat wyraenia przypisania, x = a + b, do y. Jeli a, b, x oraz y byyby zmiennymi cakowitymi, za a miaoby warto 2, a b miaoby warto 5, wtedy zarwno zmiennej x, jak i y zostaaby przypisana warto 7. Ilustruje to listing 4.1. Listing 4.1. Obliczanie wyrae zoonych
0: 1: 2: 3: 4: 5: 6: 7: 8: #include <iostream> int main() { using std::cout; using std::endl; int a=0, b=0, x=0, y=35; cout << "a: " << a << " b: " << b; cout << " x: " << x << " y: " << y << endl;

9: 10: 11: 12: 13: 14: 15:

a = 9; b = 7; y = x = a+b; cout << "a: " << a << " b: " << b; cout << " x: " << x << " y: " << y << endl; return 0; }

Wynik
a: 0 b: 0 x: 0 y: 35 a: 0 b: 7 x: 16 y: 16

Analiza W linii 6. deklarowane i inicjalizowane s cztery zmienne. Ich wartoci s wypisywane w liniach 7. i 8. W linii 9. zmiennej a jest przypisywana warto 9. W linii 10., zmiennej b jest przypisywana warto 7. W linii 11. zmienne a i b s sumowane, za wynik sumowania jest przypisywany zmiennej x. To wyraenie (x = a+b) powoduje obliczenie sumy a oraz b i przypisanie jej do zmiennej x, warto tego przypisania jest nastpnie przypisywana zmiennej y.

Operatory
Operator jest symbolem, ktry powoduje, e kompilator rozpoczyna dziaanie. Operatory dziaaj na operandach, za wszystkie operandy w C++ s wyraeniami. W C++ istnieje kilka kategorii operatorw. Dwie z tych kategorii to: operatory przypisania, operatory matematyczne.

Operator przypisania
Operator przypisania (=) powoduje, e operand znajdujcy si po lewej stronie operatora przypisania zmienia warto na warto operandu znajdujcego si po prawej stronie operatora. Wyraenie:
x = a + b; Usunito: To w

przypisuje operandowi x wynik dodawania wartoci a i b. Operand, ktry moe wystpi po lewej stronie operatora przypisania jest nazywany l-wartoci (lvalue). Natomiast ten, ktry moe znale si po prawej stronie, jest nazywany (jak mona si domyli), r-wartoci (r-value). Stae s r-wartociami. Nie mog by l-wartociami. Zatem moesz napisa:
x = 35; // OK Usunito: To, co Usunito: e

lecz nie moesz napisa:


35 = x; // bd, 35 nie moe by l-wartoci!

L-warto jest operandem, ktry moe znale si po lewej stronie wyraenia. R-warto jest operandem, ktry moe wystpowa po prawej stronie wyraenia. Zwr uwag, e wszystkie lwartoci mog by r-wartociami, ale nie wszystkie r-wartoci mog by l-wartociami. Przykadem r-wartoci, ktra nie jest l-wartoci, moe by litera. Zatem moesz napisa x = 5;, lecz nie moesz napisa 5 = x; (x moe by l- lub r-wartoci, lecz 5 moe by tylko rwartoci).

Usunito: e

Operatory matematyczne
Pitka operatorw matematycznych to: dodawanie (+), odejmowanie (), mnoenie (*), dzielenie (/) oraz reszta z dzielenia (%). Dodawanie i odejmowanie dziaaj rutynowo, cho odejmowanie liczb cakowitych bez znaku moe prowadzi do zadziwiajcych rezultatw gdy wynik bdzie ujemny. Z czym takim spotkae si w poprzednim rozdziale, kiedy opisywalimy przepenienie (przewinicie wartoci). Listing 4.2 pokazuje, co si stanie gdy odejmiesz du liczb cakowit bez znaku od maej liczby cakowitej bez znaku. Listing 4.2. Przykad odejmowania i przepenienia wartoci cakowitej
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: // Listing 4.2 - Demonstracja odejmowania // i przepenienia wartoci cakowitej. #include <iostream> int main() { using std::cout; using std::endl; unsigned int difference; unsigned int bigNumber = 100; unsigned int smallNumber = 50; difference = bigNumber - smallNumber; cout << "Roznica to: " << difference; difference = smallNumber - bigNumber; cout << "\nTeraz roznica to: " << difference <<endl; return 0;

Wynik
Roznica to: 50 Teraz roznica to: 4294967246

Analiza Operator odejmowania jest wywoywany w linii 12., za wynik jest wypisywany w linii 13. (taki, jakiego moglimy oczekiwa). Operator odejmowania jest ponownie wywoywany w linii 14., jednak tym razem od maej liczby cakowitej bez znaku jest odejmowana dua liczba cakowita bez znaku. Wynik powinien by ujemny, ale poniewa wartoci s obliczane (i wypisywane) jako liczby cakowite bez znaku, efektem tej operacji jest przepenienie, tak jak opisywalimy w

poprzednim rozdziale. Ten temat jest szczegowo omawiany w dodatku C, Kolejno operatorw.

Dzielenie cakowite i reszta z dzielenia


Dzielenie cakowite poznae w drugiej klasie szkoy podstawowej. Gdy w dzieleniu cakowitym podzielisz 21 przez 4 (21/4), otrzymasz w wyniku 5 (oraz pewn reszt). Reszt z dzielenia cakowitego zwraca operator reszty z dzielenia (tzw. operator modulo). Aby otrzyma reszt, oblicz 21 modulo 4 (21 % 4). W wyniku otrzymasz 1. Obliczanie reszty z dzielenia moe by bardzo przydatne. Moesz na przykad zechcie wypisywa komunikat po kadej dziesitej akcji. Kada liczba, dla ktrej wynikiem reszty z dzielenia przez 10 jest zero, stanowi pen wielokrotno dziesiciu. Tak wic 1 % 10 wynosi 1, 2 % 10 wynosi 2, itd., a do 10 % 10, ktre ponownie wynosi 0. 11 % 10 to znw 1, wzr ten powtarza si a do nastpnej wielokrotnoci dziesiciu, ktr jest liczba 20. 20 % 0 to ponownie 0. T technik wykorzystujemy wewntrz ptli, ktre zostan omwione w rozdziale 7.
Czsto zadawane pytanie

Gdy dziel 5/3, otrzymuj w wyniku 1. Czy co robi nie tak?

Odpowied

Gdy dzielisz jedn liczb cakowit przez inn, w wyniku otrzymujesz take liczb cakowit. Zatem 5/3 wyniesie 1. (W rzeczywistoci wynikiem jest 1 i reszta 2. Aby otrzyma reszt, sprbuj napisa 5%3, uzyskasz w ten sposb warto 2.)
Usunito: ozycyjnych

Aby uzyska uamkow warto z dzielenia, musisz uy zmiennych zmiennoprzecinkowych.


Usunito: ozycyjn

5.0/3.0 da warto zmiennoprzecinkow 1.66667.


Usunito: ozycyjna

Jeli zmiennoprzecinkowa jest dzielna lub dzielnik, kompilator wygeneruje zmiennoprzecinkowy iloraz.

Usunito: ozycyjny

czenie operatora przypisania z operatorem matematycznym


Czsto zdarza si, e chcemy do zmiennej doda warto, za wynik umieci z powrotem w tej zmiennej. Jeli masz zmienn myAge (mj wiek) i chcesz zwikszy jej warto o dwa, moesz napisa:
int myAge = 5; int temp; temp = myAge + 2; // czyli 5 + 2 jest umieszczane w zmiennej temp myAge = temp; // wynik umieszczamy z powrotem w myAge

Ta metoda jest jednak bardzo mudna i nieefektywna. W C++ istnieje moliwo umieszczenia tej samej zmiennej po obu stronach operatora przypisania; w takim przypadku poprzedni przykad mona zapisa jako:
myAge = myAge + 2;

Jest to duo lepsza metoda. W algebrze to wyraenie nie miaoby sensu, ale w C++ jest traktowane jako dodaj dwa do wartoci zawartej w zmiennej myAge, za wynik umie ponownie w tej zmiennej. Jeszcze prostsze w zapisie, cho moe nieco trudniejsze do odczytania, jest:
myAge += 2;

Operator += sumuje r-warto z l-wartoci, za wynik umieszcza ponownie w l-wartoci. Ten operator wymawia si jako plus-rwna si, zatem caa instrukcja powinna zosta odczytana jako myAge plus-rwna si dwa. Jeli zmienna myAge miaaby pocztkowo warto 4, to po wykonaniu tej instrukcji przyjaby warto 6. Oprcz operatora += istniej take operatory -= (odejmowania), /= (dzielenia), *= (mnoenia), %= (reszty z dzielenia) i inne.

Inkrementacja i dekrementacja
Najczciej dodawan (i odejmowan), z ponownym przypisaniem wyniku zmiennej, wartoci jest 1. W C++ zwikszenie wartoci o jeden jest nazywane inkrementacj, za zmniejszenie o jeden dekrementacj. Su do tego specjalne operatory.

Operator inkrementacji (++) zwiksza warto zmiennej o jeden, za operator dekrementacji (--) zmniejsza j o jeden. Jeli chcemy inkrementowa zmienn C, moemy uy nastpujcej instrukcji:
C++; // zaczynamy od C i inkrementujemy

Ta instrukcja stanowi odpowiednik bardziej jawnie zapisanej operacji:


C = C + 1;

ktr, jak wiemy, moemy zapisa w nieco prostszy sposb:


C += 1;

UWAGA Jak mona si domyli, jzyk C++ otrzyma swoj nazw dziki zastosowaniu operatora inkrementacji do nazwy jzyka, od ktrego pochodzi (C). C++ jest kolejn, poprawion wersj jzyka C.

Przedrostki i przyrostki
Zarwno operator inkrementacji (++), jak i dekrementacji (--) wystpuje w dwch odmianach: przedrostkowej i przyrostkowej. Odmiana przedrostkowa jest zapisywana przed nazw zmiennej (++myAge), za odmiana przyrostkowa po niej (myAge++). W przypadku instrukcji prostej nie ma znaczenia, ktrej wersji uyjesz, jednak w wyraeniach zoonych, gdy inkrementujesz (lub dekrementujesz) zmienn, a nastpnie przypisujesz rezultat innej zmiennej, rnica jest bardzo wana. Operator przedrostkowy jest obliczany przed przypisaniem, za operator przyrostkowy po przypisaniu. Operator przedrostkowy dziaa nastpujco: zwiksz warto zmiennej i zapamitaj j. Operator przyrostkowy dziaa inaczej: zapamitaj pierwotn warto zmiennej, po czym zwiksz warto w zmiennej. Na pocztku moe to wydawa si do niezrozumiae, ale jeli x jest zmienn cakowit o wartoci 5, to gdy napiszesz
int a = ++x;

poinformujesz kompilator, by inkrementowa zmienn x (nadajc jej warto 6), po czym pobra t warto i przypisa j zmiennej a. Zatem po wykonaniu tej instrukcji zarwno zmienna x, jak i zmienna a maj warto 6.

Jeli nastpnie napiszesz


int b = x++;

to poinformujesz kompilator, by pobra warto zmiennej x (wynoszc 6) i przypisa j zmiennej b, po czym powrci do zmiennej x i inkrementowa j. W tym momencie zmienna b ma warto 6, a zmienna x ma warto 7. Zastosowanie i dziaanie obu wersji operatora przedstawia listing 4.3. Listing 4.3. Przykad dziaania operatora przedrostkowego i przyrostkowego
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: // Listing 4.3 - demonstruje uycie // przedrostkowych i przyrostkowych operatorw // inkrementacji i dekrementacji #include <iostream> int main() { using std::cout; int myAge = 39; // inicjalizujemy dwie zmienne cakowite int yourAge = 39; cout << "Ja mam: " << myAge << " lat.\n"; cout << "Ty masz: " << yourAge << " lat\n"; myAge++; // inkrementacja przyrostkowa ++yourAge; // inkrementacja przedrostkowa cout << "Minal rok...\n"; cout << "Ja mam: " << myAge << " lat.\n"; cout << "Ty masz: " << yourAge << " lat\n"; cout << "Minal kolejny rok\n"; cout << "Ja mam: " << myAge++ << " lat.\n"; cout << "Ty masz: " << ++yourAge << " lat\n"; cout << "Wypiszmy to jeszcze raz.\n"; cout << "Ja mam: " << myAge << " lat.\n"; cout << "Ty masz: " << yourAge << " lat\n"; return 0; }

Wynik
Ja mam: 39 lat. Ty masz: 39 lat Minal rok... Ja mam: 40 lat. Ty masz: 40 lat Minal kolejny rok Ja mam: 40 lat. Ty masz: 41 lat Wypiszmy to jeszcze raz. Ja mam: 41 lat. Ty masz: 41 lat

Analiza W liniach 8. i 9. deklarujemy dwie zmienne cakowite i inicjalizujemy je wartoci 39. Ich wartoci s wypisywane w liniach 10. i 11.

W linii 12. zmienna myAge (mj wiek) jest inkrementowana za pomoc operatora przyrostkowego, za w linii 13. zmienna yourAge (twj wiek) jest inkrementowana za pomoc operatora przedrostkowego. Wyniki wypisywane w liniach 15. i 16. s identyczne (40). W linii 18. zmienna myAge jest inkrementowana jako cz instrukcji wypisywania danych, za pomoc operatora przyrostkowego. Poniewa jest to operator przyrostkowy, inkrementacja odbywa si ju po wypisaniu tekstu, dlatego ponownie wypisywana jest warto 40. Dla odrnienia od linii 18., w linii 19. zmienna yourAge jest inkrementowana za pomoc operatora przedrostkowego. Poniewa w takim przypadku inkrementacja odbywa si przed wypisaniem tekstu, zostaje wypisana warto 41. Na zakoczenie, w liniach 21. i 22., ponownie wypisywane s wartoci zmiennych. Poniewa instrukcje inkrementacji zostay dokoczone, zmienna myAge, podobnie jak zmienna yourAge przyjmuje warto 41.

Kolejno dziaa
Ktre dziaanie instrukcji zoonej, takiej jak ta
x = 5 + 3 * 8;

jest wykonywane jako pierwsze: dodawanie czy mnoenie? Gdyby najpierw wykonywane byo dodawanie, wynik wynosiby 8 * 8, czyli 64. Gdyby najpierw wykonywane byo mnoenie, uzyskalibymy wynik 5 + 24, czyli 29. Kady operator posiada swj priorytet, ktry okrela kolejno wykonywania dziaa. Pen list priorytetw operatorw mona znale w dodatku C. Mnoenie ma priorytet nad dodawaniem, wic w tym przypadku wartoci wyraenia jest 29. Gdy dwa operatory matematyczne osigaj ten sam priorytet, s obliczane w kolejnoci od lewej do prawej. Zatem w wyraeniu
x = 5 + 3 + 8 * 9 + 6 * 4;

mnoenia s wykonywane jako pierwsze (najpierw lewe, potem prawe). Otrzymujemy 8*9 = 72 oraz 6*4 = 24. Teraz wyraenie mona zapisa jako
x = 5 + 3 + 72 + 24.

Nastpnie obliczane s dodawania, od lewej do prawej: 5 + 3 = 8; 8 + 72 = 80; 80 + 24 = 104. Bd ostrony. Niektre operatory, takie jak operator przypisania, s obliczane w kolejnoci od prawej do lewej!

Co zrobi gdy kolejno wykonywania dziaa nie odpowiada naszym potrzebom? Wemy na przykad takie wyraenie:
TotalSeconds = NumMinutesToThink + NumMinutesToType * 60

W tym wyraeniu nie chcemy mnoy zmiennej NumMinutesToType (ilo minut wpisywania) przez 60, a nastpnie dodawa otrzymanej wartoci do zmiennej NumMinutesToThink (ilo minut namysu). Chcemy zsumowa obie zmienne, aby otrzyma czn ilo minut, a dopiero potem przemnoy j przez ilo sekund w minucie, w celu otrzymania cznej iloci sekund (TotalSeconds). W tym przypadku, w celu zmiany kolejnoci dziaa uyjemy nawiasw. Dziaania na elementach w nawiasach s zawsze wykonywane przed innymi operacjami matematycznymi. Zatem zamierzony wynik uzyskamy dopiero wtedy, gdy napiszemy:
TotalSeconds = (NumMinutesToThink + NumMinutesToType) * 60

Zagniedanie nawiasw
W przypadku zoonych wyrae mona zagnieda nawiasy jeden wewntrz drugiego. Na przykad, przed obliczeniem cznej iloci osobosekund (TotalPersonSeconds) moesz zechcie obliczy czn ilo sekund oraz czn ilo osb zajtych prac:
TotalPersonSeconds = ( ( (NumMinutesToThink+NumMinutesToType) * 60) * (PeopleInTheOffice + PeopleOnVacation) ) Usunito: *

To zoone wyraenie jest odczytywane od wewntrz na zewntrz. Najpierw sumowane s zmienne NumMinutesToThink oraz NumMinutesToType, gdy znajduj si w wewntrznych nawiasach. Potem ta suma jest mnoona przez 60. Nastpnie sumowane s zmienne PeopleInTheOffice (osoby w biurze) oraz PeopleOnVacation (osoby na urlopie). Na koniec czna ilo osb jest przemnaana przez czn ilo sekund. Z tym przykadem wie si jeszcze jedno wane zagadnienie. To wyraenie jest atwe do zrozumienia przez komputer, lecz jest bardzo trudne do odczytania, zrozumienia lub zmodyfikowania przez czowieka. Oto to samo wyraenie, przepisane z uyciem kilku tymczasowych zmiennych cakowitych:
TotalMinutes = NumMinutesToThink + NumMinutesToType; TotalSeconds = TotalMinutes * 60; TotalPeople = PeopleInTheOffice + PeopleOnVacation; TotalPersonSeconds = TotalPeople * TotalSeconds;

Napisanie tego przykadu wymaga wicej czasu do napisania i skorzystania z wikszej iloci tymczasowych zmiennych, lecz sprawi, e bdzie on duo atwiejszy do zrozumienia. Gdy dodasz do niego komentarz, opisujcy, do czego suy ten kod oraz gdy zamienisz warto 60 na sta symboliczn, otrzymasz atwy do zrozumienia i modyfikacji kod. TAK Pamitaj, e wyraenia maj warto. Uywaj operatora przedrostkowego (++zmienna) do inkrementacji lub dekrementacji zmiennej przed jej uyciem w wyraeniu. Uywaj operatora przyrostkowego (zmienna++) do inkrementacji lub dekrementacji zmiennej po jej uyciu w wyraeniu. W celu zmiany kolejnoci dziaa uywaj nawiasw. NIE Nie zagniedaj nawiasw zbyt gboko, gdy wyraenie stanie si zbyt trudne do zrozumienia i modyfikacji.

Prawda i fasz
W poprzednich wersjach C++, prawda i fasz byy reprezentowane jako liczby cakowite; w standardzie ANSI wprowadzono nowy typ: bool. Typ ten moe mie tylko dwie wartoci, true (prawda) oraz false (fasz). Mona sprawdzi prawdziwo kadego wyraenia. Wyraenia, ktrych matematycznym wynikiem jest zero, zwracaj warto false. Wszystkie inne wyraenia zwracaj warto true.
UWAGA Wiele kompilatorw oferowao typ bool ju wczeniej, by on wewntrznie reprezentowany jako typ long int i mia rozmiar czterech bajtw. Nowe, zgodne z ANSI kompilatory czsto korzystaj z jednobajtowych zmiennych typu bool.

Operatory relacji
Operatory relacji s uywane do sprawdzania, czy dwie liczby s rwne, albo czy jedna z nich jest wiksza lub mniejsza od drugiej. Kady operator relacji zwraca prawd lub fasz. Operatory relacji zostan przedstawione nieco dalej, w tabeli 4.1.
UWAGA Wszystkie operatory relacji zwracaj warto typu bool, czyli warto true albo false. W poprzednich wersjach C++ operatory te zwracay albo warto 0 dla faszu, albo warto rn od zera (zwykle 1) dla prawdy.

Jeli zmienna cakowita myAge ma warto 45, za zmienna cakowita yourAge ma warto 50, moesz sprawdzi, czy s rwne, uywajc operatora rwna si:
myAge == yourAge; // czy warto myAge jest rwna wartoci yourAge?

Wyraenie ma warto false (fasz), gdy wartoci tych zmiennych nie s rwne. Z kolei wyraenie
myAge < yourAge; // czy myAge jest mniejsze od yourAge?

ma warto true (prawda).


OSTRZEENIE Wielu pocztkujcych programistw C++ myli operator przypisania (=) z operatorem relacji rwnoci (==). Moe to prowadzi do uporczywych i trudnych do wykrycia bdw w programach.

Sze operatorw relacji to: rwne (==), mniejsze (<), wiksze (>), mniejsze lub rwne (<=), wiksze lub rwne (>=) oraz rne (!=). Zostay one zebrane (wraz z przykadami uycia w kodzie) w tabeli 4.1. Tabela 4.1. Operatory relacji Nazwa Rwne Operator
==

Przykad
100 == 50; 50 == 50;

Wynik
false (fasz) true (prawda) true (prawda) false (fasz) true (prawda) false (fasz) true (prawda) true (prawda) false (fasz) false (fasz) false (fasz) true (prawda)

Nie rwne

!=

100 != 50; 50 != 50;

Wiksze

>

100 > 50; 50 > 50;

Wiksze lub rwne

>=

100 >= 50; 50 >= 50;

Mniejsze

<

100 < 50; 50 < 50;

Mniejsze lub rwne

<=

100 <= 50; 50 <= 50;

TAK

NIE

Pamitaj, e operatory relacji zwracaj warto true (prawda) lub false (fasz).

Nie myl operatora przypisania (=) z operatorem relacji rwnoci (==). Jest to jeden z najczstszych bdw popenianych przez programistw C++. Strze si go.

Instrukcja if
Program jest wykonywany linia po linii, w takiej kolejnoci, w jakiej linie te wystpuj w tekcie kodu rdowego. Instrukcja if umoliwia sprawdzenie spenienia warunku (na przykad, czy dwie zmienne s rwne) i przejcie do wykonania innej czci kodu. Najprostsza forma instrukcji if jest nastpujca:
if (wyraenie) instrukcja;

Wyraenie w nawiasach moe by cakowicie dowolne, ale najczciej jest to jedno z wyrae relacji. Jeli wyraenie to ma warto false, wtedy instrukcja jest pomijana. Jeli wyraenie jest prawdziwe (ma warto true), wtedy instrukcja jest wykonywana. Wemy poniszy przykad:
if (bigNumber > smallNumber) bigNumber = smallNumber;

Ten kod porwnuje zmienn bigNumber (dua liczba) ze zmienn smallNumber (maa liczba). Jeli warto zmiennej bigNumber jest wiksza, w drugiej linii tej zmiennej jest przypisywana warto zmiennej smallNumber. Poniewa blok instrukcji ujtych w nawiasy klamrowe stanowi odpowiednik instrukcji pojedynczej, warunkowo wykonywany fragment kodu moe by do rozbudowany:
if (wyraenie) { instrukcja1; instrukcja2; instrukcja3; }

Oto prosty przykad wykorzystania tej moliwoci:


if (bigNumber > smallNumber) {

bigNumber = smallNumber; std::cout << "duza liczba: " << bigNumber << "\n"; std::cout << "mala liczba: " << smallNumber << "\n"; }

Tym razem, jeli zmienna bigNumber jest wiksza od zmiennej smallNumber, przypisywana jest jej warto zmiennej smallNumber, a ponadto wypisywany jest informacyjny komunikat. Listing 4.4 przedstawia szczegowo przykad warunkowego wykonywania kodu (z zastosowaniem operatorw relacji). Listing 4.4. Przykad warunkowego wykonania kodu (z zastosowaniem operatorw relacji)
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: // Listing 4.5 - demonstruje instrukcje if // uywane z operatorami relacji #include <iostream> int main() { using std::cout; using std::cin; int MetsScore, YankeesScore; cout << "Wpisz wynik dla Metsow: "; cin >> MetsScore; cout << "\nWpisz wynik dla Yankees: "; cin >> YankeesScore; cout << "\n"; if (MetsScore > YankeesScore) cout << "Let's Go Mets!\n"; if (MetsScore < YankeesScore) { cout << "Go Yankees!\n"; } if (MetsScore == YankeesScore) { cout << "Remis? Eeee, nie moze byc...\n"; cout << "Podaj mi prawdziwy wynik dla Yanks: "; cin >> YankeesScore; if (MetsScore > YankeesScore) cout << "Wiedzialem! Let's Go Mets!"; if (YankeesScore > MetsScore) cout << "Wiedzialem! Go Yanks!"; if (YankeesScore == MetsScore) cout << "Coz, rzeczywiscie byl remis!";

cout << "\nDzieki za informacje.\n"; return 0; }

Wynik

Wpisz wynik dla Metsow: 10 Wpisz wynik dla Yankees: 10 Remis? Eeee, nie moze byc... Podaj mi prawdziwy wynik dla Yanks: 8 Wiedzialem! Let's Go Mets! Dzieki za informacje.

Analiza Ten program pyta uytkownika o wyniki spotka dwch druyn baseballowych; wyniki s przechowywane w zmiennych cakowitych. Te zmienne s porwnywane w instrukcjach if w liniach 17., 20. i 25. (W poprzednich wydaniach ksiki Yankees wystpowali przeciw Red Sox. W tym roku mamy inn seri, wic zaktualizowaem przykad!) Jeli jeden z wynikw jest wyszy ni drugi, wypisywany jest komunikat informacyjny. Jeli wyniki s rwne, wtedy program przechodzi do bloku kodu zaczynajcego si w linii 25. i koczcego w linii 39. Pojawia si w nim proba o ponowne podanie drugiego wyniku, po czym wyniki s porwnywane jeszcze raz. Zwr uwag, e gdyby pocztkowy wynik Yankees by wikszy ni wynik Metsw, wtedy w instrukcji if w linii 17. otrzymalibymy wynik false, co spowodowaoby e linia 18. nie zostaaby wykonana. Test w linii 20. miaby warto true, wic wykonana zostaaby instrukcja w linii 22.. Nastpnie zostaaby wykonana instrukcja if w linii 25. i jej wynikiem byoby false (jeli wynikiem w linii 17. bya prawda). Tak wic program pominby cay blok, a do linii 39. Ten przykad ilustruje e otrzymanie wyniku true w jednej z instrukcji if nie powoduje zaprzestania sprawdzania pozostaych instrukcji if. Zauwa, e wykonywan zawartoci dwch pierwszych instrukcji if s pojedyncze linie (wypisujce Lets Go Mets! lub Go Yankees!). W pierwszym przykadzie (w linii 18.) nie umieciem linii w nawiasach klamrowych, gdy pojedyncza instrukcja nie wymaga ich zastosowania. Nawiasy klamrowe s jednak dozwolone, wic uyem ich w liniach 21. i 23.
OSTRZEENIE Wielu pocztkujcych programistw C++ niewiadomie umieszcza rednik za nawiasem zamykajcym instrukcj if:
if(SomeValue < 10); SomeValue = 10; Usunito: 9

Zamiarem programisty byo tu sprawdzenie, czy zmienna SomeValue (jaka warto) jest mniejsza ni 10, i gdy warunek ten zostaby speniony, przypisalibymy tej zmiennej minimaln warto 10. Uruchomienie tego fragmentu kodu pokazuje, e zmienna SomeValue jest zawsze ustawiana na 10! Dlaczego? Poniewa instrukcja if koczy si rednikiem (czyli instrukcj pust).

Pamitaj, e wcicia w kodzie rdowym nie maj dla kompilatora adnego znaczenia. Ten fragment mgby zosta zapisany (bardziej poprawnie) jako:
if(SomeValue < 10) // sprawdzenie ; // nic nie rb

SomeValue = 10; // przypisz

Usunicie rednika wystpujcego w pierwszym przykadzie spowoduje, e druga linia stanie si czci instrukcji if i kod zadziaa zgodnie z planem.

Styl wci
Listing 4.3 pokazuje jeden ze stylw wcinania instrukcji if. Nie ma chyba jednak lepszego sposobu na wszczcie wojny religijnej ni zapytanie grupy programistw, jaki jest najlepszy styl wyrwnywania nawiasw klamrowych. Cho dostpne s tuziny ich odmian, wyglda na to, e najczciej stosowane s trzy z nich: umieszczenie otwierajcego nawiasu klamrowego po warunku i wyrwnanie nawiasu klamrowego zamykajcego blok zawierajcy pocztek instrukcji if:
if (wyraenie){ instrukcje } Usunito: powodu

wyrwnanie nawiasw klamrowych z instrukcj if i wcicie jedynie bloku instrukcji:


if (wyraenie) { instrukcje }

wcicie zarwno instrukcji, jak i nawiasw klamrowych:


if (wyraenie) { instrukcje }

W tej ksice stosujemy drug z podanych wyej wersji, gdy uwaam, e najlepiej pokazuje gdzie zaczyna si, a gdzie si koczy blok instrukcji. Pamitaj jednak, e nie ma znaczenia ktry styl sam wybierzesz, o ile tylko bdziesz go konsekwentnie stosowa.

else
Czsto zdarza si, e w swoim programie chcesz wykona jaki fragment kodu, jeeli speniony zostanie pewien warunek, oraz inny fragment kodu, gdy warunek ten nie zostanie speniony. Na listingu 4.4 chcielimy wypisa komunikat (Lets Go Mets!), pod warunkiem, e pierwszy test

(MetsScore > YankeesScore) da warto true, lub inny komunikat (Go Yankees!), gdy ten test da warto false. Pokazana ju wczeniej metoda sprawdzenie najpierw pierwszego warunku, a potem drugiego dziaa poprawnie, lecz jest nieco mudna. Dziki zastosowaniu sowa kluczowego else moemy to zamieni na bardziej czytelny fragment kodu:
if (wyraenie) instrukcja; else instrukcja;

Uycie sowa kluczowego else demonstruje listing 4.5. Listing 4.5. Uycie sowa kluczowego else
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: // Listing 4.5 - demonstruje instrukcj if // z klauzul else #include <iostream> int main() { using std::cout; using std::cin; int firstNumber, secondNumber; cout << "Prosze wpisac wieksza liczbe: "; cin >> firstNumber; cout << "\nProsze wpisac mniejsza liczbe: "; cin >> secondNumber; if (firstNumber > secondNumber) cout << "\nDzieki!\n"; else cout << "\nPomylka! Druga liczba jest wieksza!"; return 0; }

Wynik
Prosze wpisac wieksza liczbe: 10 Prosze wpisac mniejsza liczbe: 12 Pomylka! Druga liczba jest wieksza!

Analiza Obliczany jest warunek w instrukcji if w linii 13. Jeli warunek jest speniony (prawdziwy), wykonywana jest instrukcja w linii 14.; jeli nie jest speniony (jest faszywy), wykonywana jest instrukcja w linii 16. Gdyby klauzula else w linii 15. zostaa usunita, wtedy instrukcja w linii 16. byaby wykonywana zawsze, bez wzgldu na to, czy warunek instrukcji if byby speniony, czy nie. Pamitaj e instrukcja if koczy si po linii 14. Gdyby nie byo else, linia 16. byaby po prostu kolejn lini programu.

Usunito: by

Pamitaj, e obie instrukcje wykonywane warunkowo mona zastpi blokami kodu ujtymi w nawiasy klamrowe.
Instrukcja if

Skadnia instrukcji if jest nastpujca:

Forma 1
if (wyraenie) instrukcja; nastpna instrukcja;

Jeli wyraenie ma warto true, to instrukcja jest wykonywana i program przechodzi do wykonania nastpnej instrukcji. Jeli wyraenie nie jest prawdziwe, instrukcja jest ignorowana i program przechodzi bezporednio do nastpnej instrukcji.

Pamitaj, e instrukcja moe by pojedyncz instrukcj zakoczon rednikiem lub blokiem instrukcji ujtym w nawiasy klamrowe.

Forma 2
if (wyraenie) instrukcja1; else instrukcja2; nastpna instrukcja;

Jeli wyraenie ma warto true, wykonywana jest instrukcja1; w przeciwnym razie wykonywana jest instrukcja2. Nastpnie program przechodzi do wykonania nastpnej instrukcji.

Przykad 1
if (SomeValue < 10) cout << "SomeValue jest mniejsze ni 10"; else cout << "SomeValue nie jest mniejsze ni 10"; cout << "Gotowe." << endl;

Zaawansowane instrukcje if
Warto zauway, e w klauzuli if lub else moe by zastosowana dowolna instrukcja, nawet inna instrukcja if lub else. Z tego powodu moemy natrafi na zoone instrukcje if, przyjmujce posta:

if (wyraenie1) { if (wyraenie2) instrukcja1; else { if (wyraenie3) instrukcja2; else instrukcja3; } } else instrukcja4;

Ta rozbudowana instrukcja if dziaa nastpujco: jeli wyraenie1 ma warto true i wyraenie2 ma warto true, wykonaj instrukcj1. Jeli wyraenie1 ma warto true, lecz wyraenie2 ma warto false, wtedy, jeli wyraenie3 ma warto true, wykonaj instrukcj2. Jeli wyraenie1 ma warto true, lecz wyraenie2 i wyraenie3 maj warto false, wtedy wykonaj instrukcj3. Na zakoczenie, jeli wyraenie1 ma warto false, wykonaj instrukcj4. Jak wida, zoone instrukcje if mog wprawia w zakopotanie! Przykad takiej zoonej instrukcji if zawiera listing 4.6. Listing 4.6. Zoona, zagniedona instrukcja if
0: // Listing 4.6 - a zoona zagniedona 1: // instrukcja if 2: #include <iostream> 3: int main() 4: { 5: // Popro o dwie liczby. 6: // Przypisz je zmiennym bigNumber i littleNumber 7: // Jeli bigNumber jest wiksze ni littleNumber, 8: // sprawd, czy si dziel bez reszty. 9: // Jeli tak, sprawd, czy s to te same liczby. 10: 11: using namespace std; 12: 13: int firstNumber, secondNumber; 14: cout << "Wpisz dwie liczby.\nPierwsza: "; 15: cin >> firstNumber; 16: cout << "\nDruga: "; 17: cin >> secondNumber; 18: cout << "\n\n"; 19: 20: if (firstNumber >= secondNumber) 21: { 22: if ((firstNumber%secondNumber) == 0) // dziela sie bez reszty? 23: { 24: if (firstNumber == secondNumber) 25: cout << "One sa takie same!\n"; 26: else 27: cout << "One dziela sie bez reszty!\n"; 28: }

29: 30: 31: 32: 33: 34: 35:

else cout << "One nie dziela sie bez reszty!\n"; } else cout << "Hej! Druga liczba jest wieksza!\n"; return 0; }

Wynik
Wpisz dwie liczby. Pierwsza: 10 Druga: 2 One dziela sie bez reszty!

Analiza Program prosi o wpisanie dwch liczb, jednej po drugiej. Nastpnie s one porwnywane. Pierwsza instrukcja if, w linii 20., sprawdza, czy pierwsza liczba jest wiksza lub rwna drugiej. Jeli nie, wykonywana jest klauzula else w linii 32. Jeli pierwsza instrukcja if jest prawdziwa, wykonywany jest blok kodu zaczynajcy si w linii 21., po czym w linii 22. przeprowadzany jest kolejny test w instrukcji if. W tym przypadku sprawdzamy, czy reszta z dzielenia pierwszej liczby przez drug wynosi zero, to jest czy obie liczby s przez siebie podzielne. Jeli tak, liczby te mog by takie same lub mog by podzielne przez siebie. Instrukcja if w linii 24. sprawdza, czy te liczby s rwne i w obu przypadkach wywietla odpowiedni komunikat. Jeli warunek instrukcji if w linii 22. nie zostanie speniony, wtedy wykonywana jest instrukcja
else w linii 29.

Uycie nawiasw klamrowych w zagniedonych instrukcjach if


Cho dozwolone jest pomijanie nawiasw klamrowych w instrukcjach if zawierajcych tylko pojedyncze instrukcje, i cho dozwolone jest zagniedanie instrukcji if:
if (x > y) // gdy x jest wiksze od y if (x < z) // oraz gdy x jest mniejsze od z x = y; // wtedy przypisz zmiennej x warto zmiennej y Usunito: lecz

moe to powodowa zbyt duo problemw ze zrozumieniem struktury kodu w przypadku, gdy piszesz due zagniedone instrukcje. Pamitaj, biae spacje i wcicia s uatwieniem dla programisty, lecz nie stanowi adnej rnicy dla kompilatora. atwo jest si pomyli i bdnie wstawi instrukcj else do niewaciwej instrukcji if. Problem ten ilustruje listing 4.7.

Listing 4.7. Przykad: nawiasy klamrowe uatwiaj zorientowanie si, ktre instrukcje else nale do ktrych instrukcji if.
0: // Listing 4.7 demonstruje, dlaczego nawiasy klamrowe 1: // maj due znaczenie w zagniedonych instrukcjach if 2: #include <iostream> 3: int main() 4: { 5: int x; 6: std::cout << "Wpisz liczbe mniejsza niz 10 lub wieksza niz 100: "; 7: std::cin >> x; 8: std::cout << "\n"; 9: 10: if (x >= 10) 11: if (x > 100) 12: std::cout << "Wieksza niz 100, Dzieki!\n"; 13: else // nie tego else chcielimy! 14: std::cout << "Mniejsza niz 10, Dzieki!\n"; 15: 16: return 0; 17: }

Wynik
Wpisz liczbe mniejsza niz 10 lub wieksza niz 100: 20 Mniejsza niz 10, Dzieki!

Analiza Programista mia zamiar poprosi o liczb mniejsz ni 10 lub wiksz od 100, sprawdzi czy warto jest poprawna, po czym wypisa podzikowanie. Jeli instrukcja if w linii 10. jest prawdziwa, zostaje wykonana nastpna instrukcja (w linii 11.). W tym przypadku linia 11. jest wykonywana, gdy wprowadzona liczba jest wiksza ni 10. Linia 11. take zawiera instrukcj if. Ta instrukcja jest prawdziwa, gdy wprowadzona liczba jest wiksza od 100. Jeli liczba jest wiksza ni 100, wtedy wykonywana jest instrukcja w linii 12. Jeli wprowadzona liczba jest mniejsza od 10, wtedy instrukcja if w linii 10. daje wynik false i program przechodzi do nastpnej linii po instrukcji if, czyli w tym przypadku do linii 16. Jeli wpiszesz liczb mniejsz ni 10, otrzymasz wynik:
Wpisz liczbe mniejsza niz 10 lub wieksza niz 100: 9 Usunito: 7

Klauzula else w linii 13. miaa by doczona do instrukcji if w linii 10., i w zwizku z tym zostaa odpowiednio wcita. Jednak w rzeczywistoci ta instrukcja else jest doczona do instrukcji if w linii 11., co powoduje, e w programie wystpuje subtelny bd. Bd jest subtelny, gdy kompilator go nie zauway i nie zgosi. Jest to w peni poprawny program jzyka C++, lecz nie wykonuje tego, do czego zosta stworzony. Na dodatek, w wikszoci testw przeprowadzanych przez programist bdzie dziaa poprawnie. Dopki wprowadzane bd liczby wiksze od 100, program bdzie dziaa poprawnie.

Listing 4.8 przedstawia rozwizanie tego problemu wstawienie koniecznych nawiasw klamrowych. Listing 4.8. Przykad waciwego uycia nawiasw klamrowych w instrukcji if
0: // Listing 4.8 - demonstruje waciwe uycie nawiasw 1: // klamrowych w zagniedonych instrukcjach if 2: #include <iostream> 3: int main() 4: { 5: int x; 6: std::cout << "Wpisz liczbe mniejsza niz 10 lub wieksza niz 100: "; 7: std::cin >> x; 8: std::cout << "\n"; 9: 10: if (x >= 10) 11: { 12: if (x > 100) 13: std::cout << "Wieksza niz 100, Dzieki!\n"; 14: } 15: else // poprawione! 16: std::cout << "Mniejsza niz 10, Dzieki!\n"; 17: return 0; 18: }

Wynik
Wpisz liczbe mniejsza niz 10 lub wieksza niz 100: 9 Mniejsza niz 10, Dzieki!

Analiza Nawiasy klamrowe w liniach 11. i 14. powoduj, e cay zawarty midzy nimi kod jest traktowany jak pojedyncza instrukcja, dziki czemu instrukcja else w linii 15. odnosi si teraz do instrukcji if w linii 10., czyli tak, jak zamierzono.
UWAGA Programy przedstawione w tej ksice zostay napisane w celu zilustrowania omawianych zagadnie. S wic z zaoenia uproszczone i nie zawieraj adnych mechanizmw kontroli bdw wpisywanych przez uytkownika danych. W profesjonalnym kodzie naley przewidzie kady bd i odpowiednio na niego zareagowa.

Operatory logiczne
Czsto zdarza si, e chcemy zada wicej ni jedno relacyjne pytanie na raz. Czy jest prawd e x jest wiksze od y i czy jest jednoczenie prawd, e y jest wiksze od z? Aby mc podj dziaanie, program musi mie moliwo sprawdzenia, czy oba te warunki s prawdziwe lub czy przynajmniej ktry z nich jest prawdziwy. Wyobramy sobie skomplikowany system alarmowy, dziaajcy zgodnie z nastpujc zasad: gdy zabrzmi alarm przy drzwiach I jest ju po szstej po poudniu I NIE ma wit LUB jest

weekend, wtedy zadzwo po policj. Do tego rodzaju oblicze stosowane s trzy operatory logiczne jzyka C++. Zostay one przedstawione w tabeli 4.2. Tabela 4.2. Operatory logiczne Operator
I (AND) LUB (OR) NIE (NOT)

Symbol
&& || !

Przykad
wyraenie1 && wyraenie2 wyraenie1 || wyraenie2 !wyraenie

Logiczne I
Instrukcja logicznego I (AND) oblicza dwa wyraenia, jeeli oba maj warto true, wartoci caego wyraenia I take jest true. Jeli prawd jest, e jeste godny I prawd jest, e masz pienidze, WTEDY moesz kupi obiad. Zatem
if ( (x == 5) && (y == 5) )

bdzie prawdziwe, gdy zarwno x, jak i y ma warto 5, za bdzie nieprawdziwe, gdy ktra z tych zmiennych bdzie miaa warto rn od 5. Zapamitaj, e aby cae wyraenie byo prawdziwe, prawdziwe musz by oba wyraenia. Zauwa, e logiczne I to podwjny symbol, &&. Pojedynczy symbol, &, jest zupenie innym operatorem, ktry opiszemy w rozdziale 21., Co dalej.

Logiczne LUB
Instrukcja logicznego LUB (OR) oblicza dwa wyraenia, gdy ktre z nich ma warto true, wtedy wartoci caego wyraenia LUB take jest true. Jeli prawd jest, e masz gotwk LUB prawd jest e, masz kart kredytow, WTEDY moesz zapaci rachunek. Nie potrzebujesz jednoczenie gotwki i karty kredytowej, cho posiadanie obu jednoczenie nie przeszkadza. Zatem
if ( (x == 5) || (y == 5) )

bdzie prawdziwe, gdy x lub y ma warto 5, lub gdy obie zmienne maj warto 5. Zauwa, e logiczne LUB to podwjny symbol ||. Pojedynczy symbol | jest zupenie innym operatorem, ktry opiszemy w rozdziale 21., Co dalej.
Usunito: , Usunito: , Usunito: ,

Logiczne NIE
Instrukcja logicznego NIE (NOT) ma warto true, gdy sprawdzane wyraenie ma warto false. Jeeli sprawdzane wyraenie ma warto true, operator logiczny NIE zwraca warto false. Zatem
if ( !(x == 5) ) Usunito: ego

jest prawdziwe tylko wtedy, gdy x jest rne od 5. Identycznie dziaa zapis:
if (x != 5)

Skrcone obliczanie wyrae logicznych


Gdy kompilator oblicza instrukcj I, na przykad tak, jak:
if ( (x == 5) && (y == 5) )

wtedy najpierw sprawdza prawdziwo pierwszego wyraenia (x == 5). Gdy jest ono nieprawdziwe, POMIJA sprawdzanie prawdziwoci drugiego wyraenia (y == 5), gdy instrukcja I wymaga, aby oba wyraenia byy prawdziwe. Gdy kompilator oblicza instrukcj LUB, na przykad tak, jak:
if ( (x == 5) || (y == 5) )

wtedy w przypadku prawdziwoci pierwszego wyraenia (x == 5), nigdy NIE JEST sprawdzane drugie wyraenie (y == 5), gdy w instrukcji LUB wystarczy prawdziwo ktregokolwiek z wyrae.

Kolejno operatorw logicznych


Operatory logiczne, podobnie jak operatory relacji, s w jzyku C++ wyraeniami, wic zwracaj wartoci; w tym przypadku warto true lub false. Tak jak wszystkie wyraenia, posiadaj priorytet (patrz dodatek C), okrelajcy kolejno ich obliczania. Ma on znaczenie podczas wyznaczania wartoci instrukcji
if ( x > 5 && y > 5 || z > 5)

By moe programista chcia, by to wyraenie miao warto true, gdy zarwno x, jak i y s wiksze od 5 lub gdy z jest wiksze od 5. Z drugiej strony, programista mg chcie, by to wyraenie byo prawdziwe tylko wtedy, gdy x jest wiksze od 5 i gdy y lub z jest wiksze od 5. Jeli x ma warto 3, za y i z maj warto 10, wtedy prawdziwa jest pierwsza interpretacja (z jest wiksze od 5, wic x i y s ignorowane). Jednak w drugiej interpretacji otrzymujemy warto false (x nie jest wiksze od 5, wic nie ma znaczenia, co jest po prawej stronie symbolu &&, gdy obie jego strony musz by prawdziwe). Cho o kolejnoci oblicze decyduj priorytety operatorw, jednak do zmiany ich kolejnoci i jasnego wyraenia naszych zamiarw moemy uy nawiasw:
if ( (x > 5) && (y > 5 || z > 5) )

Uywajc poprzednio opisanych wartoci otrzymujemy dla tego wyraenia warto false. Poniewa x nie jest wiksze od 5, lewa strona instrukcji I jest nieprawdziwa, wic cae wyraenie jest traktowane jako nieprawdziwe. Pamitaj, e instrukcja I wymaga, by obie strony byy prawdziwe.
UWAGA Dobrym pomysem jest uywanie dodatkowych nawiasw pomagaj one lepiej oznaczy operatory, ktre chcesz pogrupowa. Pamitaj, e twoim celem jest pisanie programw, ktre nie tylko dziaaj, ale s take atwe do odczytania i zrozumienia.

Kilka sw na temat prawdy i faszu


W jzyku C++ warto zero jest traktowana jako logiczna warto false, za wszystkie inne wartoci s traktowane jako logiczna warto true. Poniewa wyraenie zawsze posiada jak warto, wielu programistw wykorzystuje j w swoich instrukcjach if. Instrukcja taka jak
if(x) x = 0; // jeli x ma warto true (rn od zera)

moe by odczytywana jako jeli x ma warto rn od zera, ustaw x na 0. Jest to efektowna sztuczka; zamiast tego lepiej bdzie, gdy napiszesz:
if (x != 0) // jeli x ma warto rn od zera x = 0;

Obie instrukcje s dozwolone, ale druga z nich lepiej wyraa intencje programisty. Do dobrych obyczajw programistw naley pozostawienie pierwszej z form dla prawdziwych testw logicznych (a nie dla sprawdzania czy warto jest rna od zera).

Te dwie instrukcje take s rwnowane:


if (!x) // jeli x ma warto false (rwn zeru) if (x == 0) // jeli x ma warto zero

Druga z nich jest nieco atwiejsza do zrozumienia i wyraniej sugeruje, e sprawdzamy matematyczn warto zmiennej x, a nie jej stan logiczny. TAK NIE

Aby lepiej wyrazi kolejno oblicze, Nie uywaj if(x) jako synonimu dla if(x != umieszczaj nawiasy wok wyrae logicznych. 0); druga z tych form jest bardziej czytelna. Aby unikn bdw i lepiej wyrazi przynaleno instrukcji else, uywaj nawiasw klamrowych w zagniedonych instrukcjach if. Nie uywaj if(!x) jako synonimu dla if(x == 0); druga z tych form jest bardziej czytelna.

Operator warunkowy (trjelementowy)


Operator warunkowy (?:) jest w jzyku C++ jedynym operatorem trjelementowym, tj. operatorem korzystajcym z trzech wyrae. Operator warunkowy skada si z trzech wyrae i zwraca warto:
(wyraenie1) ? (wyraenie2) : (wyraenie3)

T lini odczytuje si jako: jeli wyraenie1 jest prawdziwe, zwr warto wyraenia2; w przeciwnym razie zwr warto wyraenia3. Zwracana warto jest zwykle przypisywana zmiennej. Listing 4.9 przedstawia instrukcj if przepisan z uyciem operatora warunkowego. Listing 4.9. Przykad uycia operatora warunkowego
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: // Listing 4.9 - demonstruje operator warunkowy // #include <iostream> int main() { using namespace std; int x, y, z; cout << "Wpisz dwie liczby.\n"; cout << "Pierwsza: "; cin >> x; cout << "\nDruga: "; cin >> y;

13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:

cout << "\n"; if (x > y) z = x; else z = y; cout << "z: " << z; cout << "\n"; z = (x > y) ? x : y;

cout << "z: " << z; cout << "\n"; return 0; }

Wynik
Wpisz dwie liczby. Pierwsza: 5 Druga: 8 z: 8 z: 8

Analiza Tworzone s trzy zmienne cakowite: x, y oraz z. Wartoci dwch pierwszych s nadawane przez uytkownika. Instrukcja if w linii 15. sprawdza, ktra warto jest wiksza i przypisuje j zmiennej z. Ta warto jest wypisywana w linii 20. Operator warunkowy w linii 23. przeprowadza ten sam test i przypisuje zmiennej z wiksz z wartoci. Mona go odczyta jako: jeli x jest wiksze od y, zwr warto x; w przeciwnym razie zwr warto y. Zwracana warto jest przypisywana zmiennej z, za jej warto jest wypisywana w linii 25. Jak wida, instrukcja warunkowa stanowi krtszy odpowiednik instrukcji if...else.

Rozdzia 5. Funkcje
Cho w programowaniu zorientowanym obiektowo zainteresowanie uytkownikw zaczo koncentrowa si na obiektach, jednak mimo to funkcje w dalszym cigu pozostaj gwnym komponentem kadego programu. Funkcje globalne wystpuj poza obiektami, za funkcje skadowe (zwane take metodami skadowymi) wystpuj wewntrz obiektw, wykonujc ich prac. Z tego rozdziau dowiesz si: czym jest funkcja i z jakich czci si skada, jak deklarowa i definiowa funkcje, jak przekazywa argumenty do funkcji, jak zwraca warto z funkcji.

Zaczniemy od funkcji globalnych; w nastpnym rozdziale dowiesz si, w jaki sposb funkcje dziaaj wewntrz obiektw.

Czym jest funkcja?


Oglnie, funkcja jest podprogramem, operujcym na danych i zwracajcym warto. Kady program C++ posiada przynajmniej jedn funkcj, main(). Gdy program rozpoczyna dziaanie, funkcja main() jest wywoywana automatycznie. Moe ona wywoywa inne funkcje, ktre z kolei mog wywoywa kolejne funkcje. Poniewa funkcje te nie stanowi czci jakiego obiektu, s nazywane globalnymi mog by dostpne z dowolnego miejsca programu. W tym rozdziale, gdy bdziemy mwi o funkcjach, bdziemy mieli na myli wanie funkcje globalne (chyba, e postanowimy inaczej). Kada funkcja posiada nazw; gdy ta nazwa zostanie napotkana przez program, przechodzi on do wykonywania kodu zawartego wewntrz ciaa tej funkcji. Nazywa si to wywoaniem funkcji. Gdy funkcja wraca, wykonanie programu jest wznawiane od instrukcji nastpujcej po wywoaniu tej funkcji. Ten przepyw sterowania zosta pokazany na rysunku 5.1.

Rysunek 5.1. Gdy program wywouje funkcj, sterowanie przechodzi do jej ciaa, po czym jest wznawiane od instrukcji wystpujcej po wywoaniu tej funkcji

Dobrze zaprojektowane funkcje wykonuj okrelone, atwo zrozumiae zadania. Zoone zadania powinny by dzielone na kilka, odpowiednio wywoywanych funkcji. Funkcje wystpuj w dwch odmianach: zdefiniowane przez uytkownika (programist) oraz wbudowane. Funkcje wbudowane stanowi cz pakietu dostarczanego wraz z kompilatorem zostay one stworzone przez producenta kompilatora, z ktrego korzystasz. Funkcje zdefiniowane przez uytkownika s funkcjami, ktre piszesz samodzielnie.

Zwracane wartoci, parametry i argumenty


Funkcja moe zwraca warto. Gdy wywoujesz funkcj, moe ona wykona swoj prac, po czym zwrci warto stanowic rezultat tej pracy. Ta warto jest nazywana wartoci zwracan, za jej typ musi by zadeklarowany. Zatem, gdy piszesz:
int myFunction();

deklarujesz, e funkcja myFunction zwraca warto cakowit. Moesz take przekazywa wartoci do funkcji. Te wartoci peni rol zmiennych, ktrymi moesz manipulowa wewntrz funkcji. Opis przekazywanych wartoci jest nazywany list parametrw.
int myFunction(int someValue, float someFloat);

Ta deklaracja wskazuje, e funkcja myFunction nie tylko zwraca liczb cakowit, ale take, e jej parametrami s: warto cakowita oraz warto typu float. Parametr opisuje typ wartoci, jaka jest przekazywana funkcji podczas jej wywoania. Wartoci przekazywane funkcji s nazywane argumentami.
int theValueReturned = myFunction(5, 6.7); Usunito: W Usunito: tutaj

W tej deklaracji widzimy, e zmienna cakowita theValueReturned (zwracana warto) jest inicjalizowana wartoci zwracan przez funkcj myFunction, ktrej zostay przekazane wartoci 5 oraz 6.7 (jako argumenty). Typy argumentw musz odpowiada zadeklarowanym typom parametrw.

Deklarowanie i definiowanie funkcji


Aby uy funkcji w programie, naley najpierw zadeklarowa funkcj, a nastpnie j zdefiniowa. Deklaracja informuje kompilator o nazwie funkcji, typie zwracanej przez ni wartoci, oraz o jej parametrach. Z kolei definicja informuje, w jaki sposb dana funkcja dziaa. adna funkcja nie moe zosta wywoana z jakiejkolwiek innej funkcji, jeli nie zostanie wczeniej zadeklarowana. Deklaracja funkcji jest nazywana prototypem.

Deklarowanie funkcji
Istniej trzy sposoby deklarowania funkcji: zapisanie prototypu funkcji w pliku, a nastpnie uycie dyrektywy #include w celu doczenia go do swojego programu, zapisanie prototypu w pliku, w ktrym dana funkcja jest uywana, zdefiniowanie funkcji zanim zostanie wywoana przez inne funkcje. Jeli tego nie dokonasz, definicja bdzie peni jednoczenie rol deklaracji funkcji.

Cho moesz zdefiniowa funkcj przed jej uyciem i unikn w ten sposb koniecznoci tworzenia jej prototypu, nie naley to do dobrych obyczajw programistycznych z trzech powodw. Po pierwsze, niedobrze jest, gdy funkcje musz wystpowa w pliku rdowym w okrelonej kolejnoci. Powoduje to, e w razie wprowadzenia zmian trudno jest zmodyfikowa taki program. Po drugie, istnieje moliwo, e w pewnych warunkach funkcja A() musi by w stanie wywoa funkcj B(), a funkcja B() take musi by w stanie wywoa funkcj A(). Nie jest moliwe zdefiniowanie funkcji A() przed zdefiniowaniem funkcji B() i jednoczesne zdefiniowanie funkcji B() przed zdefiniowaniem funkcji A(), dlatego przynajmniej jedna z nich zawsze musi zosta zadeklarowana.

Po trzecie, prototypy funkcji stanowi wydajn technik debuggowania (usuwania bdw w programach). Jeli z prototypu wynika, e funkcja otrzymuje okrelony zestaw parametrw lub e zwraca okrelony typ wartoci, to w przypadku gdy funkcja nie jest zgodna z tym prototypem, kompilator, zamiast czeka na wystpienie bdu podczas dziaania programu, moe wskaza t niezgodno. Przypomina to dwustronn ksigowo. Prototyp i definicja sprawdzaj si wzajemnie, redukujc prawdopodobiestwo, e zwyka literwka spowoduje bd w programie.

Prototypy funkcji
Wiele z wbudowanych funkcji posiada ju gotowe prototypy. Wystpuj one w plikach, ktre s doczane do programu za pomoc dyrektywy #include. W przypadku funkcji pisanych samodzielnie, musisz stworzy samodzielnie take ich prototypy. Prototyp funkcji jest instrukcj, co oznacza, e koczy si on rednikiem. Skada si ze zwracanego przez funkcj typu oraz tzw. sygnatury funkcji. Sygnatura funkcji to jej nazwa oraz lista parametrw. Lista parametrw jest list wszystkich parametrw oraz ich typw, oddzielonych od siebie przecinkami. Elementy prototypu funkcji przedstawia rysunek 5.2. Rysunek 5.2. Elementy prototypu funkcji

Zwracany typ oraz sygnatura prototypu i definicji funkcji musz zgadza si dokadnie. Jeli nie s one zgodne, wystpi bd kompilacji. Zauwa jednak, e prototyp funkcji nie musi zawiera nazw parametrw, a jedynie ich typy. Poniszy prototyp jest poprawny:
long Area(int, int);

Ten prototyp deklaruje funkcj o nazwie Area (obszar), ktra zwraca warto typu long i posiada dwa parametry, bdce wartociami cakowitymi. Cho ten zapis jest poprawny, jednak jego stosowanie nie jest dobrym pomysem. Dodanie nazw parametrw powoduje, e prototyp staje si bardziej czytelny. Ta sama funkcja z nazwanymi parametrami mogaby by zadeklarowana nastpujco:
long Area(int length, int width );

W tym przypadku jest oczywiste, do czego suy ta funkcja oraz jakie s jej parametry.

Zwr uwag, e wszystkie funkcje zwracaj warto pewnego typu. Jeli ten typ nie zostanie podany jawnie, zakada si, e jest wartoci cakowit, a konkretnie typem int. Twoje programy bd jednak atwiejsze do zrozumienia, jeli we wszystkich funkcjach, wcznie z funkcj main(), bdziesz deklarowa zwracany typ.

Definiowanie funkcji
Definicja funkcji skada si z nagwka funkcji oraz z jej ciaa. Nagwek przypomina prototyp funkcji, w ktrym wszystkie parametry musz by nazwane a na kocu nagwka nie wystpuje rednik. Ciao funkcji jest ujtym w nawiasy klamrowe zestawem instrukcji. Rysunek 5.3 przedstawia nagwek i ciao funkcji. Rysunek 5.3. Nagwek i ciao funkcji

Listing 5.1 demonstruje program zawierajcy prototyp oraz deklaracj funkcji Area(). Listing 5.1. Deklaracja i definicja funkcji oraz ich wykorzystanie w programie
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: // Listing 5.1 - demonstruje uycie prototypw funkcji #include <iostream> int Area(int length, int width); //prototyp funkcji int main() { using std::cout; using std::cin; int lengthOfYard; int widthOfYard; int areaOfYard;

14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30:

cout << "\nJak szerokie jest twoje podworko? "; cin >> widthOfYard; cout << "\nJak dlugie jest twoje podworko? "; cin >> lengthOfYard; areaOfYard= Area(lengthOfYard,widthOfYard); cout << "\nTwoje podworko ma "; cout << areaOfYard; cout << " metrow kwadratowych\n\n"; return 0; } int Area(int l, int w) { return l * w; }

Wynik
Jak szerokie jest twoje podworko? 100 Jak dlugie jest twoje podworko? 200 Twoje podworko ma 20000 metrow kwadratowych

Analiza Prototyp funkcji Area() znajduje si w linii 3. Porwnaj ten prototyp z definicj funkcji, zaczynajc si od linii 27. Zwr uwag, e nazwa, zwracany typ oraz typy parametrw s takie same. Gdyby byy rne, wystpiby bd kompilacji. W rzeczywistoci jedyn rnic stanowi fakt, e prototyp funkcji koczy si rednikiem i nie posiada ciaa. Zwr take uwag, e nazwy parametrw w prototypie to length (dugo) oraz width (szeroko), ale nazwy parametrw w definicji to l oraz w. Jak wspomniano wczeniej, nazwy w prototypie nie s uywane i su wycznie jako informacja dla programisty. Do dobrych obyczajw programistycznych naley dopasowanie nazw parametrw prototypu do nazw parametrw definicji (nie jest to wymaganie jzyka). Argumenty s przekazywane funkcji w takiej kolejnoci, w jakiej zostay zadeklarowane i zdefiniowane parametry, lecz ich nazwy nie musz do siebie pasowa. Gdy przekaesz zmienn widthOfYard (szeroko podwrka), a po niej lengthOfYard (dugo podwrka), wtedy funkcja FindArea (znajd obszar) uyje wartoci widthOfYard jako dugoci oraz wartoci lengthOfYard jako szerokoci. Ciao funkcji jest zawsze ujte w nawiasy klamrowe, nawet jeli (tak jak w tym przypadku) skada si z jednej tylko instrukcji.

Wykonywanie funkcji
Gdy wywoujesz funkcj, jej wykonanie rozpoczyna si od pierwszej instrukcji nastpujcej po otwierajcym nawiasie klamrowym ({). Rozgazienie dziaania mona uzyska za pomoc instrukcji if. (Instrukcja if oraz instrukcje z ni zwizane zostan omwione w rozdziale 7.).

Funkcje mog take wywoywa inne funkcje, a nawet wywoywa siebie same (patrz podrozdzia Rekurencja w dalszej czci tego rozdziau).

Zmienne lokalne
Zmienne mona przekazywa funkcjom; mona rwnie deklarowa zmienne wewntrz ciaa funkcji. Zmienne deklarowane wewntrz ciaa funkcji s nazywane lokalnymi, gdy istniej tylko lokalnie wewntrz danej funkcji. Gdy funkcja wraca (koczy dziaanie), zmienne lokalne przestaj by dostpne i zostaj zniszczone przez kompilator. Zmienne lokalne s definiowane tak samo, jak wszystkie inne zmienne. Parametry przekazywane do funkcji take s uwaane za zmienne lokalne i mog by uywane identycznie, jak zmienne zadeklarowane wewntrz ciaa funkcji. Przykad uycia parametrw oraz zmiennych zadeklarowanych lokalnie wewntrz funkcji przedstawia listing 5.2. Listing 5.2. Uycie zmiennych lokalnych oraz parametrw
0: #include <iostream> 1: 2: float Convert(float); 3: int main() 4: { 5: using namespace std; 6: 7: float TempFer; 8: float TempCel; 9: 10: cout << "Podaj prosze temperature w stopniach Fahrenheita: "; 11: cin >> TempFer; 12: TempCel = Convert(TempFer); 13: cout << "\nOdpowiadajaca jej temperatura w stopniach Celsjusza: "; 14: cout << TempCel << endl; 15: return 0; 16: } 17: 18: float Convert(float TempFer) 19: { 20: float TempCel; 21: TempCel = ((TempFer - 32) * 5) / 9; 22: return TempCel; 23: }

Wynik
Podaj prosze temperature w stopniach Fahrenheita: 212 Odpowiadajaca jej temperatura w stopniach Celsjusza: 100 Podaj prosze temperature w stopniach Fahrenheita: 32 Odpowiadajaca jej temperatura w stopniach Celsjusza: 0

Podaj prosze temperature w stopniach Fahrenheita: 85 Odpowiadajaca jej temperatura w stopniach Celsjusza: 29.4444

Analiza W liniach 7. i 8. s deklarowane dwie zmienne typu float, z ktrych jedna przechowuje temperatur w stopniach Fahrenheita, za druga w stopniach Celsjusza. W linii 10. uytkownik jest proszony o podanie temperatury w stopniach Fahrenheita, za uzyskana warto jest przekazywana funkcji Convert() (konwertuj). Wykonanie programu przechodzi do pierwszej linii funkcji Convert() w linii 20., w ktrej jest deklarowana zmienna lokalna, take o nazwie TempCel (temperatura w stopniach Celsjusza). Zwr uwag, e ta zmienna lokalna nie jest rwnowana zmiennej TempCel w linii 8. Ta zmienna istnieje tylko wewntrz funkcji Convert(). Warto przekazywana jako parametr, TempFer (temperatura w stopniach Fahrenheita), take jest tylko lokaln kopi zmiennej przekazywanej przez funkcj main(). Ta funkcja mogaby posiada parametr o nazwie FerTemp i zmienn lokaln CelTemp, a program dziaaby rwnie dobrze. Aby przekona si e program dziaa, moesz wpisa te nazwy i ponownie go skompilowa. Lokalnej zmiennej funkcji, TempCel, jest przypisywana warto, bdca wynikiem odjcia 32 od parametru TempFer, pomnoenia przez 5, a nastpnie podzielenia przez 9. Ta warto jest nastpnie zwracana jako warto funkcji, ktra w linii 12. jest przypisywana zmiennej TempCel wewntrz funkcji main(). Ta warto jest wypisywana w linii 14. Program zosta uruchomiony trzykrotnie. Za pierwszym razem zostaa podana warto 212, w celu upewnienia si, czy punkt wrzenia wody w stopniach Fahrenheita (212) daje waciwy wynik w stopniach Celsjusza (100). Drugi test sprawdza temperatur zamarzania wody. Trzeci test to przypadkowa warto, wybrana w celu wygenerowania wyniku uamkowego.
Usunito: a Usunito: k

Zakres
Zmienna posiada zakres, ktry okrela, jak dugo i w ktrych miejscach programu jest ona dostpna. Zmienne zadeklarowane wewntrz bloku maj zakres obejmujcy ten blok; mog by dostpne tylko wewntrz tego bloku i przestaj istnie po wyjciu programu z tego bloku. Zmienne globalne maj zakres globalny i s dostpne w kadym miejscu programu.

Zmienne globalne
Zmienne zdefiniowane poza funkcj maj zakres globalny i s dostpne z kadej funkcji w programie, wcznie z funkcj main(). Zmienne lokalne o takich samych nazwach, jak zmienne globalne nie zmieniaj zmiennych globalnych. Jednak zmienna lokalna o takiej samej nazwie, jak zmienna globalna przesania ukrywa zmienn globaln. Jeli funkcja posiada zmienn o takiej samej nazwie jak zmienna

globalna, to nazwa uyta wewntrz funkcji odnosi si do zmiennej lokalnej, a nie do globalnej. Ilustruje to listing 5.3. Listing 5.3. Przykad zmiennych lokalnych i globalnych
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: #include <iostream> void myFunction(); int x = 5, y = 7; int main() { using std::cout; // prototyp // zmienne globalne

Usunito: ta

cout << "x z funkcji main: " << x << "\n"; cout << "y z funkcji main: " << y << "\n\n"; myFunction(); cout << "Wrocilem z myFunction!\n\n"; cout << "x z funkcji main: " << x << "\n"; cout << "y z funkcji main: " << y << "\n"; return 0; } void myFunction() { using std::cout; int y = 10; cout << "x z funkcji myFunction: " << x << "\n"; cout << "y z funkcji myFunction: " << y << "\n\n"; }

Wynik
x z funkcji main: 5 y z funkcji main: 7 x z funkcji myFunction: 5 y z funkcji myFunction: 10 Wrocilem z myFunction! x z funkcji main: 5 y z funkcji main: 7

Analiza Ten prosty program ilustruje kilka kluczowych i potencjalnie niezrozumiaych zagadnie, dotyczcych zmiennych lokalnych i globalnych. W linii 3. s deklarowane dwie zmienne globalne, x oraz y. Zmienna globalna x jest inicjalizowana wartoci 5, za zmienna globalna y jest inicjalizowana wartoci 7. W liniach 8. i 9. w funkcji main() te wartoci s wypisywane na ekranie. Zauwa, e funkcja main() nie definiuje tych zmiennych; poniewa s one globalne, s one dostpne w funkcji main(). Gdy w linii 10. zostaje wywoana funkcja myFunction(), dziaanie programu przechodzi do linii 17. a w linii 21. jest definiowana lokalna zmienna y, inicjalizowana wartoci 10. W linii 23.

funkcja myFunction() wypisuje warto zmiennej x; w tym przypadku zostaje uyta warto globalnej zmiennej x, tak jak w funkcji main(). Jednak w linii 24., w ktrej zostaje uyta nazwa zmiennej y, wykorzystywana jest lokalna zmienna y, gdy przesonia (ukrya) ona zmienn globaln o tej samej nazwie. Funkcja koczy swoje dziaanie i zwraca sterowanie do funkcji main(), ktra ponownie wypisuje wartoci zmiennych globalnych. Zauwa, e przypisanie wartoci do zmiennej lokalnej y w funkcji myFunction() w aden sposb nie wpyno na warto globalnej zmiennej y.

Zmienne globalne: ostrzeenie


W C++ dozwolone s zmienne globalne, ale prawie nigdy nie s one uywane. C++ wyroso z jzyka C, za w tym jzyku zmienne globalne byy niebezpiecznym, cho niezbdnym narzdziem. Zmienne globalne s konieczne, gdy zdarzaj si sytuacje, w ktrych dane musz by atwo dostpne dla wielu funkcji i nie chcemy ich przekazywa z funkcji do funkcji w postaci parametrw. Zmienne globalne s niebezpieczne, gdy zawieraj wsplne dane, ktre mog by zmienione przez ktr z funkcji w sposb niewidoczny dla innych. Moe to powodowa bardzo trudne do odszukania bdy. W rozdziale 15., Specjalne klasy i funkcje, poznasz alternatyw dla zmiennych globalnych, stanowi j statyczne zmienne skadowe.

Kilka sw na temat zmiennych lokalnych


Zmienne mona definiowa w dowolnym miejscu funkcji, nie tylko na jej pocztku. Zakresem zmiennej jest blok, w ktrym zostaa zdefiniowana. Dlatego, jeli zdefiniujesz zmienn wewntrz nawiasw klamrowych wewntrz funkcji, bdzie ona dostpna tylko wewntrz tego bloku. Ilustruje to listing 5.4. Listing 5.4. Zakres zmiennych ogranicza si do bloku, w ktrym zostay zadeklarowane
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: // Listing 5.4 - demonstruje e zakres zmiennej // ogranicza si do bloku, w ktrym zostaa zadeklarowana #include <iostream> void myFunc(); int main() { int x = 5; std::cout << "\nW main x ma wartosc: " << x; myFunc(); std::cout << "\nPonownie w main, x ma wartosc: " << x; return 0;

18: void myFunc() 19: { 20: int x = 8; 21: std::cout << "\nW myFunc, lokalne x: " << x << std::endl; 22: 23: { 24: std::cout << "\nW bloku w myFunc, x ma wartosc: " << x; 25: 26: int x = 9; 27: 28: std::cout << "\nBardzo lokalne x: " << x; 29: } 30: 31: std::cout << "\nPoza blokiem, w myFunc, x: " << x << std::endl; 32: }

Wynik
W main x ma wartosc: 5 W myFunc, lokalne x: 8 W bloku w myFunc, x ma wartosc: 8 Bardzo lokalne x: 9 Poza blokiem, w myFunc, x: 8 Ponownie w main, x ma wartosc: 5

Analiza Ten program zaczyna dziaanie od inicjalizacji lokalnej zmiennej x w linii 9., w funkcji main(). Komunikat wypisywany w linii 10. potwierdza, e x zostao zainicjalizowane wartoci 5. Wywoywana jest funkcja myFunc(), w ktrej, w linii 20., wartoci 8 jest inicjalizowana lokalna zmienna, take o nazwie x. Jej warto jest wypisywana w linii 21. W linii 23. rozpoczyna si blok, a w linii 24. ponownie wypisywana jest warto zmiennej x z funkcji. Wewntrz bloku, w linii 26., tworzona jest nowa zmienna, take o nazwie x, ktra jednak jest lokalna dla bloku. Jest ona inicjalizowana wartoci 9. Warto najnowszej zmiennej jest wypisywana w linii 28. Lokalny blok koczy si w linii 29., gdzie zmienna stworzona w linii 26. wychodzi z zakresu i nie jest ju widoczna. Gdy w linii 31. jest wypisywana warto x, jest to warto zmiennej x zadeklarowanej w linii 20. Na t zmienn nie miaa wpywu deklaracja zmiennej x w linii 26.; jej warto wynosi wci 8. W linii 32., funkcja myFunc() wychodzi z zakresu, a jej zmienna lokalna x staje si niedostpna. Wykonanie wraca do linii 14., w ktrej jest wypisywana warto lokalnej zmiennej x, stworzonej w linii 9. Nie ma na ni wpywu adna ze zmiennych zdefiniowanych w funkcji myFunc(). Mimo wszystko, program ten sprawiaby duo mniej kopotw, gdyby te trzy zmienne posiaday rne nazwy!
Usunito: lokalnej zmiennej x

Instrukcje funkcji
Praktycznie ilo rodzajw instrukcji, ktre mog by umieszczone wewntrz ciaa funkcji, jest nieograniczona. Cho wewntrz danej funkcji nie mona definiowa innych funkcji, jednak mona je wywoywa, z czego korzysta oczywicie funkcja main() w wikszoci programw C++. Funkcje mog nawet wywoywa same siebie (co zostanie wkrtce omwione w podrozdziale powiconym rekurencji). Chocia rozmiar funkcji w jzyku C++ nie jest ograniczony, jednak dobrze zaprojektowane funkcje s zwykle niewielkie. Wielu programistw radzi, by funkcja miecia si na pojedynczym ekranie tak, aby mona j byo widzie w caoci. Ta regua jest czsto amana, take przez bardzo dobrych programistw. Jednake mniejsze funkcje s atwiejsze do zrozumienia i utrzymania. Kada funkcja powinna spenia pojedyncze, dobrze okrelone zadanie. Jeli funkcja zbytnio si rozrasta, poszukaj miejsc, w ktrych moesz podzieli j na mniejsze podzadania.

Kilka sw na temat argumentw funkcji


Argumenty funkcji nie musz by tego samego typu. Najzupeniej poprawne i sensowne jest na przykad posiadanie funkcji, ktrej argumentami s liczba cakowita, dwie liczby typu long oraz znak (char). Argumentem funkcji moe by kade poprawne wyraenie jzyka C++, cznie ze staymi, wyraeniami matematycznymi i logicznymi, a take innymi funkcjami zwracajcymi warto.

Uycie funkcji jako parametrw funkcji


Cho jest dozwolone, by parametrem funkcji bya inna zwracajca warto funkcja, moe to spowodowa trudnoci w debuggowaniu kodu i jego nieczytelno. Na przykad, przypumy, e masz funkcje: myDouble() (podwojenie), triple() (potrojenie), square() (do kwadratu) oraz cube() (do trzeciej potgi), z ktrych kada zwraca warto. Mgby napisa:
Answer = (myDouble(triple(square(cube(myValue)))));

Ta instrukcja pobiera zmienn, myValue i przekazuje j jako argument do funkcji cube(), ktrej zwracana warto jest przekazywana jako argument do funkcji square(), ktrej zwracana warto jest przekazywana z kolei jako argument do funkcji triple(), za jej zwracana warto jest przekazywana do funkcji myDouble(). Ostatecznie zwracana warto tej funkcji jest przypisywana zmiennej Answer (odpowied). Trudno jest przewidzie, do czego suy ten kod (warto jest potrajana przed, czy po podniesieniu do kwadratu?), a gdy odpowied jest niepoprawna, trudno bdzie sprawdzi, ktra z funkcji dziaa niewaciwie.

Alternatyw jest uycie w kadym kroku oddzielnej, poredniej zmiennej:


unsigned unsigned unsigned unsigned unsigned long long long long long myValue = 2; cubed = cube(myValue); squared = square(cubed); tripled = triple(squared); Answer = myDouble(tripled);

// // // //

cubed = 8 squared = 64 tripled = 192 Answer = 384

Teraz kady poredni wynik moe zosta sprawdzony, za kolejno wykonywania jest bardzo dobrze widoczna.

Parametry s zmiennymi lokalnymi


Argumenty przekazywane funkcji s lokalne dla tej funkcji. Zmiany dokonane w argumentach nie wpywaj na wartoci w funkcji wywoujcej. Nazywa si to przekazywaniem przez warto, co oznacza, e wewntrz funkcji jest tworzona lokalna kopia kadego z argumentw. Te lokalne kopie s traktowane tak samo, jak kada inna zmienna lokalna. Ilustruje to listing 5.5. Listing 5.5. Przykad przekazywania przez warto
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: " y: 11: 12: y: " 13: 14: 15: 16: 17: 18: 19: 20: " << 21: 22: 23: 24: 25: 26: << y 27: // Listing 5.5 - demonstracja przekazywania przez warto. #include <iostream> void swap(int x, int y); int main() { int x = 5, y = 10; std::cout << "Funkcja Main. Przed funkcja Swap, x: " << x << " << y << "\n"; swap(x,y); std::cout << "Funkcja Main. Po funkcji Swap, x: " << x << " << y << "\n"; return 0; } void swap (int x, int y) { int temp; std::cout << "Funkcja Swap. Przed zamiana, x: " << x << " y: y << "\n"; temp = x; x = y; y = temp; std::cout << "Funkcja Swap. Po zamianie, x: " << x << " y: " << "\n"; }

Wynik
Funkcja Main. Przed funkcja Swap, x: 5 y: 10

Funkcja Swap. Przed zamiana, x: 5 y: 10 Funkcja Swap. Po zamianie, x: 10 y: 5 Funkcja Main. Po funkcji Swap, x: 5 y: 10

Analiza Program inicjalizuje w funkcji main() dwie zmienne, po czym przekazuje je do funkcji swap() (zamie), ktra wydaje si wzajemnie wymienia ich wartoci. Jednak gdy ponownie sprawdzimy ich warto w funkcji main(), okazuje si, e pozostay niezmienione! Zmienne s inicjalizowane w linii 8., a ich wartoci s pokazywane w linii 10. Nastpnie wywoywana jest funkcja swap(), do ktrej przekazywane s zmienne. Dziaanie programu przechodzi do funkcji swap(), gdzie w linii 20 wartoci s wypisywane ponownie. Maj t sam kolejno, jak w funkcji main(), czego zreszt oczekiwalimy. W liniach od 22. do 24. wartoci s zamieniane, co potwierdza komunikat wypisywany w linii 26. Rzeczywicie, wewntrz funkcji swap() wartoci zostay zamienione. Wykonanie programu powraca nastpnie do linii 12., z powrotem do funkcji main(), gdzie okazuje si, e wartoci nie s ju zamienione. Jak zapewne si domylasz, wartoci przekazane funkcji swap() zostay przekazane przez warto, co oznacza, e w tej funkcji zostay utworzone ich lokalne kopie. Wanie te zmienne lokalne s zamieniane w liniach od 22. do 24., bez odwoywania si do zmiennych funkcji main(). W rozdziale 8., Wskaniki, oraz rozdziale 10., Funkcje zaawansowane, poznasz alternatywne sposoby przekazywania przez warto, ktre umoliwi zmian wartoci w funkcji main().
Usunito: Usunito: a

Kilka sw na temat zwracanych wartoci


Funkcja zwraca albo warto, albo typ void (pusty). Typ void jest dla kompilatora sygnaem, e funkcja nie zwraca adnej wartoci. Aby zwrci warto z funkcji, uyj sowa kluczowego return, a po nim wartoci, ktr chcesz zwrci. Warto ta moe by wyraeniem zwracajcym warto. Na przykad:
return 5; return (x > 5); return (MyFunction());

Usunito: aby Usunito: przekazanych przez Usunito:

Wszystkie te instrukcje s poprawne, pod warunkiem, e funkcja MyFunction() take zwraca warto. Wartoci w drugiej instrukcji, return (x > 5); bdzie false, gdy x nie jest wiksze od 5, lub true w odwrotnej sytuacji. Zwracana jest warto wyraenia, false lub true, a nie warto x. Gdy program natrafia na sowo kluczowe return, nastpujca po nim warto jest zwracana jako warto funkcji. Wykonanie programu powraca natychmiast do funkcji wywoujcej, za instrukcje wystpujce po instrukcji return nie s ju wykonywane.

Pojedyncza funkcja moe zawiera wicej ni jedn instrukcj return. Ilustruje to listing 5.6. Listing 5.6. Przykad kilku instrukcji return zawartych w tej samej funkcji
0: // Listing 5.6 - Demonstracja kilku instrukcji return 1: // zawartych w tej samej funkcji. 2: 3: #include <iostream> 4: 5: int Doubler(int AmountToDouble); 6: 7: int main() 8: { 9: using std::cout; 10: 11: int result = 0; 12: int input; 13: 14: cout << "Wpisz liczbe do podwojenia (od 0 do 10 000): "; 15: std::cin >> input; 16: 17: cout << "\nPrzed wywolaniem funkcji Doubler... "; 18: cout << "\nwejscie: " << input << " podwojone: " << result << "\n"; 19: 20: result = Doubler(input); 21: 22: cout << "\nPo powrocie z funkcji Doubler...\n"; 23: cout << "\nwejscie: " << input << " podwojone: " << result << "\n"; 24: 25: return 0; 26: } 27: 28: int Doubler(int original) 29: { 30: if (original <= 10000) 31: return original * 2; 32: else 33: return -1; 34: std::cout << "Nie mozesz tu byc!\n"; 35: }

Wynik
Wpisz liczbe do podwojenia (od 0 do 10 000): 9000 Przed wywolaniem funkcji Doubler... wejscie: 9000 podwojone: 0 Po powrocie z funkcji Doubler... wejscie: 9000 podwojone: 18000

Wpisz liczbe do podwojenia (od 0 do 10 000): 11000 Przed wywolaniem funkcji Doubler... wejscie: 11000 podwojone: 0

Po powrocie z funkcji Doubler... wejscie: 11000 podwojone: -1

Analiza W liniach 14. i 15. program prosi o podanie liczby, ktra jest wypisywana w linii 18., razem z wynikiem w zmiennej lokalnej. Nastpnie w linii 20. jest wywoywana funkcja Doubler() (podwojenie), ktrej argumentem jest zmienna input (wejcie). Rezultat jest przypisywany lokalnej zmiennej result (wynik), a w linii 23. ponownie wypisywane s wartoci. W linii 30., w funkcji Doubler(), nastpuje sprawdzenie, czy parametr jest wikszy od 10000. Jeli nie, funkcja zwraca podwojon liczb pierwotn. Jeli jest wikszy od 10000, funkcja zwraca 1 jako warto bdu. Instrukcja w linii 34. nigdy nie jest wykonywana, poniewa bez wzgldu na to, czy warto jest wiksza od 10000, czy nie, funkcja wraca (do main) w linii 31. lub 33. czyli przed przejciem do linii 34. Dobry kompilator ostrzee, e ta instrukcja nie moe zosta wykonana, a dobry programista j usunie!
Czsto zadawane pytanie

Usunito: za Usunito: powinien si tym zaj!

Jaka jest rnica pomidzy int main() a void main(); ktrej formy powinienem uy? Uywaem obu i obie dziaaj poprawnie, dlaczego wic powinienem uywa int main(){ return 0;}?

Odpowied: W wikszoci kompilatorw dziaaj obie formy, ale zgodna z ANSI jest tylko forma int main() i tylko jej uycie gwarantuje, e program bdzie mg by bez zmian kompilowany take w przyszoci.

Oto rnica: int main() zwraca warto do systemu operacyjnego. Gdy program koczy dziaanie, ta warto moe by odczytana przez, na przykad, program wsadowy.

Nie bdziemy uywa tej zwracanej wartoci (rzadko si z niej korzysta), ale wymaga jej standard ANSI.

Parametry domylne
Funkcja wywoujca musi przekaza warto dla kadego parametru zadeklarowanego w prototypie i definicji funkcji. Przekazywana warto musi by zgodna z zadeklarowanym typem. Zatem, gdy masz funkcj zadeklarowan jako

long myFunction(int);

wtedy funkcja ta musi otrzyma warto cakowit. Jeli definicja funkcji jest inna lub nie przekaesz jej wartoci cakowitej, wystpi bd kompilacji. Jedyny wyjtek od tej reguy obowizuje, gdy prototyp funkcji deklaruje domyln warto parametru. Ta domylna warto jest uywana wtedy, gdy nie zostanie przekazany argument funkcji. Poprzedni deklaracj mona przepisa jako
long myFunction (int x = 50);

Ten prototyp informuje, e funkcja myFunction() zwraca warto typu long i otrzymuje parametr bdcy wartoci cakowit. Jeli argument nie zostanie podany, uyta zostanie domylna warto 50. Poniewa nazwy parametrw nie s wymagane w prototypach funkcji, t deklaracj mona zapisa nastpujco:
long myFunction (int = 50);

Definicja funkcji nie zmienia si w wyniku zadeklarowania parametru domylnego. W tym przypadku nagwek definicji funkcji przyjmie posta:
long myFunction (int x)

Jeli funkcja wywoujca nie przekae parametru, kompilator wypeni parametr x domyln wartoci 50. Nazwa domylnego parametru w prototypie nie musi by tak sama, jak nazwa w nagwku funkcji; domylna warto jest przypisywana na podstawie pozycji, a nie nazwy. Warto domylna moe zosta przypisana kademu parametrowi funkcji. Istnieje tylko jedno ograniczenie: jeli ktry z parametrw nie ma wartoci domylnej, nie moe jej mie take aden z wczeniejszych parametrw. Jeli prototyp funkcji ma posta:
long myFunction (int Param1, int Param2, int Param3);

to parametrowi Param1 moesz przypisa domyln warto tylko wtedy, gdy przypiszesz j rwnie parametrom Param2 i Param3. Uycie parametrw domylnych ilustruje listing 5.7. Listing 5.7. Uycie parametrw domylnych
0: 1: 2: 3: 4: 5: // Listing 5.7 - demonstruje uycie // domylnych wartoci parametrw #include <iostream> int VolumeCube(int length, int width = 25, int height = 1);

6: 7: int 8: { 9: 10: 11: 12: 13: 14: 15: << "\n"; 16: 17: 18: "\n"; 19: 20: 21: "\n"; 22: 23: } 24: 25: int 26: { 27: 28: 29: }

main() int int int int length = 100; width = 50; height = 2; area;

area = VolumeCube(length, width, height); std::cout << "Za pierwszym razem objetosc wynosi: " << area area = VolumeCube(length, width); std::cout << "Za drugim razem objetosc wynosi: " << area << area = VolumeCube(length); std::cout << "Za trzecim razem objetosc wynosi: " << area << return 0; VolumeCube(int length, int width, int height) return (length * width * height);

Wynik
Za pierwszym razem objetosc wynosi: 10000 Za drugim razem objetosc wynosi: 5000 Za trzecim razem objetosc wynosi: 2500

Analiza W linii 5., prototyp funkcji VolumeCube() (objto szecianu) okrela, e ta funkcja posiada trzy parametry, bdce wartociami cakowitymi. Dwa ostatnie posiadaj wartoci domylne. Ta funkcja oblicza objto szecianu, ktrego wymiary zostay jej przekazane. Jeli nie zostanie podana szeroko (width), funkcja uyje szerokoci rwnej 25 i wysokoci (height) rwnej 1. Jeli zostanie podana szeroko, lecz nie zostanie podana wysoko, funkcja uyje wysokoci rwnej 1. Nie ma moliwoci przekazania wysokoci bez przekazania szerokoci. W liniach od 9. do 11. inicjalizowane s wymiary, a w linii 14. s one przekazywane funkcji VolumeCube(). Obliczana jest objto, za wynik jest wypisywany w linii 15. Wykonanie przechodzi do linii 17., w ktrej ponownie wywoywana jest funkcja VolumeCube() (lecz tym razem bez podawania wysokoci). Uywana jest warto domylna, a objto jest ponownie obliczana i wypisywana. Wykonanie przechodzi do linii 20., lecz tym razem nie jest przekazywana ani szeroko, ani wysoko. Wykonanie po raz trzeci przechodzi do linii 25. Uyte zostaj domylne wartoci. Obliczana i wypisywana jest objto. TAK Pamitaj, e parametry funkcji peni wewntrz NIE Nie prbuj tworzy domylnej wartoci dla
Usunito: powraca

Usunito: powraca Usunito: powraca

tej funkcji rol zmiennych lokalnych.

pierwszego parametru, jeli nie istnieje domylna warto dla drugiego. Nie zapominaj, e argumenty przekazywane przez warto nie wpywaj na zmienne w funkcji wywoujcej. Nie zapominaj, e zmiana zmiennej globalnej w jednej z funkcji zmienia jej warto we wszystkich funkcjach.

Przecianie funkcji
C++ umoliwia tworzenie wikszej iloci funkcji o tej samej nazwie. Nazywa si to przecianiem lub przeadowaniem funkcji (ang. function overloading). Listy parametrw funkcji przecionych musz si rni od siebie albo typami parametrw, albo ich iloci, albo jednoczenie typami i iloci. Oto przykad:
int myFunction (int, int); int myFunction (long, long); int myFunction (long); Usunito: z Usunito: funkcje Usunito: funkcji Usunito: si

Funkcja myFunction() jest przeciona trzema listami parametrw. Pierwsza i druga wersja rni si od siebie typem parametrw, za trzecia wersja rni si od nich iloci parametrw. Typy wartoci zwracanych przez funkcje przecione mog by takie same lub rne.
UWAGA Dwie funkcje o tych samych nazwach i listach parametrw, rnice si tylko typem zwracanej wartoci, powoduj wystpienie bdu kompilacji. Aby zmieni zwracany typ, musisz zmieni take sygnatur funkcji (tj. jej nazw i (lub) list parametrw).

Przecianie funkcji zwane jest take polimorfizmem funkcji. Poli oznacza wiele, za morf oznacza form; tak wic polimorfizm oznacza wiele form. Polimorfizm funkcji oznacza moliwo przecienia funkcji wicej ni jednym znaczeniem. Zmieniajc ilo lub typ parametrw, moemy nadawa jednej lub wicej funkcjom t sam nazw, a mimo to, na podstawie uytych parametrw, zostanie wywoana waciwa funkcja. Dziki temu mona na przykad tworzy funkcje uredniajce liczby cakowite, zmiennoprzecinkowe i inne wartoci bez koniecznoci tworzenia osobnych nazw dla kadej funkcji, np. AverageInts() (uredniaj wartoci cakowite), AverageDoubles() (uredniaj wartoci typu double), itd. Przypumy, e piszesz funkcj, ktra podwaja kad warto, ktr jej przekaesz. Chciaby mie moliwo przekazywania jej wartoci typu int, long, float oraz double. Bez przeciania funkcji musiaby wymyli cztery jej nazwy:

int DoubleInt(int); long DoubleLong(long); float DoubleFloat(float); double DoubleDouble(double);

Dziki przecianiu funkcji moesz zastosowa deklaracje:


int Double(int); long Double(long); float Double(float); double Double(double);

S one atwiejsze do odczytania i wykorzystania. Nie musisz pamita, ktr funkcj naley wywoa; po prostu przekazujesz jej zmienn, a waciwa funkcja zostaje wywoana automatycznie. Takie zastosowanie przeciania funkcji przedstawia listing 5.8. Listing 5.8. Przykad polimorfizmu funkcji
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: // Listing 5.8 - demonstruje // polimorfizm funkcji #include <iostream> int Double(int); long Double(long); float Double(float); double Double(double); using namespace std; int main() { int long float double int long float double cout cout cout cout << << << <<

myInt = 6500; myLong = 65000; myFloat = 6.5F; myDouble = 6.5e20; doubledInt; doubledLong; doubledFloat; doubledDouble; "myInt: " << myInt << "\n"; "myLong: " << myLong << "\n"; "myFloat: " << myFloat << "\n"; "myDouble: " << myDouble << "\n";

doubledInt = Double(myInt); doubledLong = Double(myLong); doubledFloat = Double(myFloat); doubledDouble = Double(myDouble); cout cout cout cout << << << << "doubledInt: " << doubledInt << "\n"; "doubledLong: " << doubledLong << "\n"; "doubledFloat: " << doubledFloat << "\n"; "doubledDouble: " << doubledDouble << "\n";

38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64:

return 0; } int Double(int original) { cout << "Wewnatrz Double(int)\n"; return 2 * original; } long Double(long original) { cout << "Wewnatrz Double(long)\n"; return 2 * original; } float Double(float original) { cout << "Wewnatrz Double(float)\n"; return 2 * original; } double Double(double original) { cout << "Wewnatrz Double(double)\n"; return 2 * original; }

Wynik
myInt: 6500 myLong: 65000 myFloat: 6.5 myDouble: 6.5e+020 Wewnatrz Double(int) Wewnatrz Double(long) Wewnatrz Double(float) Wewnatrz Double(double) doubledInt: 13000 doubledLong: 130000 doubledFloat: 13 doubledDouble: 1.3e+021

Analiza Funkcja MyDouble() jest przeciona dla parametrw typu int, long, float oraz double. Ich prototypy znajduj si w liniach od 5. do 8., za definicje w liniach od 42. do 64. Zwr uwag, e w tym przykadzie, w linii 10., uyem instrukcji using namespace std; poza jakkolwiek funkcj. Sprawia to, e ta instrukcja staa si dla tego pliku globaln i e przestrze nazw std jest uywana we wszystkich zdefiniowanych w tym pliku funkcjach. W ciele gwnego programu jest deklarowanych osiem zmiennych lokalnych. W liniach od 14. do 17. s inicjalizowane cztery z tych zmiennych, za w liniach od 29. do 32. pozostaym czterem zmiennym s przypisywane wyniki przekazania kadej z pierwszych czterech zmiennych do funkcji MyDouble(). Zauwa, e w chwili wywoywania tej funkcji, funkcja wywoujca nie

rozrnia, ktra wersja ma zosta wywoana; po prostu przekazuje argument, i to ju zapewnia wywoanie waciwej wersji. Kompilator sprawdza argumenty i na tej podstawi wybiera waciw wersj funkcji MyDouble(). Z wypisywanych komunikatw wynika, e wywoywane s kolejno poszczeglne wersje funkcji (tak jak moglimy si spodziewa).

Usunito: a kompilator zajmuje si reszt. Usunito: z

Zagadnienia zwizane z funkcjami


Poniewa funkcje s istotn czci programowania, omwimy teraz kilka zagadnie, ktre mog si okaza przydatne przy rozwizywaniu pewnych problemw. Waciwe wykorzystanie funkcji typu inline moe pomc w zwikszeniu wydajnoci programu. Natomiast rekurencyjne wywoywanie funkcji jest jednym z tych cudownych elementw programowania, ktre mog atwo rozwiza skomplikowane problemy, trudne do rozwizania w inny sposb.
Usunito: ci zainteresowa w momencie natrafienia na rzadko wystpujce problemy. Usunito: rekurencja Usunito: zagadnie

Funkcje typu inline


Gdy definiujesz funkcj, kompilator zwykle tworzy w pamici osobny zestaw instrukcji. Gdy wywoujesz funkcj, wykonanie programu przechodzi (wykonuje skok) do tego zestawu instrukcji, za gdy funkcja skoczy dziaanie, wykonanie wraca do instrukcji nastpnej po wywoaniu funkcji. Jeli wywoujesz funkcj dziesi razy, program za kadym razem skacze do tego samego zestawu instrukcji. Oznacza to, e istnieje tylko jedna kopia funkcji, a nie dziesi. Z wchodzeniem do funkcji i wychodzeniem z niej wie si pewien niewielki narzut. Okazuje si, e pewne funkcje s bardzo mae, zawieraj tylko jedn czy dwie linie kodu, wic istnieje moliwo poprawienia efektywnoci dziaania programu przez rezygnacj z wykonywania skokw w celu wykonania jednej czy dwch krtkich instrukcji. Gdy programici mwi o efektywnoci, zwykle maj na myli szybko dziaania programu; jeli unikniemy wywoywania funkcji, program bdzie dziaa szybciej. Jeli funkcja zostanie zadeklarowana ze sowem kluczowym inline, kompilator nie tworzy prawdziwej funkcji tylko kopiuje kod z funkcji typu inline bezporednio do kodu funkcji wywoujcej (w miejscu wywoania funkcji inline). Nie odbywa si aden skok; program dziaa tak, jakby zamiast wywoania funkcji wpisa instrukcje tej funkcji rcznie. Zauwa, e funkcje typu inline mog oznacza due koszty (w sensie czasu procesora). Gdy funkcja jest wywoywana w dziesiciu rnych miejscach programu, jej kod jest kopiowany do kadego z tych dziesiciu miejsc. Niewielkie zwikszenie szybkoci moe zosta zniwelowane przez znaczny wzrost objtoci pliku wykonywalnego, co w efekcie moe doprowadzi do spowolnienia dziaania programu! Wspczesne kompilatory prawie zawsze lepiej radz sobie z podjciem takiej decyzji ni programista, dlatego dobrym pomysem jest rezygnacja z deklarowania funkcji jako inline, chyba e faktycznie skada si ona z jednej czy dwch linii. Jeli masz jakiekolwiek wtpliwoci, zrezygnuj z uycia sowa kluczowego inline. Funkcja typu inline zostaa przedstawiona na listingu 5.9.

Listing 5.9. Przykad funkcji typu inline


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: // Listing 5.9 - demonstruje funkcje typu inline #include <iostream> inline int Double(int); int main() { int target; using std::cout; using std::cin; using std::endl; cout << "Wpisz liczbe: "; cin >> target; cout << "\n"; target = Double(target); cout << "Wynik: " << target << endl; target = Double(target); cout << "Wynik: " << target << endl; target = Double(target); cout << "Wynik: " << target << endl; return 0; } int Double(int target) { return 2*target; }

Wynik
Wpisz liczbe: 20 Wynik: 40 Wynik: 80 Wynik: 160

Analiza W linii 4. funkcja MyDouble() jest deklarowana jako funkcja typu inline, otrzymujca parametr typu int i zwracajca warto cakowit. Ta deklaracja jest taka sama, jak w przypadku innych prototypw, jednak tu przed typem zwracanej wartoci zastosowano sowo kluczowe inline. Program kompiluje si do kodu, ktry ma posta tak, jakby w kadym miejscu wystpienia instrukcji
target = Double(target);

rcznie wpisano

target = 2 * target;

W czasie dziaania programu instrukcje s ju na miejscu, wkompilowane do pliku .obj. Dziki temu unika si skokw w wykonaniu kodu (kosztem nieco obszerniejszego pliku wykonywalnego).
UWAGA Sowo kluczowe inline jest wskazwk dla kompilatora, e dana funkcja moe by funkcj kopiowan do kodu. Kompilator moe jednak zignorowa t wskazwk i stworzy zwyczajn, wywoywan funkcj.

Rekurencja
Funkcja moe wywoywa sam siebie. Nazywa si to rekurencj (lub rekursj). Rekurencja moe by bezporednia lub porednia. Rekurencja bezporednia ma miejsce, gdy funkcja wywouje sam siebie; rekurencja porednia nastpuje wtedy, gdy funkcja wywouje inn funkcj, ktra z kolei (by moe take porednio) wywouje funkcj pierwotn. Niektre problemy najatwiej rozwizuje si stosujc wanie rekurencj. Zwykle s to czynnoci, w trakcie ktrych operuje si na danych, a potem w podobny sposb operuje si na wyniku. Oba rodzaje rekurencji, porednia i bezporednia, wystpuj w dwch wersjach: takiej, ktra si koczy i zwraca wynik, oraz takiej, ktra si nigdy nie koczy i zwraca bd czasu dziaania. Programici uwaaj, e ta druga jest bardzo zabawna (gdy przytrafia si komu innemu). Naley pamita, e gdy funkcja wywouje siebie sam, tworzona jest nowa kopia lokalnych zmiennych tej funkcji. Zmienne lokalne w wersji wywoywanej s zupenie niezalene od zmiennych lokalnych w wersji wywoujcej i w aden sposb nie mog na siebie wpywa. Zmienne lokalne w funkcji main() rwnie nie s zwizane ze zmiennymi lokalnymi w wywoywanej przez ni funkcji (ilustrowa to listing 5.4). Aby zilustrowa zastosowanie rekurencji, wykorzystajmy obliczanie cigu Fibonacciego: 1, 1, 2, 3, 5, 8, 13, 21, 34... Kada warto, poczwszy od trzeciej, stanowi sum dwch poprzednich elementw. Zadaniem Fibonacciego moe by na przykad wyznaczenie dwunastego elementu takiego cigu. Aby rozwiza to zadanie, musimy dokadnie sprawdza cig. Pierwsze dwa elementy maj warto jeden. Kady kolejny element stanowi sum dwch poprzednich elementw. Np., sidmy element jest sum elementu pitego i szstego. Przyjmujemy regu, e n-ty element jest sum n-2 i n-1 elementu (przy zaoeniu, e n jest wiksze od dwch). Funkcje rekurencyjne wymagaj istnienia warunku zatrzymania (tzw. warunku stopu). Musi wydarzy si co, co powoduje zatrzymanie rekurencji, gdy w przeciwnym razie nigdy si ona nie skoczy (tzn. zakoczy si bdem dziaania programu). W cigu Fibonacciego warunkiem

stopu jest n < 3 (tzn. gdy n stanie si mniejsze od trzech, moemy przesta pracowa nad zadaniem). Algorytm jest to zestaw krokw podejmowanych w celu rozwizania zadania. Jeden z algorytmw obliczania elementw cigu Fibonacciego jest nastpujcy: 1. 2. 3. Popro uytkownika o podanie numeru elementu cigu. Wywoaj funkcj fib(), przekazujc jej uzyskany od uytkownika numer elementu. Funkcja fib() sprawdza argument (n). Jeli n < 3, zwraca warto 1; w przeciwnym razie wywouje (rekurencyjnie) sam siebie, przekazujc jako argument warto n-2. Nastpnie wywouje si ponownie, przekazujc warto n-1, po czym zwraca sum pierwszego i drugiego wywoania.

Gdy wywoasz fib(1), zwrci ona warto 1. Gdy wywoasz fib(2), take zwrci 1. Jeli wywoasz fib(3), to zwrci ona sum z wywoa fib(2) oraz fib(1). Poniewa fib(2) zwraca 1 a fib(1) te zwraca 1, fib(3) zwrci 2 (sum 1+1). Jeeli wywoasz fib(4), to zwrci ona sum z wywoa fib(3) oraz fib(2). Ju wiesz, e fib(3) zwraca 2 (z wywoa fib(2) i fib(1)) oraz, e fib(2) zwraca 1. fib(4) zsumuje te liczby i zwrci 3 (co stanowi czwarty element szeregu). Gdy wywoasz fib(5), zwrci ona sum fib(4) oraz fib(3). Sprawdzilimy, e fib(4) zwraca 3, za fib(3) zwraca 2, wic zwrcon sum bdzie 5. Ta metoda nie jest najbardziej efektywnym sposobem rozwizywania tego problemu (w fib(20) funkcja fib() jest wywoywana 13 529 razy!), ale dziaa. Bd ostrony jeli podasz zbyt du liczb, w komputerze zabraknie pamici potrzebnej do dziaania programu. Przy kadym wywoaniu funkcji fib() rezerwowany jest fragment pamici. Gdy funkcja wraca, pami jest zwalniana. W przypadku rekurencji pami jest wci rezerwowana przed zwolnieniem, wic moe si bardzo szybko skoczy. Listing 5.10 przedstawia implementacj funkcji fib().
OSTRZEENIE Gdy uruchomisz listing 5.10, uyj niewielkiej liczby (mniejszej ni 15). Poniewa program uywa rekurencji, moe zuy mnstwo pamici.

Komentarz [PaG1]: Poniej brak jednego akapitu - str. 118. Usunito: Wiemy wic e fib(3) zwraca warto 2 (w wyniku wywoania fib(1) i fib(2)) oraz e fib(2) zwraca warto 1, wic fib(4) zsumuje te wartoci i zwrci 3, czyli warto czwartego elementu cigu.

Listing 5.10. Rekurencyjne obliczanie elementw cigu Fibonacciego


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: // Obliczanie cigu Fibonacciego z uyciem rekurencji #include <iostream> int fib (int n); int main() { int n, answer; std::cout << "Podaj numer elementu ciagu: "; std::cin >> n; std::cout << "\n\n"; answer = fib(n); std::cout << "Wartoscia " << n << "-go elementu ciagu "; std::cout << "Fibonacciego jest " << answer << "\n"; return 0;

19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36:

} int fib (int n) { std::cout << "Przetwarzanie fib(" << n << ")... "; if (n < 3 ) { std::cout << "Zwraca 1!\n"; return (1); } else { std::cout << "Wywoluje fib(" << n-2 << ") "; std::cout << "oraz fib(" << n-1 << ").\n"; return( fib(n-2) + fib(n-1)); }

Wynik
Podaj numer elementu ciagu: 6 Przetwarzanie fib(6)... Przetwarzanie fib(4)... Przetwarzanie fib(2)... Przetwarzanie fib(3)... Przetwarzanie fib(1)... Przetwarzanie fib(2)... Przetwarzanie fib(5)... Przetwarzanie fib(3)... Przetwarzanie fib(1)... Przetwarzanie fib(2)... Przetwarzanie fib(4)... Przetwarzanie fib(2)... Przetwarzanie fib(3)... Przetwarzanie fib(1)... Przetwarzanie fib(2)... Wartoscia 6-go elementu Wywoluje fib(4) oraz fib(5). Wywoluje fib(2) oraz fib(3). Zwraca 1! Wywoluje fib(1) oraz fib(2). Zwraca 1! Zwraca 1! Wywoluje fib(3) oraz fib(4). Wywoluje fib(1) oraz fib(2). Zwraca 1! Zwraca 1! Wywoluje fib(2) oraz fib(3). Zwraca 1! Wywoluje fib(1) oraz fib(2). Zwraca 1! Zwraca 1! ciagu Fibonacciego jest 8
Usunito: wewntrz

UWAGA Niektre kompilatory maj problem z uyciem operatorw w instrukcji zawierajcej cout. Jeli w linii 32. pojawi si ostrzeenie, umie nawiasy wok operacji odejmowania tak, by linie 32. i 33. wyglday nastpujco:
32: 33: std::cout << "Wywoluje fib(" << (n-2) << ") "; std::cout << "oraz fib(" << (n-1) << ").\n";

Analiza W linii 9. program prosi o podanie numeru elementu cigu i przypisuje ten numer zmiennej n. Nastpnie wywouje funkcj fib(), przekazujc jej t warto. Wykonanie przechodzi do funkcji fib(), ktra wypisuje warto swojego argumentu w linii 23. W linii 25. argument n jest sprawdzany w celu upewnienia si czy jest mniejszy od 3; jeli tak, funkcja fib() zwraca warto 1. W przeciwnym razie zwraca sum wartoci otrzymanych w wyniku wywoania funkcji fib() z argumentami n-2 oraz n-1.

Funkcja nie moe zwrci tej sumy do momentu powrotu z obu wywoa fib(). Moemy sobie wyobrazi jak program cigle wykonuje skoki do fib, do chwili, w ktrej natrafia na wywoanie, w ktrym funkcja fib() zwraca warto. Jedyne wywoania zwracajce warto bezporednio to wywoania fib(2) oraz fib(1). Te wartoci s nastpnie przekazywane w gr, do oczekujcych na nie funkcji, ktre z kolei przekazuj sum do swoich funkcji wywoujcych. T rekurencj dla funkcji fib() przedstawiaj rysunki 5.4 oraz 5.5. Rysunek 5.4. Uycie rekurencji

Usunito: przedstawi Usunito: zagbia si coraz bardziej

Rysunek 5.5. Powrt z rekurencji

W tym przykadzie n ma warto 6, dlatego w funkcji main() jest wywoywana funkcja fib(6). Wykonanie przechodzi do funkcji fib(), w ktrej (w linii 25.) nastpuje sprawdzenie czy n jest mniejsze od 3. Warto n jest wiksza od 3, wic funkcja fib(6) zwraca sum wartoci zwracanych przez funkcje fib(4) oraz fib(5).
34: return( fib(n-2) + fib(n-1));

Oznacza to, e odbywa si wywoanie fib(4) (poniewa n == 6, wic fib(n-2) to w istocie fib(4)) oraz wywoanie fib(5) (czyli fib(n-1)), po czym funkcja, w ktrej si znajdujemy (w tym przypadku fib(6)) czeka, a te wywoania zwrc wartoci. Gdy wartoci te zostan zwrcone, funkcja ta zwraca rezultat sumowania tych wartoci. Poniewa fib(5) otrzymuje argument wikszy od 3, funkcja fib() zostaje wywoana ponownie, tym razem z argumentami 3 i 4. Funkcja fib(4) wywouje z kolei funkcje fib(2) oraz fib(3). Wypisywane komunikaty pokazuj te wywoania oraz zwracane wartoci. Skompiluj, zbuduj i uruchom ten program, podajc warto 1, nastpnie 2, potem 3 i tak a do 6. Uruchamiajc program uwanie led komunikaty. To doskonaa okazja, aby rozpocz samodzielne eksperymenty z debuggerem. Umie punkt przerwania w linii 21., po czym obserwuj kade wywoanie funkcji fib(), ledzc warto n w kadym rekurencyjnym wywoaniu tej funkcji. W programach C++ rekurencja nie jest uywana zbyt czsto, ale moe stanowi wydajne i eleganckie narzdzie rozwizywania pewnych problemw.
Usunito: wchod wewntrz (into) Usunito: go Usunito: a

UWAGA Rekurencja jest elementem programowania zaawansowanego. Zostaa tu zaprezentowana, poniewa zrozumienie podstaw jej dziaania moe okaza si przydatne, jednak nie przejmuj si zbytnio, jeli nie zrozumiae w peni wszystkich jej szczegw.

Jak dziaaj funkcje rzut oka pod mask


Gdy wywoujesz dan funkcj, program przechodzi do tej funkcji, przekazywane s parametry i nastpuje wykonanie ciaa funkcji. Gdy funkcja zakoczy dziaanie, zwracana jest warto (chyba, e zwracana jest warto typu void) i sterowanie powraca do funkcji wywoujcej. Jak to si odbywa? Skd kod wie, gdzie skoczy? Gdzie s przechowywane przekazywane zmienne? Co si dzieje ze zmiennymi zadeklarowanymi w ciele funkcji? W jaki sposb jest przekazywana warto zwracana przez funkcj? Skd kod wie, w ktrym miejscu ma wznowi dziaanie po powrocie z funkcji? Wikszo ksiek wprowadzajcych w zagadnienia programowania nie prbuje odpowiada na te pytania, ale bez zrozumienia tych mechanizmw pisanie programw wci pozostaje programowaniem z elementami magii. Wyjanienie zasad dziaania funkcji wymaga poruszenia tematu pamici komputera.

Poziomy abstrakcji
Jednym z najwikszych wyzwa dla pocztkujcych programistw jest konieczno posugiwania si wieloma poziomami abstrakcji. Oczywicie, komputery s jedynie urzdzeniami elektronicznymi. Nie maj pojcia o oknach czy menu, nie znaj programw ani instrukcji, a nawet nie wiedz nic o zerach i jedynkach. W rzeczywistoci jedyne zmiany, jakie zauwaaj, to zmiany napicia mierzonego w odpowiednich punktach ukadw elektronicznych. Nawet to jest dla nich pewn abstrakcj: w rzeczywistoci elektryczno jest tylko wygodn intelektualn koncepcj dla zaprezentowania dziaania czstek subatomowych, ktre z kolei s abstrakcj dla czego innego (!). Bardzo niewielu programistw zadaje sobie trud zejcia poniej poziomu wartoci w pamici RAM. W kocu nie trzeba zna fizyki czsteczkowej, aby prowadzi samochd, robi kanapki czy kopa pik; nie trzeba te zna si na elektronice, aby programowa komputer. Konieczne jest jednak zrozumienie, w jaki sposb jest zorganizowana pami komputera. Bez wyranego obrazu tego, gdzie znajduj si tworzone zmienne i w jaki sposb przekazywane s wartoci midzy funkcjami, programowanie nadal pozostanie tajemnic.
Usunito: Dzielenie

Podzia pamici
Gdy uruchamiasz program, system operacyjny (taki jak DOS, Unix czy Microsoft Windows) przygotowuje rne obszary pamici (w zalenoci od wymaga kompilatora). Jako programista C++, czsto bdziesz mia do czynienia z globaln przestrzeni nazw, stert, rejestrami, przestrzeni kodu oraz stosem.

Zmienne globalne wystpuj w globalnej przestrzeni nazw. O globalnej przestrzeni nazw i stercie pomwimy dokadniej w nastpnych rozdziaach, teraz skupimy si na rejestrach, przestrzeni kodu oraz stosie. Rejestry s specjalnym obszarem pamici wbudowanym w procesor (CPU, Central Processing Unit). Odpowiadaj za wewntrzne wykonywanie programu przez procesor. Wikszo tego, co dzieje si w rejestrach, wykracza poza tematyk tej ksiki; interesuje nas tylko zestaw rejestrw, ktry w danej chwili wskazuje nastpn instrukcj kodu. Zestaw rejestrw nosi wspln nazw wskanika instrukcji (ang. instruction pointer). Zadaniem wskanika instrukcji jest ledzenie, ktra linia kodu ma zosta wykonana jako nastpna. Kod wystpuje w przestrzeni kodu, ktra jest czci pamici przygotowan tak, by zawieraa binarn posta instrukcji stanowicych program. Kada linia kodu rdowego zostaa przetumaczona na seri instrukcji procesora, z ktrych kada znajduje si w pamici pod okrelony adresem. Wskanik instrukcji zawiera adres nastpnej instrukcji przeznaczonej do wykonania. Ilustruje to rysunek 5.6. Rys. 5.6. Wskanik instrukcji
Usunito: stworzonych jako Usunito: przeznaczonej

Stos jest specjalnym obszarem pamici, zaalokowanym przez program w celu przechowywania danych potrzebnych wszystkim funkcjom programu. Jest nazywany stosem, gdy stanowi kolejk LIFO (last-in, first-out ostatni wchodzi, pierwszy wychodzi), przypominajc stos talerzy w restauracji (pokazany na rysunku 5.7). Rys. 5.7. Stos

Ostatni wchodzi, pierwszy wychodzi oznacza, e to, co zostanie umieszczone na stosie jako ostatnie, zostanie z niego zdjte jako pierwsze. Wikszo kolejek przypomina kolejki w sklepie: pierwsza osoba w kolejce jest obsugiwana jako pierwsza. Stos przypomina stos monet: gdy uoysz na stole dziesi monet, jedna na drugiej, a nastpnie cz z nich zabierasz, zabierasz najpierw te monety, ktre uoye jako ostatnie. Gdy dane s umieszczane (ang. push) na stosie, stos ronie; gdy s zdejmowane ze stosu (ang. pop), stos maleje. Nie ma moliwoci wyjcia talerza ze stosu bez zdjcia wszystkich talerzy, ktre zostay umieszczone na nim pniej. Stos talerzy jest najczciej przedstawian analogi. Jest ona poprawna, ale dziaanie pamici wyglda nieco inaczej. Bardziej odpowiednie jest wyobraenie sobie szeregu pojemnikw uoonych jeden na drugim. Szczytem stosu jest ten pojemnik, na ktry w danej chwili wskazuje wskanik stosu (ang. stack pointer), bdcy jeszcze jednym rejestrem. Kady z pojemnikw ma kolejny adres, a jeden z tych adresw jest przechowywany w rejestrze wskanika stosu. Wszystko, co znajduje si poniej tego magicznego adresu, znanego jako szczyt stosu, jest uwaane za zawarto stosu. Wszystko, co znajduje si powyej szczytu stosu, jest uwaane za znajdujce si poza stosem, a co za tym idzie, niepoprawne. Ilustruje to rysunek 5.8. Rys. 5.8. Wskanik stosu

Gdy odkadasz dan na stos, jest ona umieszczana w pojemniku znajdujcym si powyej wskanika stosu, a nastpnie wskanik stosu jest przesuwany o jeden pojemnik w gr. Gdy zdejmujesz dan ze stosu, jedyn czynnoci odbywajc si w rzeczywistoci jest przesunicie wskanika stosu o jeden pojemnik w d. Pokazuje to rysunek 5.9. Rys. 5.9. Przesunicie wskanika stosu

Dane powyej wskanika stosu (czyli poza stosem) mog (ale nie musz) ulec zmianie w dowolnej chwili. Wartoci te nazywamy odpadami (aby lepiej uwiadomi sobie, e nie powinnimy na nie liczy).

Stos i funkcje
Poniej przedstawiono przybliony opis tego, co si dzieje, gdy program przechodzi do wykonania funkcji. (Poszczeglne rozwizania rni si, w zalenoci od systemu operacyjnego i kompilatora). 1. Zwikszany jest adres we wskaniku instrukcji i wskazuje on instrukcj nastpn po tej, ktra wywouje funkcj. Ten adres jest nastpnie umieszczany na stosie; stanowi adres powrotu z funkcji. Na stosie jest tworzone miejsce dla zadeklarowanego typu wartoci zwracanej przez funkcj. Gdy zwracany typ jest zadeklarowany jako int, w przypadku systemu z dwubajtowymi liczbami cakowitymi, na stos s odkadane dwa kolejne bajty, ale nie jest w nich umieszczana adna warto (odpady, ktre si w nich dotd znajdoway, pozostaj tam nadal). Do wskanika instrukcji jest adowany adres wywoywanej funkcji (ten adres jest zawarty w kodzie aktualnie wykonywanej instrukcji wywoania funkcji), dziki czemu nastpna wykonywana instrukcja bdzie ju instrukcj funkcji. Odczytywany jest adres biecego szczytu stosu, nastpnie zostaje on umieszczony w specjalnym wskaniku nazywanym ramk stosu (ang. stack frame). Wszystko, co zostanie umieszczone na stosie od tego momentu, jest uwaane za lokalne dla funkcji. Na stosie umieszczane s argumenty funkcji. Wykonywana jest instrukcja wskazywana przez wskanik instrukcji (nastpuje wykonanie pierwszej instrukcji w funkcji). W trakcie ich definiowania, lokalne zmienne zostaj umieszczane na stosie.
Usunito: aniu Usunito: i

2.

3.

4.

5. 6. 7.

Gdy funkcja jest gotowa do powrotu, zwracana warto jest umieszczana w miejscu stosu zarezerwowanym w kroku 2. Nastpnie stos jest zwijany (tzn. wskanik stosu przesuwa si) a do wskanika ramki stosu, co oznacza odrzucenie wszystkich lokalnych zmiennych i argumentw funkcji. Zwracana warto jest zdejmowana ze stosu i przypisywana jako warto instrukcji wywoania funkcji. Nastpnie ze stosu zdejmowana jest warto odoona w kroku 1.; warto ta zostaje umieszczona we wskaniku instrukcji. Program, posiadajc warto zwrcon przez funkcj, wznawia dziaanie od instrukcji nastpujcej bezporednio po instrukcji wywoania funkcji. Niektre ze szczegw tego procesu zmieniaj si w zalenoci od kompilatora i komputera, ale podstawowy jego przebieg jest niezmienny. Gdy wywoujesz funkcj, na stosie odkadany jest adres powrotu i argumenty. W trakcie dziaania tych funkcji, na stos s odkadane zmienne lokalne. Gdy funkcja wraca, ze stosu zostaje usunite wszystko. W nastpnych rozdziaach poznamy inne miejsca pamici, uywane do przechowywania danych, ktre musz istnie duej ni czas ycia funkcji.

Rozdzia 6. Programowanie zorientowane obiektowo


Klasy rozszerzaj wbudowane w C++ moliwoci, uatwiajce rozwizywanie zoonych, rzeczywistych problemw. Z tego rozdziau dowiesz si: czym s klasy i obiekty, jak definiowa now klas oraz tworzy obiekty tej klasy, czym s funkcje i dane skadowe, czym s konstruktory i jak z nich korzysta.

Czy C++ jest zorientowane obiektowo?


Jzyk C++ stanowi pomost pomidzy programowaniem zorientowanym obiektowo a jzykiem C, najpopularniejszym jzykiem programowania aplikacji komercyjnych. Celem jego autorw byo stworzenie obiektowo zorientowanego jzyka dla tej szybkiej i efektywnej platformy. Jzyk C jest etapem porednim pomidzy wysokopoziomowymi jzykami aplikacji firmowych, takimi jak COBOL, a niskopoziomowym, wysokowydajnym, lecz trudnym do uycia asemblerem. C wymusza programowanie strukturalne, w ktrym poszczeglne zagadnienia s dzielone na mniejsze jednostki powtarzalnych dziaa, zwanych funkcjami. Programy, ktre piszemy na pocztku dwudziestego pierwszego wieku, s duo bardziej zoone ni te, ktre byy pisane pod koniec wieku dwudziestego. Programy stworzone w jzykach proceduralnych s trudne w zarzdzaniu i konserwacji, a ich rozbudowa jest niemoliwa. Graficzne interfejsy uytkownika, Internet, telefonia cyfrowa i bezprzewodowa oraz wiele innych technologii, znacznie zwikszyy poziom skomplikowania nowych projektw, a wymagania konsumentw dotyczce jakoci interfejsu uytkownika wzrosy.
Usunito: u

W obliczu rosncych wymaga, programici bacznie przyjrzeli si przemysowi informatycznemu. Wnioski, do jakich doszli, byy co najmniej przygnbiajce. Oprogramowanie powstawao z opnieniem, posiadao bdy, dziaao niestabilnie i byo drogie. Projekty regularnie przekraczay budet i trafiay na rynek z opnieniem. Koszt obsugi tych projektw by znaczny, zmarnowano ogromne iloci pienidzy. Jedynym wyjciem z tej sytuacji okazao si tworzenie oprogramowania zorientowanego obiektowo. Jzyki programowania obiektowego stworzyy silne wizy pomidzy strukturami danych a metodami manipulowania tymi danymi. A co najwaniejsze, w programowaniu zorientowanym obiektowo nie ju musisz myle o strukturach danych i manipulujcych nimi funkcjami; mylisz o obiektach. Rzeczach. wiat jest wypeniony przedmiotami: samochodami, psami, drzewami, chmurami, kwiatami. Rzeczy. Kada rzecz ma charakterystyk (szybki, przyjazny, brzowy, puszysty, adny). Wikszo rzeczy cechuje jakie zachowanie (ruch, szczekanie, wzrost, deszcz, uwid). Nie mylimy o danych psa i o tym, jak moglibymy nimi manipulowa mylimy o psie jako o rzeczy: do czego jest podobny i co robi.

Tworzenie nowych typw


Poznae ju kilka typw zmiennych, m.in. liczby cakowite i znaki. Typ zmiennej dostarcza nam kilka informacji o niej. Na przykad, jeli zadeklarujesz zmienne Height (wysoko) i Width (szeroko) jako liczby cakowite typu unsigned short int, wiesz, e w kadej z nich moesz przechowa warto z przedziau od 0 do 65 535 (przy zaoeniu e typ unsigned short int zajmuje dwa bajty pamici). S to liczby cakowite bez znaku; prba przechowania w nich czegokolwiek innego powoduje bd. W zmiennej typu unsigned short nie moesz umieci swojego imienia, nie powiniene nawet prbowa. Deklarujc te zmienne jako unsigned short int, wiesz, e moesz doda do siebie wysoko i szeroko oraz przypisa t warto innej zmiennej. Typ zmiennych informuje: o ich rozmiarze w pamici, jaki rodzaj informacji mog zawiera, jakie dziaania mona na nich wykonywa.
Usunito: 6

W tradycyjnych jzykach, takich jak C, typy byy wbudowane w jzyk. W C++ programista moe rozszerzy jzyk, tworzc potrzebne mu typy, za kady z tych nowych typw moe by w peni funkcjonalny i dysponowa t sam si, co typy wbudowane.

Po co tworzy nowy typ?


Programy s zwykle pisane w celu rozwizania jakiego realnego problemu, takiego jak prowadzenie rejestru pracownikw czy symulacja dziaania systemu grzewczego. Cho istnieje moliwo rozwizywania tych problemw za pomoc programw napisanych wycznie przy uyciu liczb cakowitych i znakw, jednak w przypadku wikszych, bardziej rozbudowanych

problemw, duo atwiej jest stworzy reprezentacje obiektw, o ktrych si mwi. Innymi sowy, symulowanie dziaania systemu grzewczego bdzie atwiejsze, gdy stworzymy zmienne reprezentujce pomieszczenia, czujniki ciepa, termostaty i bojlery. Im bardziej te zmienne odpowiadaj rzeczywistoci, tym atwiejsze jest napisanie programu.

Klasy i skadowe
Nowy typ zmiennych tworzy si, deklarujc klas. Klasa jest waciwie grup zmiennych czsto o rnych typach skojarzonych z zestawem odnoszcych si do nich funkcji. Jedn z moliwoci mylenia o samochodzie jest potraktowanie go jako zbioru k, drzwi, foteli, okien, itd. Inna moliwo to wyobraenie sobie, co samochd moe zrobi: jedzi, przyspiesza, zwalnia, zatrzymywa si, parkowa, itd. Klasa umoliwia kapsukowanie, czyli upakowanie, tych rnych czci oraz rnych dziaa w jeden zbir, ktry jest nazywana obiektem. Upakowanie wszystkiego, co wiesz o samochodzie, w jedn klas przynosi programicie liczne korzyci. Wszystko jest na miejscu, uatwia to odwoywanie si, kopiowanie i manipulowanie danymi. Klienty twojej klasy tj. te czci programu, ktre z niej korzystaj mog uywa twojego obiektu bez zastanawiania si, co znajduje si w rodku i jak on dziaa. Klasa moe skada si z dowolnej kombinacji zmiennych prostych oraz zmiennych innych klas. Zmienna wewntrz klasy jest nazywana zmienn skadow lub dan skadow. Klasa Car (samochd) moe posiada skadowe reprezentujce siedzenia, typ radia, opony, itd. Zmienne skadowe s zmiennymi w danej klasie. Stanowi one cz klasy, tak jak koa i silnik stanowi cz samochodu. Funkcje w danej klasie zwykle manipuluj zmiennymi skadowymi. Funkcje klasy nazywa si funkcjami skadowymi lub metodami klasy. Metodami klasy Car mog by Start() (uruchom) oraz Brake() (hamuj). Klasa Cat (kot) moe posiada zmienne skadowe, reprezentujce wiek i wag; jej metodami mog by Sleep() (pij), Meow() (miaucz) czy ChaseMice() (ap myszy). Funkcje skadowe (metody) s funkcjami w klasie. Podobnie jak zmienne skadowe, stanowi cz klasy i okrelaj, co dana klasa moe zrobi.
Usunito: poczonych Usunito: powizanych

Deklarowanie klasy
Aby zadeklarowa klas, uyj sowa kluczowego class, po ktrym nastpuje otwierajcy nawias klamrowy, a nastpnie lista danych skadowych i metod tej klasy. Deklaracja koczy si zamykajcym nawiasem klamrowym i rednikiem. Oto deklaracja klasy o nazwie Cat (kot):
class Cat { unsigned int unsigned int void Meow(); };

itsAge; itsWeight;

Zadeklarowanie takiej klasy nie powoduje zaalokowania pamici dla obiektu Cat. Informuje jedynie kompilator, czym jest typ Cat, jakie dane zawiera (itsAge jego wiek oraz itsWeight jego waga) oraz co moe robi (Meow() miaucz). Informuje take kompilator, jak dua jest zmienna typu Cat to jest, jak duo miejsca w pamici ma przygotowa w przypadku tworzenia zmiennej typu Cat. W tym przykadzie, o ile typ int ma cztery bajty, zmienna typu Cat zajmuje osiem bajtw: cztery bajty dla zmiennej itsAge i cztery dla zmiennej itsWeight. Funkcja Meow() nie zajmuje miejsca, gdy dla funkcji skadowych (metod) miejsce nie jest rezerwowane.

Usunito: a

Kilka sw o konwencji nazw


Jako programista, musisz nazwa wszystkie swoje zmienne skadowe, funkcje skadowe oraz klasy. Jak przeczytae w rozdziale 3., Stae i zmienne, nazwy te powinny by zrozumiae i znaczce. Dobrymi nazwami klas mog by wspomniana Cat, Rectangle (prostokt) czy Employee (pracownik). Meow(), ChaseMice() czy StopEngine() (zatrzymaj silnik) rwnie s dobrymi nazwami funkcji, gdy informuj, co robi te funkcje. Wielu programistw nadaje nazwom zmiennych skadowych przedrostek its (jego), tak jak w zmiennych itsAge, itsWeight czy itsSpeed (jego szybko). Pomaga to w odrnieniu zmiennych skadowych od innych zmiennych. Niektrzy programici wol przedrostek my (mj), tak jak w nazwach myAge, myWeight czy mySpeed. Jeszcze inni uywaj po prostu litery m (od sowa member skadowa), czasem wraz ze znakiem podkrelenia (_): mAge i m_age, mWeight i m_weight czy mSpeed i m_speed. Jzyk C++ uwzgldnia wielko liter, dlatego wszystkie nazwy klas powinny przestrzega tej samej konwencji. Dziki temu nigdy nie bdziesz musia sprawdza pisowni nazwy klasy (czy to byo Rectangle, rectangle czy RECTANGLE?). Niektrzy programici lubi poprzedzi kad nazw klasy okrelon liter na przykad cCat czy cPerson podczas, gdy inni uywaj wycznie duych lub maych liter. Ja sam korzystam z konwencji, w ktrej wszystkie nazwy klas rozpoczynaj si od duej litery, tak jak Cat czy Person (osoba). Wielu programistw rozpoczyna wszystkie nazwy funkcji od duej litery, za wszystkie nazwy zmiennych od maej. Sowa zwykle rozdzielane s znakiem podkrelenia tak jak w Chase_Mice lub poprzez zastosowanie duej litery dla kadego sowa na przykad ChaseMice czy DrawCircle (rysuj okrg). Wane jest, by wybra okrelony styl i trzyma si go w kadym programie. Z czasem rozwiniesz swj styl nie tylko na konwencje nazw, ale take na wcicia, wyrwnanie nawiasw klamrowych oraz styl komentarzy.
UWAGA W firmach programistycznych powszechne jest okrelenie standardu wielu elementw stylu zapisu kodu rdowego. Sprawia on, e wszyscy programici mog atwo odczytywa wzajemnie swj kod.

Definiowanie obiektu
Definiowanie obiektu nowego typu przypomina definiowanie zmiennej cakowitej:

unsigned int GrossWeight; // definicja zmiennej typu unsigned int Cat Mruczek; // definicja zmiennej typu Cat

Ten kod definiuje zmienn o nazwie GrossWeight (czna waga), ktrej typem jest unsigned int. Oprcz tego definiuje zmienn o nazwie Mruczek, ktra jest obiektem klasy (typu) Cat.

Klasy a obiekty
Nigdy nie karmi si definicji kota, lecz konkretnego kota. Naley dokona rozrnienia pomidzy ide kota a konkretnym kotem, ktry wanie ociera si o twoje nogi. C++ rwnie dokonuje rozrnienia pomidzy klas Cat, bdc ide kota, a poszczeglnymi obiektami typu Cat. Tak wic Mruczek jest obiektem typu Cat, tak jak GrossWeight jest zmienn typu unsigned int. Obiekt jest indywidualnym egzemplarzem klasy.

Dostp do skadowych klasy


Gdy zdefiniujesz ju faktyczny obiekt Cat na przykad Mruczek w celu uzyskania dostpu do jego skadowych moesz uy operatora kropki (.). Aby zmiennej skadowej itsWeight obiektu Mruczek przypisa warto 50, powiniene napisa:
Mruczek.itsWeight = 50;

Aby wywoa funkcj Meow(), moesz napisa:


Mruczek.Meow();

Gdy uywasz metody klasy, oznacza to, e wywoujesz t metod. W tym przykadzie wywoae metod Meow() obiektu Mruczek.

Przypisywa naley obiektom, nie klasom


W C++ nie przypisuje si wartoci typom; przypisuje si je zmiennym. Na przykad, nie mona napisa:
int = 5; // le

Kompilator uzna to za bd, gdy nie mona przypisa wartoci pi typowi cakowitemu. Zamiast tego musisz zdefiniowa zmienn typu cakowitego i przypisa jej warto 5. Na przykad:

int x ; x = 5;

// definicja zmiennej typu int // ustawienie wartoci zmiennej x na 5

Jest to skrcony zapis stwierdzenia: Przypisz warto 5 zmiennej x, ktra jest zmienn typu int. Nie mona rwnie napisa:
Cat.itsAge = 5; // le

Kompilator uzna to za bd, gdy nie moesz przypisa wartoci 5 do elementu itsAge klasy Cat. Zamiast tego musisz zdefiniowa egzemplarz obiektu klasy Cat i dopiero wtedy przypisa warto jego skadowej. Na przykad:
Cat Mruczek; Mruczek.itsAge = 5; // podobnie jak // podobnie jak int x; x = 5;

Czego nie zadeklarujesz, tego klasa nie bdzie miaa


Przeprowad taki eksperyment: podejd do trzylatka i poka mu kota. Nastpnie powiedz: To jest Mruczek. Mruczek zna sztuczk. Mruczek, zaszczekaj! Dziecko rozemieje si i powie: Nie, guptasie, koty nie szczekaj! Jeli napisae:
Cat Mruczek; Mruczek.Bark(); // tworzy obiekt Cat o nazwie Mruczek // nakazuje Mruczkowi szczeka

Kompilator wypisze: Nie, guptasie, koty (cats) nie szczekaj! (By moe w twoim kompilatorze ten komunikat bdzie brzmia nieco inaczej.) Kompilator wie, e Mruczek nie moe szczeka, gdy klasa Cat nie posiada metody Bark() (szczekaj). Kompilator nie pozwoli Mruczkowi nawet zamiaucze, jeli nie zdefiniujesz dla niego funkcji Meow() (miaucz). TAK Do deklarowania klasy uywaj sowa kluczowego class. W celu uzyskania dostpu do zmiennych i funkcji skadowych klasy uywaj operatora kropki (.). NIE Nie myl deklaracji z definicj. Deklaracja mwi czym jest klasa, a definicja przygotowuje pami dla obiektu. Nie myl klasy z obiektem. Nie przypisuj klasie wartoci. Wartoci przypisuj danym skadowym obiektu.

Prywatne i publiczne
W deklaracji klasy uywanych jest take kilka innych sw kluczowych. Dwa najwaniejsze z nich to: public (publiczny) i private (prywatny). Wszystkie skadowe klasy dane i metody s domylnie prywatne. Prywatne skadowe mog by uywane tylko przez metody nalece do danej klasy. Skadowe publiczne s dostpne dla innych funkcji i klas. To rozrnienie jest wane, cho na pocztku moe sprawia kopot. Aby to lepiej wyjani, spjrzmy na poprzedni przykad:
class Cat { unsigned int unsigned int viod Meow(); };

itsAge; itsWeight;

W tej deklaracji, skadowe itsAge, itsWeight oraz Meow() s prywatne, gdy wszystkie skadowe klasy s prywatne domylnie. Oznacza to, e dopki nie postanowisz inaczej, pozostan one prywatne. Jeli jednak w funkcji main() napiszesz na przykad:
Cat Bobas; Bobas.itsAge = 5;

// bd! nie mona uywa prywatnych danych!

kompilator uzna to za bd. We wczeniejszej deklaracji powiedziae kompilatorowi, e skadowych itsAge, itsWeight oraz Meow() bdziesz uywa tylko w funkcjach skadowych klasy Cat. W powyszym fragmencie kodu prbujesz odwoa si do zmiennej skadowej obiektu Bobas spoza metody klasy Cat. To, e Bobas jest obiektem klasy Cat, nie oznacza, e moesz korzysta z tych elementw obiektu Bobas, ktre s prywatne. Wanie to jest rdem niekoczcych si kopotw pocztkujcych programistw C++. Ju sysz, jak narzekasz: Hej! Wanie napisaem, e Bobas jest kotem, tj. obiektem klasy Cat. Dlaczego Bobas nie ma dostpu do swojego wasnego wieku? Odpowied brzmi: Bobas ma dostp, ale ty nie masz. Bobas, w swoich wasnych metodach, ma dostp do wszystkich swoich skadowych, zarwno publicznych, jak i prywatnych. Nawet, jeli to ty tworzysz obiekt klasy Cat, nie moesz przeglda ani zmienia tych jego skadowych, ktre s prywatne. Aby mie dostp do skadowych obiektu Cat, powiniene napisa:
class Cat { public: Usunito:

unsigned int unsigned int void Meow(); };

itsAge; itsWeight;

Teraz skadowe itsAge, itsWeight oraz Meow() s publiczne. Bobas.itsAge = 5; kompiluje si bez problemw. Listing 6.1 przedstawia deklaracj klasy Cat z publicznymi zmiennymi skadowymi. Listing 6.1. Dostp do publicznych skadowych w prostej klasie
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: // Demonstruje deklaracje klasy oraz // definicje obiektu tej klasy. #include <iostream> class Cat { public: int itsAge; int itsWeight; }; // deklaruje klas Cat (kot) // // // // nastpujce po tym skadowe s publiczne zmienna skadowa zmienna skadowa zwr uwag na rednik

int main() { Cat Mruczek; Mruczek.itsAge = 5; // przypisanie do zmiennej skadowej std::cout << "Mruczek jest kotem i ma " ; std::cout << Mruczek.itsAge << " lat.\n"; return 0; }

Wynik
Mruczek jest kotem i ma 5 lat.

Analiza Linia 5. zawiera sowo kluczowe class. Informuje ono kompilator, e nastpuje po nim deklaracja klasy. Nazwa nowej klasy nastpuje bezporednio po sowie kluczowym class. W tym przypadku nazw klasy jest Cat (kot). Ciao deklaracji rozpoczyna si w linii 6. od otwierajcego nawiasu klamrowego i koczy si zamykajcym nawiasem klamrowym i rednikiem w linii 10. Linia 7. zawiera sowo kluczowe public, ktre wskazuje, e wszystko, co po nim nastpi, bdzie publiczne, a do natrafienia na sowo kluczowe private lub koniec deklaracji klasy. Linie 8. i 9. zawieraj deklaracje skadowych klasy, itsAge (jego wiek) oraz itsWeight (jego waga). W linii 13. rozpoczyna si funkcja main(). W linii 15. Mruczek jest definiowany jako egzemplarz klasy Cat tj. jako obiekt klasy Cat. W linii 16. wiek Mruczka jest ustawiany na 5. W liniach 17. i 18. zmienna skadowa itsAge zostaje uyta do wypisania informacji o kocie Mruczku.

UWAGA Sprbuj wykomentowa lini 7., po czym skompiluj program ponownie. W linii 16. wystpi bd, gdy zmienna skadowa itsAge nie bdzie ju skadow publiczn. Domylnie, wszystkie skadowe klasy s prywatne.

Oznaczanie danych skadowych jako prywatnych


Powiniene przyj jako ogln regu, e dane skadowe klasy naley utrzymywa jako prywatne. W zwizku z tym musisz stworzy publiczne funkcje skadowe, zwane funkcjami dostpowymi lub akcesorami. Funkcje te umoliwi odczyt zmiennych skadowych i przypisywanie im wartoci. Te funkcje dostpowe (akcesory) s funkcjami skadowymi, uywanymi przez inne czci programu w celu odczytywania i ustawiania prywatnych zmiennych skadowych. Publiczny akcesor jest funkcj skadow klasy, uywan albo do odczytu wartoci prywatnej zmiennej skadowej klasy, albo do ustawiania wartoci tej zmiennej. Dlaczego miaby utrudnia sobie ycie dodatkowym poziomem poredniego dostpu? atwiej ni posugiwa si akcesorami jest uywa danych,. Akcesory umoliwiaj oddzielenie szczegw przechowywania danych klasy od szczegw jej uywania. Dziki temu moesz zmienia sposb przechowywania danych klasy bez koniecznoci przepisywania funkcji, ktre z tych danych korzystaj. Jeli funkcja, ktra chce pozna wiek kota, odwoa si bezporednio do zmiennej itsAge klasy Cat, bdzie musiaa zosta przepisana, jeeli ty, jako autor klasy Cat, zdecydujesz si na zmian sposobu przechowywania tej zmiennej. Jednak posiadajc funkcj skadow GetAge() (pobierz wiek), klasa Cat moe atwo zwrci waciw warto bez wzgldu na to, w jaki sposb

Usunito: A Usunito: zmienn

przechowywany bdzie wiek. Funkcja wywoujca nie musi wiedzie, czy jest on przechowywany jako zmienna typu unsigned int czy long, lub czy wiek jest obliczany w miar potrzeb. Ta technika uatwia zapanowanie nad programem. Przedua istnienie kodu, gdy zmiany projektowe nie powoduj, e program staje si przestarzay. Listing 6.2 przedstawia klas Cat zmodyfikowan tak, by zawieraa prywatne dane skadowe i publiczne akcesory. Zwr uwag, e ten listing przedstawia wycznie deklaracj klasy, nie ma w nim kodu wykonywalnego. Listing 6.2. Klasa z akcesorami
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: // Deklaracja klasy Cat // Dane skadowe s prywatne, publiczne akcesory porednicz // w ustawianiu i odczytywaniu wartoci skadowych prywatnych class Cat { public: // publiczne akcesory unsigned int GetAge(); void SetAge(unsigned int Age); unsigned int GetWeight(); void SetWeight(unsigned int Weight); // publiczna funkcja skadowa void Meow();

16: 17: 18: 19: 20: 21: 22:

// prywatne dane skadowe private: unsigned int itsAge; unsigned int itsWeight; };

Analiza Ta klasa posiada pi metod publicznych. Linie 8. i 9. zawieraj akcesory dla skadowej itsAge. Linie 11. i 12. zawieraj akcesory dla skadowej itsWeight. Te akcesory ustawiaj zmienne skadowe i zwracaj ich wartoci. W linii 15. jest zadeklarowana publiczna funkcja skadowa Meow(). Ta funkcja nie jest akcesorem. Nie zwraca wartoci ani ich nie ustawia; wykonuje inn usug dla klasy wypisuje sowo Miau. Zmienne skadowe s zadeklarowane w liniach 19. i 20. Aby ustawi wiek Mruczka, powiniene przekaza warto metodzie SetAge() (ustaw wiek), na przykad:
Cat Mruczek; Mruczek.SetAge(5); // ustawia wiek Mruczka // uywajc publicznego akcesora

Prywatno a ochrona
Zadeklarowanie metod lub danych jako prywatnych umoliwia kompilatorowi wyszukanie w programach pomyek, zanim stan si one bdami. Kady szanujcy si programista potrafi znale sposb na obejcie prywatnoci skadowych. Stroustrup, autor jzyka C++, stwierdza e ...mechanizmy ochrony z poziomu jzyka chroni przed pomyk, a nie przed wiadomym oszustwem. (WNT, 1995).
Sowo kluczowe class

Skadnia sowa kluczowego class jest nastpujca:


class nazwa_klasy { // sowa kluczowe kontroli dostpu // zadeklarowane zmienne i skadowe klasy };

Sowo kluczowe class suy do deklarowania nowych typw. Klasa stanowi zbir danych skadowych klasy, ktre s zmiennymi rnych typw, take innych klas. Klasa zawiera take funkcje klasy tzw. metody ktre s funkcjami uywanymi do manipulowania danymi w danej klasie i wykonywania innych usug dla klasy.

Usunito: danych

Obiekty nowego typu definiuje si w taki sam sposb, w jaki definiuje si inne zmienne. Naley okreli typ (klas), a po nim nazw zmiennej (obiektu). Do uzyskania dostpu do funkcji i danych klasy suy operator kropki (.).

Sowa kluczowe kontroli dostpu okrelaj, ktre sekcje klasy s prywatne, a ktre publiczne. Domylnie wszystkie skadowe klasy s prywatne. Kade sowo kluczowe zmienia kontrol dostpu od danego miejsca a do koca klasy, lub kontrol wystpienia nastpnego sowa kluczowego kontroli dostpu. Deklaracja klasy koczy si zamykajcym nawiasem klamrowym i rednikiem.

Przykad 1
class Cat { public: unsigned int Age; unsigned int Weight; void Meow(); }; Cat Mruczek; Mruczek.Age = 8; Mruczek.Weight = 18; Mruczek.Meow();

Przykad 2
class Car { public: // pi nastpnych skadowych jest publicznych void Start(); void Accelerate(); void Brake(); void SetYear(int year); int GetYear(); private: // pozostaa cz jest prywatna int Year; char Model [255]; }; // koniec deklaracji klasy Car OldFaithful; // tworzy egzemplarz klasy int bought; // lokalna zmienna typu int OldFaithful.SetYear(84); // ustawia skadow Year na 84 bought = OldFaithful.GetYear(); // ustawia zmienn bought na 84 OldFaithful.Start(); //wywouje metod Start

TAK Deklaruj zmienne skadowe jako prywatne. Uywaj publicznych akcesorw, czyli publicznych funkcji dostpowych.

NIE Nie uywaj prywatnych zmiennych skadowych klasy poza t klas.


Usunito: .

Odwouj si do prywatnych zmiennych skadowych z funkcji skadowych klasy.

Usunito: e Usunito: skadowych

Implementowanie metod klasy


Akcesory stanowi publiczny interfejs do prywatnych danych klasy. Kady akcesor musi posiada, wraz z innymi zadeklarowanymi metodami klasy, implementacj. Implementacja jest nazywana definicj funkcji. Definicja funkcji skadowej rozpoczyna si od nazwy klasy, po ktrej wystpuj dwa dwukropki, nazwa funkcji i jej parametry. Listing 6.3 przedstawia pen deklaracj prostej klasy Cat, wraz z implementacj jej akcesorw i jednej oglnej funkcji tej klasy. Listing 6.3. Implementacja metod prostej klasy
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: // Demonstruje deklarowanie klasy oraz // definiowanie jej metod #include <iostream> class Cat { public: int GetAge(); void SetAge (int age); void Meow(); private: int itsAge; }; // dla cout // pocztek deklaracji klasy // // // // // // pocztek sekcji publicznej akcesor akcesor oglna funkcja pocztek sekcji prywatnej zmienna skadowa Usunito: klasy

// GetAge, publiczny akcesor // zwracajcy warto skadowej itsAge int Cat::GetAge() { return itsAge; } // definicja SetAge, akcesora // publicznego // ustawiajcego skadow itsAge void Cat::SetAge(int age) { // ustawia zmienn skadow itsAge // zgodnie z wartoci przekazan w parametrze age itsAge = age; } // definicja metody Meow // zwraca: void // parametery: brak // dziaanie: wypisuje na ekranie sowo "miauczy" void Cat::Meow() { std::cout << "Miauczy.\n";

39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52:

} // tworzy kota, ustawia jego wiek, sprawia, // e miauczy, wypisuje jego wiek i ponownie miauczy. int main() { Cat Mruczek; Mruczek.SetAge(5); Mruczek.Meow(); std::cout << "Mruczek jest kotem i ma " ; std::cout << Mruczek.GetAge() << " lat.\n"; Mruczek.Meow(); return 0; }

Wynik
Miauczy. Mruczek jest kotem i ma 5 lat. Miauczy.

Analiza Linie od 5. do 13. zawieraj definicj klasy Cat (kot). Linia 7. zawiera sowo kluczowe public, ktre informuje kompilator, e to, co po nim nastpuje, jest zestawem publicznych skadowych. Linia 8. zawiera deklaracj publicznego akcesora GetAge() (pobierz wiek). GetAge() zapewnia dostp do prywatnej zmiennej skadowej itsAge (jego wiek), zadeklarowanej w linii 12. Linia 9. zawiera publiczny akcesor SetAge() (ustaw wiek). Funkcja SetAge() otrzymuje parametr typu int, ktry nastpnie przypisuje skadowej itsAge. Linia 10. zawiera deklaracj metody Meow() (miaucz). Funkcja Meow() nie jest akcesorem. Jest to oglna metoda klasy, wypisujca na ekranie sowo Miauczy. Linia 11. rozpoczyna sekcj prywatn, ktra obejmuje jedynie zadeklarowan w linii 12. prywatn skadow itsAge. Deklaracja klasy koczy si zamykajcym nawiasem klamrowym i rednikiem. Linie od 17. do 20. zawieraj definicj skadowej funkcji GetAge(). Ta metoda nie ma parametrw i zwraca warto cakowit. Zauwa, e ta metoda klasy zawiera nazw klasy, dwa dwukropki oraz nazw funkcji (linia 17.). Ta skadnia informuje kompilator, e definiowana funkcja GetAge() jest wanie t funkcj, ktra zostaa zadeklarowana w klasie Cat. Poza formatem tego nagwka, definiowanie funkcji wasnej GetAge() niczym nie rni si od definiowania innych (zwykych) funkcji. Funkcja GetAge() posiada tylko jedn lini i zwraca po prostu warto zmiennej skadowej itsAge. Zauwa, e funkcja main() nie ma dostpu do tej zmiennej skadowej, gdy jest ona prywatna dla klasy Cat. Funkcja main() ma za to dostp do publicznej metody GetAge(). Poniewa ta metoda jest skadow klasy Cat, ma peny dostp do zmiennej itsAge. Dziki temu moe zwrci funkcji main() warto zmiennej itsAge. Linia 25. zawiera definicj funkcji skadowej SetAge(). Ta funkcja posiada parametr w postaci wartoci cakowitej i przypisuje skadowej itsAge jego warto (linia 29.). Poniewa jest skadow klasy Cat, ma bezporedni dostp do jej zmiennych prywatnych i publicznych.

Usunito: deklaracja Usunito: innych klas.

Linia 36. rozpoczyna definicj (czyli implementacj) metody Meow() klasy Cat. Jest to jednoliniowa funkcja wypisujca na ekranie sowo Miaucz, zakoczone znakiem nowej linii. Pamitaj, e znak \n powoduje przejcie do nowej linii. Linia 43. rozpoczyna ciao funkcji main(), czyli waciwy program. W tym przypadku funkcja main() nie posiada argumentw. W linii 45., funkcja main() deklaruje obiekt Cat o nazwie Mruczek. W linii 46. zmiennej itsAge tego obiektu jest przypisywana warto 5 (poprzez uycie akcesora SetAge()). Zauwa, e wywoanie tej metody nastpuje dziki uyciu nazwy obiektu (Mruczek), po ktrej zastosowano operator kropki (.) i nazw metody (SetAge()). W podobny sposb wywoywane s wszystkie inne metody wszystkich klas. Linia 47. wywouje funkcj skadow Meow(), za w linii 48. za pomoc akcesora GetAge(),wypisywany jest komunikat. Linia 50. ponownie wywouje funkcj Meow().

Usunito: wypisuje Usunito: Usunito:

Konstruktory i destruktory
Istniej dwa sposoby definiowania zmiennej cakowitej. Mona zdefiniowa zmienn, a nastpnie, w dalszej czci programu, przypisa jej warto. Na przykad:
int Weight; ... Weight = 7; // definiujemy zmienn // tu inny kod // przypisujemy jej warto

Moemy te zdefiniowa zmienn i jednoczenie zainicjalizowa j. Na przykad:


int Weight = 7; // definiujemy i inicjalizujemy wartoci 7

Inicjalizacja czy w sobie definiowanie zmiennej oraz pocztkowe przypisanie wartoci. Nic nie stoi na przeszkodzie temu, by zmieni pniej warto zmiennej. Inicjalizacja powoduje tylko e zmienna nigdy nie bdzie pozbawiona sensownej wartoci. W jaki sposb zainicjalizowa skadowe klasy? Klasy posiadaj specjalne funkcje skadowe, zwane konstruktorami. Konstruktor (ang. constructor) moe w razie potrzeby posiada parametry, ale nie moe zwraca wartoci nawet typu void. Konstruktor jest metod klasy o takiej samej nazwie, jak nazwa klasy. Gdy zadeklarujesz konstruktor, powiniene take zadeklarowa destruktor (ang. destructor). Konstruktor tworzy i inicjalizuje obiekt danej klasy, za destruktor porzdkuje obiekt i zwalnia pami, ktr moge w niej zaalokowa. Destruktor zawsze nosi nazw klasy, poprzedzon znakiem tyldy (~). Destruktory nie maj argumentw i nie zwracaj wartoci. Dlatego deklaracja destruktora klasy Cat ma nastpujc posta:
~Cat();

Domylne konstruktory i destruktory


Jeli nie zadeklarujesz konstruktora lub destruktora, zrobi to za ciebie kompilator. Istnieje wiele rodzajw konstruktorw; niektre z nich posiadaj argumenty, inne nie. Konstruktor, ktrego mona wywoa bez adnych argumentw, jest nazywany konstruktorem domylnym. Istnieje tylko jeden rodzaj destruktora. On take nie posiada argumentw. Jeli nie stworzysz konstruktora lub destruktora, kompilator stworzy je za ciebie. Konstruktor dostarczany przez kompilator jest konstruktorem domylnym czyli konstruktorem bez argumentw. Taki konstruktor domylny moesz stworzy samodzielnie. Stworzone przez kompilator domylny konstruktor i destruktor nie maj adnych argumentw, a na dodatek w ogle nic nie robi!
Usunito: nie posiadajcy

Uycie domylnego konstruktora


Do czego moe przyda si konstruktor, ktry nic nie robi? Jest to problem techniczny: wszystkie obiekty musz by konstruowane i niszczone, dlatego w odpowiednich momentach wywoywane s te nic nie robice funkcje. Aby mc zadeklarowa obiekt bez przekazywania parametrw, na przykad
Cat Filemon; // Filemon nie ma parametrw

musisz posiada konstruktor w postaci


Cat();

Gdy definiujesz obiekt klasy, wywoywany jest konstruktor. Gdyby konstruktor klasy Cat mia dwa parametry, mgby zdefiniowa obiekt Cat, piszc
Cat Mruczek (5, 7);

Gdyby konstruktor mia jeden parametr, napisaby


Cat Mruczek (3);

W przypadku, gdy konstruktor nie ma adnych parametrw (gdy jest konstruktorem domylnym), moesz opuci nawiasy i napisa
Cat Mruczek;

Jest to wyjtek od reguy, zgodnie z ktr wszystkie funkcje wymagaj zastosowania nawiasw, nawet jeli nie maj parametrw. Wanie dlatego moesz napisa:
Cat Mruczek;

Usunito:

Jest to interpretowane jako wywoanie konstruktora domylnego. Nie dostarczamy mu parametrw i pomijamy nawiasy. Zwr uwag, e nie musisz uywa domylnego konstruktora dostarczanego przez kompilator. Zawsze moesz napisa wasny konstruktor domylny tj. konstruktor bez parametrw. Moesz zastosowa w nim ciao funkcji, w ktrym moesz zainicjalizowa obiekt. Zgodnie z konwencj, gdy deklarujesz konstruktor, powiniene take zadeklarowa destruktor, nawet jeli nie robi on niczego. Nawet jeli destruktor domylny bdzie dziaa poprawnie, nie zaszkodzi zadeklarowa wasnego. Dziki niemu kod staje si bardziej przejrzysty. Listing 6.4 zawiera now wersj klasy Cat, w ktrej do zainicjalizowania obiektu kota uyto konstruktora. Wiek kota zosta ustawiony zgodnie z wartoci otrzyman jako parametr konstruktora. Listing 6.4. Uycie konstruktora i destruktora
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: // Demonstruje deklarowanie konstruktora // i destruktora dla klasy Cat // Domylny konstruktor zosta stworzony przez programist #include <iostream> class Cat { public: Cat(int initialAge); ~Cat(); int GetAge(); void SetAge(int age); void Meow(); private: int itsAge; }; // konstruktor klasy Cat Cat::Cat(int initialAge) { itsAge = initialAge; } Cat::~Cat() { } // destruktor, nic nie robi // dla cout // pocztek deklaracji klasy // // // // // pocztek sekcji publicznej konstruktor destruktor akcesor akcesor

// pocztek sekcji prywatnej // zmienna skadowa

// GetAge, publiczny akcesor // zwraca warto skadowej itsAge int Cat::GetAge() { return itsAge; }

35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67:

// definicja SetAge, akcesora // publicznego void Cat::SetAge(int age) { // ustawia zmienn skadow itsAge // zgodnie z wartoci przekazan w parametrze age itsAge = age; } // definicja metody Meow // zwraca: void // parametery: brak // dziaanie: wypisuje na ekranie sowo "miauczy" void Cat::Meow() { std::cout << "Miauczy.\n"; } // tworzy kota, ustawia jego wiek, sprawia, // e miauczy, wypisuje jego wiek i ponownie miauczy. int main() { Cat Mruczek(5); Mruczek.Meow(); std::cout << "Mruczek jest kotem i ma " ; std::cout << Mruczek.GetAge() << " lat.\n"; Mruczek.Meow(); Mruczek.SetAge(7); std::cout << "Teraz Mruczek ma " ; std::cout << Mruczek.GetAge() << " lat.\n"; return 0; }

Wynik
Miauczy. Mruczek jest kotem i ma 5 lat. Miauczy. Teraz Mruczek ma 7 lat.

Analiza Listing 6.4 przypomina listing 6.3, jednak w linii 9. dodano konstruktor, posiadajcy argument w postaci wartoci cakowitej. Linia 10. deklaruje destruktor, ktry nie posiada parametrw. Destruktory nigdy nie maj parametrw, za destruktory i konstruktory nie zwracaj adnych wartoci nawet typu void. Linie od 19. do 22. zawieraj implementacj konstruktora. Jest ona podobna do implementacji akcesora SetAge(). Konstruktor nie zwraca wartoci. Linie od 24. do 26. przedstawiaj implementacj destruktora ~Cat(). Ta funkcja nie robi nic, ale jeli deklaracja klasy zawiera deklaracj destruktora, zdefiniowany musi zosta wtedy take ten destruktor. Linia 58. zawiera definicj obiektu Mruczek, stanowicego egzemplarz klasy Cat. Do konstruktora obiektu Mruczek przekazywana jest warto 5. Nie ma potrzeby wywoywania funkcji SetAge(), gdy Mruczek zosta stworzony z wartoci 5 znajdujc si w zmiennej

skadowej itsAge, tak jak pokazano w linii 61. W linii 63. zmiennej itsAge obiektu Mruczek jest przypisywana warto 7. T now warto wypisuje linia 65. TAK W celu zainicjalizowania obiektw uywaj konstruktorw. NIE Pamitaj, e konstruktory i destruktory nie mog zwraca wartoci. Pamitaj, e destruktory nie mog mie parametrw.

Funkcje skadowe const


Jeli zadeklarujesz metod klasy jako const, obiecujesz w ten sposb, e metoda ta nie zmieni wartoci adnej ze skadowych klasy. Aby zadeklarowa metod w ten sposb, umie sowo kluczowe const za nawiasami, lecz przed rednikiem. Pokazana poniej deklaracja funkcji skadowej const o nazwie SomeFunction() nie posiada argumentw i zwraca typ void:
void SomeFunction() const;

Wraz z modyfikatorem const czsto deklarowane s akcesory. Klasa Cat posiada dwa akcesory:
void SetAge(int anAge); int GetAge();

Funkcja SetAge() nie moe by funkcj const, gdy modyfikuje warto zmiennej skadowej itsAge. Natomiast funkcja GetAge() moe by const, gdy nie modyfikuje wartoci adnej ze skadowych klasy. Funkcja GetAge() po prostu zwraca biec warto skadowej itsAge. Zatem deklaracje tych funkcji mona przepisa nastpujco:
void SetAge(int anAge); int GetAge() const;

Gdy zadeklarujesz funkcj jako const, za implementacja tej funkcji modyfikuje obiekt poprzez modyfikacj wartoci ktrejkolwiek ze skadowych, kompilator zgosi bd. Na przykad, gdy napiszesz funkcj GetAge() w taki sposb, e bdziesz zapamitywa ilo zapyta o wiek kota, spowodujesz bd kompilacji. Jest to spowodowane tym, e wywoujc t metod, modyfikujesz zawarto obiektu Cat.

UWAGA Deklaruj funkcje jako const wszdzie, gdzie to jest moliwe. Deklaruj je tam, gdzie nie przewidujesz modyfikowania obiektu. Kompilator moe w ten sposb pomc ci w wykryciu bdw w programie; tak jest szybciej i dokadniej.

Usunito: Uywaj Usunito:

Deklarowanie funkcji jako const wszdzie tam, gdzie jest to moliwe, naley do tradycji programistycznej. Za kadym razem, gdy to zrobisz, umoliwisz kompilatorowi wykrycie pomyki zanim stanie si ona bdem, ktry ujawni si ju podczas dziaania programu.

Interfejs a implementacja
Jak wiesz, klienty s tymi elementami programu, ktre tworz i wykorzystuj obiekty twojej klasy. Publiczny interfejs swojej klasy deklaracj klasy moesz traktowa jako kontrakt z tymi klientami. Ten kontrakt informuje, jak zachowuje si dana klasa. Na przykad, w deklaracji klasy Cat, stworzye kontrakt informujcy, e wiek kadego kota moe by zainicjalizowany w jego konstruktorze, modyfikowany za pomoc akcesora SetAge() oraz odczytywany za pomoc akcesora GetAge(). Oprcz tego obiecujesz, e kady kot moe miaucze (funkcj Meow()). Zwr uwag, e w publicznym interfejsie nie ma ani sowa o zmiennej skadowej itsAge; jest to szczeg implementacji, ktry nie stanowi elementu kontraktu. Na danie dostarczysz wieku (GetAge()) i ustawisz go (SetAge()), ale sam mechanizm (itsAge) jest niewidoczny. Gdy uczynisz funkcj GetAge()funkcj const a powiniene to zrobi kontrakt obiecuje take, e funkcja GetAge() nie modyfikuje obiektu Cat, dla ktrego jest wywoana. C++ jest jzykiem zapewniajcym siln kontrol typw, co oznacza, e kompilator wymusza przestrzeganie kontraktu, zgaszajc bdy kompilacji za kadym razem, gdy naruszysz reguy tego kontraktu. Listing 6.5 przedstawia program, ktry nie skompiluje si z powodu naruszenia ustale takiego kontraktu.
OSTRZEENIE Listing 6.5 nie skompiluje si!

Listing 6.5. Przykad naruszenia ustale interfejsu


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: // Demonstruje bdy kompilacji // Ten program si nie kompiluje! #include <iostream> class Cat { public: Cat(int initialAge); ~Cat(); int GetAge() const; void SetAge (int age); void Meow(); private: int itsAge; }; // dla cout

// akcesor typu const

17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63:

// konstruktor klasy Cat, Cat::Cat(int initialAge) { itsAge = initialAge; std::cout << "Konstruktor klasy Cat\n"; } Cat::~Cat() // destruktor, nic nie robi { std::cout << "Destruktor klasy Cat\n"; } // GetAge, funkcja const, // ale narusza zasad const! int Cat::GetAge() const { return (itsAge++); // narusza const! } // definicja SetAge, publicznego // akcesora void Cat::SetAge(int age) { // ustawia zmienn skadow itsAge // zgodnie z wartoci przekazan w parametrze age itsAge = age; } // definicja metody Meow // zwraca: void // parametery: brak // dziaanie: wypisuje na ekranie sowo "miauczy" void Cat::Meow() { std::cout << "Miauczy.\n"; } // demonstruje rne naruszenia regu interfejsu // oraz wynikajce z tego bdy kompilatora int main() { Cat Mruczek; // nie pasuje do deklaracji Mruczek.Meow(); Mruczek.Bark(); // Nie, guptasie, koty nie szczekaj. Mruczek.itsAge = 7; // itsAge jest skadow prywatn return 0; }

Analiza Program w przedstawionej powyej postaci si nie kompiluje, wic nie ma wynikw dziaania. Pisanie go byo do zabawne, poniewa zawiera tak duo bdw. Linia 10 deklaruje funkcj GetAge() jako akcesor typu const i tak powinno by. Jednak w ciele funkcji GetAge(), w linii 32., inkrementowana jest zmienna skadowa itsAge. Poniewa ta metoda zostaa zadeklarowana jako const, nie moe zmienia wartoci tej zmiennej. Dlatego podczas kompilacji programu zostanie to zgoszone jako bd.

W linii 12., funkcja Meow() nie jest zadeklarowana jako const. Cho nie jest to bdem, stanowi zy obyczaj. Naleaoby wzi pod uwag, e ta metoda nie modyfikuje zmiennych skadowych klasy. Dlatego funkcja Meow() powinna by funkcj const. Linia 58. pokazuje definicj obiektu Mruczek klasy Cat. W tym programie klasa Cat posiada konstruktor, ktry wymaga podania argumentu, bdcego wartoci cakowit. Oznacza to, e musisz taki argument przekaza. Poniewa w linii 58. nie wystpuje argument konstruktora, kompilator zgosi bd.
UWAGA Jeli stworzysz jakikolwiek konstruktor, kompilator zrezygnuje z dostarczenia swojego konstruktora domylnego. Gdy stworzysz konstruktor wymagajcy parametru, nie bdziesz mia konstruktora domylnego, chyba e stworzysz go sam.

Linia 60. zawiera wywoanie metody Bark() dla obiektu Mruczek. Metoda Bark() nie zostaa zadeklarowana, wic jest niedozwolona. Linia 61. zawiera przypisanie wartoci 7 do zmiennej itsAge. Poniewa itsAge jest skadow prywatn, kompilator zgosi bd kompilacji.
Po co uywa kompilatora do wykrywania bdw?

Gdyby mona byo tworzy programy w stu procentach pozbawione bdw, byoby cudowanie, jednak tylko bardzo niewielu programistw jest w stanie tego dokona. Wielu programistw opracowao jednak system pozwalajcy zminimalizowa ilo bdw przez wczesne ich wykrycie i poprawienie.

Cho bdy kompilatora s irytujce i stanowi dla programisty przeklestwo, jednak s czym duo lepszym ni opisana dalej alternatywa. Jzyk o sabej kontroli typw umoliwia naruszanie zasad kontraktu bez sowa sprzeciwu ze strony kompilatora, jednak program moe zaama si w trakcie dziaania na przykad wtedy, gdy pracuje z nim twj szef.

Bdy czasu kompilacji tj. bdy wykryte podczas kompilowania programu s zdecydowanie lepsze ni bdy czasu dziaania tj. bdy wykryte podczas dziaania programu. S lepsze, gdy duo atwiej i precyzyjniej mona okreli ich przyczyn. Moe si zdarzy e program zostanie wykonany wielokrotnie bez wykonania wszystkich istniejcych cieek wykonania kodu. Dlatego bd czasu dziaania moe przez duszy czas pozosta niezauwaony. Bdy kompilacji s wykrywane podczas kadej kompilacji, s wic duo atwiejsze do zidentyfikowania i poprawienia. Celem dobrego programowania jest ochrona przed pojawianiem si bdw czasu dziaania. Jedn ze znanych i sprawdzonych technik jest wykorzystanie kompilatora do wykrycia pomyek ju na wczesnym etapie tworzenia programu.

Gdzie umieszcza deklaracje klasy i definicje metod


Kada funkcja, ktr zadeklarujesz dla klasy, musi posiada definicj. Definicja jest nazywana take implementacj funkcji. Podobnie jak w przypadku innych funkcji, definicja metody klasy posiada nagwek i ciao. Definicja musi znajdowa si w pliku, ktry moe zosta znaleziony przez kompilator. Wikszo kompilatorw C++ wymaga, by taki plik mia rozszerzenie .c lub .cpp. W tej ksice korzystamy z rozszerzenia .cpp, ale aby mie pewno, sprawd, czego oczekuje twj kompilator.
UWAGA Wiele kompilatorw zakada, e pliki z rozszerzeniem .c s programami C, za pliki z rozszerzeniem .cpp s programami C++. Moesz uywa dowolnego rozszerzenia, ale rozszerzenie .cpp wyeliminuje ewentualne nieporozumienia.

W pliku, w ktrym umieszczasz implementacj funkcji, moesz umieci rwnie jej deklaracj, ale nie naley to do dobrych obyczajw. Zgodnie z konwencj zaadoptowan przez wikszo programistw, deklaracje umieszcza si w tak zwanych plikach nagwkowych, zwykle posiadajcych t sam nazw, lecz z rozszerzeniem .h, .hp lub .hpp. W tej ksice dla plikw nagwkowych stosujemy rozszerzenie .hpp, ale sprawd w swoim kompilatorze, jakie rozszerzenie powiniene stosowa. Na przykad, deklaracj klasy Cat powiniene umieci w pliku o nazwie CAT.hpp, za definicj metod tej klasy w pliku o nazwie CAT.cpp. Nastpnie powiniene doczy do pliku .cpp plik nagwkowy, poprzez umieszczenie na pocztku pliku CAT.cpp nastpujcej dyrektywy:
#include "Cat.hpp"

Usunito: tym Usunito: funkcji

Informuje ona kompilator, by wstawi w tym miejscu zawarto pliku CAT.hpp tak, jakby j wpisa rcznie. Uwaga: niektre kompilatory nalegaj, by wielko liter w nazwie pliku w dyrektywie #include zgadzaa si z wielkoci liter w nazwie pliku na dysku. Dlaczego masz si trudzi, rozdzielajc program na pliki .hpp i .cpp, skoro i tak plik .hpp jest wstawiany do pliku .cpp? W wikszoci przypadkw klienty klasy nie dbaj o szczegy jej implementacji. Odczytanie pliku nagwkowego daje im wystarczajc ilo informacji by zignorowa plik implementacji. Poza tym, ten sam plik .hpp moesz docza do wielu rnych plikw .cpp.
UWAGA Deklaracja klasy mwi kompilatorowi, czym jest ta klasa, jakie dane zawiera oraz jakie funkcje posiada. Deklaracja klasy jest nazywana jej interfejsem, gdy informuje kompilator w jaki sposb ma z ni wspdziaa. Ten interfejs jest zwykle przechowywany w pliku .hpp, czsto nazywanym plikiem nagwkowym.

Definicja funkcji mwi kompilatorowi, jak dziaa dana funkcja. Definicja funkcji jest nazywana implementacj metody klasy i jest przechowywana w pliku .cpp. Szczegy dotyczce implementacji klasy nale wycznie do jej autora. Klienty klasy tj. czci programu

uywajce tej klasy nie musz, ani nie powinny wiedzie, jak zaimplementowane zostay funkcje.

Implementacja inline
Moesz poprosi kompilator, by uczyni zwyk funkcj funkcj inline, funkcjami inline mog sta si rwnie metody klasy. W tym celu naley umieci sowo kluczowe inline przed typem zwracanej wartoci. Na przykad, implementacja inline funkcji GetWeight() wyglda nastpujco:
inline int Cat::GetWeight() { return itsWeight; // zwraca dan skadow itsWeight }

Definicj funkcji mona take umieci w deklaracji klasy, co automatycznie sprawia, e ta funkcja staje si funkcj inline. Na przykad:
class Cat { public: int GetWeight() { return itsWeight; } void SetWeight(int aWeight); };

// inline

Zwr uwag na skadni definicji funkcji GetWeight(). Ciao funkcji inline zaczyna si natychmiast po deklaracji metody klasy; po nawiasach nie wystpuje rednik. Podobnie jak w innych funkcjach, definicja zaczyna si od otwierajcego nawiasu klamrowego i koczy zamykajcym nawiasem klamrowym. Jak zwykle, biae spacje nie maj znaczenia; moesz zapisa t deklaracj jako:
class Cat { public: int GetWeight() const { return itsWeight; } // inline void SetWeight(int aWeight); };

Listingi 6.6 i 6.7 odtwarzaj klas Cat, tym razem jednak deklaracja klasy zostaa umieszczona w pliku CAT.hpp, za jej definicja w pliku CAT.cpp. Oprcz tego, na listingu 6.7 akcesor Meow() zosta zadeklarowany jako funkcja inline.

Listing 6.6. Deklaracja klasy Cat w pliku CAT.hpp


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: #include <iostream> class Cat { public: Cat (int initialAge); ~Cat(); int GetAge() const { return itsAge;} // inline! void SetAge (int age) { itsAge = age;} // inline! void Meow() const { std::cout << "Miauczy.\n";} // inline! private: int itsAge; };

Listing 6.7. Implementacja klasy Cat w pliku CAT.cpp


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: // Demonstruje funkcje inline // oraz doczanie pliku nagwkowego // pamitaj o wczeniu plikw nagwkowych! #include "cat.hpp" Cat::Cat(int initialAge) { itsAge = initialAge; } Cat::~Cat() { } //konstruktor

//destruktor, nic nie robi

// tworzy kota, ustawia jego wiek, sprawia // e miauczy, wypisuje jego wiek i ponownie miauczy. int main() { Cat Mruczek(5); Mruczek.Meow(); std::cout << "Mruczek jest kotem i ma " ; std::cout << Mruczek.GetAge() << " lat.\n"; Mruczek.Meow(); Mruczek.SetAge(7); std::cout << "Teraz Mruczek ma " ; std::cout << Mruczek.GetAge() << " lat.\n"; return 0; }

Wynik
Miauczy. Mruczek jest kotem i ma 5 lat. Miauczy. Teraz Mruczek ma 7 lat.

Analiza

Kod zaprezentowany na listingach 6.6 i 6.7 jest podobny do kodu z listingu 6.4, trzy metody zostay zadeklarowane w pliku deklaracji jako inline, a deklaracja zostaa przeniesiona do pliku CAT.hpp (listing 6.6). Funkcja GetAge() jest deklarowana w linii 6., gdzie znajduje si take jej implementacja. Linie 7. i 8. zawieraj kolejne funkcje inline, jednak w stosunku do poprzednich, zwykych implementacji, dziaanie tych funkcji nie zmienia si. Linia 4. listingu 6.7 zawiera dyrektyw #include "cat.hpp", ktra powoduje wstawienie do pliku zawartoci pliku CAT.hpp. Doczajc plik CAT.hpp, informujesz prekompilator, by odczyta zawarto tego pliku i wstawi j w miejscu wystpienia dyrektywy #include (tak jakby, poczwszy od linii 5, sam wpisa t zawarto). Ta technika umoliwia umieszczenie deklaracji w pliku innym ni implementacja, a jednoczenie zapewnienie kompilatorowi dostpu do niej. W programach C++ technika ta jest powszechnie wykorzystywana. Zwykle deklaracje klas znajduj si w plikach .hpp, ktre s doczane do powizanych z nimi plikw .cpp za pomoc dyrektyw #include. Linie od 18. do 29. stanowi powtrzenie funkcji main() z listingu 6.4. Oznacza to, e funkcje inline dziaaj tak samo jak zwyke funkcje.

Klasy, ktrych danymi skadowymi s inne klasy


Budowanie zoonych klas przez deklarowanie prostszych klas i doczanie ich do deklaracji bardziej skomplikowanej klasy nie jest niczym niezwykym. Na przykad, moesz zadeklarowa klas koa, klas silnika, klas skrzyni biegw, itd., a nastpnie poczy je w klas samochd. Deklaruje to relacj posiadania. Samochd posiada silnik, koa i skrzyni biegw. Wemy inny przykad. Prostokt skada si z odcinkw. Odcinek jest zdefiniowany przez dwa punkty. Punkt jest zdefiniowany przez wsprzdn x i wsprzdn y. Listing 6.8 przedstawia pen deklaracj klasy Rectangle (prostokt), ktra moe wystpi w pliku RECTANGLE.hpp. Poniewa prostokt jest zdefiniowany jako cztery odcinki czce cztery punkty, za kady punkt odnosi si do wsprzdnej w ukadzie, najpierw zadeklarujemy klas Point (punkt) jako przechowujc wsprzdne x oraz y punktu. Listing 6.9 zawiera implementacje obu klas. Listing 6.8. Deklarowanie kompletnej klasy
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: // pocztek Rect.hpp #include <iostream> class Point // przechowuje wsprzdne x,y { // bez konstruktora, uywa domylnego public: void SetX(int x) { itsX = x; } void SetY(int y) { itsY = y; } int GetX()const { return itsX;} int GetY()const { return itsY;} private: int itsX; int itsY; Usunito: Taka deklaracja posiada zwizek relacji. Usunito: ma

14: }; // koniec deklaracji klasy Point 15: 16: 17: class Rectangle 18: { 19: public: 20: Rectangle (int top, int left, int bottom, int right); 21: ~Rectangle () {} 22: 23: int GetTop() const { return itsTop; } 24: int GetLeft() const { return itsLeft; } 25: int GetBottom() const { return itsBottom; } 26: int GetRight() const { return itsRight; } 27: 28: Point GetUpperLeft() const { return itsUpperLeft; } 29: Point GetLowerLeft() const { return itsLowerLeft; } 30: Point GetUpperRight() const { return itsUpperRight; } 31: Point GetLowerRight() const { return itsLowerRight; } 32: 33: void SetUpperLeft(Point Location) {itsUpperLeft = Location;} 34: void SetLowerLeft(Point Location) {itsLowerLeft = Location;} 35: void SetUpperRight(Point Location) {itsUpperRight = Location;} 36: void SetLowerRight(Point Location) {itsLowerRight = Location;} 37: 38: void SetTop(int top) { itsTop = top; } 39: void SetLeft (int left) { itsLeft = left; } 40: void SetBottom (int bottom) { itsBottom = bottom; } 41: void SetRight (int right) { itsRight = right; } 42: 43: int GetArea() const; 44: 45: private: 46: Point itsUpperLeft; 47: Point itsUpperRight; 48: Point itsLowerLeft; 49: Point itsLowerRight; 50: int itsTop; 51: int itsLeft; 52: int itsBottom; 53: int itsRight; 54: }; 55: // koniec Rect.hpp

Listing 6.9. RECTANGLE.cpp


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: // pocztek rect.cpp #include "rect.hpp" Rectangle::Rectangle(int top, int left, int bottom, int right) { itsTop = top; itsLeft = left; itsBottom = bottom; itsRight = right; itsUpperLeft.SetX(left); itsUpperLeft.SetY(top);

12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44:

itsUpperRight.SetX(right); itsUpperRight.SetY(top); itsLowerLeft.SetX(left); itsLowerLeft.SetY(bottom); itsLowerRight.SetX(right); itsLowerRight.SetY(bottom); } // oblicza obszar prostokta przez obliczenie // i pomnoenie szerokoci i wysokoci int Rectangle::GetArea() const { int Width = itsRight-itsLeft; int Height = itsTop - itsBottom; return (Width * Height); } int main() { //inicjalizuje lokaln zmienn typu Rectangle Rectangle MyRectangle (100, 20, 50, 80 ); int Area = MyRectangle.GetArea(); std::cout << "Obszar: " << Area << "\n"; std::cout << "Wsp. X lewego gornego rogu: "; std::cout << MyRectangle.GetUpperLeft().GetX(); return 0; }

Wynik
Obszar: 3000 Wsp. X lewego gornego rogu: 20

Analiza Linie od 3. do 14. listingu 6.8 deklaruj klas Point (punkt), ktra suy do przechowywania wsprzdnych x i y okrelonego punktu rysunku. W tym programie nie wykorzystujemy naleycie klasy Point. Jej zastosowania wymagaj jednak inne metody rysunkowe.
UWAGA Gdy nadasz klasie nazw Rectangle, niektre kompilatory zgosz bd, W takim przypadku po prostu zmie nazw klasy na myRectangle.
Usunito: swojej postaci, Usunito: Usunito:

W deklaracji klasy Point, w liniach 12. i 13., zadeklarowalimy dwie zmienne skadowe (itsX oraz itsY). Te zmienne przechowuj wsprzdne punktu. Zakadamy, e wsprzdna x ronie w prawo, a wsprzdna y w gr. Istniej take inne systemy. W niektrych programach okienkowych wsprzdna y ronie w d okna. Klasa Point uywa akcesorw inline, zwracajcych i ustawiajcych wsprzdne X i Y punktu. Te akcesory zostay zadeklarowane w liniach od 7. do 10. Punkty uywaj konstruktora i destruktora domylnego. W zwizku z tym ich wsprzdne trzeba ustawia jawnie.

Linia 17. rozpoczyna deklaracj klasy Rectangle (prostokt). Klasa ta kada si z czterech punktw reprezentujcych cztery naroniki prostokta. Konstruktor klasy Rectangle (linia 20.) otrzymuje cztery wartoci cakowite, top (grna), left (lewa), bottom (dolna) oraz right (prawa). Do czterech zmiennych skadowych (listing 6.9) kopiowane s cztery parametry konstruktora i tworzone s cztery punkty. Oprcz standardowych akcesorw, klasa Rectangle posiada funkcj GetArea() (pobierz obszar), zadeklarowan w linii 43. Zamiast przechowywa obszar w zmiennej, funkcja GetArea() oblicza go w liniach od 28. do 30. listingu 6.9. W tym celu oblicza szeroko i wysoko prostokta, nastpnie mnoy je przez siebie. Uzyskanie wsprzdnej x lewego grnego wierzchoka prostokta wymaga dostpu do punktu UpperLeft (lewy grny) i zapytania o jego wsprzdn X. Poniewa funkcja GetUpperLeft() jest funkcj klasy Rectangle, moe ona bezporednio odwoywa si do prywatnych danych tej klasy, wcznie ze zmienn (itsUpperLeft). Poniewa itsUpperLeft jest obiektem klasy Point, a zmienna itsX tej klasy jest prywatna, funkcja GetUpperLeft() nie moe odwoywa si do niej bezporednio. Zamiast tego, w celu uzyskania tej wartoci musi uy publicznego akcesora GetX().

Usunito: i 29

Linia 33. listingu 6.9 stanowi pocztek ciaa programu. Pami nie jest alokowana a do linii 36.; w obszarze tym nic si nie dzieje. Jedyna rzecz, jak zrobilimy, to poinformowanie kompilatora, jak ma stworzy punkt i prostokt (gdyby byy potrzebne w przyszoci). W linii 36. definiujemy obiekt typu Rectangle, przekazujc mu wartoci Top, Left, Bottom oraz Right. W linii 38. tworzymy lokaln zmienn Area (obszar) typu int. Ta zmienna przechowuje obszar stworzonego przez nas prostokta. Zmienn Area inicjalizujemy za pomoc wartoci zwrconej przez funkcj GetArea() klasy Rectangle. Klient klasy Rectangle moe stworzy obiekt tej klasy i uzyska jego obszar, nie znajc nawet implementacji funkcji GetArea(). Plik RECT.hpp zosta przedstawiony na listingu 6.8. Obserwujc plik nagwkowy, ktry zawiera deklaracj klasy Rectangle, programista moe wysnu wniosek, e funkcja GetArea() zwraca warto typu int. Sposb, w jaki funkcja GetArea() uzyskuje t warto, nie interesuje klientw klasy Rectangle. Autor klasy Rectangle mgby zmieni funkcj GetArea(); nie wpynoby to na programy, ktre z niej korzystaj.
Czsto zadawane pytanie

Jaka jest rnica pomidzy deklaracj a definicj?

Odpowied: Deklaracja wprowadza now nazw, lecz nie alokuje pamici; dokonuje tego definicja.

Wszystkie deklaracje (z kilkoma wyjtkami) s take definicjami. Najwaniejszym wyjtkiem jest deklaracja funkcji globalnej (prototyp) oraz deklaracja klasy (zwykle w pliku nagwkowym).

Struktury
Bardzo bliskim kuzynem sowa kluczowego class jest sowo kluczowe struct, uywane do deklarowania struktur. W C++ struktura jest odpowiednikiem klasy, ale wszystkie jej skadowe s domylnie publiczne. Moesz zadeklarowa struktur dokadnie tak, jak klas; moesz zastosowa w niej te same zmienne i funkcje skadowe. Gdy przestrzegasz jawnego deklarowania publicznych i prywatnych sekcji klasy, nie ma adnej rnicy pomidzy klas a struktur. Sprbuj wprowadzi do listingu 6.8 nastpujce zmiany: w linii 3., zmie class Point na struct Point, w linii 17., zmie class Rectangle na struct Rectangle.

Nastpnie skompiluj i uruchom program. Otrzymane wyniki nie powinny si od siebie rni.

Dlaczego dwa sowa kluczowe speniaj t sam funkcj


Prawdopodobnie zastanawiasz si dlaczego dwa sowa kluczowe speniaj t sam funkcj. Przyczyn naley szuka w historii jzyka. Jzyk C++ powstawa jako rozszerzenie jzyka C. Jzyk C posiada struktury, ale nie posiadaj one metod. Bjarne Stroustrup, twrca jzyka C++, rozbudowa struktury, ale zmieni ich nazw na klasy, odzwierciedlajc w ten sposb ich nowe, rozszerzone moliwoci. TAK Umieszczaj deklaracj klasy w pliku .hpp, za funkcje skadowe definiuj w pliku .cpp. Uywaj const wszdzie tam, gdzie jest to moliwe. Zanim przejdziesz dalej, postaraj si dokadnie zrozumie zasady dziaania klasy.

Rozdzia 7. Sterowanie przebiegiem dziaania programu


Wikszo dziaa programu powizanych jest z warunkowymi rozgazieniami i ptlami. W rozdziale 4., Wyraenia i instrukcje, poznae sposb, w jaki naley rozgazi dziaanie programu za pomoc instrukcji if. W tym rozdziale: dowiesz si, czym s ptle i jak si z nich korzysta, nauczysz si tworzy rnorodne ptle, poznasz alternatyw dla gboko zagniedonych instrukcji if-else.

Ptle
Wiele problemw programistycznych rozwizywanych jest przez powtarzanie operacji wykonywanych na tych samych danych. Dwie podstawowe techniki to: rekurencja (omawiana w rozdziale 5., Funkcje) oraz iteracja. Iteracja oznacza cige powtarzanie tych samych czynnoci. Podstawow metod wykorzystywan przy iteracji jest ptla.

Pocztki ptli: instrukcja goto


W pocztkowym okresie rozwoju informatyki, programy byy nieporadne, proste i krtkie. Ptle skaday si z etykiety, zestawu wykonywanych instrukcji i skoku. W C++ etykieta jest zakoczon dwukropkiem nazw (:). Etykieta moe by umieszczona po lewej stronie instrukcji jzyka C++, za skok odbywa si w wyniku wykonania instrukcji goto (id do) z nazw etykiety. Ilustruje to listing 7.1. Listing 7.1. Ptla z uyciem sowa kluczowego goto
0: // Listing 7.1

1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:

// Ptla z instrukcj goto #include <iostream> int main() { int counter = 0; // inicjalizujemy licznik loop: counter ++; // pocztek ptli std::cout << "Licznik: " << counter << "\n"; if (counter < 5) // sprawdzamy warto goto loop; // skok do pocztku std::cout << "Gotowe. Licznik: " << counter << ".\n"; return 0; }

Wynik
Licznik: 1 Licznik: 2 Licznik: 3 Licznik: 4 Licznik: 5 Gotowe. Licznik: 5.

Analiza W linii 7., zmienna counter (licznik) jest inicjalizowana wartoci 0. W linii 8 wystpuje etykieta loop (ptla), oznaczajca pocztek ptli. Zmienna counter jest inkrementowana, nastpnie wypisywana jest jej nowa warto. W linii 10. sprawdzana jest warto zmiennej. Gdy jest ona mniejsza od 5, wtedy instrukcja if jest prawdziwa i wykonywana jest instrukcja goto. W efekcie wykonanie programu wraca do linii 8. Program dziaa w ptli do chwili, gdy, warto zmiennej counter osignie 5; to powoduje e program wychodzi z ptli i wypisuje kocowy komunikat.

Dlaczego nie jest zalecane stosowanie instrukcji goto?


Programici unikaj instrukcji goto, i maj ku temu znaczce powody. Instrukcja goto umoliwia wykonanie skoku do dowolnego miejsca w kodzie rdowym, do przodu lub do tyu. Nierozwane uycie tej instrukcji sprawia e kod rdowy jest zagmatwany, nieestetyczny i trudny do przeanalizowania, kod taki nazywany kodem spaghetti.
Instrukcja goto

Aby uy instrukcji goto, powiniene napisa sowo kluczowe goto, a nastpnie nazw etykiety. Spowoduje to wykonanie skoku bezwarunkowego.

Przykad

if (value > 10) goto end; if (value < 10) goto end; cout << "Wartosc jest rowna 10!"; end: cout << "gotowe"; Usunito: wysoce

Aby unikn uycia instrukcji goto, opracowano bardziej skomplikowane, cile kontrolowalne instrukcje ptli: for, while oraz do...while.

Ptle while
Ptla while (dopki) powoduje powtarzanie zawartej w niej sekwencji instrukcji tak dugo, jak dugo zaczynajce ptl wyraenie warunkowe pozostaje prawdziwe. W przykadzie z listingu 7.1, licznik by inkrementowany a do osignicia wartoci 5. Listing 7.2 przedstawia ten sam program przepisany tak, aby mona byo skorzysta z ptli while. Listing 7.2. Ptla while
0: // Listing 7.2 1: // Ptla while 2: 3: #include <iostream> 4: 5: int main() 6: { 7: int counter = 0; // inicjalizacja warunku 8: 9: while(counter < 5) // sprawdzenie, czy warunek jest speniony 10: { 11: counter++; // ciao ptli 12: std::cout << "Licznik: " << counter << "\n"; 13: } 14: 15: std::cout << "Gotowe. Licznik: " << counter << ".\n"; 16: return 0; 17: }

Wynik
Licznik: 1 Licznik: 2 Licznik: 3 Licznik: 4 Licznik: 5 Gotowe. Licznik: 5.

Analiza Ten prosty program demonstruje podstawy dziaania ptli while. Gdy warunek jest speniony, wykonywane jest ciao ptli. W tym przypadku w linii 9. sprawdzane jest, czy zmienna counter

(licznik) ma warto mniejsz od 5. Jeli ten warunek jest speniony (prawdziwy), wykonywane jest ciao ptli: w linii 11. nastpuje inkrementacja licznika, za jego warto jest wypisywana w linii 12. Gdy warunek w linii 9. nie zosta speniony (tzn. gdy zmienna counter ma warto wiksz lub rwn 5), wtedy cae ciao ptli while (linie od 10. do 13.) jest pomijane i program przechodzi do nastpnej instrukcji, czyli w tym przypadku do linii 14.
Instrukcja while

Skadnia instrukcji while jest nastpujca:


while ( warunek ) instrukcja;

warunek jest wyraeniem jzyka C++, za instrukcja jest dowoln instrukcj lub blokiem instrukcji C++. Gdy wartoci wyraenia warunek jest true (prawda), wykonywana jest instrukcja, po czym nastpuje powrt do pocztku ptli i ponowne sprawdzenie warunku. Czynno ta powtarza si, dopki warunek zwraca warto true. Gdy wyraenie warunek ma warto false, dziaanie ptli while koczy si i program przechodzi do instrukcji nastpujcych po ptli.

Przykad
// zliczanie do 10 int x = 0; while (x < 10) cout << "X: " << x++;

Bardziej skomplikowane instrukcje while


Warunek sprawdzany w ptli while moe by zoony, tak jak kade poprawne wyraenie jzyka C++. Moe zawiera wyraenia tworzone za pomoc operatorw logicznych && (I), || (LUB) oraz ! (NIE). Tak nieco bardziej skomplikowan instrukcj while przedstawia listing 7.3. Listing 7.3. Warunek zoony w instrukcji while
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: // Listing 7.3 // Zoona instrukcja while #include <iostream> using namespace std; int main() { unsigned short small; unsigned long large; const unsigned short MAXSMALL=65535; cout << "Wpisz mniejsza liczbe: "; cin >> small; cout << "Wpisz duza liczbe: "; cin >> large;

17: 18: 19: 20: 21: 22: linii 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: }

cout << "mala: " << small << "..."; // w kadej iteracji sprawdzamy trzy warunki while (small < large && large > 0 && small < MAXSMALL) { if (small % 5000 == 0) // wypisuje kropk co kade 5000 cout << "."; small++; large-=2; } cout << "\nMala: " << small << " Duza: " << large << endl; return 0;

Wynik
Wpisz Wpisz mala: Mala: mniejsza liczbe: 2 duza liczbe: 100000 2......... 33335 Duza: 33334

Analiza Ten program to gra. Podaj dwie liczby, mniejsz i wiksz. Mniejsza liczba jest zwikszana o jeden, a wiksza liczba jest zmniejszana o dwa. Celem gry jest odgadnicie, kiedy si spotkaj. Linie od 12. do 15. su do wprowadzania liczb. W linii 20. rozpoczyna si ptla while, ktrej dziaanie bdzie kontynuowane, dopki spenione s wszystkie trzy ponisze warunki: 1. 2. 3. Mniejsza liczba nie jest wiksza od wikszej liczby. Wiksza liczba nie jest ujemna ani rwna zeru. Mniejsza liczba nie przekracza maksymalnej wartoci dla maych liczb cakowitych (MAXSMALL).

W linii 23. warto zmiennej small (maa) jest obliczana modulo 5 000. Nie powoduje to zmiany wartoci tej zmiennej; chodzi jedynie o to, e warto 0 jest wynikiem dziaania modulo 5 000 tylko wtedy, gdy warto zmiennej small jest wielokrotnoci piciu tysicy. Za kadym razem, gdy otrzymujemy warto zero, na ekranie wypisywana jest kropka, przedstawiajca postp dziaa. W linii 25. nastpuje inkrementacja zmiennej small, za w linii 27. zmniejszenie zmiennej large (dua) o dwa. Jeeli w ptli while nie zostanie speniony ktry z trzech warunkw, ptla koczy dziaanie, a wykonanie programu przechodzi do linii 29., za zamykajcy nawias klamrowy ptli while.
UWAGA Operator reszty z dzielenia (modulo) oraz warunki zoone zostay opisane w rozdziale 3, Stae i zmienne.

Usunito: 6 Usunito: 8 Usunito: d Usunito: ego Usunito: u Usunito: ego

continue oraz break


Moe si zdarzy, e przed wykonaniem caego zestawu instrukcji w ptli bdziesz chcie powrci do jej pocztku. Suy do tego instrukcja continue (kontynuuj). Moe zdarzy si take, e bdziesz chcie wyj z ptli jeszcze przed spenieniem warunku koca. Instrukcja break (przerwij) powoduje natychmiastowe wyjcie z ptli i przejcie wykonywania do nastpnych instrukcji programu. Listing 7.4 demonstruje uycie tych instrukcji. Tym razem gra jest nieco bardziej skomplikowana. Uytkownik jest proszony o podanie liczby mniejszej i wikszej, liczby pomijanej oraz liczby docelowej. Mniejsza liczba jest zwikszana o jeden, a wiksza liczba jest zmniejszana o dwa. Za kadym razem, gdy mniejsza liczba jest wielokrotnoci liczby pomijanej, nie jest wykonywane zmniejszanie. Gra koczy si, gdy mniejsza liczba staje si wiksza od wikszej liczby. Gdy wiksza liczba dokadnie zrwna si z liczb docelow. wypisywany jest komunikat i gra zatrzymuje si. Listing 7.4. Instrukcje break i continue
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: // Listing 7.4 // Demonstruje instrukcje break i continue #include <iostream> int main() { using namespace std; unsigned short small; unsigned long large; unsigned long skip; unsigned long target; const unsigned short MAXSMALL=65535; cout << "Wpisz cin >> small; cout << "Wpisz cin >> large; cout << "Wpisz cin >> skip; cout << "Wpisz cin >> target; cout << "\n"; // ustalamy dla ptli trzy warunki zatrzymania while (small < large && large > 0 && small < MAXSMALL) { small++; if (small % skip == 0) // pomijamy zmniejszanie? { cout << "pominieto dla " << small << endl; continue; } if (large == target) { // osignito warto docelow? mniejsza liczbe: "; wieksza liczbe: "; liczbe pomijana: "; liczbe docelowa: ";

40: 41: 42: 43: 44: 45: 46: 47: endl; 48: 49: }

cout << "Osiagnieto wartosc docelowa!"; break; } large-=2; } // koniec ptli while cout << "\nMniejsza: " << small << " Wieksza: " << large << return 0;

Wynik
Wpisz Wpisz Wpisz Wpisz mniejsza liczbe: 2 wieksza liczbe: 20 liczbe pomijana: 4 liczbe docelowa: 6

pominieto dla 4 pominieto dla 8 Mniejsza: 10 Wieksza: 8

Analiza W tej grze uytkownik przegra; zmienna small (maa) staa si wiksza, zanim zmienna large (wiksza) zrwnaa si z liczb docelow 6. W linii 26. s sprawdzane warunki instrukcji while. Jeli zmienna small jest mniejsza od zmiennej large, zmienna large jest wiksza od zera, a zmienna small nie przekroczya maksymalnej wartoci dla krtkich liczb cakowitych (short), program wchodzi do ciaa ptli. W linii 32. jest obliczana reszta z dzielenia (modulo) wartoci zmiennej small przez warto pomijan. Jeli zmienna small jest wielokrotnoci zmiennej skip (pomi), wtedy wykonywana jest instrukcja continue i program wraca do pocztku ptli, do linii 26. W efekcie pominite zostaj: sprawdzanie wartoci docelowej i zmniejszanie zmiennej large. W linii 38. nastpuje porwnanie zmiennej target (docelowa) ze zmienn large. Jeli s rwne, wygrywa uytkownik. Wypisywany jest wtedy komunikat i wykonywana jest instrukcja break. Powoduje ona natychmiastowe wyjcie z ptli i kontynuacj wykonywania programu od linii 46.
UWAGA Instrukcje continue oraz break powinny by uywane ostronie. Wraz z goto stanowi one dwie najbardziej niebezpieczne instrukcje jzyka (s one niebezpieczne z tych samych powodw co instrukcja goto). Programy zmieniajce nagle kierunek dziaania s trudniejsze do zrozumienia, a uywanie instrukcji continue i break wedug wasnego uznania moe uniemoliwi analiz nawet niewielkich ptli while.

Usunito: maych

Instrukcja continue

continue;

Powoduje pominicie pozostaych instrukcji ptli while lub for i powrt do pocztku ptli. Przykad uycia tej instrukcji znajduje si na listingu 7.4.

Instrukcja break

break;

Powoduje natychmiastowe wyjcie z ptli while lub for. Wykonanie programu przechodzi do zamykajcego nawiasu klamrowego.

Przykad
while (warunek) { if (warunek2) break; // instrukcje }

Ptla while(true)
Sprawdzanym w ptli while warunkiem moe by kade poprawne wyraenie jzyka C++. Dopki ten warunek pozostanie speniony, dziaanie ptli while nie zostanie przerwane. Uywajc wartoci true jako wyraenia w instrukcji while, moesz stworzy ptl, ktra bdzie wykonywana bez koca. Listing 7.5 przedstawia liczenie do 10 za pomoc takiej konstrukcji jzyka. Listing 7.5. Ptla while
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: // Listing 7.5 // Demonstruje ptl #include <iostream> int main() { int counter = 0; while (true) { counter ++; if (counter > 10) break; } std::cout << "Licznik: " << counter << "\n"; (true)

16: 17:

return 0; }

Wynik
Licznik: 11

Analiza W linii 9. rozpoczyna si ptla while z warunkiem, ktry zawsze jest speniony. W linii 11. ptla inkrementuje warto zmiennej licznikowej, po czym w linii 12. sprawdza, czy licznik przekroczy warto 10. Jeli nie, dziaanie ptli trwa. Jeli licznik przekroczy warto 10, wtedy instrukcja break w linii 13. powoduje wyjcie z ptli, a dziaanie programu przechodzi do linii 15., w ktrej wypisywany jest komunikat kocowy. Program dziaa, lecz nie jest elegancki stanowi dobry przykad uycia zego narzdzia. Ten sam efekt mona osign, umieszczajc funkcj sprawdzania wartoci licznika tam, gdzie powinna si ona znale w warunku instrukcji while.
OSTRZEENIE Niekoczce si ptle, takie jak while(true), mog doprowadzi do zawieszenia si komputera gdy warunek wyjcia nie zostanie nigdy speniony. Uywaj ich ostronie i dokadnie testuj ich dziaanie.

C++ oferuje wiele sposobw wykonania danego zadania. Prawdziwa sztuka polega na wybraniu odpowiedniego narzdzia dla odpowiedniego zadania. TAK W celu wykonywania ptli, dopki speniony jest warunek, uywaj ptli while. Bd ostrony uywajc instrukcji continue i
break.

NIE Nie uywaj instrukcji goto.

Upewnij si, czy ptla while w pewnym momencie koczy dziaanie.

Ptla do...while
Istnieje moliwo, e ciao ptli while nigdy nie zostanie wykonane. Instrukcja while sprawdza swj warunek przed wykonaniem ktrejkolwiek z zawartych w niej instrukcji, a gdy ten warunek nie jest speniony, cae ciao ptli jest pomijane. Ilustruje to listing 7.6. Listing 7.6. Pominicie ciaa ptli while
0: 1: 2: // Listing 7.6 // Demonstruje pominicie ciaa ptli while // w momencie, gdy warunek nie jest speniony.

3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:

#include <iostream> int main() { int counter; std::cout << "Ile pozdrowien?: "; std::cin >> counter; while (counter > 0) { std::cout << "Hello!\n"; counter--; } std::cout << "Wartosc licznika: " << counter; return 0; }

Wynik
Ile pozdrowien?: 2 Hello! Hello! Wartosc licznika: 0 Ile pozdrowien?: 0 Wartosc licznika: 0

Analiza W linii 10. uytkownik jest proszony o wpisanie wartoci pocztkowej. Ta warto jest umieszczana w zmiennej cakowitej counter (licznik). Warto licznika jest sprawdzana w linii 12. i dekrementowana w ciele ptli while. Za pierwszym razem warto licznika zostaa ustawiona na 2, dlatego ciao ptli while zostao wykonane dwukrotnie. Jednak za drugim razem uytkownik wpisa 0. Warto licznika zostaa sprawdzona w linii 12. i tym razem warunek nie zosta speniony; tj. zmienna counter nie bya wiksza od zera. Zostao wic pominite cae ciao ptli i komunikat Hello nie zosta wypisany ani razu. Co zrobi komunikat Hello zosta wypisany co najmniej raz? Nie moe tego zapewni ptla
while, gdy jej warunek jest sprawdzany przed wypisywaniem komunikatu. Mona to osign umieszczajc instrukcj if przed ptl while:
if (counter < 1) // wymuszamy minimaln warto counter = 1;

ale to rozwizanie nie jest zbyt eleganckie.

do...while
Ptla do...while (wykonuj...dopki) wykonuje ciao ptli przed sprawdzeniem warunku i sprawia e instrukcje w ptli zostan wykonane co najmniej raz. Listing 7.7 stanowi zmodyfikowan wersj listingu 7.6, w ktrej zostaa uyta ptla do...while. Listing 7.7. Przykad ptli do...while.
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: // Listing 7.7 // Demonstruje ptl do...while #include <iostream> int main() { using namespace std; int counter; cout << "Ile pozdrowien? "; cin >> counter; do { cout << "Hello\n"; counter--; } while (counter >0 ); cout << "Licznik ma wartosc: " << counter << endl; return 0; }

Wynik
Ile pozdrowien? 2 Hello Hello Licznik ma wartosc: 0

Analiza W linii 9. uytkownik jest proszony o wpisanie pocztkowej wartoci, ktra jest umieszczana w zmiennej counter. W ptli do...while, ciao ptli jest wykonywane przed sprawdzeniem warunku, dlatego w kadym przypadku zostanie wykonane co najmniej raz. W linii 13. wypisywany jest komunikat, w linii 14. dekrementowany jest licznik, za dopiero w linii 15. nastpuje sprawdzenie warunku. Jeli warunek jest speniony, wykonanie programu wraca do pocztku ptli w linii 13.; w przeciwnym razie przechodzi do linii 16. Instrukcje break i continue w ptlach do...while dziaaj tak jak w ptli loop. Jedyna rnica pomidzy ptl while a ptl do...while pojawia si w chwili sprawdzania warunku.
Instrukcja do...while

Skadnia instrukcji do...while jest nastpujca:


do instrukcja while (warunek);

Wykonywana jest instrukcja, po czym sprawdzany jest warunek. Jeli warunek jest speniony, ptla jest powtarzana; w przeciwnym razie jej dziaanie si koczy. Pod innymi wzgldami instrukcje i warunki s identyczne, jak w ptli while.

Przykad 1
// liczymy do 10 int x = 0; do cout << "X: " << x++; while (x < 10);

Przykad 2
// wypisujemy mae litery alfabetu char ch = 'a'; do { cout << ch << ' '; ch++; } while ( ch <= 'z' );

TAK Uywaj ptli do...while, gdy chcesz mie pewno e ptla zostanie wykonana co najmniej raz. Uywaj ptli while, gdy chcesz pomin ptl (gdy warunek nie jest speniony). Sprawdzaj wszystkie ptle, aby mie pewno, e robi to, czego oczekujesz.

Ptle for
Gdy korzystasz z ptli while, ustawiasz warunek pocztkowy, sprawdzasz, czy jest speniony, po czym w kadym wykonaniu ptli inkrementujesz lub w inny sposb zmieniasz zmienn kontrolujc jej wykonanie. Demonstruje to listing 7.8. Listing 7.8. Nastpna ptla while
0: 1: 2: 3: 4: 5: 6: 7: 8: // Listing 7.8 // Ptla while #include <iostream> int main() { int counter = 0;

9: 10: 11: 12: 13: 14: 15: 16: 17: 18:

while(counter < 5) { counter++; std::cout << "Petla! }

";

std::cout << "\nLicznik: " << counter << ".\n"; return 0; }

Wynik
Petla! Petla! Licznik: 5. Petla! Petla! Petla!

Analiza W linii 8. ustawiany jest warunek: zmienna counter (licznik) ustawiana jest na zero. W linii 10. nastpuje sprawdzenie, czy licznik jest mniejszy od 5. Inkrementacja licznika odbywa si w linii 12. W linii 16. wypisywany jest prosty komunikat, ale mona przypuszcza, e przy kadej inkrementacji licznika mona wykona bardziej konkretn prac. Ptla for (dla) czy powysze trzy etapy w jedn instrukcj. S to: inicjalizacja, test i inkrementacja. Ptla for skada si ze sowa kluczowego for, po ktrym nastpuje para nawiasw. Wewntrz nawiasw znajduj si trzy, oddzielone rednikami, instrukcje. Pierwsza instrukcja suy do inicjalizacji. Mona w niej umieci kad poprawn instrukcj jzyka C++, ale zwykle po prostu tworzy si i inicjalizuje zmienn licznikow. Drug instrukcj jest test, ktrym moe by kade poprawne wyraenie jzyka. Peni ono tak sam funkcj, jak warunek w ptli while. Trzecia instrukcja jest dziaaniem. Zwykle w jego wyniku warto zmiennej licznikowej jest zwikszana lub zmniejszana, ale oczywicie mona tu zastosowa kad poprawn instrukcj jzyka C++. Zwr uwag, e instrukcje pierwsza i trzecia mog by dowolnymi instrukcjami, lecz druga instrukcja musi by wyraeniem czyli instrukcj jzyka C++, zwracajc warto. Ptl for demonstruje listing 7.9. Listing 7.9. Przykad ptli for
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: // Listing 7.9 // Ptla for #include <iostream> int main() { int counter; for (counter = 0; counter < 5; counter++) std::cout << "Petla! "; std::cout << "\nLicznik: " << counter << ".\n"; return 0; }

Wynik
Petla! Petla! Petla! Petla! Petla!

Licznik: 5.

Analiza Instrukcja for w linii 9. czy w sobie inicjalizacj zmiennej counter, sprawdzenie, czy jej warto jest mniejsza od 5, oraz inkrementacj tej zmiennej. Ciao ptli for znajduje si w linii 10. Oczywicie, w tym miejscu mgby zosta uyty blok instrukcji.
Skadnia ptli for

Skadnia instrukcji for jest nastpujca:


for (inicjalizacja; test; akcja ) instrukcja;

Instrukcja inicjalizacja jest uywana w celu zainicjalizowania stanu licznika lub innego przygotowania do wykonania ptli. Instrukcja test jest dowolnym wyraeniem jzyka C++, ktre jest obliczane przed kadym wykonaniem zawartoci ptli. Jeli wyraenie test ma warto true, wykonywane jest ciao ptli, po czym wykonywana jest instrukcja akcja z nagwka ptli (zwykle po prostu nastpuje inkrementacja zmiennej licznikowej).

Usunito: i

Przykad 1
// dziesi razy wpisuje napis "Hello" for (int i = 0; i < 10; i++) cout << "Hello! ";

Przykad 2
for (int i = 0; i < 10; i++) { cout << "Hello!" << endl; cout << "wartoscia i jest: " << i << endl; }

Zaawansowane ptle for


Instrukcje for s wydajne i dziaaj w sposb elastyczny. Trzy niezalene instrukcje (inicjalizacja, test i akcja) umoliwiaj stosowanie rnorodnych rozwiza. Ptla for dziaa w nastpujcej kolejnoci: 1. 2. 3. Przeprowadza inicjalizacj. Oblicza warto warunku . Jeli warunek ma warto true, wykonuje ciao ptli, a nastpnie wykonuje instrukcj akcji.
Usunito: wyraenie Usunito: wyraenie

Przy kadym wykonaniu ptli powtarzane s kroki 2 i 3.

Wielokrotna inicjalizacja i inkrementacja


Inicjalizowanie wicej ni jednej zmiennej, testowanie zoonego wyraenia logicznego czy wykonywanie wicej ni jednej instrukcji nie s niczym niezwykym. Inicjalizacja i akcja mog by zastpione kilkoma instrukcjami C++, oddzielonymi od siebie przecinkami. Listing 7.10 przedstawia inicjalizacj i inkrementacj dwch zmiennych. Listing 7.10. Przykad instrukcji wielokrotnych w ptli for
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: //listing 7.10 // demonstruje wielokrotne instrukcje // w ptli for #include <iostream> int main() { for (int i=0, j=0; i<3; i++, j++) std::cout << "i: " << i << " j: " << j << std::endl; return 0; }

Wynik
i: 0 j: 0 i: 1 j: 1 i: 2 j: 2

Analiza W linii 9. dwie zmienne, i oraz j, s inicjalizowane wartoci 0. Obliczany jest test (i < 3); poniewa jest prawdziwy, wykonywane jest ciao ptli for, w ktrym wypisywane s wartoci zmiennych. Na koniec wykonywana jest trzecia klauzula instrukcji for, w ktrej s inkrementowane zmienne i oraz j. Po wykonaniu linii 10., warunek jest sprawdzany ponownie, jeli wci jest speniony, dziaania si powtarzaj (inkrementowane s zmienne i oraz j) i ponownie wykonywane jest ciao ptli. Dzieje si tak do momentu, w ktrym warunek nie bdzie speniony; wtedy nie jest wykonywana instrukcja akcji, a dziaanie programu wychodzi z ptli.

Puste instrukcje w ptli for


Kad z instrukcji w nagwku ptli for mona pomin. W tym celu naley oznaczy jej pooenie rednikiem (;). Aby stworzy ptl for, ktra dziaa dokadnie tak, jak ptla while, pomi pierwsz i trzeci instrukcj. Przedstawia to listing 7.11. Listing 7.11. Puste instrukcje w nagwku ptli for
0: 1: 2: 3: 4: 5: 6: 7: 8: // Listing 7.11 // Ptla for z pustymi instrukcjami #include <iostream> int main() { int counter = 0;

9: 10: 11: 12: 13: 14: 15: 16: 17: 19: }

for( ; counter < 5; ) { counter++; std::cout << "Petla! }

";

std::cout << "\nLicznik: " << counter << ".\n"; return 0;

Wynik
Petla! Petla! Licznik: 5. Petla! Petla! Petla!

Analiza By moe poznajesz, e ta ptla wyglda dokadnie tak, jak ptla while z listingu 7.8. W linii 8. inicjalizowana jest zmienna counter. Instrukcja for w linii 10. nie inicjalizuje adnych wartoci, lecz zawiera test warunku counter < 5. Nie wystpuje take instrukcja inkrementacji, wic ta ptla dziaa dokadnie tak samo, gdybymy napisali:
while (counter < 5)

Jak ju wiesz, C++ oferuje kilka sposobw osignicia tego samego celu. aden dowiadczony programista C++ nie uyby ptli for w ten sposb, przykad ten ilustruje jedynie elastyczno instrukcji for. W rzeczywistoci, dziki zastosowaniu instrukcji break i continue, istnieje moliwo stworzenia ptli for bez adnej instrukcji w nagwku. Pokazuje to listing 7.12. Listing 7.12. Instrukcja for z pustym nagwkiem
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: //Listing 7.12 ilustruje //instrukcj for z pustym nagwkiem #include <iostream> int main() { int counter=0; // inicjalizacja int max; std::cout << "Ile pozdrowie?"; std::cin >> max; for (;;) // ptla, ktra si nie koczy { if (counter < max) // test { std::cout << "Hello!\n"; counter++; // inkrementacja } else break; } return 0; }

Wynik
Ile pozdrowien?3 Hello! Hello! Hello!

Analiza Z tej ptli usunlimy wszystko, co si dao. Inicjalizacja, test i akcja zostay przeniesione poza instrukcj for. Inicjalizacja odbywa si w linii 8., przed ptl for. Test jest przeprowadzany w osobnej instrukcji if, w linii 14., i gdy si powiedzie, w linii 17. jest wykonywana akcja, czyli inkrementacja zmiennej counter. Jeli warunek nie jest speniony, w linii 20. nastpuje wyjcie z ptli (spowodowane uyciem instrukcji break). Cho program ten jest nieco absurdalny, jednak czasem ptle for(;;) lub while(true) s wanie tym, czego nam potrzeba. Bardziej sensowny przykad wykorzystania takiej ptli zobaczysz w dalszej czci rozdziau, przy okazji omawiania instrukcji switch.

Puste ptle for


Poniewa w samym nagwku ptli for mona wykona tak wiele pracy, wic czasem ciao ptli moe ju niczego nie robi. Dlatego pamitaj o zastosowaniu instrukcji pustej (;) jako ciaa funkcji. rednik moe zosta umieszczony w tej samej linii, co nagwek ptli, ale wtedy atwo go przeoczy. Uycie ptli for z pustym ciaem przedstawia listing 7.13. Listing 7.13. Instrukcja pusta w ciele ptli for.
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: //Listing 7.13 //Demonstruje instrukcj pust // w ciele ptli for #include <iostream> int main() { for (int i = 0; i<5; std::cout << "i: " << i++ << std::endl) ; return 0;

Wynik
i: i: i: i: i: 0 1 2 3 4

Analiza Ptla for w linii 8. zawiera trzy instrukcje. Instrukcja inicjalizacji definiuje i inicjalizuje zmienn licznikow i wartoci 0. Instrukcja warunku sprawdza, czy i < 5, za instrukcja akcji wypisuje warto zmiennej i oraz inkrementuje j.

Poniewa ciao ptli nie wykonuje adnych czynnoci, uyto w nim instrukcji pustej (;). Zwr uwag, e ta ptla for nie jest najlepiej zaprojektowana: instrukcja akcji wykonuje zbyt wiele pracy. Lepiej wic byoby zmieni t ptl w nastpujcy sposb:
8: 9: for (int i = 0; i<5; i++) std::cout << "i: " << i << std::endl;

Cho obie wersje dziaaj tak samo, druga z nich jest atwiejsza do zrozumienia.

Ptle zagniedone
Ptle mog by zagniedone, tj. ptla moe znajdowa si w ciele innej ptli. Ptla wewntrzna jest wykonywana wielokrotnie, przy kadym wykonaniu ptli zewntrznej. Listing 7.14 przedstawia zapisywanie znacznikw do macierzy, za pomoc zagniedonych ptli for. Listing 7.14. Zagniedone ptle for
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: //Listing 7.14 //Ilustruje zagniedone ptle for #include <iostream> int main() { using namespace std; int rows, columns; char theChar; cout << "Ile wierszy? "; cin >> rows; cout << "Ile kolumn? "; cin >> columns; cout << "Jaki znak? "; cin >> theChar; for (int i = 0; i<rows; i++) { for (int j = 0; j<columns; j++) cout << theChar; cout << "\n"; } return 0; }

Wynik
Ile wierszy? 4 Ile kolumn? 12 Jaki znak? x xxxxxxxxxxxx xxxxxxxxxxxx xxxxxxxxxxxx xxxxxxxxxxxx

Analiza

Uytkownik jest proszony o podanie iloci wierszy i kolumn oraz znaku, jaki ma zosta uyty do wydrukowania zarysu macierzy. Pierwsza ptla for, w linii 16., ustawia warto pocztkow licznika (i) na 0, po czym przechodzi do wykonania ciaa zewntrznej ptli. W linii 18., pierwszej linii ciaa zewntrznej ptli for, tworzona jest kolejna ptla for. Jest w niej inicjalizowany drugi licznik (j), take wartoci 0, po czym program przechodzi do wykonania ciaa ptli wewntrznej. W linii 19. wypisywany jest wybrany znak, a program wraca do nagwka wewntrznej ptli. Zwr uwag, e wewntrzna ptla for posiada tylko jedn instrukcj (wypisujc znak). Gdy sprawdzany warunek jest speniony (j < columns), zmienna j jest inkrementowana i wypisywany jest nastpny znak. Czynno powtarzana jest tak dugo, a j zrwna si z iloci kolumn. Gdy warunek w wewntrznej ptli nie zostanie speniony (w tym przypadku po wypisaniu dwunastu znakw), wykonanie programu przechodzi do linii 20., w ktrej wypisywany jest znak nowej linii. Nastpuje powrt do nagwka ptli zewntrznej, w ktrym odbywa si sprawdzenie warunku (i < rows). Jeli ten warunek zostaje speniony, zmienna i jest inkrementowana i ponownie wykonywane jest ciao ptli. W drugiej iteracji zewntrznej ptli for ponownie rozpoczyna si wykonanie ptli wewntrznej. Zmiennej j ponownie przypisywana jest warto 0 i caa ptla wykonywana jest jeszcze raz. Powiniene zwrci uwag, e wewntrzna ptla jest wykonywana w caoci przy kadym wykonaniu ptli zewntrznej. Dlatego wypisywanie znaku powtarza si (columns rows) razy.

Usunito: wypisania Usunito: inicjalizuje

Usunito: oznacza

UWAGA Wielu programistw nadaje zmiennym licznikowym nazwy i oraz j. Ta tradycja siga czasw jzyka FORTRAN, w ktrym jedynymi zmiennymi licznikowymi byy zmienne i, j, k, l, m oraz n.

Inni programici wol uywa dla zmiennych licznikowych bardziej opisowych nazw, takich jak licznik1 czy licznik2. Jednak zmienne i oraz j s tak popularne, e nie powoduj adnych nieporozumie, gdy zostan uyte w nagwkach ptli for.

Zakres zmiennych w ptlach for


W przeszoci zakres zmiennych zadeklarowanych w ptlach for rozciga si take na blok zewntrzny. Standard ANSI ograniczy ten zakres do bloku ptli for (nie gwarantuj tego jednak wszystkie kompilatory). Moesz sprawdzi swj kompilator za pomoc poniszego kodu:
#inlude <iostream> int main() { // czy i ogranicza si tylko do ptli for? for (int i = 0; i < 5; i++) { std::cout << "i: " << i << std::endl; } i = 7; // nie powinno by w tym zakresie! return 0;

Jeli kod skompiluje si bez kopotw, oznacza to, e twj kompilator nie obsuguje tego aspektu standardu ANSI. Jeli kompilator zaprotestuje, e i nie jest zdefiniowane (w linii i = 7), oznacza to, e obsuguje nowy standard. Aby stworzy kod, ktry skompiluje si za kadym razem, moesz zmieni go nastpujco:
#inlude <iostream> int main() { int i; // zadeklarowane poza ptl for (i = 0; i < 5; i++) { std::cout << "i: " << i << std::endl; } i = 7; // teraz jest w zakresie w kadym kompilatorze return 0; }

Podsumowanie ptli
W rozdziale 5., Funkcja, nauczye si, jak rozwizywa problem cigu Fibonacciego za pomoc rekurencji. Dla przypomnienia: cig Fibonacciego rozpoczyna si od wyrazw 1, 1, 2, 3..., za kade kolejne wyrazy stanowi sum dwch poprzednich: 1, 1, 2, 3, 5, 8, 13, 21, 34... N-ty wyraz cigu jest sum wyrazw n-1 i n-2. Problemem rozwizywanym w rozdziale pitym byo obliczenie wartoci n-tego wyrazu cigu. W tym celu uywalimy rekurencji. Tym razem, jak pokazuje listing 7.15, uyjemy iteracji. Listing 7.15. Obliczanie wyrazw cigu Fibonacciego za pomoc iteracji.
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: // Listing 7.15 // Demonstruje obliczanie wartoci n-tego // wyrazu cigu Fibonacciego za pomoc iteracji #include <iostream> int fib(int position); int main() { using namespace std; int answer, position; cout << "Ktory wyraz ciagu? ";

12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36:

cin >> position; cout << "\n"; answer = fib(position); cout << position << " wyraz ciagu Fibonacciego "; cout << "ma wartosc " << answer << ".\n"; return 0; } int fib(int n) { int minusTwo=1, minusOne=1, answer=2; if (n < 3) return 1; for (n -= 3; { minusTwo minusOne answer = } } n; n--) = minusOne; = answer; minusOne + minusTwo;

return answer;

Wynik
Ktory wyraz ciagu? 4 4 wyraz ciagu Fibonacciego ma wartosc 3. Ktory wyraz ciagu? 5 5 wyraz ciagu Fibonacciego ma wartosc 5. Ktory wyraz ciagu? 20 20 wyraz ciagu Fibonacciego ma wartosc 6765. Ktory wyraz ciagu? 30 30 wyraz ciagu Fibonacciego ma wartosc 832040.

Analiza W listingu 7.15 obliczylimy wartoci wyrazw cigu Fibonacciego, stosujc iteracj zamiast rekurencji. Ta metoda jest szybsza i zajmuje mniej pamici ni rekurencja. W linii 11. uytkownik jest proszony o podanie numeru wyrazu cigu. Nastpuje wywoanie funkcji fib(), ktra oblicza warto tego wyrazu. Jeli numer wyrazu jest mniejszy od 3, funkcja zwraca warto 1. Poczwszy od trzeciego wyrazu, funkcja iteruje (dziaa w ptli), uywajc nastpujcego algorytmu: 1. Ustawia pozycj wyjciow: przypisuje zmiennej answer (wynik) warto 2, za zmiennym minusTwo (minus dwa) i minusOne (minus jeden) warto 1. Zmniejsza numer wyrazu o trzy, gdy pierwsze dwa wyrazy zostay ju obsuone przez pozycj wyjciow. Dla kadego wyrazu, a do wyrazu poszukiwanego, obliczana jest warto cigu Fibonacciego. Odbywa si to poprzez: a. b. Przypisanie biecej wartoci zmiennej minusOne do zmiennej minusTwo. Przypisanie biecej wartoci zmiennej answer do zmiennej minusOne.

2.

c. d. 3.

Zsumowanie wartoci zmiennych minusOne oraz minusTwo i przypisanie tej sumy zmiennej answer. Dekrementacj zmiennej licznikowej n.

Gdy zmienna n osignie zero, funkcja zwraca warto zmiennej answer.

Dokadnie tak samo rozwizywalibymy ten problem na papierze. Gdyby zosta poproszony o podanie wartoci pitego wyrazu cigu Fibonacciego, napisaby: 1, 1, 2, i pomylaby: jeszcze dwa wyrazy. Nastpnie dodaby 2+1 i dopisaby 3, mylc: Jeszcze jeden. Na koniec dodaby 3+2 i otrzymaby w wyniku 5. Rozwizanie tego zadania polega na kadorazowym przesuwaniu operacji sumowania w prawo i zmniejszaniu iloci pozostaych do obliczenia wyrazw cigu. Zwr uwag na warunek sprawdzany w linii 28. (n). Jest to idiom jzyka C++, ktry stanowi odpowiednik n != 0. Ta ptla for jest wykonywana, dopki warto n nie osignie zera (ktre odpowiada wartoci logicznej false). Zatem nagwek tej ptli for mgby zosta przepisany nastpujco:
for (n -=3; n != 0; n--)

dziki temu ptla byaby bardziej czytelna. Jednak ten idiom jest tak popularny, e nie ma sensu z nim walczy. Skompiluj, zbuduj i uruchom ten program, po czym porwnaj jego dziaanie z korzystajcym z rekurencji programem z rozdziau pitego. Sprbuj obliczy warto 25. wyrazu cigu i porwnaj czas dziaania obu programw. Rekurencja to elegancka metoda, ale poniewa z wywoaniem funkcji wie si pewien narzut, i poniewa jest ona wywoywana tak wiele razy, metoda ta jest wolniejsza od iteracji. Wykonywanie operacji arytmetycznych na mikrokomputerach zostao zoptymalizowane, dlatego rozwizania iteracyjne powinny dziaa bardzo szybko. Uwaaj, by nie wpisa zbyt wysokiego numeru wyrazu cigu. Cig Fibonacciego ronie bardzo szybko i nawet przy niewielkich wartociach zmienne cakowite typu long zostaj przepenione.

Instrukcja Switch
Z rozdziau 4. dowiedziae si jak korzysta z instrukcji if i else. Gdy zostan one zbyt gboko zagniedone, staj si cakowicie niezrozumiae. Na szczcie C++ oferuje pewn alternatyw. W odrnieniu od instrukcji if, ,ktra sprawdza jedn warto, instrukcja switch (przecznik) umoliwia podjcie dziaa na podstawie jednej z wielu rnych wartoci. Oglna posta instrukcji switch wyglda nastpujco:

switch (wyraenie) { case wartoJeden: instrukcja; break; case wartoDwa: instrukcja; break; .... case wartoN: instrukcja; break; default: instrukcja; }

wyraenie jest dowolnym wyraeniem jzyka C++, za jego instrukcje s dowolnymi

instrukcjami lub blokami instrukcji, pod warunkiem jednak, e ich wynikiem jest liczba typu integer (lub jej wynik jest jednoznacznie konwertowalny do takiej liczby).. Naley rwnie pamita, e instrukcja switch sprawdza jedynie rwno wyraenia; nie mona stosowa operatorw relacji ani operacji logicznych. Jeli ktra z wartoci case jest rwna wartoci wyraenia, program przechodzi do instrukcji tu po tej wartoci case i jego wykonanie jest kontynuowane a do napotkania instrukcji break (przerwij). Jeli warto wyraenia nie pasuje do adnej z wartoci case, wykonywana jest instrukcja default (domylna). Jeli nie wystpuje default i warto wyraenia nie pasuje do adnej z wartoci case, instrukcja switch nie spowoduje adnej akcji i program przechodzi do nastpnych instrukcji w kodzie.
UWAGA Stosowanie w instrukcji switch przypadku default jest dobrym pomysem. Jeli nie znajdziesz dla niego innego zastosowania, uyj go do wykrycia sytuacji, ktrej wystpienie nie byo przewidziane; wypisz wtedy odpowiedni komunikat bdu. Moe to by bardzo pomocne podczas debuggowania programu.

Usunito: Wartoci musz by staymi (literaami lub wyraeniami o staej wartoci) Usunito: jednak Usunito: jedynie czy wartoa Usunito: odpowiada ktrej z wartoci Usunito: w zwizanej z ni klauzuli Usunito: klauzul Usunito: klauzula Usunito: y Usunito: klauzula Usunito: klauzul

Naley pamita, e w przypadku braku instrukcji break na kocu bloku instrukcji (po case), wykonanie przechodzi take do nastpnego przypadku case. Czasem takie dziaanie jest zamierzone, ale zwykle jest po prostu bdem. Jeli zdecydujesz si na wykonanie instrukcji w kilku kolejnych przypadkach case, pamitaj o umieszczeniu obok komentarza, ktry wyjani, e nie pomine instrukcji break przypadkowo. Listing 7.16 przedstawia uycie instrukcji switch. Listing 7.16. Przykad instrukcji switch
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: //Listing 7.16 // Demonstruje instrukcj switch #include <iostream> int main() { using namespace std; unsigned short int number; cout << "Wpisz liczbe pomiedzy 1 i 5: "; cin >> number; switch (number) { case 0: cout << "Za mala, przykro mi!";

Usunito: a Usunito: jest pomijana Usunito: klauzuli Usunito: j Usunito: jej Usunito: w Usunito: klauzuli Usunito: j Usunito: klauzuli Usunito: klauzulach

14: 15: dalej 16: dalej 17: dalej 18: dalej 19: 20: 21: 22: 23: 24: 25: 26: }

case 5: case 4: case 3: case 2: case 1: default:

break; cout << "Dobra robota!\n"; cout << "Niezle!\n"; cout << "Wysmienicie!\n"; cout << "Cudownie!\n"; cout << "Niesamowicie!\n"; break; cout << "Zbyt duza!\n"; break;

// przejcie // przejcie // przejcie // przejcie

} cout << "\n\n"; return 0;

Wynik
Wpisz liczbe pomiedzy 1 i 5: 3 Wysmienicie! Cudownie! Niesamowicie! Wpisz liczbe pomiedzy 1 i 5: 8 Zbyt duza!

Analiza Uytkownik jest proszony o podanie liczby. Ta liczba jest przekazywana do instrukcji switch. Jeli ma warto 0, odpowiada klauzuli case w linii 13., dlatego jest wypisywany komunikat: Za mala, przykro mi!, po czym instrukcja break koczy dziaanie instrukcji switch. Jeli liczba ma warto 5, wykonanie przechodzi do linii 15., w ktrej wypisywany jest odpowiedni komunikat, po czym przechodzi do linii 16., w ktrej wypisywany jest kolejny komunikat, i tak dalej, a do napotkania instrukcji break w linii 20. Efektem dziaania tej instrukcji switch dla liczb pomidzy 1 a 5 jest wypisanie odpowiadajcej iloci komunikatw. Jeli wartoci liczby nie jest ani 0 ani 5, zakada si, e jest ona zbyt dua i w takim przypadku w linii 21. wykonywana jest instrukcja klauzuli default.

Instrukcja switch

Skadnia instrukcji switch jest nastpujca:


switch (wyraenie) { case wartoJeden: instrukcja; case wartoDwa: instrukcja; .... case wartoN: instrukcja; default: instrukcja; }

Instrukcja switch umoliwia rozgazienie programu (w zalenoci od wartoci wyraenia). Na pocztku wykonywania instrukcji nastpuje obliczenie wartoci wyraenia, gdy odpowiada ona ktrej z wartoci przypadku case, wykonanie programu przechodzi do tego wanie przypadku. Wykonywanie instrukcji jest kontynuowane a do koca ciaa instrukcji switch lub do czasu napotkania instrukcji break.

Usunito: klauzul Usunito: danej klauzuli Usunito: i

Jeli warto wyraenia nie odpowiada adnej z wartoci przypadkw case i wystpuje przypadek default, wykonanie przechodzi do przypadku default. W przeciwnym razie wykonywanie instrukcji switch si koczy.

Usunito: klauzul Usunito: klauzula Usunito: klauzuli

Przykad 1
switch (wybor) { case 0: cout << "Zero!" << endl; break; case 1: cout << "Jeden!" << endl; break; case 2: cout << "Dwa!" << endl; break; default: cout << "Domylna!" << endl; }

Przykad 2
switch (wybor) { case 0: case 1: case 2: cout << "Mniejsza ni 3!"; break; case 3: cout << "Rwna 3!"; break; default: cout << "Wiksza ni 3!"; }

Uycie instrukcji switch w menu


Listing 7.17 wykorzystuje omawian wczeniej ptli for(;;). Takie ptle s nazywane ptlami nieskoczonymi, gdy s wykonywane bez koca, a do natrafienia na koczc ich dziaanie instrukcj. Ptla nieskoczona jest uywana do tworzenia menu, pobrania polecenia od uytkownika, wykonania odpowiednich dziaa i powrt do menu. Jej dziaanie powtarza si dopty, dopki uytkownik nie zdecyduje si na wyjcie z menu.

UWAGA Niektrzy programici wol pisa:


#define EVER ;; for (EVER) { // instrukcje... }

Ptla nieskoczona nie posiada warunku wyjcia. Aby opuci tak ptl, naley uy instrukcji break. Ptle nieskoczone s take zwane ptlami wiecznymi. Listing 7.17. Przykad ptli nieskoczonej
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: //Listing 7.17 //Uywa nieskoczonej ptli do //interakcji z uytkownikiem #include <iostream> // prototypy int menu(); void DoTaskOne(); void DoTaskMany(int); using namespace std; int main() { bool exit = false; for (;;) { int choice = menu(); switch(choice) { case (1): DoTaskOne(); break; case (2): DoTaskMany(2); break; case (3): DoTaskMany(3); break; case (4): continue; // nadmiarowa! break; case (5): exit=true; break; default: cout << "Prosze wybrac ponownie!\n"; break; } // koniec instrukcji switch if (exit) break; } return 0; } int menu() // koniec ptli for(;;) // koniec main()

47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72:

{ int choice; cout << " **** Menu ****\n\n"; cout << "(1) Pierwsza opcja.\n"; cout << "(2) Druga opcja.\n"; cout << "(3) Trzecia opcja.\n"; cout << "(4) Ponownie wyswietl menu.\n"; cout << "(5) Wyjscie.\n\n"; cout << ": "; cin >> choice; return choice; } void DoTaskOne() { cout << "Opcja pierwsza!\n"; } void DoTaskMany(int which) { if (which == 2) cout << "Opcja druga!\n"; else cout << "Opcja trzecia!\n"; }

Wynik
**** Menu **** (1) (2) (3) (4) (5) Pierwsza opcja. Druga opcja. Trzecia opcja. Ponownie wyswietl menu. Wyjscie.

: 1 Opcja pierwsza! **** Menu **** (1) (2) (3) (4) (5) Pierwsza opcja. Druga opcja. Trzecia opcja. Ponownie wyswietl menu. Wyjscie.

: 3 Opcja trzecia! **** Menu **** (1) (2) (3) (4) (5) Pierwsza opcja. Druga opcja. Trzecia opcja. Ponownie wyswietl menu. Wyjscie.

: 5

Analiza Ten program czy w sobie kilka zagadnie omawianych w tym i poprzednich rozdziaach. Oprcz tego przedstawia popularne zastosowanie instrukcji switch. W linii 15. zaczyna si ptla nieskoczona. Wywoywana jest w niej funkcja menu(), wypisujca na ekranie menu i zwracajca numer polecenia wybranego przez uytkownika. Na podstawie tego numeru polecenia, instrukcja switch (zajmujca linie od 18. do 38.) wywouje odpowiedni funkcj obsugi polecenia. Gdy uytkownik wybierze polecenie 1., nastpuje skok do instrukcji case 1: w linii 20. W linii 21. wykonanie przechodzi do funkcji DoTaskOne() (wykonaj zadanie 1.), wypisujcej komunikat i zwracajcej sterowanie. Po powrocie z tej funkcji program wznawia dziaanie od linii 22., w ktrej instrukcja break koczy dziaanie instrukcji switch, co powoduje przejcie do linii 39. W linii 40. sprawdzana jest warto zmiennej exit (wyjcie). Jeli wynosi true, w linii 41. wykonywana jest instrukcja break, powodujca wyjcie z ptli for(;;); jeli zmienna ma warto false, program wraca do pocztku ptli w linii 15. Zwr uwag, e instrukcja continue w linii 30. jest nadmiarowa. Gdybymy j pominli i napotkali instrukcj break, instrukcja switch zakoczyaby dziaanie, zmienna exit miaaby warto false, ptla zostaaby wykonana ponownie, a menu zostaoby wypisane ponownie. Jednak dziki tej instrukcji continue mona pomin sprawdzanie zmiennej exit. TAK NIE
Usunito: j Usunito: klauzuli Usunito: klauzulami Usunito: klauzule

Usunito: moe pomin

Aby unikn gboko zagniedonych instrukcji Nie zapominaj o instrukcji break na kocu if, uywaj instrukcji switch. kadego przypadku case, chyba e celowo chcesz by program przeszed bezporednio Pieczoowicie dokumentuj wszystkie dalej. zamierzone przejcia pomidzy przypadkami case. W instrukcjach switch stosuj przypadek default, choby do wykrycia sytuacji pozornie niemoliwej.

Program podsumowujcy wiadomoci


{uwaga skad: jest to zawarto rozdziau Week 1 In Review } Listing 7.18. Program podsumowujcy wiadomoci
0: 1: 2: 3: 4: #include <iostream> using namespace std; enum CHOICE { DrawRect = 1, GetArea, GetPerim, ChangeDimensions, Quit};

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

// Deklaracja klasy Rectangle class Rectangle { public: // konstruktory Rectangle(int width, int height); ~Rectangle(); // akcesory int GetHeight() const { return itsHeight; } int GetWidth() const { return itsWidth; } int GetArea() const { return itsHeight * itsWidth; } int GetPerim() const { return 2*itsHeight + 2*itsWidth; } void SetSize(int newWidth, int newHeight); // Inne metody private: int itsWidth; int itsHeight; }; // Implementacja metod klasy void Rectangle::SetSize(int newWidth, int newHeight) { itsWidth = newWidth; itsHeight = newHeight; } Rectangle::Rectangle(int width, int height) { itsWidth = width; itsHeight = height; } Rectangle::~Rectangle() {} int DoMenu(); void DoDrawRect(Rectangle); void DoGetArea(Rectangle); void DoGetPerim(Rectangle); int main () { // inicjalizujemy prostokt jako 30,5 Rectangle theRect(30,5); int choice = DrawRect; int fQuit = false; while (!fQuit) { choice = DoMenu(); if (choice < DrawRect || choice > Quit) { cout << "\nBledny wybor, prosze sprobowac ponownie.\n\n"; continue; } switch (choice)

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

{ case DrawRect: DoDrawRect(theRect); break; case GetArea: DoGetArea(theRect); break; case GetPerim: DoGetPerim(theRect); break; case ChangeDimensions: int newLength, newWidth; cout << "\nNowa szerokosc: "; cin >> newWidth; cout << "Nowa wysokosc: "; cin >> newLength; theRect.SetSize(newWidth, newLength); DoDrawRect(theRect); break; case Quit: fQuit = true; cout << "\nWyjscie...\n\n"; break; default: cout << "Blad wyboru!\n"; fQuit = true; break; } // koniec instrukcji switch } // koniec petli while return 0; } // koniec funkcji main int DoMenu() { int choice; cout << "\n\n *** Menu *** \n"; cout << "(1) Rysuj prostokat\n"; cout << "(2) Obszar\n"; cout << "(3) Obwod\n"; cout << "(4) Zmien rozmiar\n"; cout << "(5) Wyjscie\n"; cin >> choice; return choice; } void DoDrawRect(Rectangle theRect) { int height = theRect.GetHeight(); int width = theRect.GetWidth(); for (int i = 0; i<height; i++) { for (int j = 0; j< width; j++) cout << "*"; cout << "\n"; } } void DoGetArea(Rectangle theRect)

127: 128: 129: 130: 131: 132: 133: 134:

{ cout << "Obszar: " << } void DoGetPerim(Rectangle theRect) { cout << "Obwod: " << theRect.GetPerim() << endl; } theRect.GetArea() << endl;

Wynik
*** Menu *** (1) Rysuj prostokat (2) Obszar (3) Obwod (4) Zmien rozmiar (5) Wyjscie 1 ****************************** ****************************** ****************************** ****************************** ****************************** *** Menu *** (1) Rysuj prostokat (2) Obszar (3) Obwod (4) Zmien rozmiar (5) Wyjscie 2 Obszar: 150 *** Menu *** (1) Rysuj prostokat (2) Obszar (3) Obwod (4) Zmien rozmiar (5) Wyjscie 3 Obwod: 70 *** Menu *** Rysuj prostokat Obszar Obwod Zmien rozmiar Wyjscie

(1) (2) (3) (4) (5) 4

Nowa szerokosc: 10 Nowa wysokosc: 8

********** ********** ********** ********** ********** ********** ********** ********** *** Menu *** (1) Rysuj prostokat (2) Obszar (3) Obwod (4) Zmien rozmiar (5) Wyjscie 2 Obszar: 80 *** Menu *** (1) Rysuj prostokat (2) Obszar (3) Obwod (4) Zmien rozmiar (5) Wyjscie 3 Obwod: 36 *** Menu *** Rysuj prostokat Obszar Obwod Zmien rozmiar Wyjscie

(1) (2) (3) (4) (5) 5

Wyjscie...

Analiza Ten program wykorzystuje wikszo wiadomoci, jakie zdobye czytajc poprzednie rozdziay. Powiniene umie wpisa, skompilowa, poczy i uruchomi program, a ponadto zrozumie w jaki sposb dziaa (pod warunkiem e uwanie czytae dotychczasowe rozdziay). Sze pierwszych linii przygotowuje nowe typy i definicje, ktre bd uywane w programie. W liniach od 6. do 26. jest zadeklarowana klasa Rectangle (prostokt). Zawiera ona publiczne akcesory przeznaczone do odczytywania i ustawiania wysokoci i szerokoci prostokta, a take metody obliczania jego obszaru i obwodu. Linie od 29. do 40. zawieraj definicje tych funkcji klasy, ktre nie zostay zdefiniowane inline. Prototypy funkcji dla funkcji globalnych znajduj si w liniach od 44. do 47., za sam program zaczyna si w linii 49. Dziaanie programu polega na wygenerowaniu prostokta, a nastpnie

wypisaniu menu, zawierajcego pi opcji: rysowanie prostokta, obliczanie jego obszaru, obliczanie jego obwodu, zmiana rozmiarw prostokta oraz wyjcie. W linii 55. ustawiany jest znacznik (flaga); jeli wartoci tego znacznika jest false, dziaanie ptli jest kontynuowane. Warto true jest przypisywana do tego znacznika tylko wtedy, gdy uytkownik wybierze z menu polecenie Wyjcie. Inne opcje, z wyjtkiem Zmie rozmiar, wywouj odpowiednie funkcje. Dziki temu dziaanie instrukcji switch jest bardziej przejrzyste. Opcja Zmie rozmiar nie moe wywoywa funkcji, gdy zmienioby to rozmiary prostokta. Jeli prostokt zostaby przekazany (przez warto) do funkcji takiej, jak na przykad DoChangeDimensions() (zmie rozmiary), wtedy rozmiary zostayby zmienione jedynie w lokalnej kopii prostokta w tej funkcji i nie zostayby odzwierciedlone w prostokcie w funkcji main(). Z rozdziau 8., Wskaniki, oraz rozdziau 10., Funkcje zaawansowane, dowiesz si, w jaki sposb omin to ograniczenie. Na razie jednak zmiana rozmiarw odbywa si bezporednio w funkcji main(). Zwr uwag, e uycie typu wyliczeniowego sprawio, e instrukcja switch jest bardziej przejrzysta i atwiejsza do zrozumienia. Gdyby przeczanie zaleao od liczb (1 5) wybranych przez uytkownika, musiaby stale zaglda do opisu menu, aby dowiedzie si, do czego suy dana opcja. W linii 60. nastpuje sprawdzenie, czy opcja wybrana przez uytkownika mieci si w dozwolonym zakresie. Jeli nie, jest wypisywany komunikat bdu i nastpuje odwieenie (czyli ponowne wypisanie) menu. Zauwa, e instrukcja switch posiada niemoliwy przypadek default. Stanowi on pomoc przy debuggowaniu. Gdy program dziaa, instrukcja ta nigdy nie powinna zosta wykonana.
Usunito: wyliczenia

Usunito: Usunito: klauzul Usunito: a

Rozdzia 8. Wskaniki
Jedn z najbardziej przydatnych dla programisty C++ rzeczy jest moliwo bezporedniego manipulowania pamici za pomoc wskanikw. Z tego rozdziau dowiesz si: czym s wskaniki, jak deklarowa wskaniki i uywa ich, czym jest sterta i w jaki sposb mona manipulowa pamici.

Wskaniki stanowi podwjne wyzwanie dla osoby uczcej si jzyka C++: po pierwsze, mog by niezrozumiae, a po drugie, na pocztku moe nie by jasne, do czego mog si przyda. W tym rozdziale krok po kroku wyjanimy dziaanie wskanikw. Aby w peni zrozumie potrzeb ich uywania, musisz zapozna si z zawartoci kolejnych rozdziaw.

Czym jest wskanik?


Wskanik (ang. pointer) jest zmienn, przechowujc adres pamici. To wszystko. Jeli rozumiesz to proste stwierdzenie, wiesz ju wszystko o wskanikach. Jeszcze raz: wskanik jest zmienn przechowujc adres pamici.

Kilka sw na temat pamici


Aby zrozumie, do czego su wskaniki, musisz wiedzie kilka rzeczy o pamici komputera. Pami jest podzielona na kolejno numerowane lokalizacje. Kada zmienna umieszczona jest w danym miejscu pamici, jednoznacznie okrelonym przez tzw. adres pamici. Rysunek 8.1 przedstawia schemat miejsca przechowywania zmiennej typu unsigned long o nazwie theAge (wiek). Rys. 8.1. Schemat przechowywania zmiennej theAge

Uycie operatora adresu (&)


W kadym komputerze pami jest adresowana w inny sposb, za pomoc rnych, zoonych schematw. Zwykle programista nie musi zna konkretnego adresu danej zmiennej, tymi szczegami zajmuje si kompilator. Jeli jednak chcesz uzyska t informacj, moesz uy operatora adresu (&), ktry zwraca adres obiektu znajdujcego si w pamici. Jego wykorzystanie przedstawiono na listingu 8.1. Listing 8.1. Przykad uycia operatora adresu
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: // Listing 8.1 Demonstruje operator adresu // oraz adresy zmiennych lokalnych #include <iostream> int main() { using namespace std; unsigned short shortVar=5; unsigned long longVar=65535; long sVar = -65535; cout << "shortVar:\t" << shortVar; cout << "\tAdres zmiennej shortVar:\t"; cout << &shortVar << "\n"; cout << "longVar:\t" << longVar; cout << "\tAdres zmiennej longVar:\t" ; cout << &longVar << "\n"; cout << "sVar:\t\t" << sVar; cout << "\tAdres zmiennej sVar:\t" ; cout << &sVar << "\n"; return 0;

25:

Wynik
shortVar: 0012FF7C longVar: 0012FF78 sVar: 0012FF74 5 65535 -65535 Adres zmiennej shortVar: Adres zmiennej longVar: Adres zmiennej sVar:

Analiza Tworzone i inicjalizowane s trzy zmienne: typu unsigned short w linii 8., typu unsigned long w linii 9. oraz long w linii 10. Ich wartoci i adresy s wypisywane w liniach od 12 do 16. Adresy zmiennych uzyskiwane s za pomoc operatora adresu (&). Wartoci zmiennej shortVar (krtka zmienna) jest 5 (tak jak mona byo oczekiwa). W moim komputerze Pentium (32-bitowym) ta zmienna ma adres 0012FF7C. Adres zaley od komputera i moe by nieco inny przy kadym uruchomieniu programu. W twoim komputerze adresy tych zmiennych take mog si rni. Deklarujc typ zmiennej, informujesz kompilator, ile miejsca w pamici powinien dla niej zarezerwowa, jednak adres jest przydzielany zmiennej automatycznie. Na przykad dugie (long) zmienne cakowite zajmuj zwykle cztery bajty, co oznacza, e zmienna posiada adres dla czterech bajtw pamici. Zwr uwag, e twj kompilator, podobnie jak mj, moe nalega na to, by zmienne otrzymyway adresy bdce wielokrotnoci 4 (tj. zmienna longVar otrzymuje adres pooony cztery bajty za zmienn shortVar, mimo i zmienna shortVar potrzebuje tylko dwch bajtw!)

Przechowywanie adresu we wskaniku


Kada zmienna posiada adres. Moesz umieci go we wskaniku nawet bez znajomoci adresu danej zmiennej. Przypumy na przykad, e zmienna howOld jest cakowita. Aby zadeklarowa wskanik o nazwie pAge, mogcy zawiera adres tej zmiennej, moesz napisa:
int *pAge = 0;

Spowoduje to zadeklarowanie zmiennej pAge jako wskanika do typu int. Innymi sowy, zmienna pAge jest zadeklarowana jako przechowujca adresy wartoci cakowitych. Zwr uwag, e pAge jest zmienn. Gdy deklarujesz zmienn cakowit (typu int), kompilator rezerwuje tyle pamici, ile jest potrzebne do przechowania wartoci cakowitej. Gdy deklarujesz zmienn wskanikow tak jak pAge, kompilator rezerwuje ilo pamici wystarczajc do przechowania adresu (w wikszoci komputerw zajmuje on cztery bajty). pAge jest po prostu kolejnym typem zmiennej.

Puste i bdne wskaniki


W tym przykadzie wskanik pAge jest inicjalizowany wartoci zero. Wskanik, ktrego wartoci jest zero, jest nazywany wskanikiem pustym (ang. null pointer). Podczas tworzenia wskanikw, powinny by one zainicjalizowane jak wartoci. Jeli nie wiesz, jak warto przypisa wskanikowi, przypisz mu warto 0. Wskanik, ktry nie jest zainicjalizowany, jest nazywany wskanikiem bdnym (ang. wild pointer). Bdne wskaniki s bardzo niebezpieczne.
UWAGA Pamitaj o zasadzie bezpiecznego programowania: inicjalizuj swoje wskaniki!

Musisz jawnie przypisa wskanikowi adres zmiennej howOld. Poniszy przykad pokazuje, jak to zrobi:
unsigned short int howOld = 50; // tworzymy zmienn unsigned short int * pAge = 0; // tworzymy wskanik pAge = &howOld; // umieszczamy adres zmiennej hOld w zmiennej pAge

Pierwsza linia tworzy zmienn howOld typu unsigned short int oraz inicjalizuje j wartoci 50. Druga linia deklaruje zmienn pAge jako wskanik do typu unsigned short int i ustawia j na zero. To, e zmienna pAge jest wskanikiem, mona pozna po gwiazdce (*) umieszczonej pomidzy typem zmiennej a jej nazw. Trzecia, ostatnia linia, przypisuje wskanikowi pAge adres zmiennej howOld. Przypisywanie adresu mona pozna po uyciu operatora adresu (&). Gdyby operator adresu zosta pominity, wskanikowi pAge zostaaby przypisana warto zmiennej howOld. Oczywicie, warto ta mogaby by poprawnym adresem. Teraz wskanik pAge zawiera adres zmiennej howOld. Ta zmienna ma warto 50. Mona uzyska ten rezultat wykonujc o jeden krok mniej, na przykad:
unsigned short int howOld = 50; // tworzymy zmienn unsigned short int * pAge = &howOld; // tworzymy wskanik do howOld

pAge jest wskanikiem, zawierajcym teraz adres zmiennej howOld. Uywajc wskanika pAge, moesz sprawdzi warto zmiennej howOld, ktra w tym przypadku wynosi 50. Dostp do zmiennej howOld poprzez wskanik pAge jest nazywany dostpem porednim (dostp do niej rzeczywicie odbywa si poprzez ten wskanik). Z dalszej czci rozdziau dowiesz si, jak w ten sposb odwoywa si do wartoci zmiennej.

Dostp poredni oznacza dostp do zmiennej o adresie przechowywanym we wskaniku. Uycie wskanikw stanowi poredni sposb uzyskania wartoci przechowywanej pod danym adresem.
UWAGA W przypadku zwykej zmiennej, jej typ informuje kompilator, ile potrzebuje pamici do przechowania jej wartoci. W przypadku wskanikw sytuacja wyglda inaczej: kady wskanik zajmuje cztery bajty. Typ wskanika informuje kompilator, ile potrzeba miejsca w pamici do przechowania obiektu, ktrego adres zawiera wskanik!

W deklaracji
unsigned shot int * pAge = 0; // tworzymy wskanik

zmienna pAge jest zadeklarowana jako wskanik do typu unsigned short int. Mwi ona kompilatorowi, e wskanik ten (ktry do przechowania adresu wymaga czterech bajtw) bdzie przechowywa adres obiektu typu unsigned short int, ktry zajmuje dwa bajty.

Nazwy wskanikw
Podobnie jak inne zmienne, wskaniki mog mie dowolne nazwy. Wielu programistw przestrzega konwencji, w ktrej nazwy wszystkich wskanikw poprzedza si literk p (pointer), np. pAge czy pNumber.
Usunito: e

Operator wyuskania
Operator wyuskania (*) jest zwany take operatorem dostpu poredniego albo dereferencj. Podczas wyuskiwania wskanika otrzymywana jest warto wskazywana przez adres zawarty w tym wskaniku. Zwyke zmienne zapewniaj bezporedni dostp do swoich wartoci. Gdy tworzysz now zmienn typu unsigned short int o nazwie yourAge i chcesz jej przypisa warto zmiennej howOld, moesz napisa:
unsigned short int yourAge; yourAge = howOld;

Wskanik umoliwia poredni dostp do wartoci zmiennej, ktrej adres zawiera. Aby przypisa warto zmiennej howOld do zmiennej yourAge za pomoc wskanika pAge, powiniene napisa:
unsigned short int yourAge; yourAge = *pAge;

Operator wyuskania (*) znajdujcy si przed zmienn pAge oznacza warto przechowywana pod adresem. To przypisanie mona potraktowa jako: We warto przechowywan pod adresem zawartym w pAge i przypisz j do zmiennej yourAge.

UWAGA W przypadku wskanikw gwiazdka (*) moe posiada dwa znaczenia (moe symbolizowa cz deklaracji wskanika albo operator wyuskania).

Gdy deklarujesz wskanik, * jest czci deklaracji i nastpuje po typie wskazywanego obiektu. Na przykad:
// tworzymy wskanik do typu unsigned short unsigned short * page = 0;

Gdy wskanik jest wyuskiwany, operator wyuskiwania wskazuje, e odwoujemy si do wartoci, znajdujcej si w miejscu pamici okrelonym przez adres zawarty we wskaniku, a nie do tego adresu.
// wartoci wskazywanej przez pAge przypisujemy warto 5 *pAge = 5;

Zwr take uwag, e ten sam znak (*) jest uywany jako operator mnoenia. Kompilator wie z kontekstu, o ktry operator chodzi w danym miejscu programu.

Wskaniki, adresy i zmienne


Naley dokona rozrnienia pomidzy wskanikiem, adresem zawartym w tym wskaniku, a zmienn o adresie zawartym w tym wskaniku. Nieumiejtno rozrnienia ich jest najczstszym powodem nieporozumie ze wskanikami. Wemy nastpujcy fragment kodu:
int theVariable = 5; int * pPointer = &theVariable;

Zmienna theVariable jest zadeklarowana jako zmienna typu int i jest inicjalizowana wartoci 5. Zmienna pPointer jest zadeklarowana jako wskanik do typu int i jest inicjalizowana adresem zmiennej theVariable. pPointer jest wskanikiem. Adres zawarty w pPointer jest adresem zmiennej theVariable. Wartoci znajdujc si pod adresem zawartym w pPointer jest 5. Schemat zmiennych theVariable i pPointer przedstawia rysunek 8.2. Rys. 8.2. Schematyczna reprezentacja pamici

Na tym rysunku warto 5 zostaa umieszczona pod adresem 101. Jest on podany jako liczba dwjkowa
0000 0000 0000 0101

Jest to dwubajtowa (16-bitowa) warto, ktrej wartoci dziesitn jest 5. Zmienna wskanikowa ma adres 106. Jej warto to
0000 0000 0000 0000 0000 0000 0110 0101

Jest to binarna reprezentacja wartoci 101 (dziesitnie), stanowicej adres zmiennej theVariable, ktra zawiera warto 5. Przedstawiony powyej ukad pamici jest uproszczony, ale ilustruje przeznaczenie wskanikw zawierajcych adresy pamici.

Operowanie danymi poprzez wskaniki


Gdy przypiszesz wskanikowi adres zmiennej, moesz uy tego wskanika w celu uzyskania dostpu do danych zawartych w tej zmiennej. Listing 8.2 pokazuje, w jaki sposb adres lokalnej zmiennej jest przypisywany wskanikowi i w jaki sposb ten wskanik moe operowa wartoci w tej zmiennej. Listing 8.2. Operowanie danymi poprzez wskanik
0: // Listing 8.2 Uycie wskanika 1: 2: #include <iostream> 3: 4: typedef unsigned short int USHORT; 5: 6: int main() 7: { 8: 9: using std::cout; 10: 11: USHORT myAge; // zmienna 12: USHORT * pAge = 0; // wskanik 13: 14: myAge = 5; 15: 16: cout << "myAge: " << myAge << "\n"; 17: pAge = &myAge; // wskanikowi pAge przypisuje adres zmiennej myAge 18: cout << "*pAge: " << *pAge << "\n\n"; 19: 20: cout << "Ustawiam *pAge = 7...\n"; 21: *pAge = 7; // ustawia myAge na 7 22: 23: cout << "*pAge: " << *pAge << "\n";

24: 25: 26: 27: 28: 29: 30: 31: 32: 33:

cout << "myAge: " << myAge << "\n\n"; cout << "Ustawiam myAge = 9...\n"; myAge = 9; cout << "myAge: " << myAge << "\n"; cout << "*pAge: " << *pAge << "\n"; return 0; }

Wynik
myAge: 5 *pAge: 5 Ustawiam *pAge = 7... *pAge: 7 myAge: 7 Ustawiam myAge = 9... myAge: 9 *pAge: 9

Analiza Program deklaruje dwie zmienne: myAge typu unsigned short oraz wskanik do typu unsigned short, zmienn pAge. W linii 14. zmiennej myAge jest przypisywana warto 5; potwierdza to komunikat wypisywany w linii 16. W linii 17. wskanikowi pAge jest przypisywany adres zmiennej myAge. W linii 18 nastpuje wyuskanie wskanika pAge i wypisanie otrzymanej wartoci (to pokazuje, e warto o adresie zawartym w pAge jest wartoci 5, czyli wartoci zmiennej myAge). W linii 21. zmiennej o adresie zawartym w pAge jest przypisywana warto 7. Powoduje to przypisanie tej wartoci zmiennej myAge, co potwierdzaj komunikaty wypisywane w liniach 23. i 24. W linii 29. zmiennej myAge jest przypisywana warto 9. Ta warto jest pobierana bezporednio w linii 29., za w linii 30. porednio (poprzez wyuskanie wskanika pAge).

Sprawdzanie adresu
Wskaniki umoliwiaj operowanie adresami nawet bez znajomoci ich faktycznych wartoci. Do tej pory musiae przyjmowa jako oczywiste, e gdy przypisujesz wskanikowi adres zmiennej, to wartoci wskanika staje si rzeczywicie adres tej zmiennej. Dlaczego nie miaby si teraz co do tego upewni? Przedstawia to listing 8.3. Listing 8.3. Sprawdzanie zawartoci wskanika
0: 1: 2: 3: 4: 5: 6: 7: // Listing 8.3 Co zawiera wskanik?. #include <iostream> int main() { using std::cout;

8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39:

unsigned short int myAge = 5, yourAge = 10; // wskanik unsigned short int * pAge = &myAge; cout << "myAge:\t" << myAge << "\t\tyourAge:\t" << yourAge << "\n"; cout << "&myAge:\t" << &myAge << "\t&yourAge:\t" << &yourAge <<"\n"; cout << "pAge:\t" << pAge << "\n"; cout << "*pAge:\t" << *pAge << "\n"; cout << "\nPonowne przypisanie: pAge = &yourAge...\n\n"; pAge = &yourAge; // ponowne przypisanie do wskanika cout << "myAge:\t" << myAge << "\t\tyourAge:\t" << yourAge << "\n"; cout << "&myAge:\t" << &myAge << "\t&yourAge:\t" << &yourAge <<"\n"; cout << "pAge:\t" << pAge << "\n"; cout << "*pAge:\t" << *pAge << "\n"; cout << "\n&pAge:\t" << &pAge << "\n"; return 0; }

Wynik
myAge: &myAge: pAge: *pAge: 5 0012FF7C 0012FF7C 5 yourAge: &yourAge: 10 0012FF78

Ponowne przypisanie: pAge = &yourAge... myAge: &myAge: pAge: *pAge: &pAge: 5 0012FF7C 0012FF78 10 0012FF74 yourAge: &yourAge: 10 0012FF78

(Twoje wyniki mog by inne.)

Analiza

W linii 9. deklarowane s dwie zmienne, myAge oraz yourAge, obie typu unsigned short. W linii 12. deklarowana jest zmienna pAge, bdca wskanikiem do typu unsigned short; ten wskanik jest inicjalizowany adresem zmiennej myAge. W liniach od 14. do 18. nastpuje wypisanie wartoci i adresw zmiennych myAge i yourAge. Linia 20. wypisuje zawarto wskanika pAge, ktr jest adres zmiennej myAge. Linia 21. wypisuje rezultat wyuskania wskanika pAge, czyli wypisuje warto zmiennej wskazywanej przez ten wskanik (warto zmiennej myAge, wynoszc 5). W taki wanie sposb dziaaj wskaniki. Linia 20. pokazuje, e wskanik pAge zawiera adres zmiennej myAge, za linia 21. pokazuje, w jaki sposb warto przechowywana w zmiennej myAge moe zosta uzyskana w wyniku wyuskania wskanika pAge. Zanim przejdziesz dalej, upewnij si, czy to rozumiesz. Przestudiuj kod i porwnaj z wynikiem. W linii 25. zmiennej pAge jest przypisywany nowy adres, tym razem adres zmiennej yourAge. Ponownie wypisywane s wartoci i adresy. Wyniki pokazuj, e wskanik pAge zawiera teraz adres zmiennej yourAge i e jako efekt wyuskania uzyskujemy warto przechowywan w tej zmiennej. Linia 36. wypisuje adres zmiennej pAge. Tak jak wszystkie inne zmienne, swj adres posiada take wskanik. Adres ten take moe by umieszczony we wskaniku. (Przypisywanie adresu wskanika do innego wskanika zostanie omwione wkrtce.) TAK W celu uzyskania dostpu do danych przechowywanych pod adresem zawartym we wskaniku uywaj operatora wyuskania (*). Inicjalizuj wszystkie wskaniki albo adresem poprawnym, albo adresem pustym (0). Pamitaj o rnicy pomidzy adresem we wskaniku, a wartoci pod tym adresem.
Usunito: i

Uycie wskanikw

Aby zadeklarowa wskanik, napisz typ zmiennej lub obiektu, ktrego adres bdzie przechowywany w tym wskaniku, gwiazdk (*) oraz nazw wskanika. Na przykad:
unsigned short int * pPointer = 0;

Aby zainicjalizowa wskanik (przypisa mu adres), poprzed nazw zmiennej, ktrej adres chcesz przypisa, operatorem adresu (&). Na przykad;
unsigned short int theVariable = 5; unsigned short int * pPointer = & theVariable;

Aby wyuska wskanik, poprzed nazw wskanika operatorem wyuskania (*). Na przykad:
unsigned short int theValue = *pPointer;

Do czego su wskaniki?
Jak dotd, poznae krok po kroku proces przypisywania wskanikowi adresu zmiennej. W praktyce jednak nie bdziesz tego robi nigdy. Po co miaby utrudnia sobie ycie uywaniem wskanikw, skoro masz do dyspozycji zmienn, do ktrej masz peny dostp? Jedynym powodem, dla ktrego operujemy wskanikami na zmiennych automatycznych (tj. lokalnych), jest zademonstrowanie sposobu dziaania wskanikw. Teraz, gdy poznae ju skadni wskanikw, moesz pozna ich praktyczne zastosowania. Wskaniki s najczciej uywane do wykonywania trzech zada: zarzdzania danymi na stercie, uzyskiwania dostpu do danych i funkcji skadowych klasy. przekazywania zmiennych do funkcji poprzez referencj.
Usunito: skadowych

W pozostaej czci rozdziau skupimy si na zarzdzaniu danymi na stercie oraz dostpie do danych i funkcji skadowych klasy. Przekazywanie zmiennych przez referencj omwimy w nastpnym rozdziale.

Stos i sterta
W rozdziale 5., w podrozdziale Jak dziaaj funkcje rzut oka <<pod mask>> wspomniano o piciu obszarach pamici: globalnej przestrzeni nazw, stercie, rejestrach, przestrzeni kodu, stosie.

Zmienne lokalne znajduj si na stosie (podobnie jak parametry funkcji). Kod wystpuje, oczywicie, w przestrzeni kodu, za zmienne globalne w globalnej przestrzeni nazw. Rejestry s uywane do kontrolowania wewntrznych zada procesora, takich jak ledzenie szczytu stosu czy miejsca wykonania programu. Caa pozostaa pami jest prawie w caoci przeznaczona na tak zwan stert (ang. heap). Problem ze zmiennymi lokalnymi polega na tym, e nie s one trwae: gdy funkcja koczy dziaanie, zmienne te s niszczone. Rozwizuj ten problem zmienne globalne, nie s one jednak dostpne bez ogranicze w caym programie; powoduje to, kod jest trudny do zrozumienia i zmodyfikowania. Umieszczanie danych na stercie uwalnia od obu tych niedogodnoci.
Usunito: j

Moesz uznawa stert za obszerny blok pamici, zawierajcy dziesitki tysicy kolejno ponumerowanych pojemnikw, oczekujcych na twoje dane. Jednak w odrnieniu od stosu, nie moesz nadawa tym pojemnikom etykietek. Musisz poprosi o adres pojemnika, ktry rezerwujesz, a nastpnie przechowa ten adres we wskaniku. Mona take znale inn analogi: wyobra sobie, e przyjaciel da ci numer telefonu do firmy kurierskiej. Wracasz do domu, programujesz ten numer w swoim aparacie telefonicznym pod okrelonym przyciskiem, po czym wyrzucasz kartk z numerem. Gdy naciniesz przycisk, telefon wybierze jaki numer i poczy ci z firm kuriersk. Nie pamitasz numeru i nie wiesz, gdzie znajduje si firma, ale przycisk umoliwia ci dostp do niej. Firma to twoje dane na stercie. Nie wiesz gdzie jest, ale wiesz jak si z ni skontaktowa. Suy do tego jej adres w tym przypadku jest nim numer telefonu. Nie musisz zna tego numeru; wystarczy, e masz go we wskaniku (przycisku w telefonie). Wskanik umoliwia ci dostp do danych, bez koniecznoci przeprowadzania szczegowych, dodatkowych dziaa. Gdy funkcja koczy dziaanie, stos jest czyszczony automatycznie. Wszystkie zmienne lokalne wychodz poza zakres i s usuwane ze stosu. Sterta nie jest czyszczona a do chwili zakoczenia dziaania programu, dlatego to ty jeste odpowiedzialny za zwolnienie wszelkiej zaalokowanej przez siebie pamici. Zalet sterty jest to, e zaalokowana (zarezerwowana) na niej pami pozostaje dostpna a do momentu, w ktrym j zwolnisz. Jeli pami na stercie zaalokujesz w funkcji, po wyjciu z tej funkcji pami pozostanie nadal dostpna. Zalet tej metody korzystania z pamici (w przeciwiestwie do zmiennych globalnych) jest to, e dostp do tej pamici maj tylko te funkcje, ktre posiadaj do niej wskanik. Dziki temu mona cile kontrolowa interfejs do danych eliminuje to potencjalny problem nieoczekiwanej i niezauwaalnej zmiany danych przez inn funkcj. Aby ten mechanizm dziaa, musisz mie moliwo tworzenia wskanika do obszaru pamici na stercie oraz przekazywania tego wskanika pomidzy funkcjami. Proces ten opisuj nastpne podrozdziay.

Operator new
W jzyku C++, do alokowania pamici na stercie suy sowo kluczowe new (nowy). Po tym sowie kluczowym nastpuje typ obiektu, jaki chcesz zaalokowa dziki temu kompilator wie, ile miejsca powinien zarezerwowa. Instrukcja new unsigned short int alokuje na stercie dwa bajty, a instrukcja new long alokuje cztery bajty. Zwracan wartoci jest adres pamici. Musi on zosta przypisany do wskanika. Aby stworzy na stercie obiekt typu unsigned short, moesz napisa:
unsigned short int * pPointer; pPointer = new unsigned short int;

Mona oczywicie zainicjalizowa wskanik w trakcie jego tworzenia:


unsigned short int * pPointer = new unsigned short int;

W obu przypadkach, wskanik pPointer wskazuje teraz pooony na stercie obiekt typu unsigned short int. Moesz uy tego wskanika tak, jak kadego innego wskanika do zmiennej i przypisa obiektowi na stercie dowoln warto:
*pPointer = 72;

Oznacza to: Umie 72 jako warto obiektu wskazywanego przez pPointer lub Przypisz obszarowi sterty wskazywanemu przez wskanik pPointer warto 72.
UWAGA Gdy operator new nie jest w stanie zarezerwowa pamici na stercie (w kocu pami ma ograniczon objto), zgasza wyjtek (patrz rozdzia 20., Wyjtki i obsuga bdw).

delete
Gdy skoczysz korzysta z obszaru pamici na stercie, musisz uy sowa kluczowego delete (usu) z waciwym wskanikiem. Instrukcja delete zwalnia pami zaalokowan na stercie, tj. zwraca j stercie. Pamitaj, e sam wskanik w przeciwiestwie do pamici, na ktr wskazuje jest zmienn lokaln. Gdy funkcja, w ktrej zosta zadeklarowany, koczy dziaanie, wskanik wychodzi poza zakres i jest niszczony. Pami zaalokowana operatorem new nie jest zwalniana automatycznie; staje si niedostpna taka sytuacja jest nazywana wyciekiem pamici (ang. memory leak). Nazwa wzia si std, e pami nie moe by odzyskana, a do momentu zakoczenia dziaania programu (z punktu widzenia programu, pami wycieka z komputera). Aby zwrci stercie pami, uyj sowa kluczowego delete. Na przykad:
delete pPointer;

Gdy zwalniasz wskanik, w rzeczywistoci zwalniasz jedynie pami, ktrej adres jest zawarty w tym wskaniku. Mwisz: Zwr stercie pami, na ktr wskazuje ten wskanik. Wskanik nadal pozostaje wskanikiem i mona mu ponownie przypisa adres. Listing 8.4 przedstawia alokowanie zmiennej na stercie, uycie tej zmiennej, a nastpnie zwolnienie jej.
OSTRZEENIE Gdy uywasz sowa kluczowego delete dla wskanika, zwalniana jest pami, na ktr on wskazuje. Ponowne wywoanie delete dla tego wskanika spowoduje zaamanie programu! Gdy zwalniasz wskanik, ustaw go na zero (null, wskanik pusty). Kompilator gwarantuje, e wywoanie delete z pustym wskanikiem jest bezpieczne. Na przykad:
Animal delete pDog = // ... delete *pDog = new Animal; pDog; // zwalnia pami 0; // ustawia wskanik na null pDog; // nieszkodliwe

Listing 8.4. Alokowanie, uycie i zwolnienie wskanika


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: // Listing 8.4 // Alokowanie i zwalnianie wskanika #include <iostream> int main() { using std::cout; int localVariable = 5; int * pLocal= &localVariable; int * pHeap = new int; *pHeap = 7; cout << "localVariable: " << localVariable << "\n"; cout << "*pLocal: " << *pLocal << "\n"; cout << "*pHeap: " << *pHeap << "\n"; delete pHeap; pHeap = new int; *pHeap = 9; cout << "*pHeap: " << *pHeap << "\n"; delete pHeap; return 0; }

Wynik
localVariable: 5 *pLocal: 5 *pHeap: 7 *pHeap: 9

Analiza W linii 7. program deklaruje i inicjalizuje lokaln zmienn. W linii 8. deklaruje i inicjalizuje wskanik, przypisujc mu adres tej zmiennej. W linii 9. deklaruje wskanik, lecz inicjalizuje go wartoci uzyskan w wyniku wywoania operatora new int. Powoduje to zaalokowanie na stercie miejsca dla wartoci typu int. Linia 10. przypisuje warto 7 do nowo zaalokowanej pamici. Linia 11. wypisuje warto zmiennej lokalnej, a linia 12. wypisuje warto wskazywan przez pLocal (lokalna) Jak naleao oczekiwa, s one takie same. Linia 13. wypisuje warto wskazywan przez pHeap (sterta) i pokazuje, e rzeczywicie mamy dostp do wartoci zaalokowanej w linii 10. W linii 14. pami zaalokowana w linii 9. jest zwracana na stert (w wyniku wywoania delete). Czynno ta zwalnia pami i odcza od niej wskanik. Teraz pHeap moe wskazywa inne miejsce w pamici. Nowy adres i warto przypisujemy mu w liniach 15. i 16., za w linii 17. wypisujemy wynik. Linia 18. zwalnia pami i zwraca j stercie. Cho linia 18. jest nadmiarowa (zakoczenie programu automatycznie powoduje zwolnienie pamici), do dobrych obyczajw naley jawne zwalnianie wskanikw. Gdy program bdzie modyfikowany lub rozbudowywany, zapamitanie tego kroku moe okaza si bardzo przydatne.

Wycieki pamici
Inn sytuacj, ktra moe doprowadzi do wycieku pamici, jest ponowne przypisanie wskanikowi adresu, bez wczeniejszego zwolnienia pamici, na ktr w danym momencie wskazuje. Spjrzmy na poniszy fragment kodu:
0: 1: 2: 3: unsigned short int * pPointer = new unsigned short int; *pPointer = 72; pPointer = new unsigned short int; pPointer = 84;

Linia 0 tworzy pPointer i przypisuje mu adres rezerwowanego na stercie obszaru. Linia 1. umieszcza w tym obszarze warto 72. Linia 2. ponownie przypisuje wskanikowi pPointer adres innego obszaru pamici. Linia 3. umieszcza w tym obszarze warto 84. Pierwotny obszar w ktrym jest zawarta warto 72 jest niedostpny, gdy wskanik do tej pamici zosta wypeniony innym adresem. Nie ma sposobu na odzyskanie pierwotnego obszaru, nie ma te sposobu na zwolnienie go przed zakoczeniem dziaania programu. Ten kod powinien zosta napisany nastpujco:
0: 1: 2: 3: 4: unsigned short int * pPointer = new unsigned short int; *pPointer = 72; delete pPointer; pPointer = new unsigned short int; pPointer = 84;

Teraz pami, wskazywana pierwotnie przez pPointer, jest zwalniana w linii 2.


UWAGA Za kadym razem, gdy uyjesz w programie sowa kluczowego new, powiniene uy take odpowiadajcego mu sowa kluczowego delete. Naley pamita, na co wskazuje dany wskanik (aby mie pewno, e zostanie to zwolnione, gdy przestanie potrzebne).

Tworzenie obiektw na stercie


Moesz stworzy nie tylko wskanik do zmiennej cakowitej, ale i wskanik do dowolnego obiektu. Jeli zadeklarowae obiekt typu Cat (kot), moesz zadeklarowa wskanik do tej klasy i stworzy na stercie egzemplarz obiektu tej klasy (tak jak moge stworzy go na stosie). Skadnia jest taka sama, jak w przypadku innych zmiennych:
Cat *pCat = new Cat;

Powoduje to wywoanie domylnego konstruktora klasy czyli konstruktora, ktry nie ma parametrw. Konstruktor jest wywoywany za kadym razem, gdy tworzony jest obiekt klasy (na stosie lub na stercie).

Usuwanie obiektw
Gdy wywoujesz delete ze wskanikiem do obiektu na stercie, przed zwolnieniem pamici obiektu wywoywany jest jego destruktor. Dziki temu klasa ma szans posprztania po sobie, tak jak w przypadku obiektw niszczonych na stosie. Tworzenie i usuwanie obiektw na stercie przedstawia listing 8.5. Listing 8.5. Tworzenie i usuwanie obiektw na stercie
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: // Listing 8.5 // Tworzenie obiektw na stercie // z uyciem new oraz delete #include <iostream> class SimpleCat { public: SimpleCat(); ~SimpleCat(); private: int itsAge; }; SimpleCat::SimpleCat() { std::cout << "Wywolano konstruktor.\n"; itsAge = 1; } SimpleCat::~SimpleCat() { std::cout << "Wywolano destruktor.\n"; } int main() { std::cout << "SimpleCat Mruczek...\n"; SimpleCat Mruczek; std::cout << "SimpleCat *pFilemon = new SimpleCat...\n"; SimpleCat * pFilemon = new SimpleCat; std::cout << "delete pFilemon...\n"; delete pFilemon; std::cout << "Wyjscie, czekaj na Mruczka...\n"; return 0; } Usunito: t

Wynik
SimpleCat Mruczek... Wywolano konstruktor. SimpleCat *pFilemon = new SimpleCat...

Wywolano konstruktor. delete pFilemon... Wywolano destruktor. Wyjscie, czekaj na Mruczka... Wywolano destruktor.

Analiza Linie od 6. do 13. deklaruj okrojon klas SimpleCat (prosty kot). Linia 9. deklaruje konstruktor tej klasy, za linie od 15. do 19. zawieraj jego definicj. Linia 10 deklaruje destruktor klasy, a linie od 21. do 24. zawieraj jego definicj. W linii 29. na stosie tworzony jest obiekt Mruczek, powoduje to wywoanie konstruktora klasy. W linii 31. na stercie tworzony jest egzemplarz obiektu SimpleCat, wskazywany przez zmienn pFilemon; w wyniku tego dziaania nastpuje ponowne wywoanie konstruktora. W linii 33. znajduje si sowo kluczowe delete ze wskanikiem pFilemon, dlatego wywoywany jest destruktor. Gdy funkcja main() koczy dziaanie, obiekt Mruczek wychodzi z zakresu i ponownie wywoywany jest destruktor.

Dostp do skadowych klasy


W przypadku obiektw Cat stworzonych lokalnie, dostp do skadowych funkcji i danych odbywa si za pomoc operatora kropki (.). Aby odwoa si do skadowych utworzonego na stercie obiektu Cat, musisz wyuska wskanik i wywoa operator kropki dla obiektu wskazywanego przez ten wskanik. Aby odwoa si do funkcji skadowej GetAge(), moesz napisa:
(*pFilemon).GetAge();

Aby zapewni, wyuskanie wskanika pFilemon przed odwoaniem si do metody GetAge(), uyte zostay nawiasy. Poniewa taki zapis jest do skomplikowany, C++ oferuje skrtowy operator dostpu poredniego: operator wskazywania (->). Skada si on z ze znaku minus (-) i znaku wikszoci (>), zapisanych razem. Kompilator traktuje je jako pojedynczy symbol. Dostp do skadowych funkcji i danych utworzonego na stercie obiektu przedstawia listing 8.6. Listing 8.6. Dostp do skadowych funkcji i danych utworzonego na stercie obiektu.
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: // Listing 8.6 // Dostp do skadowych funkcji i danych obiektu // utworzonego na stercie, z uyciem operatora -> #include <iostream> class SimpleCat { public: SimpleCat() {itsAge = 5; } ~SimpleCat() {} int GetAge() const { return itsAge; } void SetAge(int age) { itsAge = age; }

13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:

private: int itsAge; }; int main() { SimpleCat * Mruczek = new SimpleCat; std::cout << "Mruczek ma " << Mruczek->GetAge() << " lat\n"; Mruczek->SetAge(7); std::cout << "Mruczek ma " << Mruczek->GetAge() << " lat\n"; delete Mruczek; return 0; }

Wynik
Mruczek ma 5 lat Mruczek ma 7 lat

Analiza W linii 19. na stercie tworzony jest egzemplarz obiektu klasy SimpleCat. Domylny konstruktor ustawia jego zmienn skadow itsAge (jego wiek) na 5, za w linii 20. wywoywana jest metoda GetAge(). Poniewa zmienna Mruczek jest wskanikiem, w celu uzyskania dostpu do danych i funkcji skadowych zosta uyty operator wskazania (->). W linii 21. zostaje wywoana metoda SetAge(), po czym w linii 22. ponownie wywoywana jest metoda GetAge().

Dane skadowe na stercie


Wskanikami do obiektw znajdujcych si na stercie moe by jedna lub wicej danych skadowych klasy. Pami moe by alokowana w konstruktorze klasy lub w ktrej z jej metod, za do jej zwolnienia mona wykorzysta destruktor. Przedstawia to listing 8.7. Listing 8.7. Wskaniki jako dane skadowe
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: // Listing 8.7 // Wskaniki jako dane skadowe // dostpne poprzez operator -> #include <iostream> class SimpleCat { public: SimpleCat(); ~SimpleCat(); int GetAge() const { return *itsAge; } void SetAge(int age) { *itsAge = age; } int GetWeight() const { return *itsWeight; } void setWeight (int weight) { *itsWeight = weight; } private: int * itsAge; int * itsWeight; };

21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42:

SimpleCat::SimpleCat() { itsAge = new int(5); itsWeight = new int(2); } SimpleCat::~SimpleCat() { delete itsAge; delete itsWeight; } int main() { SimpleCat *Mruczek = new SimpleCat; std::cout << "Mruczek ma " << Mruczek->GetAge() << " lat\n"; Mruczek->SetAge(7); std::cout << "Mruczek ma " << Mruczek->GetAge() << " lat\n"; delete Mruczek; return 0; }

Wynik
Mruczek ma 5 lat Mruczek ma 7 lat

Analiza Klasa SimpleCat (prosty kot) deklaruje (w liniach 18. i 19.) dwie zmienne skadowe; obie te zmienne s wskanikami do wartoci cakowitych. Konstruktor (linie od 22. do 26.) inicjalizuje na stercie pami dla tych wskanikw i przypisuje obiektom wartoci domylne. Zwr uwag, e dla tworzonych obiektw int moemy wywoa pseudo-konstruktor, przekazujc mu domyln warto obiektu. Umoliwia to stworzenie obiektu na stercie i zainicjalizowanie jego wartoci (w linii 24. jest to warto 5, za w linii 25., warto 2). Destruktor (linie od 28. do 32.) zwalnia zaalokowan pami. Nie ma sensu przypisywa wskanikom wartoci pustej (null), gdy po opuszczeniu destruktora i tak nie bd ju dostpne. Jest to jedna z okazji, przy ktrych mona bezpiecznie zama regu mwic, e usuwanym wskanikom naley przypisa warto pust (cho oczywicie nie zaszkodzi postpowa zgodnie z t regu). Funkcja wywoujca (w tym przypadku funkcja main()) nie jest wiadoma, e zmienne skadowe itsAge i itsWeight (jego waga) s wskanikami do pamici na stercie. Funkcja main()tak samo odwouje si do akcesorw GetAge() i SetAge(), za szczegy zarzdzania pamici s ukryte w implementacji klasy i tak wanie powinno by. Gdy w linii 40. zwalniany jest obiekt Mruczek, nastpuje wywoanie jego destruktora. Destruktor zwalnia wszystkie wskaniki skadowe. Gdyby wskaniki te wskazyway na obiekty innych klas (lub by moe tej samej klasy), zostayby wywoane take destruktory tych klas. Przechowywanie wasnych zmiennych skadowych jako referencji jest cakiem nierozsdne, chyba e istnieje ku temu wany powd. W tym przypadku nie ma takiego powodu, ale by moe w innej sytuacji przechowywanie zmiennych okae si bardzo przydatne.

Nasuwa si oczywiste pytanie: co prbujesz uzyska? Musisz zacz od projektu. Jeli zaprojektujesz obiekt, ktry odwouje si do innego obiektu, a ten drugi obiekt moe zaistnie przed zaistnieniem pierwszego obiektu i trwa jeszcze po jego zniszczeniu, wtedy pierwszy obiekt musi odwoywa si do drugiego obiektu poprzez referencj. Na przykad: pierwszy obiekt moe by oknem, a drugi dokumentem. Okno potrzebuje dostpu do dokumentu, ale nie kontroluje czasu jego ycia. Dlatego okno musi odwoywa si do obiektu poprzez referencj. W C++ mona to osign poprzez uycie wskanikw lub referencji. Referencje zostan opisane w rozdziale 9.
Czsto zadawane pytanie

Gdy zadeklaruj na stosie obiekt, ktrego dane skadowe tworzone s na stercie, co znajdzie si na stosie, a co na stercie? Na przykad:
#include <iostream> class SimpleCat { public: SimpleCat(); ~SimpleCat(); int GetAge() const { return *itsAge; } // inne metody private: int * itsAge; int * itsWeight; }; SimpleCat::SimpleCat() { itsAge = new int(5); itsWeight = new int(2); } SimpleCat::~SimpleCat() { delete itsAge; delete itsWeight; } int main() { SimpleCat Mruczek; std::cout << "Mruczek ma " << Mruczek.GetAge() << " lat\n"; Mruczek.SetAge(7); std::cout << "Mruczek ma " << Mruczek.GetAge() << " lat\n"; return 0; }

Odpowied: Na stosie znajdzie si lokalny obiekt Mruczek. Ten obiekt zawiera dwa wskaniki, z ktrych kady zajmuje cztery bajty stosu i zawiera adres zmiennej cakowitej zaalokowanej na stercie. W tym przykadzie, na stosie i na stercie zajtych zostanie po osiem bajtw.

Usunito: na

Wskanik this
Kada funkcja skadowa klasy posiada ukryty parametr, jest nim wskanik this (to). Wskanik this wskazuje na ten egzemplarz obiektu klasy, dla ktrego wywoana zostaa dana funkcja skadowa. W kadym wywoaniu funkcji GetAge() lub SetAge() wystpuje ukryty parametr w postaci wskanika this. Wskanika this mona uy jawnie; pokazuje to listing 8.8. Listing 8.8. Uycie wskanika this
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: // Listing 8.8 // Uycie wskanika this #include <iostream.h> class Rectangle { public: Rectangle(); ~Rectangle(); void SetLength(int length) { this->itsLength = length; } int GetLength() const { return this->itsLength; } void SetWidth(int width) { itsWidth = width; } int GetWidth() const { return itsWidth; } private: int itsLength; int itsWidth; }; Rectangle::Rectangle() { itsWidth = 5; itsLength = 10; } Rectangle::~Rectangle() {} int main() { Rectangle theRect; cout << "theRect ma dlugosc " << " metrow.\n"; cout << "theRect ma szerokosc << " metrow.\n"; theRect.SetLength(20); theRect.SetWidth(10); cout << "theRect ma dlugosc " << " metrow.\n"; cout << "theRect ma szerokosc << " metrow.\n"; return 0; }

<< theRect.GetLength() " << theRect.GetWidth()

<< theRect.GetLength() " << theRect.GetWidth()

Wynik
theRect theRect theRect theRect ma ma ma ma dlugosc 10 metrow. szerokosc 5 metrow. dlugosc 20 metrow. szerokosc 10 metrow.

Analiza Akcesory SetLength() (ustaw dugo) oraz GetLength() (pobierz dugo) w jawny sposb korzystaj ze wskanika this przy dostpie do zmiennych skadowych obiektu Rectangle (prostokt). Akcesory SetWidth() (ustaw szeroko) oraz GetWidth() (pobierz szeroko) nie korzystaj z tego wskanika jawnie. Nie ma adnej rnicy pomidzy dziaaniami tych akcesorw, cho skadnia z uyciem wskanika this jest atwiejsza do zrozumienia. Gdyby tutaj koczyy si wiadomoci na temat wskanika this, wspominanie o nim nie miaoby sensu. Naley pamita e wskanik this jest wskanikiem; oznacza to, e zawiera adres obiektu. Moe zatem okaza si bardzo przydatny. Praktyczne zastosowanie wskanika this poznasz w rozdziale 10., Funkcje zaawansowane; zostanie w nim omwione zagadnienie przeciania operatorw. Na razie pamitaj tylko o istnieniu wskanika this oraz jego przeznaczeniu: wskazywaniu na swj obiekt klasy. Nie musisz martwi si tworzeniem i usuwaniem wskanika this wszystkim zajmuje si kompilator.

Utracone wskaniki
Utracone wskaniki s jednym ze rde trudnych do zlokalizowania bdw. Wskanik zostaje utracony, gdy wywoasz dla niego delete a wic zwolnisz pami, na ktr wskazuje a nastpnie nie przypiszesz mu wartoci pustej. Jeli sprbujesz pniej uy wskanika bez ponownego przypisania mu adresu obiektu, wynik bdzie nieprzewidywalny i, o ile masz szczcie, program si zaamie. Przypomina to sytuacj, w ktrej firma kurierska zmienia adres, a ty uye zaprogramowanego przycisku w telefonie. Moe nie zdarzyoby si nic strasznego telefon zadzwoniby gdzie w magazynie na ktrej z pusty. Moe si jednak zdarzy, e numer tego telefonu zosta przydzielony fabryce amunicji, a twj telefon doprowadziby do wybuchu, ktry wysadziby w powietrze cae miasto! Innymi sowy, nie uywaj wskanikw po ich zwolnieniu. Wskanik nadal wskazuje to samo miejsce w pamici, ale kompilator moe umieci w nim zupenie nowe dane; uycie wskanika moe spowodowa zaamanie programu. Co gorsza, program moe dziaa pozornie normalnie i zaama si kilka minut pniej. Mona to nazwa bomb z opnionym zaponem, co wcale nie jest zabawne. W celu zachowania bezpieczestwa, po zwolnieniu wskanika przypisz mu warto pust (0). To spowoduje rozbrojenie wskanika. Listing 8.9 przedstawia tworzenie utraconego wskanika.

OSTRZEENIE Przedstawiony poniej program celowo tworzy utracony wskanik. NIE uruchamiaj go. Jeli bdziesz mia szczcie, program zaamie si sam.

Usunito: masz Usunito: zaamie si sam

Listing 8.9. Tworzenie utraconego wskanika


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: // Listing 8.9 // Demonstruje utracony wskanik typedef unsigned short int USHORT; #include <iostream> int main() { USHORT * pInt = new USHORT; *pInt = 10; std::cout << "*pInt: " << *pInt << std::endl; delete pInt; long * pLong = new long; *pLong = 90000; std::cout << "*pLong: " << *pLong << std::endl; *pInt = 20; // o, nie! on by usunity!

Usunito: .

std::cout << "*pInt: " << *pInt << std::endl; std::cout << "*pLong: " << *pLong << std::endl; delete pLong; return 0; }

Wynik
*pInt: 10 *pLong: 90000 *pInt: 20 *pLong: 65556

(Nie prbuj odtworzy tego wyniku; jeli masz szczcie, w twoim komputerze uzyskasz inny wynik, jeli nie masz szczcia, komputer ci si zawiesi.) Analiza Linia 8. deklaruje zmienn pInt jako wskanik do typu USHORT; zmienna ta wskazuje nowo zaalokowan pami. Linia 9. umieszcza w tej pamici warto 10, za linia 10. wypisuje jej warto. Po wypisaniu wartoci wskanik jest zwalniany za pomoc instrukcji delete. W tym momencie utracony zosta wskanik pInt. Linia 13. deklaruje nowy wskanik, pLong, wskazujcy pami zaalokowan operatorem new. W linii 14. obiektowi wskazywanemu przez pLong jest przypisywana warto 9000, za w linii 15. wypisywana jest warto tego obiektu. Linia 17. przypisuje warto 20 do miejsca w pamici, na ktre wskazuje pInt, ale wskanik ten nie wskazuje na poprawny wynik. Pami wskazywana przez pInt zostaa zwolniona w wyniku wywoania delete, wic przypisanie jej wartoci moe okaza si katastrof.

Linia 19. wypisuje warto wskazywan przez pInt. Oczywicie, jest ni 20. Linia 20. powinna wypisa warto wskazywan przez pLong, powinna ona wynosi 90000, jednak w tajemniczy sposb ta warto zmienia si na 65556. Nasuwaj si dwa pytania: 1. 2. Jak moga zmieni si warto wskazywana przez pLong, skoro nie by wykorzystywany wskanik pLong? Gdzie zostaa umieszczona warto 20, ktra w linii 17. zostaa przypisana obiektowi wskazywanemu przez pInt?

Jak mona si domyli, pytania te s ze sob powizane. Gdy w linii 17., w pamici wskazywanej przez pInt umieszczana bya warto, w miejscu wskazywanym dotd przez pInt kompilator ufnie umieci 20. Jednak poniewa w linii 11. ta pami zostaa zwolniona, kompilator mg j przydzieli czemu innemu. Gdy w linii 13. zosta stworzony wskanik pLong, otrzyma on poprzedni adres pamici wskazywanej przez pInt. (Proces ten rni si w poszczeglnych komputerach, w zalenoci od pamici, w ktrej przechowywane s wartoci.) Gdy do miejsca wskazywanego uprzednio przez pInt zostao przypisane 20, zastpio ono warto wskazywan przez pLong. Proces ten nazywa si nadpisaniem wartoci i czsto wystpuje w przypadku uycia utraconego wskanika. Jest to szczeglnie uciliwy bd, gdy zmieniona warto nie bya zwizana z utraconym wskanikiem. Zmiana wartoci wskazywanej przez pLong bya jedynie efektem ubocznym uycia utraconego wskanika pInt. W obszernym programie taki bd jest bardzo trudny do wykrycia. Dla zabawy, zastanwmy si, jak moga znale si w pamici warto 65 556: 1. 2. Wskanik pInt wskazywa okrelone miejsce w pamici, w ktrym zostaa umieszczona warto 10. Instrukcja delete zwolnia wskanik pInt, dziki czemu zwolnio si miejsce do przechowania innej wartoci. Nastpnie to samo miejsce w pamici zostao przydzielone wskanikowi pLong. W miejscu wskazywanym przez pLong zostaa umieszczona warto 90000. W komputerze, w ktrym uruchomiono ten przykadowy program, do przechowania tej wartoci uyto czterech bajtw (00 01 5F 90), przechowywanych w odwrconej kolejnoci. Ta warto bya przechowywana jako 5F 90 00 01. W pamici wskazywanej przez pInt zostaa umieszczona warto 20 (czyli w zapisie szesnastkowym: 00 14). Poniewa pInt przez cay czas wskazywao ten sam adres, zastpione zostay pierwsze dwa bajty pamici wskazywanej przez pLong, co dao warto 00 14 00 01. Gdy warto wskazywana przez pLong zostaa wypisana, odwrcenie bajtw dao wynik 00 01 00 14, co odpowiada wartoci dziesitnej 65556.

3.

Usunito: zostaa

4.

5.

Czsto zadawane pytanie

Jaka jest rnica pomidzy wskanikiem pustym a wskanikiem utraconym?

Odpowied: Gdy zwalniasz wskanik, informujesz kompilator, by zwolni pami. Wskanik istnieje nadal i zawiera ten sam adres. Jest jedynie wskanikiem utraconym.

Gdy napiszesz myPtr = 0; zmieniasz go ze wskanika utraconego na wskanik pusty.

Normalnie, gdy usuniesz (zwolnisz) wskanik, po czym usuniesz go ponownie, wynik bdzie nieprzewidywalny. Oznacza to, e moe zdarzy si dosownie wszystko jeli masz szczcie, program si zaamie. Natomiast przy zwalnianiu pustego wskanika nie dzieje si nic; jest to bezpieczne.

Uycie utraconego lub pustego wskanika (na przykad napisanie myPtr = 5;) jest niedozwolone i moe spowodowa zaamanie programu. Jeli wskanik jest pusty, program musi si zaama stanowi to kolejn przewag wskanika pustego nad wskanikiem utraconym. Programici zdecydowanie wol, gdy program zaamuje si w sposb przewidywalny, znacznie uatwia to usuwanie bdw.

Wskaniki const
W przypadku wskanikw, sowo kluczowe const moesz umieci przed typem, po nim lub w obu tych miejscach. Wszystkie ponisze deklaracje s poprawne:
const int *pOne; int * const pTwo; const int * const pThree;

pOne jest wskanikiem do staej wartoci cakowitej. Wskazywana przez niego warto nie moe by zmieniana. pTwo jest staym wskanikiem do wartoci cakowitej. Warto moe by zmieniana, ale pTwo nie moe wskazywa na nic innego. pThree jest staym wskanikiem do staej wartoci cakowitej. Wskazywana przez niego warto nie moe by zmieniana, pThree nie moe rwnie wskazywa na nic innego.

Aby dowiedzie si, ktra warto jest staa, wystarczy spojrze na prawo od sowa kluczowego const. Jeli znajduje si tam typ, staa jest warto. Jeli znajduje si zmienna, wtedy stay jest wskanik.
const int * p1; // wskazywana warto typu int jest staa int * const p2; // p2 jest stae i nie moe wskazywa na nic innego

Wskaniki const i funkcje skadowe const


Z rozdziau 6., Programowanie zorientowane obiektowo, dowiedziae si, e funkcja skadowa moe by zadeklarowana za pomoc sowa kluczowego const. Gdy funkcja zostanie zadeklarowana w taki wanie sposb, przy kadej prbie zmiany danych obiektu wewntrz tej funkcji kompilator zgosi bd. Jeli zadeklarujesz wskanik do obiektu const, za pomoc pomocy tego wskanika moesz wywoywa tylko metody const. Ilustruje to listing 8.10. Listing 8.10. Uycie wskanika do obiektu const
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: // Listing 8.10 // Uycie wskanikw z metodami const #include <iostream> using namespace std; class Rectangle { public: Rectangle(); ~Rectangle(); void SetLength(int length) { itsLength = length; } int GetLength() const { return itsLength; } void SetWidth(int width) { itsWidth = width; } int GetWidth() const { return itsWidth; } private: int itsLength; int itsWidth; }; Rectangle::Rectangle() { itsWidth = 5; itsLength = 10; } Rectangle::~Rectangle() {} int main() { Rectangle* pRect = new Rectangle; const Rectangle * pConstRect = new Rectangle; Rectangle * const pConstPtr = new Rectangle; cout << "szerokosc pRect: " << pRect->GetWidth() << " metrow\n"; cout << "szerokosc pConstRect: " << pConstRect->GetWidth() << " metrow\n"; cout << "szerokosc pConstPtr: " << pConstPtr->GetWidth() << " metrow\n"; pRect->SetWidth(10); // pConstRect->SetWidth(10); pConstPtr->SetWidth(10); cout << "szerokosc pRect: " << pRect->GetWidth()

48: 49: 50: 51: 52: 53: 54:

<< " metrow\n"; cout << "szerokosc pConstRect: " << pConstRect->GetWidth() << " metrow\n"; cout << "szerokosc pConstPtr: " << pConstPtr->GetWidth() << " metrow\n"; return 0; }

Wynik
szerokosc szerokosc szerokosc szerokosc szerokosc szerokosc pRect: 5 metrow pConstRect: 5 metrow pConstPtr: 5 metrow pRect: 10 metrow pConstRect: 5 metrow pConstPtr: 10 metrow

Analiza Linie od 6. do 19. deklaruj klas Rectangle (prostokt). Linia 14. deklaruje metod GetWidth() (pobierz szeroko) jako funkcj skadow const. Linia 32. deklaruje wskanik do obiektu typu Rectangle. Linia 33. deklaruje pConstRect, ktry jest wskanikiem do staego obiektu typu Rectangle. Linia 34. deklaruje pConstPtr, ktry jest staym wskanikiem do obiektu typu Rectangle. Linie od 36. do 41. wypisuj ich wartoci. W linii 43. wskanik pRect jest uywany do ustawienia szerokoci prostokta na 10. W linii 44. zostaby uyty wskanik pConstRect, ale zosta on zadeklarowany jako wskazujcy na stay obiekt typu Rectangle. W zwizku z tym nie moe legalnie wywoywa funkcji skadowej, nie bdcej funkcj const, dlatego zosta wykomentowany. W linii 45. wskanik pConstPtr wywouje metod SetWidth() (ustaw szeroko). Wskanik pConstPtr zosta zadeklarowany jako stay wskanik do obiektu typu Rectangle. Innymi sowy, zawarto wskanika jest staa i nie moe wskazywa na nic innego, natomiast wskazywany prostokt nie jest stay.

Usunito: Width

Wskaniki const this


Gdy deklarujesz obiekt jako const, deklarujesz jednoczenie, e wskanik this jest wskanikiem do obiektu const. Wskanik const this moe by uywany tylko z funkcjami skadowymi const. Stae obiekty i stae wskaniki omwimy szerzej w nastpnym rozdziale, przy okazji omawiania referencji do staych obiektw. TAK NIE

Jeli nie chcesz, by obiekty przekazywane przez Nie usuwaj wskanika wicej ni jeden raz. referencj byy zmieniane, chro je sowem kluczowym const. Jeli obiekt moe by zmieniany, przekazuj go

przez referencj. Jeli nie chcesz, by zmieniany by may obiekt, przekazuj go przez warto.

Dziaania arytmetyczne na wskanikach temat dla zaawansowanych


Wskaniki mona od siebie odejmowa. Jedn z uytecznych technik jest przypisanie dwm wskanikom rnych elementw tablicy, a nastpnie odjcie wskanikw od siebie (w celu obliczenia iloci elementw rozdzielajcych dwa wskazywane elementy). Moe to by bardzo uyteczne w przypadku przetwarzania tablic znakw. Pokazuje to listing 8.11. Listing 8.11. Wydzielanie sw z acucha znakw
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: #include <iostream> #include <ctype.h> #include <string.h> bool GetWord(char* theString, char* word, int& wordOffset); // program sterujcy int main() { const int bufferSize = 255; char buffer[bufferSize+1]; // zawiera cay acuch char word[bufferSize+1]; // zawiera sowo int wordOffset = 0; // zaczynamy od pocztku std::cout << "Wpisz lancuch znakow:\n"; std::cin.getline(buffer,bufferSize); while (GetWord(buffer,word,wordOffset)) { std::cout << "Wydzielono slowo: " << word << std::endl; } return 0; } // funkcja wydzielajca sowa z acucha. bool GetWord(char* theString, char* word, int& wordOffset) { if (!theString[wordOffset]) return false; // koniec acucha?

char *p1, *p2; p1 = p2 = theString+wordOffset; // pomijamy wiodce spacje

// wskazuje nastpne sow

39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75:

for (int i = 0; i<(int)strlen(p1) && !isalnum(p1[0]); i++) p1++; // sprawdzamy, czy mamy sowo if (!isalnum(p1[0])) return false; // p1 wskazuje teraz na pocztek nastpnego sowa; // niech na nie wskazuje take p2 p2 = p1; // przesuwamy p2 na koniec sowa while (isalnum(p2[0])) p2++; // teraz p2 wskazuje na koniec sowa // p1 wskazuje na pocztek sowa // rnic jest dugo sowa int len = int (p2 - p1); // kopiujemy sowo do bufora strncpy (word,p1,len); // koczymy je bajtem zerowym word[len]='\0'; // szukamy pocztku nastpnego sowa for (int j = int(p2-theString); j<(int)strlen(theString) && !isalnum(p2[0]); j++) { p2++; } wordOffset = int(p2-theString); return true; }

Wynik
Wpisz lancuch znakow: Ten kod po raz pierwszy pojawil sie w raporcie jezyka C++ Wydzielono slowo: Ten Wydzielono slowo: kod Wydzielono slowo: po Wydzielono slowo: raz Wydzielono slowo: pierwszy Wydzielono slowo: pojawil Wydzielono slowo: sie Wydzielono slowo: w Wydzielono slowo: raporcie Wydzielono slowo: jezyka Wydzielono slowo: C

Analiza W linii 15. uytkownik jest proszony o wpisanie acucha znakw. Otrzymany acuch jest przekazywany funkcji GetWord() (pobierz sowo) w linii 18., wraz z buforem do przechowania

pierwszego sowa i zmienn wordOffset (przesunicie sowa), inicjalizowan w linii 13. wartoci zero. Sowa zwracane przez funkcj GetWord() s wypisywane do chwili, w ktrej funkcja ta zwrci warto false. Kade wywoanie funkcji GetWord() powoduje skok do linii 29. W linii 32. sprawdzamy, czy wartoci theString[wordOffset] jest zero to bdzie oznacza, e doszlimy do koca acucha; w takim przypadku funkcja GetWord() zwrci warto false. Zwr uwag na fakt, e C++ uwaa zero za warto false. Moglibymy przepisa t lini nastpujco:
32: if (theString[wordOffset] == 0) // koniec acucha?

W linii 35. s deklarowane dwa wskaniki do znakw, p1 i p2, ktre w linii 36. s inicjalizowane tak, aby wskazyway na miejsce acucha o przesuniciu wordOffset wzgldem jego pocztku. Pocztkowo zmienna wordOffset ma warto zero, wic oba wskaniki wskazuj pocztek acucha. Linie 39. i 40. przechodz poprzez acuch, do chwili, w ktrej wskanik p1 wskae pierwszy znak alfanumeryczny. Linie 43. i 44. zapewniaj, e faktycznie znalelimy znak alfanumeryczny; jeli tak nie jest, zwracamy warto false. Wskanik p1 wskazuje teraz na nastpne sowo, a linia 48. ustawia wskanik p2 tak, aby wskazywa na to samo miejsce. Nastpnie linie 51. i 52. powoduj, e wskanik p2 przechodzi poprzez sowo, zatrzymujc si na pierwszym znaku nie bdcym znakiem alfanumerycznym. W tym momencie wskanik p2 wskazuje na koniec sowa, na ktrego pocztek wskazuje wskanik p1. Odejmujc wskanik p1 od wskanika p2 w linii 55. i rzutujc rezultat do wartoci cakowitej, moemy obliczy dugo sowa. Nastpnie kopiujemy sowo do bufora word (sowo), przekazujc wskanik (wskazujcy pocztkowy znak p1) oraz obliczon dugo sowa. W linii 63. na kocu sowa w buforze dopisujemy warto null (zero). Wskanik p2 jest inkrementowany tak, by wskazywa na pocztek nastpnego sowa, nastpnie przesunicie tego sowa (wzgldem pocztku acucha) jest umieszczane w zmiennej referencyjnej wordOffset. Na koniec, funkcja zwraca warto true, aby wskaza, e znaleziono nastpne sowo. Jest to klasyczny przykad kodu, ktry najlepiej analizowa uruchamiajc w debuggerze, ledzc jego dziaanie krok po kroku.
Usunito: l

Rozdzia 9. Referencje
W poprzednim rozdziale poznae wskaniki i dowiedziae si, jak za ich pomoc mona operowa obiektami na stercie oraz jak odwoywa si do obiektw porednio. Referencje maj prawie te same moliwoci, co wskaniki, ale posiadaj przy tym duo prostsz skadni. Z tego rozdziau dowiesz si: czym s referencje, czym rni si od wskanikw, jak si je tworzy i wykorzystuje, jakie s ich ograniczenia, w jaki sposb przekazywa obiekty i wartoci do i z funkcji za pomoc referencji.

Czym jest referencja?


Referencja jest aliasem (inn nazw); gdy tworzysz referencj, inicjalizujesz j nazw innego obiektu, bdcego celem referencji. Od tego momentu referencja dziaa jak alternatywna nazwa celu. Wszystko, co robisz z referencj, w rzeczywistoci dotyczy jej obiektu docelowego. Referencj tworzy si, zapisujc typ obiektu docelowego, operator referencji (&) oraz nazw referencji. Nazwy referencji mog by dowolne, ale wielu programistw woli poprzedza jej nazw liter r. Jeli masz zmienn cakowit o nazwie someInt, moesz stworzy referencj do niej piszc:
int &rSomeRef = someInt; Usunito: wartoci Usunito: u Usunito: jest robione Usunito: z Usunito: em

Odczytuje si to jako: rSomeRef jest referencj do zmiennej typu int. Ta referencja zostaa zainicjalizowana tak, aby odnosia si do zmiennej someInt. Sposb tworzenia referencji i korzystania z niej przedstawia listing 9.1.

UWAGA Operator referencji (&) ma taki sam symbol, jak operator adresu. Nie s to jednak te same operatory (cho oczywicie s ze sob powizane).

Zastosowanie spacji przed operatorem referencji jest obowizkowe, uycie spacji pomidzy operatorem referencji a nazw zmiennej referencyjnej jest opcjonalne. Tak wic:
int &rSomeRef = someInt; // ok int & rSomeRef = someInt; // ok

Listing 9.1. Tworzenie referencji i jej uycie


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: //Listing 9.1 // Demonstruje uycie referencji #include <iostream> int main() { using namespace std; int intOne; int &rSomeRef = intOne; intOne = 5; cout << "intOne: " << intOne << endl; cout << "rSomeRef: " << rSomeRef << endl; rSomeRef = 7; cout << "intOne: " << intOne << endl; cout << "rSomeRef: " << rSomeRef << endl; return 0; }

Wynik
intOne: 5 rSomeRef: 5 intOne: 7 rSomeRef: 7

Analiza W linii 8. jest deklarowana lokalna zmienna intOne. W linii 9. referencja rSomeRef (jaka referencja) jest deklarowana i inicjalizowana tak, by odnosia si do zmiennej intOne. Jeli zadeklarujesz referencj, lecz jej nie zainicjalizujesz, kompilator zgosi bd powstay podczas kompilacji. Referencje musz by zainicjalizowane. W linii 11. zmiennej intOne jest przypisywana warto 5. W liniach 12. i 13. s wypisywane wartoci zmiennej intOne i referencji rSomeRef; s one oczywicie takie same. W linii 17. referencji rSomeRef jest przypisywana warto 7. Poniewa jest to referencja, czyli inna nazwa zmiennej intOne, w rzeczywistoci warto ta jest przypisywana tej zmiennej (co potwierdzaj komunikaty wypisywane w liniach 16. i 17.).

Uycie operatora adresu z referencj


Gdy pobierzesz adres referencji, uzyskasz adres jej celu. Wynika to z natury referencji (s one aliasami dla obiektw docelowych). Pokazuje to listing 9.2. Listing 9.2. Odczytywanie adresu referencji
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: //Listing 9.2 // Demonstruje uycie referencji #include <iostream> int main() { using namespace std; int intOne; int &rSomeRef = intOne; intOne = 5; cout << "intOne: " << intOne << endl; cout << "rSomeRef: " << rSomeRef << endl; cout << "&intOne: " << &intOne << endl; cout << "&rSomeRef: " << &rSomeRef << endl; return 0; }

Wynik
intOne: 5 rSomeRef: 5 &intOne: 0012FF7C &rSomeRef: 0012FF7C

UWAGA W twoim komputerze dwie ostatnie linie mog wyglda inaczej.

Analiza W tym przykadzie referencja rSomeRef ponownie odnosi si do zmiennej intOne. Tym razem jednak wypisywane s adresy obu zmiennych; s one identyczne. C++ nie umoliwia dostpu do adresu samej referencji, gdy jego uycie, w odrnieniu od uycia adresu zmiennej, nie miaoby sensu. Referencje s inicjalizowane podczas tworzenia i zawsze stanowi synonim dla swojego obiektu docelowego (nawet gdy zostanie zastosowany operator adresu). Na przykad, jeli masz klas o nazwie President, jej egzemplarz moesz zadeklarowa nastpujco:
President George_Washington;

Moesz wtedy zadeklarowa referencj do klasy President i zainicjalizowa j tym obiektem:

President &FatherOfOurCountry = George_Washington;

Istnieje tylko jeden obiekt klasy President; oba identyfikatory odnosz si do tego samego egzemplarza obiektu tej samej klasy. Wszelkie operacje, jakie wykonasz na zmiennej FatherOfOurCountry (ojciec naszego kraju), bd odnosi si do obiektu George_Washington. Naley odrni symbol & w linii 9. listingu 9.2 (deklarujcy referencj o nazwie rSomeRef) od symboli & w liniach 15. i 16., ktre zwracaj adresy zmiennej cakowitej intOne i referencji rSomeRef. Zwykle w trakcie uywania referencji nie uywa si operatora adresu. Referencji uywa si tak, jak jej zmiennej docelowej. Pokazuje to linia 13.

Nie mona zmienia przypisania referencji


Nawet dowiadczonym programistom C++, ktrzy wiedz, e nie mona zmienia przypisania referencji, gdy jest ona aliasem swojego obiektu docelowego, zdarza si prba zmiany jej przypisania. To, co wyglda w takiej sytuacji na ponowne przypisanie referencji, w rzeczywistoci jest przypisaniem nowej wartoci obiektowi docelowemu. Przedstawia to listing 9.3. Listing 9.3. Przypisanie do referencji
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: //Listing 9.3 //Ponowne przypisanie referencji #include <iostream> int main() { using namespace std; int intOne; int &rSomeRef = intOne; intOne = 5; cout << "intOne:\t" << intOne << endl; cout << "rSomeRef:\t" << rSomeRef << endl; cout << "&intOne:\t" << &intOne << endl; cout << "&rSomeRef:\t" << &rSomeRef << endl; int intTwo = 8; rSomeRef = intTwo; // to nie to o czym mylisz! cout << "\nintOne:\t" << intOne << endl; cout << "intTwo:\t" << intTwo << endl; cout << "rSomeRef:\t" << rSomeRef << endl; cout << "&intOne:\t" << &intOne << endl; cout << "&intTwo:\t" << &intTwo << endl; cout << "&rSomeRef:\t" << &rSomeRef << endl; return 0; }

Wynik
intOne: 5

rSomeRef: &intOne: &rSomeRef: intOne: 8 intTwo: 8 rSomeRef: &intOne: &intTwo: &rSomeRef:

5 0012FF7C 0012FF7C

8 0012FF7C 0012FF74 0012FF7C

Analiza Take w tym programie zostay zadeklarowane (w liniach 8. i 9.) zmienna cakowita i referencja do niej. W linii 11. zmiennej jest przypisywana warto 5, po czym w liniach od 12. do 15. wypisywane s wartoci i ich adresy. W linii 17. tworzona jest nowa zmienna, intTwo, inicjalizowana wartoci 8. W linii 18. programista prbuje zmieni przypisanie referencji rSomeRef tak, aby odnosia si do zmiennej intTwo, lecz mu si to nie udaje. W rzeczywistoci referencja rSomeRef w dalszym cigu jest aliasem dla zmiennej intOne, wic to przypisanie stanowi ekwiwalent dla:
intOne = intTwo;

Potwierdzaj to wypisywane w liniach 19. do 21. komunikaty, pokazujce wartoci zmiennej intOne i referencji rSomeRef. Ich wartoci s takie same, jak warto zmiennej intTwo. W rzeczywistoci, gdy w liniach od 22. do 24. s wypisywane adresy, okazuje si, e rSomeRef w dalszym cigu odnosi si do zmiennej intOne, a nie do zmiennej intTwo. TAK W celu stworzenia aliasu do obiektu uywaj referencji. Inicjalizuj wszystkie referencje. NIE Nie zmieniaj przypisania referencji. Nie myl operatora adresu z operatorem referencji.

Do czego mog odnosi si referencje?


Referencje mog odnosi si do kadego z obiektw, take do obiektw zdefiniowanych przez uytkownika. Zwr uwag, e referencja odnosi si do obiektu, a nie do klasy, do ktrej ten obiekt naley. Nie moesz napisa:
int & rIntRef = int; // le

Musisz zainicjalizowa referencj rIntRef tak, aby odnosia si do konkretnej zmiennej cakowitej, na przykad:
int howBig = 200; int & rIntRef = howBig; Usunito: W ten sam sposb

Nie moesz zainicjalizowa te referencji do klasy CAT:


CAT & rCatRef = CAT; // le

Musisz zainicjalizowa referencj rCatRef tak, aby odnosia si do konkretnego egzemplarza tej klasy:
CAT mruczek; CAT & rCatRef = mruczek;

Usunito: Int

Referencje do obiektw s uywane w taki sam sposb, jak obiekty. Dane i funkcje skadowe s dostpne poprzez ten sam operator dostpu do skadowych (.) i, podobnie jak w typach wbudowanych, referencja dziaa jak inna nazwa obiektu. Ilustruje to listing 9.4. Listing 9.4. Referencje do obiektw
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: // Listing 9.4 // Referencje do obiektw klas #include <iostream> class SimpleCat { public: SimpleCat (int age, int weight); ~SimpleCat() {} int GetAge() { return itsAge; } int GetWeight() { return itsWeight; } private: int itsAge; int itsWeight; }; SimpleCat::SimpleCat(int age, int weight) { itsAge = age; itsWeight = weight; } int main() { SimpleCat Mruczek(5,8); SimpleCat & rCat = Mruczek; std::cout << "Mruczek ma: "; std::cout << Mruczek.GetAge() << " lat. \n"; std::cout << "i wazy: ";

31: 32: 33:

std::cout << rCat.GetWeight() << " funtow. \n"; return 0; }

Wynik
Mruczek ma: 5 lat. i wazy: 8 funtow.

Analiza W linii 25. zmienna Mruczek jest deklarowana jako obiekt klasy SimpleCat (zwyky kot). W linii 26. jest deklarowana referencja rCat do obiektu klasy SimpleCat, ktra odnosi si do obiektu Mruczek. W liniach 29. i 31. s wykorzystywane akcesory klasy SimpleCat, najpierw poprzez obiekt klasy, a potem poprzez referencj. Zwr uwag, e dostp do nich jest identyczny. Take w tym przypadku referencja jest inn nazw (aliasem) rzeczywistego obiektu.
Usunito: prosty

Referencje

Referencj deklaruje si, zapisujc typ, operator referencji (&) oraz nazw referencji. Referencje musz by inicjalizowane w trakcie ich tworzenia.

Przykad 1
int hisAge; int &rAge = hisAge;

Przykad 2
CAT Filemon; CAT &rCatRef = Filemon; Usunito: Puste Usunito: puste

Zerowe wskaniki i zerowe referencje


Gdy wskaniki nie s inicjalizowane lub zostan zwolnione, powinno si im przypisa warto zerow null (0). W przypadku referencji sytuacja wyglda inaczej. Referencja nie moe by zerowa, a program zawierajcy referencj do nie istniejcego (czyli pustego) obiektu, jest uwaany za niewaciwy. Gdy program jest niewaciwy, moe zdarzy si prawie wszystko. Moe si zdarzy, e taki program dziaa, ale rwnie dobrze moe te usun wszystkie pliki z dysku. Wikszo kompilatorw obsuguje puste obiekty, powodujc zaamanie programu tylko wtedy, gdy sprbujesz uy takiego obiektu. Obsuga pustych obiektw nie jest dobrym pomysem. Gdy przeniesiesz program do innego komputera lub kompilatora, puste obiekty mog spowodowa tajemnicze bdy w dziaaniu programu.
Usunito: pusta

Przekazywanie argumentw funkcji przez referencj


Z rozdziau 5., Funkcje, dowiedziae si, e funkcje maj dwa ograniczenia: argumenty s przekazywane przez warto, a funkcja moe zwrci tylko jedn warto. Przekazywanie argumentw funkcji poprzez referencj moe zlikwidowa oba te ograniczenia. W C++, przekazywanie przez referencj odbywa si na dwa sposoby: z wykorzystaniem wskanikw i z wykorzystaniem referencji. Zauwa rnic: przekazujesz poprzez referencj, uywajc wskanika lub przekazujesz poprzez referencj, uywajc referencji. Skadnia uycia wskanika jest inna ni uycia referencji, ale oglny efekt jest taki sam. W duym uproszczeniu mona powiedzie, e zamiast tworzy w funkcji kopi przekazywanego obiektu, program przekazuje jej obiekt oryginalny. Z rozdziau 5. dowiedziae si, e argumenty funkcji s im przekazywane poprzez stos. Gdy funkcja otrzymuje warto poprzez referencj (z uyciem wskanika lub referencji), na stosie umieszczany jest adres obiektu, a nie cay obiekt. W niektrych komputerach adres jest przechowywany w rejestrze i nie jest umieszczany na stosie. Kompilator wie, jak odwoa si do oryginalnego obiektu, wic zmiany s dokonywane w tym obiekcie, a nie w jego kopii. Przekazanie obiektu przez referencj umoliwia funkcji dokonywanie zmian w tym obiekcie. Przypomnij sobie, e listing 5.5 z rozdziau pitego pokazywa, e wywoanie funkcji swap() nie miao wpywu na wartoci w funkcji wywoujcej. Listing 5.5 zosta tu dla wygody powtrzony jako listing 9.5. Listing 9.5. Przykad przekazywania przez warto
0: //Listing 9.5 Demonstruje przekazywanie przez warto 1: 2: #include <iostream> 3: 4: using namespace std; 5: void swap(int x, int y); 6: 7: int main() 8: { 9: int x = 5, y = 10; 10: 11: cout << "Funkcja main(). Przed funkcja swap(), x: " << x << " y: " << y << "\n"; 12: swap(x,y); 13: cout << "Funkcja main(). Po funkcji swap(), x: " << x << " y: " << y << "\n"; 14: return 0; 15: } 16: 17: void swap (int x, int y) 18: { 19: int temp; 20: 21: cout << "Funkcja swap(). Przed zamiana, x: " << x << " y: " << y << "\n"; Usunito: odtworzony

22: 23: temp = x; 24: x = y; 25: y = temp; 26: 27: cout << "Funkcja swap(). Po zamianie, x: " << x << " y: " << y << "\n"; 28: 29: }

Wynik
Funkcja Funkcja Funkcja Funkcja main(). swap(). swap(). main(). Przed funkcja swap(), x: 5 y: 10 Przed zamiana, x: 5 y: 10 Po zamianie, x: 10 y: 5 Po funkcji swap(), x: 5 y: 10

Analiza Wewntrz funkcji main() program inicjalizuje dwie zmienne i przekazuje je funkcji swap() (zamie), ktra wydaje si je zamienia. Jednak gdy ponownie sprawdzimy ich wartoci w funkcji main(), okae si, e nie ulegy one zmianie! Problem polega na tym, e zmienne x i y s przekazywane funkcji swap() poprzez warto. Oznacza to, e wewntrz tej funkcji s tworzone ich lokalne kopie. Nam potrzebne jest przekazanie zmiennych x i y przez referencj. W C++ istniej dwie moliwoci rozwizania tego problemu: parametry funkcji swap() moesz zamieni na wskaniki do oryginalnych wartoci, lub przekaza referencje do pierwotnych wartoci.

Tworzenie funkcji swap() otrzymujcej wskaniki


Przekazujc wskanik, przekazujesz adres obiektu, dlatego funkcja moe manipulowa wartoci znajdujc si pod tym adresem. Aby za pomoc wskanikw umoliwi funkcji swap() zamian wartoci swoich argumentw, powiniene zadeklarowa j jako przyjmujc dwa wskaniki do zmiennych cakowitych. Nastpnie, poprzez wyuskanie wskanikw (czyli dereferencj), moesz zamieni wartoci zmiennych miejscami . Demonstruje to listing 9.6. Listing 9.6. Przekazywanie przez referencj za pomoc wskanikw
0: //Listing 9.6 Demonstruje przekazywanie przez referencj 1: 2: #include <iostream> 3: 4: using namespace std; 5: void swap(int *x, int *y); 6: 7: int main() 8: { 9: int x = 5, y = 10; 10: 11: cout << "Funkcja main(). Przed funkcja swap(), x: " << x << " y: " << y << "\n"; 12: swap(&x,&y);

Usunito: miejscami

13: cout << "Funkcja main(). Po funkcji swap(), x: " << x << " y: " << y << "\n"; 14: return 0; 15: } 16: 17: void swap (int *px, int *py) 18: { 19: int temp; 20: 21: cout << "Funkcja swap(). Przed zamiana, *px: " << *px << 22: " *py: " << *py << "\n"; 23: 24: temp = *px; 25: *px = *py; 26: *py = temp; 27: 28: cout << "Funkcja swap(). Po zamianie, *px: " << *px << 29: " *py: " << *py << "\n"; 30: 31: }

Wynik
Funkcja Funkcja Funkcja Funkcja main(). swap(). swap(). main(). Przed funkcja swap(), x: 5 y: 10 Przed zamiana, *px: 5 *py: 10 Po zamianie, *px: 10 *py: 5 Po funkcji swap(), x: 10 y: 5

Analiza Udao si! W linii 5. zosta zmieniony prototyp funkcji swap(), w ktrym zadeklarowano e oba parametry funkcji s wskanikami do zmiennych typu int, a nie zmiennymi tego typu. Gdy w linii 12. nastpuje wywoanie funkcji swap(), jako argumenty s jej przekazywane adresy zmiennych x i y. W linii 19., w funkcji swap(),deklarowana jest lokalna zmienna temp 1. Ta zmienna nie musi by wskanikiem; w czasie ycia funkcji swap() przechowuje ona warto *px (tj. warto zmiennej x zadeklarowanej w funkcji wywoujcej). Gdy funkcja swap() zakoczy dziaanie, zmienna temp nie bdzie ju potrzebna. W linii 24. zmiennej temp przypisywana jest warto wskazywana przez px. W linii 25. zmiennej wskazywanej przez px przypisywana jest warto wskazywana przez py. W linii 26. warto przechowywana w zmiennej temp (tj. oryginalna warto wskazywana przez px) jest umieszczana w zmiennej wskazywanej przez py. Efektem przeprowadzonych przez nas dziaa jest zamiana wartoci tych zmiennych, ktrych adresy zostay przekazane do funkcji swap().
Usunito: zmiennej Usunito: umieszczona

1 Ta nazwa jest skrtem od sowa temporary (tymczasowa) i bardzo czsto wystpuje w programach. przyp.tum.

Implementacja funkcji swap() za pomoc referencji


Przedstawiony wczeniej program dziaa, ale skadnia pokazanej w nim funkcji swap() ma dwie wady. Po pierwsze, konieczno wyuskiwania wskanikw wewntrz funkcji swap() uatwia popenieni bdw i zmniejsza czytelno programu. Po drugie, konieczno przekazania adresw zmiennych przez funkcj wywoujc zdradza uytkownikom sposb dziaania funkcji swap(). W jzyku C++ uytkownik funkcji nie ma moliwoci poznania sposobu jej dziaania. Przekazywanie wskanikw do parametrw oznacza konieczno odpowiednich przygotowa w funkcji wywoujcej, a przecie przygotowania te powinny nalee do obowizkw funkcji wywoywanej. W listingu 9.7 funkcja swap() zostaa ponownie przepisana, tym razem z zastosowaniem referencji, a nie wskanikw. Listing 9.7. Funkcja swap() przepisana z zastosowaniem referencji
0: //Listing 9.7 Demonstruje przekazywanie przez referencj 1: // z zastosowaniem referencji! 2: 3: #include <iostream> 4: 5: using namespace std; 6: void swap(int &x, int &y); 7: 8: int main() 9: { 10: int x = 5, y = 10; 11: 12: cout << "Funkcja main(). Przed funkcja swap(), x: " << x << " y: " 13: << y << "\n"; 14: 15: swap(x,y); 16: 17: cout << "Funkcja main(). Po funkcji swap(), x: " << x << " y: " 18: << y << "\n"; 19: 20: return 0; 21: } 22: 23: void swap (int &rx, int &ry) 24: { 25: int temp; 26: 27: cout << "Funkcja swap(). Przed zamiana, rx: " << rx << " ry: " 28: << ry << "\n"; 29: 30: temp = rx; 31: rx = ry; 32: ry = temp; 33: 34: 35: cout << "Funkcja swap(). Po zamianie, rx: " << rx << " ry: " 36: << ry << "\n"; 37: 38: } Usunito: argumentw

Wynik
Funkcja Funkcja Funkcja Funkcja main(). swap(). swap(). main(). Przed funkcja swap(), x: 5 y: 10 Przed zamiana, rx: 5 ry: 10 Po zamianie, rx: 10 ry: 5 Po funkcji swap(), x: 10 y: 5

Analiza Podobnie, jak w przykadzie ze wskanikami, take i tu (w linii 10.) deklarowane s dwie zmienne, ktrych wartoci wypisywane s w linii 12. W linii 15. nastpuje wywoanie funkcji swap(), ale zwr uwag, e tym razem nie s przekazywane adresy zmiennych x i y, lecz same zmienne. Funkcja wywoujca po prostu przekazuje zmienne. Gdy wywoywana jest funkcja swap(), dziaanie programu przechodzi do linii 23., w ktrej zmienne zostaj zidentyfikowane jako referencje. Ich wartoci s wypisywane w linii 27., zwr uwag, e nie wymagaj one przeprowadzania adnych dodatkowych operacji. S to aliasy oryginalnych wartoci, ktre mog zosta uyte jako te wartoci. W liniach od 30. do 32. wartoci s zamieniane, a nastpnie ponownie wypisywane w linii 35. Wykonanie programu wraca do funkcji wywoujcej, zatem funkcja main() (w linii 17.) ponownie wypisuje wartoci zmiennych. Poniewa parametry funkcji swap() zostay zadeklarowane jako referencje, wartoci w funkcji main() zostay przekazane przez referencj, dlatego s zamienione rwnie w tej funkcji. Referencje uatwiaj korzystanie z normalnych zmiennych, zachowujc przy tym moliwo przekazywania argumentw poprzez referencj.

Nagwki i prototypy funkcji


Listing 9.6 zawiera funkcj swap(), uywajc wskanikw, za listing 9.7 zawiera t sam funkcj uywajc referencji. Stosowanie funkcji korzystajcej z referencji jest atwiejsze; atwiejsze jest take zrozumienie kodu, ale skd funkcja wywoujca wie, czy wartoci s przekazywane poprzez warto, czy poprzez referencj? Jako klient (czyli uytkownik) funkcji swap(), programista musi mie pewno, e funkcja ta faktycznie zamieni swoje parametry. Oto kolejne zastosowanie prototypw funkcji. Sprawdzajc parametry zadeklarowane w prototypie, ktry zwykle znajduje si w pliku nagwkowym wraz z innymi prototypami, programista wie, e wartoci przekazywane do funkcji swap() s przekazywane poprzez referencj i wie, jak powinien ich uy. Gdyby funkcja swap() bya czci klasy, informacji tych dostarczyaby deklaracja klasy, take umieszczana zwykle w pliku nagwkowym. W jzyku C++ wszystkich informacji potrzebnych klientom klas i funkcji mog dostarczy pliki nagwkowe; peni one rol interfejsu dla klasy lub funkcji. Implementacja jest natomiast ukrywana przed klientem. Dziki temu programista moe skupi si na analizowanym aktualnie problemie i korzysta z klasy lub funkcji bez zastanawiania si, w jaki sposb ona dziaa. Gdy John Roebling projektowa Most Brookliski, zajmowa si takimi szczegami, jak sposb wylewania betonu czy metoda produkcji drutu do kabli nonych. Zna kady fizyczny i chemiczny proces zwizany z tworzeniem materiaw przeznaczonych do budowy mostu. Obecnie

inynierowie oszczdzaj czas, uywajc dobrze znanych materiaw budowlanych, nie zastanawiajc si, w jaki sposb s one tworzone przez producenta. Jzyka C++ umoliwia programistom korzystanie z dobrze znanych klas i funkcji, bez koniecznoci zajmowania si szczegami ich dziaania. Te czci skadowe mog zosta poczone w celu stworzenia programu (podobnie jak czone s kable, rury, klamry i inne czci w celu stworzenia mostu czy budynku). Inynier przeglda specyfikacj betonu w celu poznania jego wytrzymaoci, ciaru wasnego, czasu krzepnicia, itd., a programista przeglda interfejs funkcji lub klasy w celu poznania usug, jakich ona dostarcza, parametrw, ktrych potrzebuje i wartoci, jakie zwraca.
Usunito: zostay zoone

Usunito: jcy

Zwracanie kilku wartoci


Jak wspominalimy wczeniej, funkcja moe zwraca (bezporednio) tylko jedn warto. Co zrobi, gdy chcesz otrzyma od funkcji dwie wartoci? Jednym ze sposobw rozwizania tego problemu jest przekazanie funkcji dwch obiektw poprzez referencje. Funkcja moe wtedy wypeni te obiekty waciwymi wartociami. Poniewa przekazywanie przez referencj umoliwia funkcji zmian pierwotnego obiektu, moe ona zwrci dwie oddzielne informacje. Dziki temu warto zwracana przez funkcj bezporednio moe zosta wykorzystana w inny sposb, na przykad do zgoszenia informacji o bdach. Take w tym przypadku do zwracania wartoci w ten sposb mona uy wskanikw lub referencji. Listing 9.8 przedstawia funkcj zwracajc trzy wartoci: dwie zwracane jako parametry majce posta wskanikw i jedn jako warto zwracan funkcji. Listing 9.8. Zwracanie wartoci poprzez wskaniki
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: //Listing 9.8 // Zwracanie kilku wartoci z funkcji #include <iostream> using namespace std; short Factor(int n, int* pSquared, int* pCubed); int main() { int number, squared, cubed; short error; cout << "Wpisz liczbe (0 - 20): "; cin >> number; error = Factor(number, &squared, &cubed); if (!error) { cout << cout << cout << } else cout << return 0; Usunito: ten Usunito: przekazywane przez Usunito: otn

"liczba: " << number << "\n"; "do kwadratu: " << squared << "\n"; "do trzeciej potegi: " << cubed << "\n"; "Napotkano blad!!\n";

27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41:

} short Factor(int n, int *pSquared, int *pCubed) { short Value = 0; if (n > 20) Value = 1; else { *pSquared = n*n; *pCubed = n*n*n; Value = 0; } return Value; }

Wynik
Wpisz liczbe (0 - 20): 3 liczba: 3 do kwadratu: 9 do trzeciej potegi: 27

Analiza W linii 10. zostay zadeklarowane trzy krtkie zmienne cakowite: number (liczba), squared (do kwadratu) oraz cubed (do trzeciej potgi). Warto zmiennej number jest wpisywana przez uytkownika. Ta liczba oraz adresy zmiennych squared i cubed s przekazywane do funkcji Factor() (czynnik). Funkcja Factor() sprawdza pierwszy parametr, ktry jest przekazywany przez warto. Jeli jest wikszy od 20 (maksymalnej wartoci, jak moe obsuy funkcja), zwracanej wartoci Value (warto) przypisywany jest prosty kod bdu. Zwr uwag, e warto zwracana funkcji Factor() jest zarezerwowana dla zwrotu albo tej wartoci bdu albo wartoci 0, oznaczajcej, e wszystko poszo dobrze; warto t funkcja zwraca w linii 40. Obliczane w funkcji wartoci, czyli podniesiona do potgi drugiej i do trzeciej liczba, s zwracane nie poprzez instrukcj return, ale bezporednio poprzez zmian wartoci zmiennych wskazywanych przez wskaniki przekazane do funkcji. W liniach 36. i 37. zmiennym wskazywanym poprzez wskaniki przypisywane s obliczone wartoci wczeniej. W linii 38. zmiennej Value jest przypisywany kod sukcesu, ktry jest zwracany w linii 40. Jednym z ulepsze wprowadzonych do tej funkcji mogaby by deklaracja:
enum ERROR_VALUE { SUCCESS, FAILURE};

Usunito: a Usunito: moe Usunito: ci Usunito: t Usunito: Usunito: lub Usunito: Usunito: Usunito: potgi Usunito: wartociom Usunito: wy Usunito: o

Dziki temu, zamiast zwraca wartoci 0 lub 1, program mgby zwraca odpowiedni warto typu wyliczeniowego ERROR_VALUE (warto bdu), czyli albo SUCCESS (sukces) albo FAILURE (poraka).

Usunito: napisanie Usunito: sta Usunito: a Usunito: lub

Zwracanie wartoci przez referencj


Cho program z listingu 9.8 dziaa poprawnie, byby atwiejszy w uyciu i modyfikacji, gdyby zamiast wskanikw zastosowano w nim referencje. Listing 9.9 przedstawia ten sam program przepisany tak, aby wykorzystywa referencje i typ wyliczeniowy ERR_CODE (kod bdu). Listing 9.9. Listing 9.8 przepisany z zastosowaniem referencji
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: //Listing 9.9 // Zwracanie kilku wartoci z funkcji // z zastosowaniem referencji #include <iostream> using namespace std; typedef unsigned short USHORT; enum ERR_CODE { SUCCESS, ERROR }; ERR_CODE Factor(USHORT, USHORT&, USHORT&); int main() { USHORT number, squared, cubed; ERR_CODE result; cout << "Wpisz liczbe (0 - 20): "; cin >> number; result = Factor(number, squared, cubed); if (result == SUCCESS) { cout << "liczba: " << number << "\n"; cout << "do kwadratu: " << squared << "\n"; cout << "do trzeciej potegi: " << cubed << "\n"; } else cout << "Napotkano blad!!\n"; return 0; } ERR_CODE Factor(USHORT n, USHORT &rSquared, USHORT &rCubed) { if (n > 20) return ERROR; // prosty kod bdu else { rSquared = n*n; rCubed = n*n*n; return SUCCESS; } } Usunito: e

Wynik
Wpisz liczbe (0 - 20): 3 liczba: 3 do kwadratu: 9 do trzeciej potegi: 27

Analiza Listing 9.9 jest prawie identyczny z listingiem 9.8, z dwiema rnicami. Dziki zastosowaniu wyliczenia ERR_CODE zgaszanie bdw w liniach 36. i 41., a take ich obsuga w linii 22., s bardziej przejrzyste. Istotn zmian jest to, e tym razem funkcja Factor() zostaa zadeklarowana jako przyjmujca referencje, a nie wskaniki, do zmiennych squared i cubed. Dziki temu operowanie tymi parametrami jest prostsze i bardziej zrozumiae.

Przekazywanie przez referencj zwiksza efektywno dziaania programu


Za kadym razem, gdy przekazujesz obiekt do funkcji poprzez warto, tworzona jest kopia tego obiektu. Za kadym razem, gdy zwracasz z funkcji obiekt poprzez warto, tworzona jest kolejna kopia. Z rozdziau 5. dowiedziae si, e obiekty te s kopiowane na stos. Wymaga to sporej iloci czasu i pamici. W przypadku niewielkich obiektw, takich jak wbudowane typy cakowite, koszt ten jest niewielki. Jednak w przypadku wikszych, zdefiniowanych przez uytkownika obiektw, ten koszt staje si duo wikszy. Rozmiar zdefiniowanego przez uytkownika obiektu umieszczonego na stosie jest sum rozmiarw wszystkich jego zmiennych skadowych. Kada z tych zmiennych take moe by obiektem zdefiniowanym przez uytkownika, a przekazywanie takich rozbudowanych struktur przez kopiowanie ich na stos moe by mao wydajne i zuywa duo pamici. Pojawiaj si take dodatkowe koszty. W przypadku tworzonych przez ciebie klas, za kadym razem gdy kompilator tworzy kopi tymczasow, wywoywany jest specjalny konstruktor: konstruktor kopiujcy. Dziaanie konstruktorw kopiujcych i metody ich tworzenia zostan omwione w nastpnym rozdziale, na razie wystarczy, e bdziesz wiedzia, e konstruktor taki jest wywoywany za kadym razem, gdy na stosie jest umieszczana tymczasowa kopia obiektu. Gdy niszczony jest obiekt tymczasowy (na zakoczenie dziaania funkcji), wywoywany jest destruktor obiektu. Jeli obiekt jest zwracany z funkcji poprzez warto, konieczne jest stworzenie i zniszczenie kopii take i tego obiektu. W przypadku duych obiektw, takie wywoania konstruktorw i destruktorw mog by kosztowne ze wzgldu na szybko i zuycie pamici. Aby to zilustrowa, listing 9.9 tworzy okrojony, zdefiniowany przez uytkownika obiekt klasy SimpleCat. Prawdziwy obiekt byby wikszy i droszy, ale nasz obiekt wystarczy do pokazania, jak czsto wywoywany jest konstruktor kopiujcy oraz destruktor. Listing 9.10 tworzy obiekt typu SimpleCat, po czym wywouje dwie funkcje. Pierwsza z nich otrzymuje obiekt poprzez warto i zwraca go rwnie poprzez warto. Druga funkcja otrzymuje wskanik do obiektu i zwraca take wskanik, bez przekazywania samego obiektu. Listing 9.10. Przekazywanie obiektw poprzez referencj, za pomoc wskanikw
0: 1: //Listing 9.10 // Przekazywanie wskanikw do obiektw

Usunito: e

Usunito: i Usunito: i

Usunito: : Usunito: i

2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55:

#include <iostream> using namespace std; class SimpleCat { public: SimpleCat (); SimpleCat(SimpleCat&); ~SimpleCat(); };

// konstruktor // konstruktor kopiujcy // destruktor

Usunito: i

SimpleCat::SimpleCat() { cout << "Konstruktor klasy SimpleCat...\n"; } SimpleCat::SimpleCat(SimpleCat&) { cout << "Konstruktor kopiujcy klasy SimpleCat...\n"; } SimpleCat::~SimpleCat() { cout << "Destruktor klasy SimpleCat...\n"; } SimpleCat FunctionOne (SimpleCat theCat); SimpleCat* FunctionTwo (SimpleCat *theCat); int main() { cout << "Tworze obiekt...\n"; SimpleCat Mruczek; cout << "Wywoluje funkcje FunctionOne...\n"; FunctionOne(Mruczek); cout << "Wywoluje funkcje FunctionTwo...\n"; FunctionTwo(&Mruczek); return 0; } // FunctionOne, parametr przekazywany poprzez warto SimpleCat FunctionOne(SimpleCat theCat) { cout << "FunctionOne. Wracam...\n"; return theCat; } // FunctionTwo, parametr przekazywany poprzez wskanik SimpleCat* FunctionTwo (SimpleCat *theCat) { cout << "FunctionTwo. Wracam...\n"; return theCat; }

Usunito: i

Wynik
Tworze obiekt... Konstruktor klasy SimpleCat... Wywoluje funkcje FunctionOne...

Konstruktor kopiujcy klasy SimpleCat... FunctionOne. Wracam... Konstruktor kopiujcy klasy SimpleCat... Destruktor klasy SimpleCat... Destruktor klasy SimpleCat... Wywoluje funkcje FunctionTwo... FunctionTwo. Wracam... Destruktor klasy SimpleCat...

Usunito: i Usunito: i

Analiza W liniach od 6. do 12. zostaa zadeklarowana bardzo uproszczona klasa SimpleCat. Zarwno konstruktor, jak i konstruktor kopiujcy oraz destruktor wypisuj odpowiednie dla siebie komunikaty, dziki ktrym wiadomo, w ktrym momencie zostay wywoane. W linii 34. funkcja main() wypisuje komunikat widoczny w pierwszej linii wyniku. W linii 35. tworzony jest egzemplarz obiektu klasy SimpleCat. Powoduje to wywoanie konstruktora tej klasy, co potwierdza druga linia wyniku. W linii 36. funkcja main() zgasza (poprzez wypisanie komunikatu w trzeciej linii wydruku), e wywouje funkcj FunctionOne.. Poniewa ta funkcja otrzymuje obiekt typu SimpleCat przekazywany poprzez warto, na stosie tworzona jest lokalna dla tej funkcji kopia obiektu klasy SimpleCat. To powoduje wywoanie konstruktora kopiujcego, ktry wypisuje czwart lini wyniku. Wykonanie programu przechodzi do wywoywanej funkcji, do linii 46., w ktrej wypisywany jest komunikat informacyjny, stanowicy pit lini wyniku. Nastpnie funkcja wraca i zwraca obiekt typu SimpleCat poprzez warto. To powoduje utworzenie kolejnej kopii obiektu (poprzez wywoanie konstruktora kopiujcego, wypisujcego te szst lini wyniku). Warto zwracana przez funkcj FunctionOne() nie jest niczemu przypisywana, wic tymczasowy obiekt utworzony na stosie jest odrzucany, co powoduje wywoanie destruktora, ktry wypisuje sidm lini wyniku. Poniewa dziaanie funkcji FunctionOne() si zakoczyo, jej lokalna kopia obiektu wychodzi z zakresu i jest niszczona; powoduje to wywoanie destruktora i wypisanie smej linii wyniku. Program wraca do funkcji main(), w ktrej zostaje teraz wywoana funkcja FunctionTwo(), lecz tym razem jej parametr jest przekazywany przez referencj. Nie jest tworzona adna kopia, dlatego nie jest wypisywany aden komunikat konstruktora. Funkcja FunctionTwo() wypisuje jedynie wasny komunikat w dziesitej linii wyniku, po czym zwraca obiekt typu SimpleCat, take poprzez wskanik, zatem take tym razem nie jest wywoywany konstruktor ani destruktor. Program koczy swoje dziaanie i obiekt Mruczek wychodzi z zakresu, powodujc jeszcze jedno wywoanie destruktora, wypisujcego komunikat w jedenastej linii wyniku. Poniewa parametr funkcji FunctionOne() jest przekazywany i zwracany przez warto, jej wywoanie wie si z dwoma wywoaniami konstruktora kopiujcego i dwoma wywoaniami destruktora; natomiast wywoanie funkcji FunctionTwo() nie wymagao wywoania ani konstruktora, ani destruktora.
Usunito: i Usunito: , ktra wypisuje komunikat w trzeciej linii wyniku Usunito: i Usunito: i

Usunito: cznie z Usunito: m Usunito: i

Przekazywanie wskanika const


Cho przekazywanie wskanika jest duo bardziej efektywne w funkcji FunctionTwo(), jednak jest take bardziej niebezpieczne. Funkcja FunctionTwo() nie powinna mie moliwoci zmiany otrzymanego obiektu SimpleCat, mimo, e otrzymuje wskanik do tego obiektu. Ten wskanik daje jej jednak moliwo zmiany wartoci obiektu, co nie jest moliwe w przypadku przekazywania obiektu przez warto. Przekazywanie poprzez warto przypomina przekazanie do muzeum reprodukcji arcydziea, zamiast prawdziwego obrazu. Nawet, gdy do muzeum zakradnie si wandal, orygina nie poniesie uszczerbku. Przekazywanie poprzez referencj przypomina przesanie do muzeum swojego adresu domowego i zaproszenie goci do ogldania oryginaw. Rozwizaniem tego problemu jest przekazanie wskanika do staego (const) obiektu typu SimpleCat. W ten sposb zabezpieczamy ten obiekt przed wywoywaniem metod tej klasy innych ni metody typu const, chronic go tym samym przed zmianami. Przekazanie referencji typu const umoliwia gociom ogldanie oryginau, ale nie umoliwia jego modyfikacji. Demonstruje to listing 9.11. Listing 9.11. Przekazywanie wskanika do obiektu const
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: //Listing 9.11 // Przekazywanie wskanikw do obiektw #include <iostream> using namespace std; class SimpleCat { public: SimpleCat(); SimpleCat(SimpleCat&); ~SimpleCat(); int GetAge() const { return itsAge; } void SetAge(int age) { itsAge = age; } private: int itsAge; }; SimpleCat::SimpleCat() { cout << "Konstruktor klasy SimpleCat...\n"; itsAge = 1; } SimpleCat::SimpleCat(SimpleCat&) { cout << "Konstruktor kopiujcy klasy SimpleCat...\n"; } SimpleCat::~SimpleCat() { cout << "Destruktor klasy SimpleCat...\n"; } const SimpleCat * const FunctionTwo Usunito: ale Usunito: To Usunito: tego

Usunito: ni Usunito: Usunito: metod tej klasy, czyli chronimy go

Usunito: i

37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68:

(const SimpleCat * const theCat); int main() { cout << "Tworze obiekt...\n"; SimpleCat Mruczek; cout << "Mruczek ma " ; cout << Mruczek.GetAge(); cout << " lat\n"; int age = 5; Mruczek.SetAge(age); cout << "Mruczek ma " ; cout << Mruczek.GetAge(); cout << " lat\n"; cout << "Wywoluje funkcje FunctionTwo...\n"; FunctionTwo(&Mruczek); cout << "Mruczek ma " ; cout << Mruczek.GetAge(); cout << " lat\n"; return 0; } // functionTwo, otrzymuje wskanik const const SimpleCat * const FunctionTwo (const SimpleCat * const theCat) { cout << "FunctionTwo. Wracam...\n"; cout << "Mruczek ma teraz " << theCat->GetAge(); cout << " lat \n"; // theCat->SetAge(8); const! return theCat; }

Wynik
Tworze obiekt... Konstruktor klasy SimpleCat... Mruczek ma 1 lat Mruczek ma 5 lat Wywoluje funkcje FunctionTwo... FunctionTwo. Wracam... Mruczek ma teraz 5 lat Mruczek ma 5 lat Destruktor klasy SimpleCat...

Analiza Klasa SimpleCat zawiera dwa akcesory: GetAge() w linii 13., bdcy funkcj const oraz SetAge() w linii 14., nie bdcy funkcj const. Oprcz tego posiada zmienn skadow itsAge, deklarowan w linii 17. Konstruktor, konstruktor kopiujcy oraz destruktor wypisuj odpowiednie komunikaty. Jednak konstruktor kopiujcy nie jest wywoywany, gdy obiekt przekazywany jest poprzez referencj i nie jest tworzona adna kopia. Na pocztku programu, w linii 42., tworzony jest obiekt, a w liniach od 43. do 45. jest wypisywany wiek pocztkowy. W linii 47. zmienna skadowa itsAge jest ustawiana za pomoc akcesora SetAge(), za wynik jest wypisywany w liniach od 48. do 50. W tym programie nie jest uywana funkcja
Usunito: i Usunito: i Usunito: i

Usunito: i

zmianie; jej nagwek zosta zmodyfikowany tak, e funkcja przyjmuje teraz stay wskanik do staego obiektu i zwraca stay wskanik do staego obiektu.

FunctionOne(). Posugujemy si tylko funkcj FunctionTwo(). Ulega ona jednak niewielkiej

Usunito: i Usunito: p Usunito: otna Usunito: i

Poniewa parametr i warto zwracana wci s przekazywane poprzez referencje, nie s tworzone adne kopie, nie jest zatem wywoywany konstruktor kopiujcy. Jednak obecnie obiekt wskazywany w funkcji FunctionTwo() jest obiektem const, wic nie mona wywoywa jego metod, nie bdcych metodami const, czyli nie mona wywoa jego metody SetAge(). Gdyby wywoanie tej metody w linii 66. nie zostao umieszczone w komentarzu, program nie skompilowaby si. Zwr uwag, e obiekt tworzony w funkcji main() nie jest const, wic moemy dla niego wywoa funkcj SetAge(). Do funkcji FunctionTwo()przekazywany jest adres tego zwykego obiektu, ale poniewa deklaracja tej funkcji okrela, e ten parametr jest wskanikiem const do obiektu const, obiekt ten jest traktowany, jakby by stay!

Referencje jako metoda alternatywna


Listing 9.11 rozwizuje problem tworzenia dodatkowych kopii i w ten sposb zmniejsza ilo wywoa konstruktora kopiujcego i destruktora. Uywa staych wskanikw do staych obiektw, rozwizujc w ten sposb problem zmiany obiektu przez funkcj. Jednak w dalszym cigu jest do nieczytelny, gdy obiekty przekazywane do funkcji s wskanikami. Poniewa wiemy, e ten obiekt nie jest pusty, moemy uatwi sobie prac w funkcji, stosujc przekazanie przez referencj, a nie przez wskanik. Pokazuje to listing 9.12. Listing 9.12. Przekazywanie referencji do obiektw
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: //Listing 9.12 // Przekazywanie wskanikw do obiektw #include <iostream> using namespace std; class SimpleCat { public: SimpleCat(); SimpleCat(SimpleCat&); ~SimpleCat(); int GetAge() const { return itsAge; } void SetAge(int age) { itsAge = age; } private: int itsAge; }; SimpleCat::SimpleCat() { cout << "Konstruktor klasy SimpleCat...\n"; itsAge = 1; } SimpleCat::SimpleCat(SimpleCat&) { Usunito: i

28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66:

cout << "Konstruktor kopiujcy klasy SimpleCat...\n"; } SimpleCat::~SimpleCat() { cout << "Destruktor klasy SimpleCat...\n"; } const SimpleCat & FunctionTwo (const SimpleCat & theCat); int main() { cout << "Tworze obiekt...\n"; SimpleCat Mruczek; cout << "Mruczek ma " ; cout << Mruczek.GetAge(); cout << " lat\n"; int age = 5; Mruczek.SetAge(age); cout << "Mruczek ma " ; cout << Mruczek.GetAge(); cout << " lat\n"; cout << "Wywoluje funkcje FunctionTwo...\n"; FunctionTwo(Mruczek); cout << "Mruczek ma " ; cout << Mruczek.GetAge(); cout << " lat\n"; return 0; } // functionTwo, otrzymuje referencj do obiektu const const SimpleCat & FunctionTwo (const SimpleCat & theCat) { cout << "FunctionTwo. Wracam...\n"; cout << "Mruczek ma teraz " << theCat.GetAge(); cout << " lat \n"; // theCat.SetAge(8); const! return theCat; }

Usunito: i

Wynik
Tworze obiekt... Konstruktor klasy SimpleCat... Mruczek ma 1 lat Mruczek ma 5 lat Wywoluje funkcje FunctionTwo... FunctionTwo. Wracam... Mruczek ma teraz 5 lat Mruczek ma 5 lat Destruktor klasy SimpleCat...

Analiza Wynik jest identyczny z wynikiem z listingu 9.11. Jedyn istotn rnic w programie jest to, e obecnie funkcja FunctionTwo() otrzymuje i zwraca referencj do staego obiektu. Take tym razem praca z referencjami jest nieco prostsza od pracy ze wskanikami, a na dodatek zapewnia t sam efektywno oraz bezpieczestwo obiektu const.
Referencje const

Programici C++ zwykle nie uznaj rnicy pomidzy sta referencj do obiektu typu SimpleCat a referencj do staego obiektu typu SimpleCat. Referencje nigdy nie mog otrzyma ponownego przypisania i odnosi si do innego obiektu, wic s zawsze stae. Jeli sowo kluczowe const zostanie zastosowane w odniesieniu do referencji, sprawi, e to obiekt zwizany z referencj staje si stay.

Kiedy uywa wskanikw, a kiedy referencji


Programici C++ zdecydowanie przedkadaj referencje nad wskaniki. Referencje s bardziej przejrzyste i atwiejsze w uyciu, ponadto lepiej ukrywaj szczegy implementacji, co moglimy zobaczy w poprzednim przykadzie. Nie mona zmienia obiektu docelowego referencji. Jeli chcesz najpierw wskaza na jeden obiekt, a potem na inny, musisz uy wskanika. Referencje nie mog by zerowe , wic jeli istnieje jakakolwiek moliwo, e dany obiekt bdzie pusty (tzn., e moe przesta istnie), nie moesz uy referencji. Musisz uy wskanika. To ostatnie zagadnienie dotyczy operatora new. Gdy new nie moe zaalokowa pamici na stercie, zwraca wskanik null (wskanik zerowy, czyli pusty). Poniewa referencje nie mog by puste, nie wolno ci przypisa referencji do tej pamici, dopki nie upewnisz si, e nie jest pusta. Waciwy sposb przypisania pokazuje poniszy przykad:
int *pInt = new int; if (pInt != NULL) int &rInt = *pInt; Usunito: puste

W tym przykadzie deklarowany jest wskanik pInt do typu int; jest on inicjalizowany adresem pamici zwracanym przez operator new. Nastpnie jest sprawdzany adres w pInt i jeli nie jest on pusty, wyuskiwany jest wskanik. Rezultatem wyuskania wskanika do typu int jest obiekt int, wic referencja rInt jest inicjalizowana jako odnoszca si do tego obiektu. W efekcie, referencja rInt staje si aliasem do wartoci int o adresie zwrconym przez operator new. TAK NIE
Usunito: e Usunito: warto Usunito: otn

Jeli jest to moliwe, przekazuj parametry przez Nie uywaj wskanikw tam, gdzie mona uy referencji. referencj. Jeli jest to moliwe, przekazuj przez referencj warto zwracan przez funkcj. Jeli jest to moliwe, uywaj const do ochrony referencji i wskanikw.

czenie referencji i wskanikw


Dozwolone jest jednoczesne deklarowanie wskanikw oraz referencji na tej samej licie parametrw funkcji, a take obiektw przekazywanych przez warto. Na przykad:
CAT * SomeFunction (Person &theOwner, House *theHouse, int age);

Ta deklaracja informuje, e funkcja SomeFunction ma trzy parametry. Pierwszy z nich jest referencj do obiektu klasy Person (osoba), drugim jest wskanik do obiektu klasy House (dom), za trzecim jest warto typu int. Funkcja zwraca wskanik do obiektu klasy CAT. Pytanie, gdzie powinien zosta umieszczony operator referencji (&) lub wskanika (*), jest bardzo kontrowersyjne. Moesz zastosowa ktry z poniszych zapisw:
1: CAT& rMruczek; 2: CAT & rMruczek; 3: CAT &rMruczek;

Usunito: a

UWAGA Biae spacje s cakowicie ignorowane, dlatego wszdzie tam, gdzie mona umieci spacj, mona take umieci dowoln ilo innych spacji, tabulatorw czy nowych linii.
Usunito: Pozostawiajc zagadnienia wyrae Usunito: zapisw

Jeli powysze zapisy s rwnowane, ktry z nich jest najlepszy? Oto argumenty przemawiajce za wszystkimi trzema:

Argumentem przemawiajcym za przypadkiem 1. jest to, e rMruczek jest zmienn, ktrej nazw jest rMruczek, za typ moe by traktowany jako referencja do obiektu klasy CAT. Zgodnie z t argumentacj, & powinno znale si przy typie.

Argumentem przeciwko przypadkowi 1. jest to, e typem jest klasa CAT. Symbol & jest czci deklaratora zawierajcego nazw klasy i znak ampersand (&). Jednak umieszczenie & przy CAT moe spowodowa wystpienie poniszego bdu:
CAT& rMruczek, rFilemon;

Szybkie sprawdzenie tej linii moe doprowadzi ci do odkrycia, e zarwno rMruczek, jak i rFilemon s referencjami do obiektw klasy CAT, ale w rzeczywistoci tak nie jest. Ta deklaracja informuje, e rMruczek jest referencj do klasy CAT, za rFilemon (mimo zastosowanego przedrostka) nie jest referencj, lecz zwykym obiektem klasy CAT. T deklaracj naley przepisa nastpujco:
CAT &rMruczek, rFilemon;

Wniosek pyncy z powyszych rozwaa brzmi nastpujco: deklaracje referencji i zmiennych nigdy nie powinny wystpowa w tej samej linii. Oto poprawny zapis:
CAT& rMruczek; CAT Filemon;

Wielu programistw optuje za zastosowaniem operatora porodku, tak jak pokazuje przypadek 2.

Oczywicie, wszystko, co powiedziano dotd na temat operatora referencji (&), odnosi si take do operatora wskanika (*). Naley zdawa sobie spraw, e styl zapisu zaley od programisty. Wybierz wic styl, ktry ci odpowiada i konsekwentnie stosuj go w programach; przejrzysto kodu jest w kocu jednym z twoich gwnych celw.

Deklarujc referencje i wskaniki, wielu programistw przestrzega nastpujcych konwencji:

1.

Umieszczaj znak ampersand lub gwiazdk porodku, ze spacj po obu stronach.

2.

Nigdy nie deklaruj w tej samej linii referencji, wskanikw i zmiennych.

Nie pozwl funkcji zwraca referencji do obiektu, ktrego nie ma w zakresie!


Gdy programici C++ naucz si korzysta z referencji, przejawiaj tendencj do uywania ich bez zastanowienia, wszdzie, gdzie tylko si da. Mona z tym przesadzi. Pamitaj, e referencja jest zawsze aliasem do innego obiektu. Gdy przekazujesz referencje do lub z funkcji, pamitaj, by zada sobie pytanie: Czym jest obiekt, do ktrego odnosi si referencja, i czy bdzie istnia przez cay czas, gdy bd z niego korzysta? Listing 9.13 pokazuje niebezpieczestwo zwrcenia referencji do obiektu, ktry ju nie istnieje. Listing 9.13. Zwracanie referencji do nieistniejcego obiektu
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: // Listing 9.13 // Zwracanie referencji do obiektu // ktry ju nie istnieje #include <iostream> class SimpleCat { public: SimpleCat (int age, int weight);

11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39:

~SimpleCat() {} int GetAge() { return itsAge; } int GetWeight() { return itsWeight; } private: int itsAge; int itsWeight; }; SimpleCat::SimpleCat(int age, int weight) { itsAge = age; itsWeight = weight; } SimpleCat &TheFunction(); int main() { SimpleCat &rCat = TheFunction(); int age = rCat.GetAge(); std::cout << "rCat ma " << age << " lat!\n"; return 0; } SimpleCat &TheFunction() { SimpleCat Mruczek(5,9); return Mruczek; }

Wynik
Bd kompilacji: prba zwrcenia referencji do lokalnego obiektu! OSTRZEENIE Ten program nie skompiluje si z kompilatorem firmy Borland. Skompiluje si jednak z kompilatorem firmy Microsoft, co mimo wszystko powinno to by uwaane za bd.
Usunito: jednak

Analiza W liniach od 7. do 17. deklarowana jest klasa SimpleCat. W linii 29. referencja do klasy SimpleCat jest inicjalizowana rezultatem wywoania funkcji TheFunction(), zadeklarowanej w linii 25. jako zwracajca referencj do obiektw klasy SimpleCat. W ciele funkcji TheFunction() jest deklarowany lokalny obiekt typu SimpleCat; konstruktor inicjalizuje jego wiek i wag. Nastpnie ten obiekt lokalny jest zwracany poprzez referencj. Niektre kompilatory s na tyle inteligentne, by wychwyci ten bd i nie pozwoli na uruchomienie programu. Inne pozwol na jego skompilowanie i uruchomienie, co moe spowodowa nieprzewidywalne zachowanie komputera. Gdy funkcja TheFunction() koczy dziaanie, jej obiekt lokalny, Mruczek, jest niszczony (zapewniam, e bezbolenie). Referencja zwracana przez t funkcj staje si aliasem do nieistniejcego obiektu, a to powany bd.

Zwracanie referencji do obiektu na stercie


By moe kusi ci rozwizanie problemu z listingu 9.13 modyfikacja funkcji TheFunction() tak, by tworzya Mruczka na stercie. Dziki temu, gdy funkcja zakoczy dziaanie, Mruczek bdzie nadal istnia. W tym miejscu pojawia si nastpujcy problem: co zrobisz z pamici zaalokowan dla obiektu Mruczek, gdy nie bdzie ju potrzebny? To zagadnienie ilustruje listing 9.14. Listing 9.14. Wycieki pamici
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: // Listing 9.14 // Unikanie wyciekw pamici #include <iostream> class SimpleCat { public: SimpleCat (int age, int weight); ~SimpleCat() {} int GetAge() { return itsAge; } int GetWeight() { return itsWeight; } private: int itsAge; int itsWeight; }; SimpleCat::SimpleCat(int age, int weight) { itsAge = age; itsWeight = weight; } SimpleCat & TheFunction(); int main() { SimpleCat & rCat = TheFunction(); int age = rCat.GetAge(); std::cout << "rCat ma " << age << " lat!\n"; std::cout << "&rCat: " << &rCat << std::endl; // jak si go pozbdziesz z pamici? SimpleCat * pCat = &rCat; delete pCat; // a do czego teraz odnosi si rCat?? return 0; } SimpleCat &TheFunction() { SimpleCat * pMruczek = new SimpleCat(5,9); std::cout << "pMruczek: " << pMruczek << std::endl; return *pMruczek; }

Wynik

pMruczek: 004800F0 rCat ma 5 lat! &rCat: 004800F0 OSTRZEENIE Ten program kompiluje si, uruchamia i sprawia wraenie, e dziaa poprawnie. Jest jednak swego rodzaju bomb zegarow, ktra moe w kadej chwili wybuchn.

Funkcja TheFunction() zostaa zmieniona tak, e ju nie zwraca referencji do lokalnej zmiennej. W linii 41. funkcja alokuje pami na stercie i przypisuje jej adres do wskanika. Adres zawarty w tym wskaniku jest wypisywany w nastpnej linii, po czym wskanik jest wyuskiwany, a wskazywany przez niego obiekt typu SimpleCat jest zwracany przez referencj. W linii 28. wynik funkcji TheFunction() jest przypisywany referencji do obiektu klasy SimpleCat, po czym ta referencja jest uywana w celu uzyskania wieku kota, wypisywanego w linii 30. Aby udowodni, e referencja zadeklarowana w funkcji main() odnosi si do obiektu umieszczonego na stercie przez funkcj TheFunction(), do referencji rCat zosta zastosowany operator adresu. Oczywicie, wywietla on adres obiektu, do ktrego odnosi si referencja, zgodny z adresem pamici na stercie. Jak dotd wszystko jest w porzdku. Ale w jaki sposb moemy zwolni t pami? Nie mona wywoa operatora delete dla referencji. Sprytnym rozwizaniem jest utworzenie kolejnego wskanika i zainicjalizowanie go adresem uzyskanym od referencji rCat. Dziki temu mona zwolni pami i powstrzyma jej wyciek. Powstaje jednak pewien problem: do czego odnosi si referencja rCat po wykonaniu linii 34.? Jak ju wspomnielimy, referencja zawsze musi stanowi alias rzeczywistego obiektu; jeli odnosi si do obiektu pustego (tak, jak w tym przypadku), program jest bdny.
UWAGA Jeszcze raz naley przypomnie, e program z referencj do pustego obiektu moe si skompilowa, ale jest bdny i jego dziaanie jest nieprzewidywalne.
Usunito: na

Istniej trzy rozwizania tego problemu. Pierwszym jest zadeklarowanie obiektu typu SimpleCat w linii 28. i zwrot tego obiektu z funkcji TheFunction() poprzez warto. Drugim jest zadeklarowanie w funkcji TheFunction() obiektu SimpleCat na stercie, lecz ze zwrceniem wskanika. Wtedy funkcja wywoujca moe sama usun ten wskanik gdy, nie bdzie ju potrzebowa obiektu. Trzecim rozwizaniem, tym waciwym, jest zadeklarowanie obiektu w funkcji wywoujcej i przekazanie go funkcji TheFunction() przez referencj.

Wskanik, wskanik, kto ma wskanik?


Gdy program alokuje pami na stercie, otrzymuje wskanik. Przechowywanie tego wskanika jest koniecznoci, gdy zostanie on utracony, pami nie bdzie moga zosta zwolniona i powikszy tzw. wyciek pamici.
Usunito: stanie si Usunito: iem

W czasie przekazywania bloku pamici pomidzy funkcjami, kto przez cay czas posiada ten wskanik. Zwykle wartoci w bloku s przekazywane poprzez referencje, za funkcja, ktra stworzya pami, zajmuje si jej zwolnieniem. Jest to jednak regua poparta dowiadczeniem, a nie zasada wyryta w kamieniu. Tworzenie pamici w jednej funkcji i zwalnianie jej w innej moe by niebezpieczne. Nieporozumienia co do tego, kto posiada wskanik, mog spowodowa dwa nastpujce problemy: zapomnienie o zwolnieniu wskanika lub dwukrotnie zwolnienie go. W obu przypadkach jest to powany bd programu. Bezpieczniej jest budowa funkcje tak, by usuway pami, ktr stworzyy. Jeli piszesz funkcj, ktra musi stworzy pami, po czym przekaza j funkcji wywoujcej, zastanw si nad zmian jej interfejsu. Niech funkcja wywoujca sama alokuje pami i przekazuje j innej funkcji przez referencj. Dziki temu zarzdzanie pamici pozostaje w tej funkcji, ktra jest przygotowana do jej usunicia. TAK Gdy jeste do tego zmuszony, przekazuj parametry przez warto. Gdy jeste do tego zmuszony, zwracaj wynik funkcji przez warto. NIE Nie przekazuj referencji, jeli obiekt, do ktrego si ona odnosi, moe znale si poza zakresem. Nie uywaj referencji do pustych obiektw.
Usunito:

Rozdzia 10. Funkcje zaawansowane


W rozdziale 5., Funkcje, poznae podstawy pracy z funkcjami. Teraz, gdy wiesz take, jak dziaaj wskaniki i referencje, moesz zgbi zagadnienia dotyczce funkcji. Z tego rozdziau dowiesz si, w jaki sposb: przecia funkcje skadowe, przecia operatory, pisa funkcje, majc na celu tworzenie klas z dynamicznie alokowanymi zmiennymi.

Przecione funkcje skadowe


Z rozdziau 5. dowiedziae si jak implementowa polimorfizm funkcji, czyli ich przecianie, przez tworzenie dwch lub wicej funkcji o tych samych nazwach, lecz innych parametrach. Funkcje skadowe klas mog by przeciane w dokadnie ten sam sposb. Klasa Rectangle (prostokt), zademonstrowana na listingu 10.1, posiada dwie funkcje DrawShape() (rysuj ksztat). Pierwsza z nich, nie posiadajca parametrw, rysuje prostokt na podstawie biecych wartoci skadowych danego egzemplarza klasy. Druga funkcja otrzymuje dwie wartoci (szeroko i dugo) i rysuje na ich podstawie prostokt, ignorujc biece wartoci zmiennych skadowych. Listing 10.1. Przecione funkcje skadowe
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: //Listing 10.1 Przecianie funkcji skadowych klasy #include <iostream> // Deklaracja klasy Rectangle class Rectangle { public: // konstruktory Rectangle(int width, int height); ~Rectangle(){}

12: // przeciona funkcja skadowa klasy 13: void DrawShape() const; 14: void DrawShape(int aWidth, int aHeight) const; 15: 16: private: 17: int itsWidth; 18: int itsHeight; 19: }; 20: 21: // implementacja konstruktora 22: Rectangle::Rectangle(int width, int height) 23: { 24: itsWidth = width; 25: itsHeight = height; 26: } 27: 28: 29: // Przeciona funkcja DrawShape - nie ma parametrw 30: // Rysuje ksztat w oparciu o biece wartoci zmiennych skadowych 31: void Rectangle::DrawShape() const 32: { 33: DrawShape( itsWidth, itsHeight); 34: } 35: 36: 37: // Przeciona funkcja DrawShape - z dwoma parametrami 38: // Rysuje ksztat w oparciu o podane wartoci 39: void Rectangle::DrawShape(int width, int height) const 40: { 41: for (int i = 0; i<height; i++) 42: { 43: for (int j = 0; j< width; j++) 44: { 45: std::cout << "*"; 46: } 47: std::cout << "\n"; 48: } 49: } 50: 51: // Gwna funkcja demonstrujca przecione funkcje 52: int main() 53: { 54: // inicjalizujemy prostokt 30 na 5 55: Rectangle theRect(30,5); 56: std::cout << "DrawShape(): \n"; 57: theRect.DrawShape(); 58: std::cout << "\nDrawShape(40,2): \n"; 59: theRect.DrawShape(40,2); 60: return 0; 61: }

Wynik
DrawShape(): ****************************** ****************************** ****************************** ****************************** ******************************

DrawShape(40,2): **************************************** ****************************************

Analiza Listing 10.1 prezentuje okrojon wersj programu, zamieszczonego w podsumowaniu wiadomoci po rozdziale 7. Aby zaoszczdzi miejsce, z programu usunito sprawdzanie niepoprawnych wartoci, a take niektre z akcesorw. Gwny program zosta sprowadzony do duo prostszej postaci, w ktrej nie ma ju menu. Najwaniejszy kod znajduje si w liniach 13. i 14., gdzie przeciona zostaa funkcja
DrawShape(). Implementacja tych przecionych funkcji skadowych znajduje si w liniach od

29. do 49. Zwr uwag, e funkcja w wersji bez parametrw po prostu wywouje funkcj z parametrami, przekazujc jej biece zmienne skadowe. Postaraj si nigdy nie powtarza tego samego kodu w dwch funkcjach, moe to spowodowa wiele problemw z zachowaniem ich w zgodnoci w trakcie wprowadzaniu poprawek (moe sta si to przyczyn bdw). Gwna funkcja tworzy w liniach od 51. do 61. obiekt prostokta, po czym wywouje funkcj DrawShape(), najpierw bez parametrw, a potem z dwoma parametrami typu int.

Usunito: kilku Usunito: go w synchronizacji

Kompilator na podstawie iloci i typu podanych parametrw wybiera metod. Mona sobie wyobrazi take trzeci przecion funkcj o nazwie DrawShape(), ktra otrzymywaaby jeden wymiar oraz warto wyliczeniow, okrelajc, czy jest on wysokoci czy szerokoci (wybr naleaby do uytkownika).

Uycie wartoci domylnych


Podobnie, jak w przypadku funkcji skadowych klasy, funkcje globalne rwnie mog mie jedn lub wicej wartoci domylnych. W przypadku deklaracji wartoci domylnych w funkcjach skadowych stosujemy takie same reguy, jak w funkcjach globalnych, co ilustruje listing 10.2. Listing 10.2. Uycie wartoci domylnych
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: //Listing 10.2 Domylne wartoci w funkcjach skadowych #include <iostream> using namespace std; // Deklaracja klasy Rectangle class Rectangle { public: // konstruktory Rectangle(int width, int height); ~Rectangle(){} void DrawShape(int aWidth, int aHeight, bool UseCurrentVals = false) const; private: int itsWidth; int itsHeight; };

20: 21: // implementacja konstruktora 22: Rectangle::Rectangle(int width, int height): 23: itsWidth(width), // inicjalizacje 24: itsHeight(height) 25: {} // puste ciao konstruktora 26: 27: 28: // dla trzeciego parametru jest uywana domylna warto 29: void Rectangle::DrawShape( 30: int width, 31: int height, 32: bool UseCurrentValue 33: ) const 34: { 35: int printWidth; 36: int printHeight; 37: 38: if (UseCurrentValue == true) 39: { 40: printWidth = itsWidth; // uywa biecych wartoci klasy 41: printHeight = itsHeight; 42: } 43: else 44: { 45: printWidth = width; // uywa wartoci z parametrw 46: printHeight = height; 47: } 48: 49: 50: for (int i = 0; i<printHeight; i++) 51: { 52: for (int j = 0; j< printWidth; j++) 53: { 54: cout << "*"; 55: } 56: cout << "\n"; 57: } 58: } 59: 60: // Gwna funkcja demonstrujca przecione funkcje 61: int main() 62: { 63: // inicjalizujemy prostokt 30 na 5 64: Rectangle theRect(30,5); 65: cout << "DrawShape(0,0,true)...\n"; 66: theRect.DrawShape(0,0,true); 67: cout <<"DrawShape(40,2)...\n"; 68: theRect.DrawShape(40,2); 69: return 0; 70: }

Wynik
DrawShape(0,0,true)... ****************************** ****************************** ******************************

****************************** ****************************** DrawShape(40,2)... **************************************** ****************************************

Analiza Listing 10.2 zastpuje przecione funkcje DrawShape() pojedyncz funkcj z domylnym parametrem. Ta funkcja, zadeklarowana w linii 13., posiada trzy parametry. Dwa pierwsze, aWidth (szeroko) i aHeight (wysoko) s typu int, za trzeci, UseCurrentVals (uyj biecych wartoci), jest zmienn typu bool o domylnej wartoci false. Implementacja tej nieco udziwnionej funkcji rozpoczyna si w linii 28. Sprawdzany jest w niej trzeci parametr, UseCurrentValue. Jeli ma on warto true, wtedy do ustawienia lokalnych zmiennych printWidth (wypisywana szeroko) i printHeight (wypisywana wysoko) s uywane zmienne skadowe klasy, itsWidth oraz itsHeight. Jeli parametr UseCurrentValue ma warto false, podan przez uytkownika, lub ustawion domylnie, wtedy zmiennym printWidth i printHeight s przypisywane wartoci dwch pierwszych argumentw funkcji. Zwr uwag, e gdy parametr UseCurrentValue ma warto true, wartoci dwch pierwszych parametrw s cakowicie ignorowane.

Usunito: wartoci logiczn

Usunito: s

Usunito: s

Usunito: s

Wybr pomidzy wartociami domylnymi a przecianiem funkcji


Listingi 10.1 i 10.2 daj ten sam wynik, lecz przecione funkcje z listingu 10.1 s atwiejsze do zrozumienia i wygodniejsze w uyciu. Poza tym, gdy jest potrzebna trzecia wersja na przykad, gdy uytkownik chce dostarczy szerokoci albo wysokoci osobno mona atwo stworzy kolejn przecion funkcj. Z drugiej strony, w miar dodawania kolejnych wersji, wartoci domylne mog szybko sta si zbyt skomplikowane. W jaki sposb podj decyzj, czy uy przeciania funkcji, czy wartoci domylnych? Oto oglna regua: Przeciania funkcji uywaj, gdy: nie istnieje sensowna warto domylna, uywasz rnych algorytmw, chcesz korzysta z rnych rodzajw parametrw funkcji.

Konstruktor domylny
Jak mwilimy w rozdziale 6., Programowanie zorientowane obiektowo, jeli nie zadeklarujesz konstruktora klasy jawnie, zostanie dla niej stworzony konstruktor domylny, ktry nie ma adnych parametrw i nic nie robi. Moesz jednak stworzy wasny konstruktor domylny, ktry take nie posiada parametrw, ale odpowiednio przygotowuje obiekt do dziaania. Taki konstruktor take jest nazywany konstruktorem domylnym, bo zgodnie z konwencj, jest nim konstruktor nie posiadajcy parametrw. Moe to budzi wtpliwoci, ale zwykle jasno wynika z kontekstu danego miejsca w programie. Zwr uwag, e gdy stworzysz jakikolwiek konstruktor, kompilator nie dostarcza ju konstruktora domylnego. Gdy potrzebujesz konstruktora nie posiadajcego parametrw i stworzysz jakikolwiek inny konstruktor, musisz stworzy take konstruktor domylny!

Przecianie konstruktorw
Przeznaczeniem konstruktora jest przygotowanie obiektu; na przykad, celem konstruktora Rectangle jest stworzenie poprawnego obiektu prostokta. Przed wykonaniem konstruktora nie istnieje aden prostokt, a jedynie miejsce w pamici. Gdy konstruktor koczy dziaanie, w pamici istnieje kompletny, gotowy do uycia obiekt prostokta. Konstruktory, tak jak wszystkie inne funkcje skadowe, mog by przeciane. Moliwo przeciania ich jest bardzo przydatna. Na przykad: moesz mie obiekt prostokta posiadajcy dwa konstruktory. Pierwszy z nich otrzymuje szeroko oraz dugo i tworzy prostokt o podanych rozmiarach. Drugi nie ma adnych parametrw i tworzy prostokt o rozmiarach domylnych. Ten pomys wykorzystano na listingu 10.3. Listing 10.3. Przecianie konstruktora
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: // Listing 10.3 // Przecianie konstruktorw #include <iostream> using namespace std; class Rectangle { public: Rectangle(); Rectangle(int width, int length); ~Rectangle() {} int GetWidth() const { return itsWidth; } int GetLength() const { return itsLength; } private: int itsWidth; int itsLength; }; Rectangle::Rectangle() {

21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47:

itsWidth = 5; itsLength = 10; } Rectangle::Rectangle (int width, int length) { itsWidth = width; itsLength = length; } int main() { Rectangle Rect1; cout << "Rect1 szerokosc: " << Rect1.GetWidth() << endl; cout << "Rect1 dlugosc: " << Rect1.GetLength() << endl; int aWidth, aLength; cout << "Podaj szerokosc: "; cin >> aWidth; cout << "\nPodaj dlugosc: "; cin >> aLength; Rectangle Rect2(aWidth, aLength); cout << "\nRect2 szerokosc: " << Rect2.GetWidth() << endl; cout << "Rect2 dlugosc: " << Rect2.GetLength() << endl; return 0;

Wynik
Rect1 szerokosc: 5 Rect1 dlugosc: 10 Podaj szerokosc: 20 Podaj dlugosc: 50 Rect2 szerokosc: 20 Rect2 dlugosc: 50

Analiza Klasa Rectangle jest zadeklarowana w liniach od 6. do 17. Posiada dwa konstruktory: domylny konstruktor w linii 9. oraz drugi konstruktor w linii 10., przyjmujcy dwie liczby cakowite. W linii 33. za pomoc domylnego konstruktora tworzony jest prostokt; jego rozmiary s wypisywane w liniach 34. i 35. W liniach od 38. do 41. uytkownik jest proszony o podanie szerokoci i dugoci, po czym w linii 43. wywoywany jest konstruktor, ktry otrzymuje dwa parametry. Na koniec, w liniach 44. i 45. wypisywane s rozmiary drugiego prostokta. Tak jak w przypadku innych funkcji przecionych, kompilator wybiera waciwy konstruktor na podstawie typw i iloci parametrw.

Inicjalizowanie obiektw
Do tej pory ustawiae zmienne skadowe wewntrz ciaa konstruktora. Konstruktory s jednak wywoywane w dwch fazach: inicjalizacji i ciaa. Wikszo zmiennych moe by ustawiana w dowolnej z tych faz, podczas inicjalizacji lub w wyniku przypisania w ciele konstruktora. Lepiej zrozumiae, i czsto bardziej efektywne, jest inicjalizowanie zmiennych skadowych w fazie inicjalizacji konstruktora. Sposb inicjalizowania zmiennych skadowych przedstawia poniszy przykad:
CAT(): itsAge(5), itsWeight(8) { } // nazwa konstruktora i parametry // lista inicjalizacyjna // ciao konstruktora

Usunito: ji

Po nawiasie zamykajcym list parametrw wpisz dwukropek. Nastpnie wpisz nazw zmiennej skadowej oraz par nawiasw. Wewntrz nawiasw wpisz wyraenie, ktrego warto ma zainicjalizowa zmienn skadow. Jeli chcesz zainicjalizowa kilka zmiennych, kad z inicjalizacji oddziel przecinkiem. Listing 10.4 przedstawia definicj konstruktora z listingu 10.3, w ktrej zamiast przypisania w ciele konstruktora zastosowano inicjalizacj zmiennych. Listing 10.4. Fragment kodu, przedstawiajcy inicjalizacj zmiennych skadowych
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: //Listing 10.4 - Inicjalizacja zmiennych skadowych Rectangle::Rectangle(): itsWidth(5), itsLength(10) { } Rectangle::Rectangle (int width, int length): itsWidth(width), itsLength(length) { }

Bez wyniku. Niektre zmienne musz by inicjalizowane i nie mona im niczego przypisywa; dotyczy to referencji i staych. Wewntrz ciaa konstruktora mona zawrze take inne przypisania i dziaania, jednak najlepiej maksymalnie wykorzysta faz inicjalizacji.

Konstruktor kopiujcy
Oprcz domylnego konstruktora i destruktora, kompilator dostarcza take domylnego konstruktora kopiujcego. Konstruktor kopiujcy jest wywoywany za kadym razem, gdy tworzona jest kopia obiektu. Gdy przekazujesz obiekt przez warto, czy to jako parametr funkcji czy te jako jej warto zwracan , tworzona jest tymczasowa kopia tego obiektu. Jeli obiekt jest obiektem zdefiniowanym przez uytkownika, wywoywany jest konstruktor kopiujcy danej klasy, taki moge zobaczy w poprzednim rozdziale na listingu 9.6. Wszystkie konstruktory kopiujce posiadaj jeden parametr; jest nim referencja do obiektu tej samej klasy. Dobrym pomysem jest oznaczenie tej referencji jako const, gdy wtedy konstruktor nie ma moliwoci modyfikacji otrzymanego obiektu. Na przykad:
CAT(const CAT & theCat);

Usunito: i Usunito: y Usunito: i Usunito: i Usunito: zwrotn Usunito: i Usunito: i

W tym przypadku konstruktor CAT otrzymuje sta referencj do istniejcego obiektu klasy CAT. Celem konstruktora kopiujcego jest utworzenie kopii obiektu theCat. Domylny konstruktor kopiujcy po prostu kopiuje kad zmienn skadow z obiektu otrzymanego jako parametr do odpowiedniej zmiennej skadowej obiektu tymczasowego. Nazywa si to kopiowaniem skadowych (czyli kopiowaniem pytkim), i cho w przypadku wikszoci skadowych nie jest potrzebne nic wicej, proces ten jednak nie sprawdza si w przypadku zmiennych bdcych wskanikami do obiektw na stercie. W pytkiej kopii (czyli bezporedniej kopii skadowych) kopiowane s dokadne wartoci skadowych jednego obiektu do skadowych drugiego obiektu. Wskaniki zawarte w obu obiektach wskazuj wtedy na to samo miejsce w pamici. W przypadku gbokiej kopii, wartoci zaalokowane na stercie s kopiowane do nowo alokowanej pamici. Gdyby klasa CAT zawieraa zmienn skadow itsAge, bdc wskanikiem do zmiennej cakowitej zaalokowanej na stercie, wtedy domylny konstruktor kopiujcy skopiowaby warto zmiennej itsAge otrzymanego obiektu do zmiennej itsAge nowego obiektu. Oba obiekty wskazywayby wic to samo miejsce w pamici, co ilustruje rysunek 10.1. Rys. 10.1. Uycie domylnego konstruktora kopiujcego

Usunito: i Usunito: i Usunito: Usunito: Usunito:

Usunito: i

Usunito: i

Gdy ktry z obiektw CAT znajdzie si poza zakresem, nastpi katastrofa. Jak opisano w rozdziale 8., Wskaniki, zadaniem destruktora jest uporzdkowanie i zwolnienie pamici po obiekcie. Jeli destruktor pierwotnego obiektu CAT zwolni t pami, za wskanik w nowym obiekcie CAT nadal bdzie na ni wskazywa, oznacza to bdzie pojawienie si bdnego (zagubionego) wskanika, a program znajdzie si w miertelnym niebezpieczestwie. Ten problem ilustruje rysunek 10.2. Rys. 10.2. Powstawanie zagubionego wskanika

Usunito: t

Rozwizaniem tego problemu jest stworzenie wasnego konstruktora kopiujcego, alokujcego wymagan pami. Po zaalokowaniu pamici, stare wartoci mog by skopiowane do nowej pamici. Pokazuje to listing 10.5. Listing 10.5. Konstruktor kopii
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: // Listing 10.5 // Konstruktory kopii #include <iostream> using namespace std; class CAT { public: CAT(); // domylny konstruktor CAT (const CAT &); // konstruktor kopiujcy ~CAT(); // destruktor int GetAge() const { return *itsAge; } int GetWeight() const { return *itsWeight; } void SetAge(int age) { *itsAge = age; } private: int *itsAge; int *itsWeight; }; CAT::CAT() { itsAge = new int; itsWeight = new int; *itsAge = 5; *itsWeight = 9; } CAT::CAT(const CAT & rhs)

Usunito: i

Usunito: i

30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60:

{ itsAge = new int; itsWeight = new int; *itsAge = rhs.GetAge(); // dostp publiczny *itsWeight = *(rhs.itsWeight); // dostp prywatny } CAT::~CAT() { delete itsAge; itsAge = 0; delete itsWeight; itsWeight = 0; } int main() { CAT mruczek; cout << "Wiek Mruczka: " << mruczek.GetAge() << endl; cout << "Ustawiam wiek Mruczka na 6 lat...\n"; mruczek.SetAge(6); cout << "Tworze Filemona z Mruczka\n"; CAT filemon(mruczek); cout << "Wiek Mruczka: " << mruczek.GetAge() << endl; cout << "Wiek Filemona: " << filemon.GetAge() << endl; cout << "Ustawiam wiek Mruczka na 7 lat...\n"; mruczek.SetAge(7); cout << "Wiek Mruczka: " << mruczek.GetAge() << endl; cout << "Wiek Filemona: " << filemon.GetAge() << endl; return 0; }

Wynik
Wiek Mruczka: 5 Ustawiam wiek Mruczka na 6 lat... Tworze Filemona z Mruczka Wiek Mruczka: 6 Wiek Filemona: 6 Ustawiam wiek Mruczka na 7 lat... Wiek Mruczka: 7 Wiek Filemona: 6

Analiza W liniach od 6. do 19. deklarowana jest klasa CAT. Zwr uwag, e w linii 9. zosta zadeklarowany konstruktor domylny, a w linii 10. zosta zadeklarowany konstruktor kopiujcy. W liniach 17. i 18. s deklarowane dwie zmienne skadowe, bdce wskanikami do zmiennych typu int. Zwykle klasa nie ma powodw do przechowywania danych skadowych typu int w postaci wskanikw, ale w tym przypadku suy to do zilustrowania operowania zmiennymi skadowymi na stercie. Domylny konstruktor, zdefiniowany w liniach od 21. do 27. alokuje miejsce na stercie dla dwch zmiennych typu int, po czym przypisuje im wartoci. Definicja konstruktora kopiujcego rozpoczyna si w linii 29. Zwr uwag, e jego parametrem jest rhs. Jest to czsto stosowana nazwa dla parametru konstruktora kopiujcego, stanowica skrt
Usunito: i Usunito: i Usunito: i

od wyraenia right-hand side (prawa strona). Gdy spojrzysz na przypisania w liniach 33. i 34., przekonasz si, e obiekt przekazywany jako parametr znajduje si po prawej stronie znaku rwnoci. Oto sposb, w jaki dziaa: W liniach 31. i 32. alokowana jest pami na stercie. Nastpnie, w liniach 33. i 34., wartociom w nowej pamici s przypisywane wartoci danych z istniejcego obiektu CAT. Parametr rhs jest obiektem CAT przekazanym do konstruktora kopiujcego jako staa referencja. Jako obiekt klasy CAT, parametr rhs posiada wszystkie skadowe tej klasy. Kady obiekt CAT moe odwoywa si do wszystkich (take prywatnych) skadowych innych obiektw tej samej klasy; jednak do tradycji programistycznej naley korzystanie z akcesorw wszdzie tam, gdzie jest to moliwe. Funkcja skadowa rhs.GetAge() zwraca warto przechowywan w pamici wskazywanej przez zmienn skadow itsAge obiektu rhs. Rysunek 10.3 przedstawia, co dzieje si w programie. Wartoci wskazywane przez zmienne skadowe istniejcego obiektu CAT s kopiowane do pamici zaalokowanej dla nowego obiektu CAT. Rys. 10.3. Przykad kopiowania gbokiego
Usunito: gbokiej Usunito: i Usunito: i

W linii 47. tworzony jest obiekt CAT o nazwie mruczek. Wypisywany jest jego wiek, po czym w linii 50. wiek Mruczka jest ustawiany na 6 lat. W linii 52. tworzony jest nowy obiekt klasy CAT, tym razem o nazwie filemon. Jest on tworzony za pomoc konstruktora kopiujcego, ktremu przekazano obiekt mruczek. Gdyby mruczek zosta przekazany do funkcji przez warto (nie przez referencj), wtedy kompilator uyby tego samego konstruktora kopii. W liniach 53. i 54. jest wypisywany wiek Mruczka i Filemona. Oczywicie, wiek Filemona jest taki sam, jak wiek Mruczka i wynosi 6 lat, a nie domylne 5. W linii 56. wiek Mruczka jest ustawiany na 7 lat, po czym wiek obu obiektw jest wypisywany ponownie. Tym razem Mruczek ma 7 lat, ale Filemon wci ma 6, co dowodzi, e dane tych obiektw s przechowywane w osobnych miejscach pamici. Gdy obiekt klasy CAT wychodzi z zakresu, automatycznie wywoywany jest jego destruktor. Implementacja destruktora klasy CAT zostaa przedstawiona w liniach od 37. do 43. Dla obu wskanikw, itsAge oraz itsWeight, wywoywany jest operator delete, zwalniajcy

Usunito: i

zaalokowan dla nich pami sterty. Oprcz tego, dla bezpieczestwa, obu wskanikom jest przypisywana warto NULL.

Przecianie operatorw
C++ posiada liczne typy wbudowane, takie jak int, float, char, itd. Kady z nich posiada wasne wbudowane operatory, takie jak dodawanie (+) czy mnoenie (*). C++ umoliwia stworzenie takich operatorw take dla klas definiowanych przez uytkownika. Aby umoliwi pene poznanie procesu przeciania operatorw, na listingu 10.6 stworzono now klas o nazwie Counter (licznik). Obiekt typu Counter bdzie uywany do (uwaga!) zliczania ptli oraz innych zada, w ktrych warto musi by inkrementowana, dekrementowana czy ledzona w inny sposb. Listing 10.6. Klasa Counter
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: // Listing 10.6 // Klasa Counter #include <iostream> using namespace std; class Counter { public: Counter(); ~Counter(){} int GetItsVal()const { return itsVal; } void SetItsVal(int x) {itsVal = x; } private: int itsVal; }; Counter::Counter(): itsVal(0) {} int main() { Counter i; cout << "Wartoscia i jest " << i.GetItsVal() << endl; return 0; }

Wynik
Wartoscia i jest 0

Analiza W obecnej postaci klasa Counter jest raczej bezuyteczna. Sama klasa jest zdefiniowana w liniach od 6. do 17. Jej jedyn zmienn skadow jest warto typu int. Domylny konstruktor,

zadeklarowany w linii 9. i zaimplementowany w linii 19., inicjalizuje jedyn zmienn skadow, itsVal (jego warto), wartoci zero. W odrnieniu od wbudowanego typu int, obiekt klasy Counter nie moe by inkrementowany, dekrementowany, dodawany, przypisywany, nie mona te nim operowa w inny sposb. Sprawia za to, e wypisywanie jego wartoci staje si jeszcze bardziej skomplikowane!

Usunito: na

Pisanie funkcji inkrementacji


Dziki przecieniu operatorw moemy odzyska wikszo dziaa, ktrych klasa ta zostaa pozbawiona. Istniej na przykad dwa sposoby uzupenienia obiektu Counter o inkrementacj. Pierwszy z nich polega na napisaniu metody do inkrementacji, zobaczymy to na listingu 10.7. Listing 10.7. Dodawanie operatora inkrementacji
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: // Listing 10.7 // Klasa Counter #include <iostream> using namespace std; class Counter { public: Counter(); ~Counter(){} int GetItsVal()const { return itsVal; } void SetItsVal(int x) {itsVal = x; } void Increment() { ++itsVal; } private: int itsVal; }; Counter::Counter(): itsVal(0) {} int main() { Counter i; cout << "Wartoscia i jest " << i.GetItsVal() << endl; i.Increment(); cout << "Wartoscia i jest " << i.GetItsVal() << endl; return 0; }

Wynik
Wartoscia i jest 0 Wartoscia i jest 1

Analiza

Listing 10.7 zawiera now funkcj Increment() (inkrementuj), zdefiniowan w linii 13. Cho ta funkcja dziaa poprawnie, jest jednak nieco kopotliwa w uyciu. Program a prosi si o uzupenienie go o operator ++, co oczywicie moemy zrobi.

Przecianie operatora przedrostkowego


Operatory przedrostkowe mona przeciy, deklarujc funkcj o postaci:
zwracanyTyp operator op()

gdzie op jest przecianym operatorem. Operator ++ mona przeciy, piszc:


void operator++ ()

T alternatyw demonstruje listing 10.8. Listing 10.8. Przecianie operatora++


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: // Listing 10.8 // Klasa Counter // przedrostkowy operator inkrementacji #include <iostream> using namespace std; class Counter { public: Counter(); ~Counter(){} int GetItsVal()const { return itsVal; } void SetItsVal(int x) {itsVal = x; } void Increment() { ++itsVal; } void operator++ () { ++itsVal; } private: int itsVal; }; Counter::Counter(): itsVal(0) {} int main() { Counter i; cout << "Wartoscia i jest " << i.GetItsVal() << endl; i.Increment(); cout << "Wartoscia i jest " << i.GetItsVal() << endl; ++i; cout << "Wartoscia i jest " << i.GetItsVal() << endl;

34: 35:

return 0; }

Wynik
Wartoscia i jest 0 Wartoscia i jest 1 Wartoscia i jest 2

Analiza W linii 15. zosta przeciony operator++, ktry jest uywany w linii 32. Jego skadnia jest ju zbliona do skadni typw wbudowanych, takich jak int. Teraz moesz wzi pod uwag wykonywanie podstawowych zada, dla ktrych zostaa stworzona klasa Counter (na przykad wykrywanie sytuacji, w ktrej licznik przekracza najwiksz warto). Jednak w zapisie operatora inkrementacji tkwi powany defekt. Jeli umiecisz obiekt typu
Counter po prawej stronie przypisania, kompilator zgosi bd. Na przykad:
Counter a = ++i;

W tym przykadzie mielimy zamiar stworzy nowy obiekt a nalecy do klasy Counter, a nastpnie, po inkrementacji tej zmiennej, przypisa mu warto i. To przypisanie obsuyby wbudowany konstruktor kopiujcy, ale wykorzystywany obecnie operator inkrementacji nie zwraca obiektu typu Counter. Zamiast tego zwraca typ void. Nie mona przypisywa obiektw void obiektom Counter. (Z pustego i Salomon nie naleje!)

Usunito: i

Zwracanie typw w przecionych funkcjach operatorw


To, czego nam teraz potrzeba, to zwrcenie obiektu klasy Counter, ktry mgby by przypisany innemu obiektowi tej klasy. Ktry z obiektw powinien zosta zwrcony? Jednym z rozwiza jest stworzenie obiektu tymczasowego i zwrcenie go. Pokazuje to listing 10.9. Listing 10.9. Zwracanie obiektu tymczasowego
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: // Listing 10.9 // operator++ zwraca tymczasowy obiekt #include <iostream> using namespace std; class Counter { public: Counter(); ~Counter(){} int GetItsVal()const { return itsVal; } void SetItsVal(int x) {itsVal = x; } void Increment() { ++itsVal; } Counter operator++ ();

16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46:

private: int itsVal; }; Counter::Counter(): itsVal(0) {} Counter Counter::operator++() { ++itsVal; Counter temp; temp.SetItsVal(itsVal); return temp; } int main() { Counter i; cout << "Wartoscia i jest " << i.GetItsVal() << endl; i.Increment(); cout << "Wartoscia i jest " << i.GetItsVal() << endl; ++i; cout << "Wartoscia i jest " << i.GetItsVal() << endl; Counter a = ++i; cout << "Wartoscia a jest: " << a.GetItsVal(); cout << " , a wartosc i to: " << i.GetItsVal() << endl; return 0; }

Wynik
Wartoscia Wartoscia Wartoscia Wartoscia i i i a jest 0 jest 1 jest 2 jest: 3 , a wartosc i to: 3

Analiza W tej wersji operator++ zosta zadeklarowany w linii 15. jako zwracajcy obiekt typu Counter. W linii 29. jest tworzona zmienna tymczasowa temp, ktrej warto jest ustawiana zgodnie z wartoci biecego obiektu. Ta tymczasowa warto jest zwracana i natychmiast przypisywana zmiennej a w linii 42.

Zwracanie obiektw tymczasowych bez nadawania im nazw


Nie ma potrzeby nadawania nazwy obiektowi tymczasowemu tworzonemu w linii 29. Gdyby klasa Counter miaa konstruktor przyjmujcy warto, jako warto zwrotn operatora inkrementacji moglibymy po prostu zwrci wynik tego konstruktora. Pokazuje to listing 10.10. Listing 10.10. Zwracanie obiektu tymczasowego bez nadawania mu nazwy
0: 1: // Listing 10.10 // operator++ zwraca tymczasowy obiekt bez nazwy

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

#include <iostream> using namespace std; class Counter { public: Counter(); Counter(int val); ~Counter(){} int GetItsVal()const { return itsVal; } void SetItsVal(int x) {itsVal = x; } void Increment() { ++itsVal; } Counter operator++ (); private: int itsVal; }; Counter::Counter(): itsVal(0) {} Counter::Counter(int val): itsVal(val) {} Counter Counter::operator++() { ++itsVal; return Counter (itsVal); } int main() { Counter i; cout << "Wartoscia i jest " << i.GetItsVal() << i.Increment(); cout << "Wartoscia i jest " << i.GetItsVal() << ++i; cout << "Wartoscia i jest " << i.GetItsVal() << Counter a = ++i; cout << "Wartoscia a jest: " << a.GetItsVal(); cout << ", zas wartosc i to: " << i.GetItsVal() return 0; }

endl; endl; endl; << endl;

Wynik
Wartoscia Wartoscia Wartoscia Wartoscia i i i a jest 0 jest 1 jest 2 jest: 3, zas wartosc i to: 3

Analiza

W linii 11. zosta zadeklarowany nowy konstruktor, przyjmujcy warto typu int. Jego implementacja znajduje si w liniach od 27. do 29.; inicjalizuje ona zmienn skadow itsVal za pomoc wartoci otrzymanej jako argument konstruktora. Implementacja operatora++ moe zosta teraz uproszczona. W linii 33. warto itsVal jest inkrementowana. Nastpnie, w linii 34., tworzony jest tymczasowy obiekt klasy Counter, ktry jest inicjalizowany wartoci zmiennej itsVal, po czym zwracany jako rezultat operatora++. To rozwizanie jest bardziej eleganckie, ale powoduje, e musimy zada nastpne pytanie: dlaczego w oglne musimy tworzy obiekt tymczasowy? Pamitajmy, e kady obiekt tymczasowy musi zosta najpierw skonstruowany, a pniej zniszczony te operacje mog by potencjalnie do kosztowne. Poza tym, obiekt ju istnieje i posiada waciw warto, wic dlaczego nie mielibymy zwrci wanie jego? Rozwiemy ten problem, uywajc wskanika this.

Usunito:

Uycie wskanika this


Wskanik this jest przekazywany wszystkim funkcjom skadowym, nawet przecionym operatorom, takim jak operator++(). Wskanik this wskazuje na i, wic gdy zostanie wyuskany, zwrci tylko obiekt i, ktry w swojej zmiennej itsVal zawiera ju waciw warto. Zwracanie wyuskanego wskanika this i zaniechanie tworzenia niepotrzebnego obiektu tymczasowego przedstawia listing 10.11. Listing 10.11. Zwracanie wskanika this
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: // Listing 10.11 // Zwracanie wyuskanego wskanika this #include <iostream> using namespace std; class Counter { public: Counter(); ~Counter(){} int GetItsVal()const { return itsVal; } void SetItsVal(int x) {itsVal = x; } void Increment() { ++itsVal; } const Counter& operator++ (); private: int itsVal; }; Counter::Counter(): itsVal(0) {}; const Counter& Counter::operator++() { ++itsVal; return *this; }

31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44:

int main() { Counter i; cout << "Wartoscia i jest " << i.GetItsVal() << i.Increment(); cout << "Wartoscia i jest " << i.GetItsVal() << ++i; cout << "Wartoscia i jest " << i.GetItsVal() << Counter a = ++i; cout << "Wartoscia a jest: " << a.GetItsVal(); cout << ", zas wartosc i to: " << i.GetItsVal() return 0; }

endl; endl; endl; << endl;

Wynik
Wartoscia Wartoscia Wartoscia Wartoscia i i i a jest 0 jest 1 jest 2 jest: 3, zas wartosc i to: 3

Analiza Implementacja operatora++, zawarta w liniach od 26. do 30., zostaa zmieniona w taki sposb, aby wyuskiwaa wskanik this i zwracaa biecy obiekt. Dziki temu zmiennej a moe by przypisany biecy egzemplarz klasy Counter. Jak wspomnielimy wczeniej, gdyby obiekt klasy Counter alokowa pami, naleaoby przysoni domylny konstruktor kopiujcy. Jednak w tym przypadku domylny konstruktor kopiujcy dziaa poprawnie. Zwr uwag, e zwracan wartoci jest referencja do obiektu klasy Counter, dziki czemu unikamy tworzenia dodatkowego obiektu tymczasowego. Jest to zmienna const, poniewa nie powinna by modyfikowana przez funkcj wykorzystujc zwracany obiekt klasy Counter.

Usunito: i Usunito: i

Dlaczego staa referencja?


Zwracany obiekt Counter musi by obiektem const. Gdyby nim nie by, mona by wykona na zwracanym obiekcie operacje, ktre mogyby zmieni jego dane skadowe. Na przykad, gdyby zwracana warto nie bya staa, mgby napisa:
40: Counter a = ++++i;

Mona to rozumie jako wywoanie operatora inkrementacji (++) na wyniku wywoania operatora inkrementacji, opcja ta powinno by zablokowane. Sprbuj wykona taki eksperyment: zarwno w deklaracji, jak i w implementacji (linie 15. i 26.) zmie zwracan warto na warto nie bdc const, po czym zmie lini 40. na pokazan powyej (++++i). Umie punkt przerwania w debuggerze na linii 40. i wejd do funkcji. Zobaczysz, e do operatora inkrementacji wejdziesz dwa razy. Inkrementacja zostanie zastosowana do (teraz nie bdcej sta) wartoci zwracanej.

Aby si przed tym zabezpieczy, deklarujemy warto zwracan jako const. Gdy zmienisz linie 15. i 26. z powrotem na stae, za lini 40. pozostawisz bez zmian (++++i), kompilator zaprotestuje przeciwko wywoaniu operatora inkrementacji dla obiektu staego.

Przecianie operatora przyrostkowego


Jak dotd, udao si nam przeciy operator przedrostkowy. A co zrobi, gdy chcemy przeciy operator przyrostkowy? Kompilator nie potrafi odrni przedrostka od przyrostka. Zgodnie z konwencj, jako parametr deklaracji operatora dostarczana jest zmienna cakowita. Warto parametru jest ignorowana; sygnalizuje on tylko, e jest to operator przyrostkowy.

Rnica pomidzy przedrostkiem a przyrostkiem


Zanim bdziemy mogli napisa operator przyrostkowy, musimy zrozumie, czym rni si on od operatora przedrostkowego. Omawialimy to szczegowo w rozdziale 4., Wyraenia i instrukcje. (Patrz listing 4.3.). Przypomnijmy: przedrostek mwi: Inkrementuj, po czym pobierz, za przyrostek mwi: Pobierz, a nastpnie inkrementuj. Operator przedrostkowy moe po prostu inkrementowa warto, a nastpnie zwrci sam obiekt, za operator przyrostkowy musi zwraca warto istniejc przed dokonaniem inkrementacji. W tym celu musimy stworzy obiekt tymczasowy, ktry bdzie zawiera pierwotn warto, nastpnie inkrementowa warto pierwotnego obiektu, po czy, zwrci obiekt tymczasowy. Przyjrzyjmy si temu procesowi od pocztku. Wemy nastpujc lini kodu:
a = x++;

Jeli x miao warto 5, wtedy po wykonaniu tej instrukcji a ma warto 5, za x ma warto 6. Zwracamy warto w x i przypisujemy j zmiennej a, po czym inkrementujemy warto x. Jeli x jest obiektem, jego przyrostkowy operator inkrementacji musi zachowa pierwotn warto (5) w obiekcie tymczasowym, inkrementowa warto x do 6, po czym zwrci obiekt tymczasowy w celu przypisania oryginalnej wartoci do zmiennej a. Zwr uwag, e skoro zwracamy obiekt tymczasowy, musimy zwraca go poprzez warto, a nie poprzez referencj (w przeciwnym razie obiekt ten znajdzie si poza zakresem natychmiast po wyjciu programu z funkcji). Listing 10.12 przedstawia uycie operatora przedrostkowego i przyrostkowego. Listing 10.12. Operator przedrostkowy i przyrostkowy
0: 1: 2: 3: 4: 5: 6: 7: // Listing 10.12 // Operator przedrostkowy i przyrostkowy #include <iostream> using namespace std; class Counter

8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53:

{ public: Counter(); ~Counter(){} int GetItsVal()const { return itsVal; } void SetItsVal(int x) {itsVal = x; } const Counter& operator++ (); // przedrostkowy const Counter operator++ (int); // przyrostkowy private: int itsVal; }; Counter::Counter(): itsVal(0) {} const Counter& Counter::operator++() { ++itsVal; return *this; } const Counter Counter::operator++(int theFlag) { Counter temp(*this); ++itsVal; return temp; } int main() { Counter i; cout << "Wartoscia i jest " << i.GetItsVal() << i++; cout << "Wartoscia i jest " << i.GetItsVal() << ++i; cout << "Wartoscia i jest " << i.GetItsVal() << Counter a = ++i; cout << "Wartoscia a jest: " << a.GetItsVal(); cout << ", zas wartosc i to: " << i.GetItsVal() a = i++; cout << "Wartoscia a jest: " << a.GetItsVal(); cout << ", zas wartosc i to: " << i.GetItsVal() return 0; }

endl; endl; endl; << endl; << endl;

Wynik
Wartoscia Wartoscia Wartoscia Wartoscia Wartoscia i i i a a jest 0 jest 1 jest 2 jest: 3, zas wartosc i to: 3 jest: 3, zas wartosc i to: 4

Analiza Operator przyrostkowy jest deklarowany w linii 15. i implementowany w liniach od 31. do 36. Operator przedrostkowy jest deklarowany w linii 14.

Parametr przekazywany do operatora przyrostkowego w linii 32. (theFlag) sygnalizuje jedynie kompilatorowi, e chodzi tu o operator przyrostkowy; warto tego parametru nigdy nie jest wykorzytywana.

Usunito: jest to

Operator dodawania
Operator inkrementacji jest operatorem unarnym, tj. operatorem dziaajcym na tylko jednym obiekcie. Operator dodawania (+) jest operatorem binarnym, co oznacza, e do dziaania potrzebuje dwch obiektw. W jaki wic sposb mona zaimplementowa przecienie operatora + dla klasy Counter? Naszym celem jest zadeklarowanie dwch zmiennych typu Counter, a nastpnie dodanie ich, tak jak w poniszym przykadzie:
Counter varOne, varTwo, varThree; varThree = varOne + varTwo;

Take w tym przypadku mgby zacz od napisania funkcji Add() (dodaj), ktra jako argument przyjmowaaby obiekt klasy Counter, dodawaaby wartoci, po czym zwracaaby obiekt klasy Counter jako wynik. Takie postpowanie ilustruje listing 10.13. Listing 10.13. Funkcja Add()
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: // Listing 10.13 // Funkcja Add #include <iostream> using namespace std; class Counter { public: Counter(); Counter(int initialValue); ~Counter(){} int GetItsVal()const { return itsVal; } void SetItsVal(int x) {itsVal = x; } Counter Add(const Counter &); private: int itsVal; }; Counter::Counter(int initialValue): itsVal(initialValue) {} Counter::Counter(): itsVal(0) {} Counter Counter::Add(const Counter & rhs) {

32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44:

return Counter(itsVal+ rhs.GetItsVal()); } int main() { Counter varOne(2), varTwo(4), varThree; varThree = varOne.Add(varTwo); cout << "varOne: " << varOne.GetItsVal()<< endl; cout << "varTwo: " << varTwo.GetItsVal() << endl; cout << "varThree: " << varThree.GetItsVal() << endl; return 0; }

Wynik
varOne: 2 varTwo: 4 varThree: 6

Analiza Funkcja Add() zostaa zadeklarowana w linii 15. Otrzymuje ona sta referencj do obiektu klasy Counter, ktry zawiera warto przeznaczon do dodania do wartoci w biecym obiekcie. Zwraca obiekt klasy Counter, ktry jest przypisywany lewej stronie instrukcji przypisania w linii 38. Innymi sowy, varOne jest obiektem, varTwo jest parametrem funkcji Add(), za wynik tej funkcji jest przypisywany do varThree. Aby stworzy varThree bez inicjalizowania wartoci tego obiektu, potrzebny jest konstruktor domylny. Ten konstruktor inicjalizuje zmienn skadow itsVal jako zero, co pokazuj linie od 26. do 28. Poniewa zmienne varOne i varTwo powinny by zainicjalizowane wartociami rnymi od zera, zosta stworzony kolejny konstruktor, znajdujcy si w liniach od 22. do 24. Innym rozwizaniem tego problemu jest zastosowanie wartoci domylnej 0 w konstruktorze zadeklarowanym w linii 11.

Przecianie operatora dodawania


Funkcja Add() znajduje si w liniach od 30. do 33. listingu 10.13. Funkcja dziaa poprawnie, ale jej uycie jest mao naturalne. Przecienie operatora + spowoduje, e uycie klasy Counter bdzie mogo przebiega bardziej naturalnie. Pokazuje to listing 10.14. Listing 10.14. operator+
0: 1: 2: 3: 4: 5: 6: 7: 8: // Listing 10.14 //Przeciony operator dodawania (+) #include <iostream> using namespace std; class Counter {

9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42:

public: Counter(); Counter(int initialValue); ~Counter(){} int GetItsVal()const { return itsVal; } void SetItsVal(int x) {itsVal = x; } Counter operator+ (const Counter &); private: int itsVal; }; Counter::Counter(int initialValue): itsVal(initialValue) {} Counter::Counter(): itsVal(0) {} Counter Counter::operator+ (const Counter & rhs) { return Counter(itsVal + rhs.GetItsVal()); } int main() { Counter varOne(2), varTwo(4), varThree; varThree = varOne + varTwo; cout << "varOne: " << varOne.GetItsVal()<< endl; cout << "varTwo: " << varTwo.GetItsVal() << endl; cout << "varThree: " << varThree.GetItsVal() << endl; return 0; }

Wynik
varOne: 2 varTwo: 4 varThree: 6

Analiza do 31.
operator+ zosta zadeklarowany w linii 15., a jego implementacja znajduje si w liniach od 28.

Porwnaj go z deklaracj i definicj funkcji Add() w poprzednim listingu: s prawie identyczne. Jednak ich skadnia jest cakiem inna. Bardziej naturalny jest zapis:
varThree = varOne + varTwo;

ni zapis:
varThree = varOne.Add(varTwo);

Nie jest to dua zmiana, ale na tyle wana, by program sta si atwiejszy do odczytania i zrozumienia. Operator jest wykorzystywany w linii 36:
36: varThree = varOne + varTwo;

Kompilator tumaczy ten zapis na:


varThree = varOne.operator+(varTwo);

Mgby oczywicie napisa t lini sam, a kompilator zaakceptowaby to bez zastrzee. Metoda operator+ jest wywoywana dla operandu po lewej stronie, a jako argument otrzymuje operand po prawej stronie.

Zagadnienia zwizane z przecianiem operatorw


Przeciane operatory mog by funkcjami skadowymi, takimi, jak opisane w tym rozdziale, lub funkcjami globalnymi. Te ostatnie zostan opisane w rozdziale 15., Specjalne klasy i funkcje, przy okazji omawiania funkcji zaprzyjanionych. Jedynymi operatorami, ktre musz by funkcjami skadowymi klasy, s operator: przypisania (=), indeksu tablicy ([]), wywoania funkcji (()) oraz wskanika (->). Operator [] zostanie omwiony w rozdziale 13., przy okazji omawiania tablic. Przecianie operatora -> zostanie omwione w rozdziale 15., przy okazji omawiania wskanikw inteligentnych.

Ograniczenia w przecianiu operatorw


Operatory dla typw wbudowanych (takich jak int) nie mog by przeciane. Nie mona zmienia priorytetu operatorw ani iloci operandw operatora, tj. operator unarny nie moe sta si operatorem binarnym i na odwrt. Nie mona take tworzy nowych operatorw, dlatego niemoliwe jest zadeklarowanie symbolu ** jako operatora podnoszenia do potgi. Niektre operatory C++ s operatorami unarnymi i wymagaj tylko jednego operandu (na przykad myValue++). Inne operatory s binarne, czyli wymagaj dwch operandw (na przykad a+b). W C++ istnieje tylko jeden operator trjargumentowy: operator ? (na przykad a > b ? x : y).

Co przecia?
Przecianie operatorw jest jednym z najczciej naduywanych przez pocztkujcych programistw aspektw jzyka C++. Tworzenie nowych zastosowa dla niektrych z mniej znanych operatorw jest bardzo kuszce, ale niezmiennie prowadzi do tworzenia nieczytelnego kodu. Oczywicie, doprowadzenie to tego, by operator + odejmowa, a operator * dodawa moe by zabawne, ale aden profesjonalny programista tego nie zrobi. Due niebezpieczestwo kryje si take w machinalnym uyciu operatora + do czenia cigw liter czy operatora / do dzielenia acuchw. Oczywicie, istniej powody, dla ktrych stosujemy te operatory, ale istnieje jeszcze wicej powodw, by zachowa przy tym du ostrono. Pamitaj e celem przeciania operatorw jest zwikszenie uytecznoci i przejrzystoci obiektw. TAK Stosuj przecianie operatorw wtedy, gdy zwiksza to przejrzysto programu. Zwracaj z przecianego operatora obiekt jego klasy. NIE Nie twrz operatorw dziaajcych niezgodnie z przeznaczeniem.

Operator przypisania
Czwart, ostatni funkcj, ktra jest dostarczana przez kompilator (gdy nie stworzysz jej sam) jest operator przypisania (operator=()). Ten operator jest wywoywany za kadym razem, gdy przypisujesz co do obiektu. Na przykad:
CAT catOne(5,7); CAT catTwo(3,4); // ... tutaj inny kod catTwo = catOne;

W tym fragmencie tworzony jest obiekt catOne; jego zmienna skadowa itsAge jest inicjalizowana wartoci 5, a zmienna skadowa itsWeight wartoci 7. W nastpnej linii tworzony jest obiekt catTwo, ktrego zmienne skadowe s inicjalizowane wartociami 3 i 4. Po jakim czasie obiektowi catTwo jest przypisywany obiekt catOne. Pojawiaj si wic dwa problemy: co si stanie, gdy zmienna skadowa itsAge jest wskanikiem i co si dzieje z pierwotnymi wartociami w obiekcie catTwo? Posugiwanie si zmiennymi skadowymi, przechowujcymi swoje wartoci na stercie, zostao omwione ju wczeniej, podczas omawiania dziaania konstruktora kopiujcego. Te same zagadnienia odnosz si take do przedstawionego tutaj przypadku, tak jak pokazano na rysunkach 10.1 i 10.2.
Usunito: i

Programici C++ dokonuj rozrnienia pomidzy kopiowaniem pytkim, czyli kopiowaniem skadowych klasy, a kopiowaniem gbokim. Przy kopiowaniu pytkim kopiowane s jedynie skadowe, wic oba obiekty wskazuj to samo miejsce na stercie. Przy kopiowaniu gbokim na stercie alokowany jest nowy obszar pamici. Ilustrowa to rysunek 10.3. Jednak w przypadku operatora przypisania pojawia si kolejny problem. Obiekt catTwo ju istnieje i posiada zaalokowan pami. Jeli nie chcemy doprowadzi do wycieku pamici, pami musi zosta zwolniona. Ale co zrobi, gdy przypiszemy obiekt catTwo samemu sobie?
catTwo = catTwo;

Usunito: Usunito: Usunito: Usunito: Usunito: Usunito: W Usunito: i Usunito: ej Usunito: W

Nikt nie ma oczywicie zamiaru tego robi, ale moe si to zdarzy przypadkiem, gdy referencje i wyuskane wskaniki ukryj fakt, e przypisanie odnosi si do tego samego obiektu. Jeli nie rozwiesz tego problemu, obiekt catTwo moe usun swoj zaalokowan pami, po czym, gdy ju bdzie gotw do skopiowania pamici z obiektu po prawej stronie operatora przypisania, znajdzie si w ogromnym kopocie: tej wartoci ju nie bdzie! Aby si przed tym zabezpieczy, operator przypisania musi sprawdza, czy operand po prawej stronie operatora nie jest tym samym obiektem. W tym celu moe sprawdzi wskanik this. Klas z przecionym operatorem przypisania przedstawia listing 10.15. Listing 10.15. Operator przypisania
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: // Listing 10.15 // Operator przypisania #include <iostream> using namespace std; class CAT { public: CAT(); // domylny konstruktor // konstruktor kopiujcy oraz destruktor zostay usunite! int GetAge() const { return *itsAge; } int GetWeight() const { return *itsWeight; } void SetAge(int age) { *itsAge = age; } CAT & operator=(const CAT &); private: int *itsAge; int *itsWeight; }; CAT::CAT() { itsAge = new int; itsWeight = new int; *itsAge = 5; *itsWeight = 9; } CAT & CAT::operator=(const CAT & rhs) {

Usunito: ej kopii

Usunito: i

33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53:

if (this == &rhs) return *this; *itsAge = rhs.GetAge(); *itsWeight = rhs.GetWeight(); return *this; } int main() { CAT mruczek; cout << "Wiek Mruczka: " << mruczek.GetAge() << endl; cout << "Ustawiam wiek Mruczka na 6...\n"; mruczek.SetAge(6); CAT filemon; cout << "Wiek Filemona: " << filemon.GetAge() << endl; cout << "Kopiuje Mruczka do Filemona...\n"; filemon = mruczek; cout << "Wiek Filemona: " << filemon.GetAge() << endl; return 0; }

Wynik
Wiek Mruczka: 5 Ustawiam wiek Mruczka na 6... Wiek Filemona: 5 Kopiuje Mruczka do Filemona... Wiek Filemona: 6

Analiza Listing 10.15 stanowi powrt do klasy CAT, z ktrej (dla zaoszczdzenia miejsca) usunito konstruktor kopiujcy oraz destruktor. Operator przypisania jest deklarowany w linii 15., za jego definicja znajduje si w liniach od 31. do 38. W linii 33. nastpuje sprawdzenie, czy biecy obiekt (obiekt CAT, do ktrego nastpuje przypisanie), jest tym samym obiektem, co przypisywany obiekt CAT. Odbywa si to poprzez sprawdzenie, czy adres rhs jest taki sam, jak adres zawarty we wskaniku this. Oczywicie, mona przeciy take operator rwnoci (==), dziki czemu moesz sam okreli co oznacza rwno twoich obiektw.
Usunito: i

Obsuga konwersji typw danych


Co si stanie, gdy sprbujesz przypisa zmienn typu wbudowanego, takiego jak int czy unsigned short, do obiektu klasy zdefiniowanej przez uytkownika? Na listingu 10.16 ponownie skorzystamy z klasy Counter, prbujc przypisa obiektowi tej klasy zmienn typu int.
OSTRZEENIE Listing 10.16 nie skompiluje si!

Listing 10.16. Prba przypisania obiektowi typu Counter zmiennej typu int
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: // Listing 10.16 // Ten kod nie skompiluje si! #include <iostream> using namespace std; class Counter { public: Counter(); ~Counter(){} int GetItsVal()const { return itsVal; } void SetItsVal(int x) {itsVal = x; } private: int itsVal; }; Counter::Counter(): itsVal(0) {} int main() { int theShort = 5; Counter theCtr = theShort; cout << "theCtr: " << theCtr.GetItsVal() << endl; return 0; }

Wynik
Bd kompilacji! Nie mona dokona konwersji z typu int do typu Counter.

Analiza Klasa Counter zadeklarowana w liniach od 7. do 17. posiada jedynie konstruktor domylny. Nie deklaruje adnej konkretnej metody zamiany zmiennych typu int w obiekty klasy Counter, wic linia 26. powoduje bd kompilacji. Kompilator nie wie, dopki go o tym nie poinformujesz, e majc zmienn typu int, powinien przypisa jej warto do zmiennej skadowej itsVal. Listing 10.17 poprawia ten bd, tworzc operator konwersji: konstruktor przyjmuje warto typu int i tworzy obiekt klasy Counter. Listing 10.17. Konwersja typu int na typ Counter
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: // Listing 10.17 // Konstruktor jako operator konwersji #include <iostream> using namespace std; class Counter { public: Counter();

11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35:

Counter(int val); ~Counter(){} int GetItsVal()const { return itsVal; } void SetItsVal(int x) {itsVal = x; } private: int itsVal; }; Counter::Counter(): itsVal(0) {} Counter::Counter(int val): itsVal(val) {} int main() { int theShort = 5; Counter theCtr = theShort; cout << "theCtr: " << theCtr.GetItsVal() << endl; return 0; }

Wynik
theCtr: 5

Analiza Wana zmiana pojawia si w linii 11., gdzie deklarowany jest przeciony konstruktor, przyjmujcy warto typu int, oraz w liniach od 24. do 26., gdzie konstruktor ten jest implementowany. Efektem jego dziaania jest stworzenie z obiektu typu int obiektu typu Counter. Na tej podstawie kompilator moe wywoa konstruktor, ktrego argumentem jest warto int. Krok 1: Tworzymy licznik o nazwie theCtr. Odpowiada to zapisowi x = 5;, ktry tworzy zmienn cakowit x i inicjalizuje j wartoci 5. W naszym przypadku tworzymy obiekt klasy Counter o nazwie theCtr i inicjalizujemy go zmienn cakowit theShort. Krok 2: Przypisujemy obiektowi theCtr warto zmiennej theShort. Zmienna theShort jest zmienn cakowit, a nie zmienn typu Counter! Najpierw musimy przekonwertowa j do typu Counter. Kompilator sprbuje dokona dla nas pewnych konwersji automatycznie, ale musimy go tego nauczy. Osigniemy to poprzez stworzenie dla klasy Counter konstruktora, ktrego jedynym parametrem jest warto cakowita:
class Counter

{ Counter(int val); // ... };

Konstruktor tworzy obiekty typu Counter na podstawie wartoci typu int. Aby tego dokona, tworzy tymczasowy, pozbawiony nazwy obiekt klasy Counter. Dla zilustrowania tego przykadu przypumy, e ten tymczasowy obiekt typu Counter, tworzony ze zmiennej typu int, ma nazw wasShort. Krok 3: Przypisujemy wasShort do theCtr, co odpowiada zapisowi:
*theCtr = wasShort;

W tym kroku wasShort (tymczasowy obiekt stworzony podczas dziaania konstruktora) jest zastpowany zapisem znajdujcym si po prawej stronie operatora przypisania. Teraz, gdy kompilator potrafi stworzy dla nas obiekt tymczasowy, moe zainicjalizowa nim zmienn theCtr. Aby zrozumie ten proces, musisz uwiadomi sobie, e wszystkie przecienia operatorw dziaaj w ten sam sposb deklarujesz przeciony operator, uywajc sowa kluczowego operator. W przypadku operatorw binarnych (takich jak = czy +), parametrem operatora staje si zmienna pooona po jego prawej stronie. Jest to zapewniane przez kompilator. Tak wic:
a = b;

staje si
a.operator=(b);

Co si jednak stanie, gdy sprbujesz odwrci przypisanie:


0: 1: 2: Counter theCtr(5); int theShort = theCtr; cout << "theShort: " << theShort << endl; Usunito: Take w tym przypadku

Znw wystpi bd kompilacji. Cho kompilator wie ju, w jaki sposb stworzy obiekt typu Counter z wartoci typu int, nie ma pojcia, jak odwrci ten proces.

Operatory konwersji
Aby rozwiza ten i podobne problemy, jzyk C++ dostarcza operatorw konwersji, ktre mog by dodawane do tworzonych klas. Dziki temu klasa moe jawnie okreli, w jaki sposb ma by dokonywana konwersja do typw wbudowanych. Pokazuje to listing 10.18. Zwr uwag, e operatory konwersji nie okrelaj zwracanej wartoci, mimo, i w efekcie zwracaj warto przekonwertowan. Listing 10.18. Konwersja z typu Counter na typ unsigned short()
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: // Listing 10.18 - Operatory konwersji #include <iostream> class Counter { public: Counter(); Counter(int val); ~Counter(){} int GetItsVal()const { return itsVal; } void SetItsVal(int x) {itsVal = x; } operator unsigned short(); private: int itsVal; }; Counter::Counter(): itsVal(0) {} Counter::Counter(int val): itsVal(val) {} Counter::operator unsigned short () { return ( int (itsVal) ); } int main() { Counter ctr(5); int theShort = ctr; std::cout << "theShort: " << theShort << std::endl; return 0; }

Wynik
theShort: 5

Analiza W linii 12. deklarowany jest operator konwersji. Zwr uwag, e nie posiada on wartoci zwrotnej. Implementacja tej funkcji znajduje si w liniach od 26. do 29. Linia 28. zwraca warto itsVal, przekonwertowan na warto typu int.

Teraz kompilator wie ju, w jaki sposb zamienia wartoci typu int w obiekty typu Counter i odwrotnie. Dziki temu mona je sobie wzajemnie przypisywa.

Rozdzia 11.

Analiza i projektowanie zorientowane obiektowo


Gdy skoncentrujesz si wycznie na skadni jzyka C++, atwo zapomnisz, dlaczego techniki te s uywane do tworzenia programw. Z tego rozdziau dowiesz si, jak: - uywa analizy zorientowanej obiektowo w celu zrozumienia problemw, ktre prbujesz rozwiza, - uywa modelowania zorientowanego obiektowo do tworzenia stabilnych, pewnych i moliwych do rozbudowania rozwiza, - uywa zunifikowanego jzyka modelowania (UML, Unified Modeling Language) do dokumentowania analizy i projektu.

Budowanie modeli
Jeli chcemy ogarn zoony problem, musimy stworzy model wiata. Zadaniem tego modelu jest symboliczne przedstawienie wiata rzeczywistego. Taki abstrakcyjny model powinien by prostszy ni wiat rzeczywisty, ale powinien poprawnie go odzwierciedla, tak, aby na podstawie modelu mona byo przewidzie zachowanie przedmiotw istniejcych w realnym wiecie. Klasycznym modelem wiata jest dziecicy globus. Model ten nie jest tylko rzecz; cho nigdy nie mylimy go z Ziemi, odwzorowuje on Ziemi na tyle dobrze, e moemy pozna jej budow ogldajc powierzchni globusa. W modelu wystpuj oczywicie znaczne uproszczenia. Na globusie mojej crki nigdy nie pada deszcz, nie ma powodzi, trzsie ziemi, itd., ale mog go uy, aby przewidzie, ile czasu zajmie mi podr z domu do Indianapolis, gdybym musia osobicie stawi si w wydawnictwie i usprawiedliwi si, dlaczego rkopis si opnia (Wiesz, wszystko szo dobrze, ale nagle pogubiem si w metaforach i przez kilka godzin nie mogem si z nich wydosta). Metoda, ktra nie jest prostsza od modelowanej rzeczy, nie jest przydatna. Komik Steve Wright zaartowa kiedy: Mam map, na ktrej jeden cal rwna si jednemu calowi. Mieszkam na E5. Projektowanie oprogramowania zorientowane obiektowo zajmuje si budowaniem dobrych modeli. Skada si z dwch wanych elementw: jzyka modelowania oraz procesu.

Projektowanie modelowania

oprogramowania:

jzyk

Jzyk modelowania jest najmniej znaczcym aspektem obiektowo zorientowanej analizy i projektowania; niestety, przyciga on najwicej uwagi. Jzyk modelowania nie jest tylko ni konwencj, okrelajc sposb rysowania modelu na papierze. Moemy zdecydowa, e trjkty bd reprezentowa klasy, a przerywane linie bd symbolizowa dziedziczenie. Przy takich zaoeniach moemy stworzy model geranium tak, jak pokazano na rysunku 11.1. Rys. 11.1. Generalizacja specjalizacja

Na tym rysunku wida, e Geranium jest szczeglnym rodzajem Kwiatu. Jeli zarwno ty, jak i ja zgodzimy si na rysowanie diagramw dziedziczenia (generalizacji specjalizacji) w ten sposb, wtedy wzajemnie si zrozumiemy. Prawdopodobnie wkrtce zechcemy stworzy model mnstwa zoonych zalenoci, w tym celu opracujemy nasz zoony zestaw konwencji i regu rysowania. Oczywicie, musimy przedstawi nasze konwencje wszystkim osobom, z ktrymi pracujemy; bdzie je musia pozna kady nowy pracownik lub wsppracownik. Moemy wsppracowa z innymi firmami, posiadajcymi wasne konwencje, w zwizku z czym bdziemy potrzebowa czasu na wynegocjowanie wsplnej konwencji i wyeliminowanie ewentualnych nieporozumie. Duo wygodniej byoby, gdyby wszyscy zgodzili si na wsplny jzyk modelowania. (Wygodnie byoby, gdyby wszyscy mieszkacy Ziemi zgodzili si na uywanie wsplnego jzyka, ale to ju inne zagadnienie.) Takim lingua franca w projektowaniu oprogramowania jest UML Unified Modeling Language (zunifikowany jzyk modelowania) 1 . Zadaniem UML jest udzielenie odpowiedzi na pytania w rodzaju: Jak rysowa relacj dziedziczenia? Model geranium z rysunku 11.1 w UML mgby zosta przedstawiony tak, jak na rysunku 11.2. Rys. 11.2. Specjalizacja narysowana w UML

Czsto okrelenie jzyk UML utosamia si z bardziej oglnym pojciem, jakim jest metodyka (projektowania) UML przyp.red.

W UML klasy s rysowane w postaci prostoktw, za dziedziczenie jest przedstawiane jako linia zakoczona strzak. Strzaka przebiega w kierunku od klasy bardziej wyspecjalizowanej do klasy bardziej oglnej. Dla wikszoci osb taki kierunek strzaki jest niezgodny ze zdrowym rozsdkiem, ale nie ma to wikszego znaczenia; gdy wszyscy si na to zgodzimy, cay system zadziaa poprawnie. Szczegy dziaania UML s raczej proste. Diagramy nie s trudne w uyciu i zrozumieniu; zostan opisane w trakcie ich wykorzystywania. Cho na temat UML mona napisa ca ksik, jednak w 90 procentach przypadkw bdziesz korzysta jedynie z maego podzbioru tego jzyka; podzbir ten jest bardzo atwy do zrozumienia.

Projektowanie oprogramowania: proces


Proces obiektowo zorientowanej analizy i projektowania jest duo bardziej zoony i waniejszy ni jzyk modelowania. Oczywicie, syszy si o nim duo mniej. Dzieje si tak dlatego, e niezgodnoci dotyczce jzyka modelowania zostay ju w duym stopniu wyeliminowane; przemys informatyczny zdecydowa si na uywanie UML. Debata na temat procesu wci trwa. Metodolog jest osob, ktra opracowuje lub studiuje jedn lub wicej metod. Zwykle metodolodzy opracowuj i publikuj wasne metody. Metoda jest jzykiem modelowania i procesem. Trzech wiodcych w brany metodologw to: Grady Booch, ktry opracowa metod Boocha, Ivar Jacobson, ktry opracowa obiektowo zorientowan inynieri oprogramowania oraz James Rumbaugh, ktry opracowa technologi OMT (Object Modeling Technology). Ci trzej mczyni stworzyli wsplnie tzw. Rational Unified Process (dawniej znany jako Objectory), metod oraz komercyjny produkt firmy Rational Software Inc. Wszyscy trzej s

zatrudnieni we wspomnianej wyej firmie, gdzie s znani jako trzej przyjaciele (Three Amigos) 2 . Ten rozdzia przedstawia w oglnym zarysie stworzony przez nich procesem. Nie bd szczegowo go przedstawia, gdy nie wierz w niewolnicze przywizanie do akademickiej teorii duo bardziej ni postpowanie zgodne z metod interesuje mnie sprzedanie produktu. Inne metody rwnie dostarczaj ciekawych rozwiza, wic bd stara si wybiera z nich to, co wydaje mi si najlepsze i czy to uyteczn cao. Proces projektowania oprogramowania jest iteracyjny. Oznacza to, e opracowujc program przechodzimy przez cay proces wielokrotnie, coraz lepiej rozumiejc jego wymagania. Projekt ukierunkowuje implementacj, ale szczegy, na ktre zwracamy uwag podczas implementacji, wpywaj z kolei na projekt. Nie prbujemy opracowa jakiegokolwiek niebanalnego projektu w pojedynczym, uporzdkowanym procesie liniowym; zamiast tego rozwijamy fragmenty projektu, wci poprawiajc jego zaoenia oraz ulepszajc szczegy implementacji. Opracowywanie iteracyjne mona odrni od opracowywania kaskadowego. W opracowywaniu kaskadowym wynik jednego etapu staje si wejciem dla nastpnego, przy czym nie istnieje moliwo powrotu (patrz rysunek 11.3). W procesie opracowywania kaskadowego wymagania s szczegowo przedstawione klientowi i podpisane przez niego (Tak, wanie tego potrzebuj); nastpnie wymagania te s przekazywane projektantowi. Projektant tworzy projekt, po czym przekazuje go programicie, w celu implementacji. Z kolei programista

Kady z tych panw by autorem odrbnej metodyki projektowania.

J. Rumbaugh opracowa Object Modelling Technique (OMT), ktra jest wystarczajca w przypadku modelowania dziedziny zagadnienia (problem domain). Nie odzwierciedla jednak dokadnie ani wymaga uytkownikw systemw, ani wymaga implementacji. I. Jacobson rozwin Object-Oriented System Engineering (OOSE), ktry w sposb zadowalajcy uwzgldnia aspekty modelowania uytkownikw i cyklu ycia systemu jako caoci. Nie odzwierciedla jednak w sposb wystarczajcy sposobu modelowania dziedziny oraz aspektu implementacji. G. Booch jest autorem Object-Oriented Analysis and Design Methods (OOAD), speniajcej wszelkie wymogi w dziedzinie projektowania, konstrukcji i zwizkw ze rodowiskiem implementacji. Nie uwzgldnia jednak w sposb dostateczny fazy rozpoznania i analizy wymaga uytkownikw. UML ma stanowi syntez wymienionych metodyk. Ma jednak wielu krytykw. W wielu publikacjach mona przeczyta, e jest to rzecz przereklamowana i w niewystarczajcy sposb zdefiniowana. Konkurencj dla cigle uzupenianej metodyki UML jest m.in. metodyka i notacja oparta na tzw. technice design by contracts. przyp.red

wrcza kod osobie zajmujcej si kontrol jakoci, ktra sprawdza jego dziaanie i przekazuje go klientowi. Wspaniae w teorii, katastrofalne w praktyce. Rys. 11.3. Model kaskadowy

Przy opracowywaniu iteracyjnym zaczynamy od koncepcji; pomysu, jak moglibymy to zbudowa. W miar poznawania szczegw nasza wizja moe rozrasta si i ewoluowa. Gdy ju dobrze znamy wymagania, moemy rozpocz projektowanie, doskonale zdajc sobie spraw, e pytania, ktre si wtedy pojawi, mog wprowadzi zmiany w wymaganiach. Pracujc nad projektem, zaczynamy tworzy prototyp, a nastpnie implementacj produktu. Zagadnienia pojawiajce si podczas opracowywania programu wpywaj na zmiany w projekcie i mog nawet wpyn na zrozumienie wymaga. Projektujemy i implementujemy tylko czci produktu, powtarzajc za kadym razem fazy projektowania i implementacji. Cho poszczeglne etapy tego procesu s powtarzane, jednak opisanie ich w sposb cykliczny jest prawie niemoliwe. Dlatego opisz je w nastpujcej kolejnoci: koncepcja pocztkowa, analiza, projekt, implementacja, testowanie, prezentacja. Nie zrozum mnie le w rzeczywistoci, podczas tworzenia pojedynczego produktu przechodzimy przez kady z tych krokw wielokrotnie. Po prostu proces iteracyjny byby trudny do przedstawienia, gdybymy chcieli pokaza cykliczne wykonywanie kadego z krokw. Oto kolejne kroki iteracyjnego procesu projektowania: 1. Konceptualizacja. 2. Analiza. 3. Projektowanie. 4. Implementacja. 5. Testowanie 6. Prezentacja.

Konceptualizacja to tworzenie wizji. Jest pojedynczym zdaniem, opisujcym dany pomys. Analiza jest procesem zrozumienia wymaga. Projektowanie jest procesem tworzenia modelu klas, na podstawie ktrego wygenerujemy kod. Implementacja jest pisaniem kodu (na przykad w C++). Testowanie jest upewnianiem si, czy wykonalimy wszystko poprawnie. Prezentacja to pokazanie produktu klientom. Buka z masem. Caa reszta to detale.

Kontrowersje

Pojawia si mnstwo kontrowersji na temat tego, co dzieje si na kadym etapie procesu projektowania iteracyjnego, a nawet na temat nazw poszczeglnych etapw. Zdradz ci sekret: to nie ma znaczenia. Podstawowe kroki s w kadym obiektowo zorientowanym procesie takie same: dowiedz si, co chcesz zbudowa, zaprojektuj rozwizanie i zaimplementuj projekt.

Cho w grupach dyskusyjnych i listach mailingowych dyskutujcych o technologii obiektowej dzieli si wos na czworo, podstawowa analiza i projektowanie obiektowe s niezmienne. W tym rozdziale przedstawi pewien punkt widzenia na ten temat, majc nadziej, e utworz w ten sposb fundament, na ktrym bdziesz mg stworzy architektur swojej aplikacji.

Celem tej pracy jest stworzenie kodu, ktry spenia zaoone wymagania i ktry jest stabilny, moliwy do rozbudowania i atwy do modyfikacji. Najwaniejsze jest stworzenie kodu o wysokiej jakoci (w okrelonym czasie i przy zaoonym funduszu).

Programowanie ekstremalne
Ostatnio pojawia si nowa koncepcja analizy i projektowania, zwana programowaniem ekstremalnym. Programowanie to zostao omwione przez Kena Becka w ksice Extreme Programming Expanded: Embrace Change (Addison-Wesley, 1999 ISBN 0201616416).

W tej ksice Beck przedstawia kilka radykalnych i cudownych pomysw, np. by nie kodowa niczego, dopki nie bdzie mona sprawdzi, czy to dziaa, a take programowanie w parach (dwch programistw przy jednym komputerze). Jednak z naszego punktu widzenia, najwaniejszym jego stwierdzeniem jest to, e zmianom ulegaj wymagania. Naley wic sprawi, by program dziaa i utrzymywa to dziaanie; naley projektowa dla wymaga, ktre si zna i nie tworzy projektw na wyrost. Jest to, oczywicie, ogromne uproszczenie wypowiedzi Becka i sdz, e mgby uzna je za przeinaczenie, jednak w kadym razie wierz w jego sedno: spraw by program dziaa, tworzc go dla wymaga, ktre rozumiesz i staraj si nie doprowadzi do sytuacji, w ktrej, programu nie mona ju zmieni. Bez zrozumienia wymaga (analiz) i planowania (projekt), trudno jest stworzy stabilny i atwy do modyfikacji program, jednak staraj si nie kontrolowa zbyt wielu czynnoci.

Pomys
Wszystkie wspaniae programy powstaj z jakiego pomysu. Kto ma wizj produktu, ktry uwaa za warty wdroenia. Poyteczne pomysy rzadko kiedy powstaj w wyniku pracy zbiorowej. Pierwsz faz obiektowo zorientowanej analizy i projektowania jest zapisanie takiego pomysu w pojedynczym zdaniu (a przynajmniej krtkim akapicie). Pomys ten staje si myl przewodni tworzonego programu, za zesp, ktry zbiera si w celu zaimplementowania tego pomysu, powinien podczas pracy odwoywa si do tej myli przewodniej w razie potrzeby nawet j modyfikujc. Nawet jeli pomys narodzi si w wyniku zespoowej pracy dziau marketingu, wizjonerem powinna zosta jedna osoba. Jej zadaniem jest utrzymywanie czystoci idei. W miar rozwoju projektu wymagania bd ewoluowa. Harmonogram pracy moe (i powinien) modyfikowa to, co prbujesz osign w pierwszej iteracji programowania, jednak wizjoner musi zapewni, e wszystko to, co zostanie stworzone, odzwierciedli pierwotny pomys. Wanie jego bezwzgldne powicenie i arliwe zaangaowanie doprowadza do ukoczenia projektu. Gdy stracisz z oczu pierwotny zamys, twj produkt jest skazany na niepowodzenie.

Analiza wymaga
Faza konceptualizacji, w ktrej precyzowany jest pomys, jest bardzo krtka. Moe trwa krcej ni bysk olnienia poczony z czasem wymaganym do zapisania pomysu na kartce. Czsto zdarza si, e doczasz do projektu jako ekspert zorientowany obiektowo wtedy, gdy wizja zostaa ju sprecyzowana. Niektre firmy myl pomys z wymaganiami. Wyrana wizja jest potrzebna, lecz sama w sobie nie jest wystarczajca. Aby przej do analizy, musisz zrozumie, w jaki sposb produkt bdzie uywany i jak musi dziaa. Celem fazy analizy jest sprecyzowanie tych wymaga. Efektem kocowym tej fazy jest stworzenie dokumentu zawierajcego opracowane wymagania. Pierwsz czci tego dokumentu jest analiza przypadkw uycia produktu.

Przypadki uycia
Istotn czci analizy, projektowania i implementacji s przypadki uycia. Przypadek uycia jest oglnym opisem sposobu, w jaki produkt bdzie uywany. Przypadki uycia nie tylko ukierunkowuj analiz, ale take pomagaj w okreleniu klas i s szczeglnie wane podczas testowania produktu. Tworzenie stabilnego i wyczerpujcego zestawu przypadkw uycia moe by najwaniejszym zadaniem caej analizy. Wanie wtedy jeste najbardziej uzaleniony od ekspertw w danej dziedzinie, to oni wiedz najwicej o dziedzinie pracy, ktrej wymagania prbujesz okreli. Przypadki uycia s w niewielkim stopniu zwizane z interfejsem uytkownika, nie s natomiast zwizane z wntrzem budowanego systemu. Kada osoba (lub system) wsppracujca z projektowanym systemem jest nazywana aktorem. Dokonajmy krtkiego podsumowania: - przypadek uycia opis sposobu, w jaki uywane bdzie oprogramowanie, - eksperci osoby znajce si na dziedzinie, dla ktrej tworzysz produkt, - aktor kada osoba (lub system) wsppracujca z projektowanym systemem. Przypadek uycia jest opisem interakcji zachodzcych pomidzy aktorem a samym systemem. W trakcie analizy przypadku uycia system jest traktowany

jako czarna skrzynka. Aktor wysya komunikat do systemu, po czym zwracana jest informacja, zmienia si stan systemu, statek kosmiczny zmienia kierunek, itd.

Identyfikacja aktorw Naley pamita, e nie wszyscy aktorzy s ludmi. Systemy wsppracujce z budowanym systemem take s aktorami. Gdy budujesz na przykad bankomat, aktorami mog by urzdnik bankowy i klient a take inny system wsppracujcy z aktualnie tworzonym systemem, na przykad system ledzenia poyczek czy udzielania kredytw studenckich. Oto podstawowa charakterystyki aktorw: - s oni na zewntrz dla systemu, - wsppracuj z systemem. Czsto najtrudniejsz czci analizy przypadkw uycia jest jej pocztek. Zwykle najlepsz metod ruszenia z miejsca jest sesja burzy mzgw. Po prostu spisz list osb i systemw, ktre bd pracowa z nowym systemem. Pamitaj, e mwic o ludziach, w rzeczywistoci mamy na myli role urzdnika bankowego, kasjera, klienta, itd. Jedna osoba moe peni wicej ni jedn rol. We wspomnianym przykadzie z bankomatem, na naszej licie mog wystpi nastpujce role: - klient - personel banku - system bankowy - osoba wypeniajca bankomat pienidzmi i materiaami Na pocztku nie ma potrzeby wychodzenia poza t list. Wygenerowanie trzech czy czterech aktorw moe wystarczy do rozpoczcia generowania przypadkw uycia. Kady z tych aktorw pracuje z systemem w inny sposb; chcemy wykry te interakcje w naszych sposobach uycia.

10

Wyznaczanie pierwszych przypadkw uycia Zacznijmy od roli klienta. Podczas burzy mzgw moemy okreli nastpujce przypadki uycia dla klienta: - klient sprawdza stan swojego rachunku, - klient wpaca pienidze na swj rachunek, - klient wypaca pienidze ze swojego rachunku, - klient przelewa pienidze z rachunku na rachunek, - klient otwiera rachunek, - klient zamyka rachunek. Czy powinnimy dokona rozrnienia pomidzy klient wpaca pienidze na swj rachunek biecy a klient wpaca pienidze na lokat, czy te powinnimy te dziaania poczy (tak jak na powyszej licie) w klient wpaca pienidze na swj rachunek? Odpowied na to pytanie zaley od tego, czy takie rozrnienie ma znaczenie dla danej dziedziny (dziedzina jest rzeczywistym rodowiskiem, ktre modelujemy w tym przypadku jest ni bankowo). Aby sprawdzi, czy te dziaania s jednym przypadkiem uycia, czy te dwoma, musisz zapyta, czy ich mechanizmy s rne (czy klient w kadym z przypadkw robi co innego) i czy rne s wyniki (czy system odpowiada na rne sposoby). W naszym przykadzie, w obu przypadkach odpowied brzmi nie: klient skada pienidze na kady z rachunkw w ten sam sposb, przy czym wynik take jest podobny, gdy bankomat odpowiada, zwikszajc stan odpowiedniego rachunku. Zakadajc, e aktor i system dziaaj i odpowiadaj mniej wicej identycznie, bez wzgldu na to, na jaki rachunek dokonuje wpaty, te dwa przypadki uycia s w rzeczywistoci jednym sposobem. Pniej, gdy opracujemy scenariusze przypadkw uycia, moemy wyprbowa obie wariacje i sprawdzi, czy ich rezultatem s jakiekolwiek rnice. Odpowiadajc na ponisze pytania, moesz odkry dodatkowe przypadki uycia: 1. Dlaczego aktor uywa tego systemu?

11

Klient uywa tego systemu, aby zdoby gotwk, zoy depozyt lub sprawdzi biecy stan rachunku. 2. Jakiego wyniku oczekuje aktor po kadym daniu? Zwikszenia stanu rachunku lub uzyskania gotwki na zakupy. 3. Co spowodowao, e aktor uywa w tym momencie systemu? By moe ostatnio otrzyma wypat lub jest na zakupach. 4. Co aktor musi zrobi, aby uy systemu? Woy kart do szczeliny w bankomacie. Aha! Potrzebujemy przypadku uycia dla logowania si klienta do systemu. 5. Jakie informacje aktor musi dostarczy systemowi? Musi wprowadzi kod PIN. Aha! Potrzebujemy przypadkw uycia dla uzyskania i edycji kodu PIN. 6. Jakich informacji aktor oczekuje od systemu? Stanu rachunku itd. Czsto dodatkowe przypadki uycia moemy znale, skupiajc si na atrybutach obiektw w danej dziedzinie. Klient posiada nazwisko, kod PIN oraz numer rachunku; czy wystpuj przypadki uycia dla zarzdzania tymi obiektami? Rachunek posiada swj numer, stan oraz histori transakcji; czy wykrylimy te elementy w przypadkach uycia? Po szczegowym przeanalizowaniu przypadkw uycia dla klienta, nastpnym krokiem w opracowywaniu listy przypadkw uycia jest opracowanie przypadkw uycia dla wszystkich pozostaych aktorw. Ponisza lista przedstawia pierwszy zestaw przypadkw uycia dla naszego przykadu z bankomatem: - klient sprawdza stan swojego rachunku, - klient wpaca pienidze na swj rachunek, - klient wypaca pienidze ze swojego rachunku, - klient przekazuje pienidze z rachunku na rachunek, - klient otwiera rachunek,

12

- klient zamyka rachunek, - klient loguje si do swojego rachunku, - klient sprawdza ostatnie transakcje, - urzdnik bankowy loguje si do specjalnego konta przeznaczonego do zarzdzania, - urzdnik bankowy dokonuje zmian w rachunku klienta, - system bankowy aktualizuje stan rachunku klienta na podstawie dziaa zewntrznych, - zmiany rachunku uytkownika s odzwierciedlane w systemie bankowym, - bankomat sygnalizuje niedobr pienidzy, - technik uzupenia w bankomacie gotwk i materiay.

Tworzenie modelu dziedziny Gdy masz ju pierwsz wersj przypadkw uycia, moesz zacz wypenia dokument wymaga szczegowym modelem dziedziny. Model dziedziny jest dokumentem zawierajcym wszystko to, co wiesz o danej dziedzinie (zagadnieniu, nad ktrym pracujesz). Jako cz modelu dziedziny tworzysz obiekty dziedziny, opisujce wszystkie obiekty wymienione w przypadkach uycia. Przykad z bankomatem zawiera nastpujce obiekty: klient, personel banku, system bankowy, rachunek biecy, lokata, itd. Dla kadego z tych obiektw dziedziny chcemy uzyska tak wane dane, jak nazwa obiektu (na przykad klient, rachunek, itd.), czy obiekt jest aktorem, podstawowe atrybuty i zachowanie obiektu, itd. Wiele narzdzi do modelowania wspiera zbieranie tych informacji w opisach klas. Na przykad, rysunek 11.4 przedstawia sposb, w jaki te informacje s zbierane w systemie Rational Rose. Rys. 11.4. Rational Rose

13

Naley zdawa sobie spraw, e to, co opisujemy, nie jest obiektem projektu, ale obiektem dziedziny. Odzwierciedla sposb funkcjonowania wiata, a nie sposb dziaania naszego systemu. Moemy okreli relacje pomidzy obiektami dziedziny pojawiajcymi si w przykadzie z bankomatem, uywajc UML korzystajc z takich samych konwencji rysowania, jakich uyjemy pniej do opisania relacji pomidzy klasami w dziedzinie. Jest to jedna z waniejszych zalet UML: moemy uywa tych samych narzdzi na kadym etapie projektu. Na przykad, uywajc konwencji UML dla klas i powiza generalizacji, moemy przedstawi rachunki biece i rachunki lokat jako specjalizacje bardziej oglnej koncepcji rachunku bankowego, tak jak pokazano na rysunku 11.5. Rys. 11.5. Specjalizacje

14

Na diagramie z rysunku 11.5 prostokty reprezentuj rne obiekty dziedziny; za strzaki wskazuj generalizacj. UML zakada, e linie s rysowane w kierunku od klasy wyspecjalizowanej do bardziej oglnej klasy bazowej. Dlatego, zarwno Rachunek biecy, jak i Rachunek lokaty, wskazuj na Rachunek bankowy, informujc e kady z nich jest wyspecjalizowan form Rachunku bankowego.

UWAGA Pamitajmy, e w tym momencie widzimy tylko zalenoci pomidzy obiektami w dziedzinie. Pniej by moe zdecydujesz si na zastosowanie w projekcie obiektw o nazwach RachunekBiezacy oraz RachunekBankowy i moe odwzorujesz te zalenoci, uywajc dziedziczenia, ale bd to decyzje podjte w czasie projektowania. W czasie analizy dokumentujemy jedynie obiekty istniejce w danej dziedzinie.

UML jest bogatym jzykiem modelowania i mona w nim umieci dowoln ilo relacji. Jednak podstawowe relacje wykrywane podczas analizy to: generalizacja (lub specjalizacja), zawieranie i powizanie.

Generalizacja Generalizacja jest czsto porwnywana z dziedziczeniem, lecz istnieje pomidzy nimi wyrana, istotna rnica. Generalizacja opisuje relacj; dziedziczenie jest programow implementacj generalizacji jest sposobem przedstawienia generalizacji w kodzie. Odwrotnoci generalizacji jest specjalizacja. Kot jest wyspecjalizowan form zwierzcia, za zwierz jest generaln form kota lub psa. Specjalizacja okrela, e obiekt wyprowadzony jest podtypem obiektu bazowego. Zatem rachunek biecy jest rachunkiem bankowym. Ta relacja jest symetryczna: rachunek bankowy generalizuje oglne zachowanie i atrybuty rachunku biecego i rachunku lokaty. Podczas analizowania dziedziny chcemy przedstawi te zalenoci dokadnie tak, jak wystpuj w realnym wiecie.

Zawieranie Czsto obiekt skada si z wielu podobiektw. Na przykad samochd skada si z kierownicy, k, drzwi, radia, itd. Rachunek biecy skada si ze stanu, historii transakcji, identyfikatora klienta, itd. Mwimy, e rachunek biecy posiada te

15

elementy; zawieranie modeluje wanie takie relacje posiadania. UML ilustruje relacj zawierania za pomoc strzaki z rombem, wskazujcej obiekt zawierany (patrz rysunek 11.6). Rys. 11.6. Zawieranie

Diagram z rysunku 11.6 sugeruje, e rachunek osobisty posiada stan. Mona poczy oba diagramy, przedstawiajc w ten sposb do zoony zestaw relacji (patrz rysunek 11.7). Rys. 11.7. Relacje pomidzy obiektami

16

Diagram z rysunku 11.7 informuje, e rachunek biecy i rachunek lokaty s rachunkami bankowymi oraz, e rachunki bankowe posiadaj zarwno stan, jak i histori transakcji.

Powizania Trzeci relacj, wykrywan zwykle podczas analizowania dziedziny, jest proste powizanie. Powizanie sugeruje, e dwa obiekty w jaki sposb ze sob wsppracuj. Ta definicja staje si duo bardziej precyzyjna w fazie projektowania, ale w fazie analizy sugerujemy jedynie, e obiekt A wsppracuje z obiektem B, i e jeden obiekt nie zawiera drugiego; a take, e aden z nich nie jest specjalizacj drugiego. W UML powizania midzy obiektami s przedstawiane jako zwyka prosta linia pomidzy obiektami, co pokazuje rysunek 11.8. Diagram z rysunku 11.8 wskazuje, e obiekt A w jaki sposb wsppracuje z obiektem B. Rys. 11.8.

17

Tworzenie scenariuszy Gdy mamy ju gotowy wstpny zestaw przypadkw uycia oraz narzdzi, dziki ktrym moemy przedstawi relacje pomidzy obiektami w dziedzinie, jestemy gotowi do uporzdkowania przypadkw uycia i zdefiniowania ich przeznaczenia. Kady przypadek uycia mona rozbi na serie scenariuszy. Scenariusz jest opisem okrelonego zestawu okolicznoci towarzyszcych danemu przypadkowi uycia. Na przykad, przypadek uycia klient wypaca pienidze ze swojego rachunku moe posiada nastpujce scenariusze: - klient da trzystu dolarw z rachunku biecego, otrzymuje gotwk, po czym system drukuje kwit, - klient da trzystu dolarw z rachunku biecego, lecz na rachunku znajduje si tylko dwiecie dolarw. Klient jest informowany, e na koncie znajduje si zbyt mao rodkw, aby speni jego danie, - klient da trzystu dolarw z rachunku biecego, ale tego dnia pobra ju sto dolarw, a limit dzienny wynosi trzysta dolarw. Klient jest informowany o problemie i moe si zdecydowa na pobranie jedynie dwustu dolarw, - klient da trzystu dolarw z rachunku biecego, ale skoczy si papier w drukarce kwitw. Klient jest informowany o problemie i moe si zdecydowa na pobranie pienidzy bez potwierdzenia w postaci kwitu. I tak dalej. Kady scenariusz przedstawia wariant tego samego przypadku uycia. Czsto te sytuacje s sytuacjami wyjtkowymi (zbyt mao rodkw na rachunku, zbyt mao gotwki w bankomacie, itd.). Czasem warianty dotycz niuansw w podejmowaniu decyzji w samym sposobie uycia (na przykad, czy przed podjciem gotwki klient chce dokona transferu rodkw).

18

Nie musimy analizowa kadego ewentualnego scenariusza. Szukamy tych scenariuszy, ktre prezentuj wymagania systemu lub szczegy interakcji z aktorem.

Tworzenie wytycznych Teraz, jako cz metodologii, bdziemy tworzy wytyczne dla udokumentowania kadego ze scenariuszy. Te wytyczne znajd si w dokumentacji wymaga. Zwykle chcemy, by kady scenariusz zawiera: - warunki wstpne jakie warunki musz by spenione, aby scenariusz si rozpocz, - wczniki co powoduje, e scenariusz si rozpoczyna, - akcje, jakie podejmuje aktor, - wyniki lub zmiany powodowane przez system, - informacj zwrotn otrzymywan przez aktora, - informacje o wystpowaniu cyklicznych operacji i o przyczynach ich wykonywania, - schematyczny opis przebiegu scenariusza, - okolicznoci powodujce zakoczenie scenariusza, - warunki kocowe jakie warunki musz by spenione w momencie zakoczenia scenariusza. Ponadto, kademu sposobowi uycia i kademu scenariuszowi powinno si nada nazw. Moesz spotka si z nastpujc sytuacj: Przypadek uycia: Scenariusz: Warunki wstpne: Wcznik: Opis: Klient wypaca pienidze. Pomylne pobranie gotwki z rachunku biecego. Klient jest ju zalogowany do systemu. Klient da gotwki. Klient decyduje si na wypacenie gotwki z rachunku

19

biecego. Na rachunku znajduje si wystarczajca ilo rodkw, w bankomacie jest wystarczajco duo pienidzy i papieru na kwity, a sie dziaa. Bankomat prosi klienta o podanie wysokoci wypaty, klient prosi o trzysta dolarw, co w tym momencie jest kwot dozwolon. Maszyna wydaje trzysta dolarw i wypisuje kwit; klient odbiera pienidze i kwit. Warunki kocowe: Rachunek klienta jest obciany kwot trzystu dolarw, za klient otrzymuje trzysta dolarw w gotwce. Ten przypadek uycia moe zosta przedstawiony za pomoc prostego diagramu, pokazanego na rysunku 11.9. Rys. 11.9. Diagram przypadku uycia

Ten diagram nie dostarcza zbyt wielu informacji, poza wysokopoziomow abstrakcj interakcji pomidzy aktorem (klientem) a systemem. Diagram stanie si nieco bardziej uyteczny, gdy przedstawimy interakcj pomidzy sposobami uycia. Tylko nieco bardziej uyteczny, gdy moliwe s tylko dwie interakcje: <<korzysta z>> (<<uses>>) i <<rozszerza>> (<<extends>>). Stereotyp <<korzysta z>> wskazuje, e jeden przypadek uycia jest nadzestawem innego. Na przykad, nie jest moliwa wypata gotwki bez wczeniejszego zalogowania si. T relacj przedstawiamy za pomoc diagramu, pokazanego na rysunku 11.10. Rys. 11.10. Stereotyp <<korzysta z>>

20

Rysunek 11.10 pokazuje, e przypadek uycia Wypata Gotwki korzysta z przypadku uycia Logowanie i w peni implementuje Logowanie jako cz Wypaty Gotwki. Przypadek uycia <<rozszerza>> zosta opracowany w celu wskazania relacji warunkowych i czciowo odnosi si do dziedziczenia, ale wywoywa tyle nieporozumie wrd projektantw obiektowych (zwizanych z odrnieniem go od <<korzysta z>>), e wielu z nich odrzuca go, uwaajc e nie jest wystarczajco dobrze zrozumiany. Ja uywam <<korzysta z>> aby unikn kopiowania i wklejania caego przypadku uycia, a <<rozszerza>> uywam wtedy, gdy korzystam z przypadku uycia tylko w okrelonych warunkach.

Diagramy interakcji Cho diagram przypadku uycia moe mie ograniczon warto, mona powiza go z przypadkiem uycia, ktry moe znacznie wzbogaci dokumentacj i uatwi zrozumienie interakcji. Na przykad wiemy, e scenariusz Wypata Gotwki reprezentuje interakcj pomidzy nastpujcymi obiektami dziedziny: klientem, rachunkiem biecym oraz interfejsem uytkownika. Moemy przedstawi t interakcj na diagramie interakcji, widocznym na rysunku 11.11. Rys. 11.11. Diagram interakcji w jzyku UML

21

Diagram interakcji z rysunku 11.11 przedstawia te szczegy scenariusza, ktre mog nie zosta zauwaane podczas czytania tekstu. Wspdziaajce ze sob obiekty s obiektami dziedziny, a cay bankomat (ATM) wraz z interfejsem uytkownika traktowany jest jako pojedynczy obiekt, wywoywany szczegowo jest tylko okrelony rachunek bankowy. Ten prosty przykad bankomatu pokazuje jedynie ograniczony zestaw interakcji, ale szczegowe ich przeanalizowanie moe okaza si bardzo pomocne w zrozumieniu zarwno dziedziny problemu, jak i wymaga nowego systemu.

Tworzenie pakietw Poniewa dla kadego problemu o znacznej zoonoci generuje si wiele przypadkw uycia, UML umoliwia grupowanie ich w pakiety. Pakiet przypomina kartotek lub folder jest zbiorem obiektw modelowania (klas, aktorw, itd.). Aby opanowa zoono przypadkw uycia, moemy tworzy pakiety pogrupowane wedug charakterystyk, majcych znaczenie dla danego projektu. Moesz wic pogrupowa swoje przypadki uycia wedug rodzaju rachunku (wszystko, co odnosi si do rachunku biecego albo do lokaty), wedug wpyww albo obcie, wedug rodzaju klienta czy wedug jakiejkolwiek innej charakterystyki, ktra ma sens w danym przypadku.

22

Pojedynczy przypadek uycia moe wystpowa w kilku rnych pakietach, uatwiajc w ten sposb projektowanie.

Analiza aplikacji
Oprcz tworzenia przypadkw uycia, dokument wymaga powinien zawiera zaoenia i ograniczenia twojego klienta, a take wymagania wobec sprztu i systemu operacyjnego. Wymagania aplikacji s zaoeniami pochodzcymi od konkretnego klienta zwykle okreliby je podczas projektowania i implementacji, ale klient zadecydowa o nich za ciebie. Wymagania aplikacji s czsto narzucane przez konieczno wsppracy z istniejcymi systemami. W takim przypadku kluczowym elementem analizy jest zrozumienie sposobw dziaania istniejcych systemw. W idealnych warunkach analizujesz problem, projektujesz rozwizanie, po czym decydujesz, jaka platforma i system operacyjny najlepiej odpowiadaj potrzebom twojego projektu. Taka sytuacja jest nie tylko idealna, ale i rzadka. Duo czciej zdarza si, e klient zainwestowa ju w okrelony sprzt lub system operacyjny. Plany jego firmy opieraj si na dziaaniu twojego oprogramowania w istniejcym ju systemie, wic musisz pozna te wymagania jak najwczeniej i odpowiednio si do nich dostosowa.

Analiza systemw
Czasem oprogramowanie jest zaprojektowane jako samodzielne; wsppracuje ono jedynie z kocowym uytkownikiem. Czsto jednak twoim zadaniem bdzie wsppraca z istniejcym systemem. Analiza systemw to proces zbierania wszystkich informacji na temat systemw, z ktrymi bdziesz wsppracowa. Czy twj nowy system bdzie serwerem, dostarczajcym usugi istniejcym systemom, czy te bdzie ich klientem? Czy bdziesz mg negocjowa interfejs pomidzy systemami, czy te musisz si dostosowa do istniejcego standardu? Czy inne systemy pozostan niezmienne, czy te przez cay czas bdziesz ledzi zachodzce w nich zmiany? Na te i inne pytania naley odpowiedzie podczas fazy analizowania, jeszcze przed przystpieniem do projektowania nowego systemu. Oprcz tego, powiniene pozna ograniczenia wynikajce ze wsppracy z innymi systemami.

23

Czy spowolni one szybko odpowiedzi twojego systemu? Czy nakadaj one na twj system wysokie wymagania, zajmujc zasoby i czas procesora?

Tworzenie dokumentacji
Gdy ju okrelisz zadania systemu i sposb jego dziaania, nadchodzi czas, aby podj pierwsz prb stworzenia dokumentu, okrelajcego czas i budet produkcji. Czsto termin jest narzucony przez klienta z gry: Masz na to osiemnacie miesicy. Byoby wspaniale, gdyby mg przeanalizowa wymagania i oszacowa czas, jaki zajmie ci zaprojektowanie i zaimplementowanie rozwizania. W praktyce wikszo systemw powstaje w bardzo krtkim terminie i przy niskich kosztach, za prawdziwa sztuka polega na okreleniu, jak dua cz zaoe moe zosta speniona w zadanym czasie oraz przy zaoonym budecie. Oto wytyczne, o ktrych powiniene pamita, okrelajc harmonogram i budet projektu: - jeli musisz zmieci si w pewnym przedziale, wtedy zaoeniem optymistycznym jest najprawdopodobniej jego ograniczenie zewntrzne, - zgodnie z prawem Libertyego, wszystko bdzie trwa duej ni tego oczekujesz nawet jeli uwzgldnisz to prawo. Konieczne bdzie te okrelenie priorytetw. Nie skoczysz w wyznaczonym terminie po prostu. Zadbaj, by system dziaa w momencie, gdy koczy si czas ukoczenia prac i by by wystarczajcy sprawny dla pierwszego wydania. Gdy budujesz most zblia si termin ukoczenia prac, a nie zostaa jeszcze wykonana cieka rowerowa, to niedobrze; moesz jednak otworzy ju most i zacz pobiera myto. Jeli jednak most siga dopiero poowy rzeki, to ju bardzo le. Dokumentw planowania przewanie s bdne. Na tak wczesnym etapie projektu praktycznie nie jest moliwe waciwe oszacowanie czasu jego trwania. Gdy ju znasz wymagania, moesz w przyblieniu okreli ilo czasu, jak zajmie projektowanie systemu, jego implementacja i testowanie. Do tego musisz zaplanowa dodatkowo od dwudziestu do dwudziestu piciu procent zapasu, ktry moesz zmniejsza w trakcie wykonywania zlecenia (gdy dowiadujesz si coraz wicej).

24

UWAGA Uwzgldnienie zapasu czasu nie moe by wymwk dla uniknicia tworzenia planu. Jest jedynie ostrzeeniem, e nie mona na nim do koca polega. W trakcie prac nad projektem lepiej poznasz dziaanie systemu, a obliczenia stan si bardziej dokadne.

Wizualizacje
Ostatnim elementem dokumentu wymaga jest wizualizacja. Jest to nazwa wszystkich diagramw, rysunkw, zrzutw ekranu, prototypw i wszelkich innych wizualnych reprezentacji, przeznaczonych do wsparcia analizy i projektu graficznego interfejsu uytkownika dla produktu. W przypadku duych projektw moesz opracowa peny prototyp, ktry pomoe tobie (i twoim klientom) zrozumie jak bdzie dziaa system. W niektrych przypadkach prototyp staje si odzwierciedleniem wymaga; prawdziwy system jest projektowany tak, by implementowa funkcje zademonstrowane w prototypie.

Dokumentacja produktu
Na koniec kadej fazy analizy i projektowania stworzysz seri dokumentw produktu. Tabela 11.1 pokazuje kilka z takich dokumentw dla fazy analizy. S one uywane przez klienta w celu upewnienia si, czy rozumiesz jego potrzeby, przez kocowego uytkownika jako wsparcie i wytyczne dla projektu, za przez zesp projektowy do zaprojektowania i zaimplementowania kodu. Wiele z tych dokumentw dostarcza take materiau istotnego zarwno dla zespou zajmujcego si dokumentacj, jak i zespou kontroli jakoci, informujc ,w jaki sposb powinien zachowywa si system. Tabela 11.1. Dokumenty produktu tworzone podczas fazy analizy Dokument Raport przypadkw uycia Opis Dokument opisujcy szczegowo przypadki uycia, scenariusze, stereotypy, warunki wstpne, warunki kocowe oraz wizualizacje. Dokument i diagramy, opisujce powizania

Analiza dziedziny

25

pomidzy obiektami dziedziny. Diagramy analizy wsppracy Diagramy analizy dziaa Analiza systemu Diagramy wsppracy, opisujce interakcje pomidzy obiektami dziedziny. Diagramy dziaa, opisujce pomidzy obiektami dziedziny. interakcje

Raport i diagramy, opisujce na niszym poziomie system i sprzt, dla ktrego bdzie tworzony projekt. Raport i diagramy, opisujce wymagania klienta wobec konkretnego produktu. Raport charakteryzujcy wydajno ograniczenia narzucone przez klienta. oraz

Dokument analizy zastosowa Raport ogranicze dziaania Dokument kosztw i harmonogramu

Raport z wykresami Ganta i Perta, opisujcymi zakadany harmonogram, etapy i koszty.

Projektowanie
Analiza skupia si na dziedzinie problemu, natomiast projektowanie zajmuje si stworzeniem rozwizania. Projektowanie jest procesem przeksztacenia wymaga w model, ktry moe by zaimplementowany w postaci oprogramowania. Rezultatem tego procesu jest stworzenie dokumentu projektowego. Dokument projektowy jest podzielony na dwie czci: projekt klas oraz mechanizmy architektury. Cz projektu klas dzieli si z kolei na projekt statyczny (szczegowo okrelajcy poszczeglne klasy, ich powizania i charakterystyki) oraz projekt dynamiczny (okrelajcy, jak te klasy ze sob wsppracuj). Cz mechanizmw architektury zawiera informacje na temat implementacji przechowywania obiektw, rozproszonego systemu obiektw, konkurencji pomidzy elementami, itd. W nastpnej czci rozdziau skupimy si na aspekcie projektowania klas; za do projektowania mechanizmw architektury wykorzystamy wiadomoci zawarte w nastpnych rozdziaach tej ksiki.

26

Czym s klasy?
Jako programista C++, przywyke do tworzenia klas. Metodologia projektowania wymaga operowania klasami C++ poprzez klasy projektu, mimo, i s one do cile powizane. Klasa C++ zapisana w kodzie programu stanowi implementacj klasy zaprojektowanej. Kada klasa stworzona w kodzie bdzie stanowi odzwierciedlenie klasy w projekcie, ale nie naley myli jednej z drug. Oczywicie, klasy projektu mona zaimplementowa take w innym jzyku, jednak skadnia definicji klasy moe by inna. Z tego powodu przez wikszo czasu bdziemy mwi o klasach bez dokonywania takiego rozrnienia, gdy rnice midzy nimi s zbyt abstrakcyjne. Gdy mwimy, e w naszym modelu klasa Cat posiada metod Meow(), naszym zdaniem oznacza to, e metod Meow() umiecimy take w naszej klasie C++. Klasy modelu przedstawia si w postaci diagramw UML, za klasy C++ jako kod, ktry moe zosta skompilowany. Rozrnienie, cho subtelne, jest jednak istotne. Najwikszym wyzwaniem dla wielu nowicjuszy jest okrelenie pocztkowego zestawu klas i zrozumienie, z czego skada si dobrze zaprojektowana klasa. Jedn z technik jest wypisanie scenariuszy przypadkw uycia, a nastpnie stworzenie osobnej klasy dla kadego rzeczownika. Spjrzmy na poniszy scenariusz przypadku uycia: Klient decyduje si na wypat gotwki z rachunku osobistego. Na rachunku znajduje si wystarczajca ilo rodkw, w bankomacie jest wystarczajca ilo gotwki i papieru, dziaa take sie. Bankomat prosi klienta o podanie kwoty wypaty, za klient prosi o wypat trzystu dolarw, co w tym momencie jest moliwe. Maszyna wydaje trzysta dolarw i drukuje kwit, po czym klient bierze pienidze i odbiera kwit. Z tego scenariusza moesz wybra nastpujce klasy: - klient - gotwka - rachunek biecy - rachunek

27

- kwity - bankomat - sie - kwota - wypata - maszyna - pienidze Moesz nastpnie usun z listy synonimy, po czym stworzy klasy dla kadego z nastpujcych rzeczownikw: - klient - gotwka (pienidze, kwota, wypata) - rachunek biecy - rachunek - kwity - bankomat (maszyna) - sie Jak na razie, to niezy pocztek. Moesz nastpnie przedstawi na diagramie relacje pomidzy niektrymi z tych klas (patrz rysunek 11.12). Rysunek 11.12. Wstpnie zdefiniowane klasy

28

Przeksztacenia
Proces, ktry zacz si w poprzednim podrozdziale, jest nie tyle wybieraniem rzeczownikw ze scenariusza, ile pocztkiem przeksztacania obiektw z analizy dziedziny w obiekty projektowe. To wany, pierwszy krok. Wiele obiektw dziedziny bdzie posiadao w projekcie reprezentacje. Obiekt jest nazywany reprezentacj w celu odrnienia, na przykad, rzeczywistego papierowego kwitu wydawanego przez bankomat od obiektu w projekcie, ktry jest jedynie zaimplementowan w kodzie abstrakcj. Najprawdopodobniej odkryjesz, e wikszo obiektw dziedziny posiada izomorficzn reprezentacj w projekcie tj. pomidzy obiektem dziedziny a obiektem projektu istnieje relacja jeden do jednego. Zdarza si jednak, e pojedynczy obiekt dziedziny jest reprezentowany w projekcie przez ca seri obiektw. Kiedy indziej seria obiektw dziedziny moe by reprezentowana przez pojedynczy obiekt projektowy. Zwr uwag, e w rysunku 11.12 ju zauwaylimy fakt, i RachunekBiecy jest specjalizacj Rachunku. Nie przygotowywalimy si do wyszukiwania relacji generalizacji, ale ta bya tak oczywista, e od razu j zauwaylimy. Z analizy dziedziny wiemy, e Bankomat wydaje Gotwk i Kwity, wic natychmiast wyszukalimy t informacj w projekcie.

29

Relacja pomidzy Klientem a RachunkiemBieacym jest ju mniej oczywista. Wiemy, e taka relacja istnieje, ale poniewa jej szczegy nie s oczywiste, na razie nie bdziemy si ni zajmowa.

Inne przeksztacenia Gdy przeksztacimy ju obiekty dziedziny, moemy zacz szuka innych uytecznych obiektw projektowych. Mog by nimi np. interfejsy. Kady interfejs pomidzy nowym systemem a systemami ju istniejcymi, powinien zosta ujty w klasie interfejsu. Jeli wsppracujesz z baz danych (obojtne, jakiego rodzaju), baza ta take jest dobrym kandydatem na klas interfejsu. Klasy interfejsw umoliwiaj ukrycie szczegw interfejsu i w ten sposb chroni nas przed zmianami w innych systemach. Klasy interfejsw pozwalaj na zmian wasnego projektu lub dostosowywanie si do zmian w projekcie innych systemw, bez zmian w pozostaej czci kodu. Dopki dwa systemy wsppracuj ze sob poprzez uzgodniony interfejs, mog si zmienia niezalenie od siebie.

Manipulowanie danymi Gdy stworzysz klasy dla manipulowania danymi, a musisz przeksztaca dane z formatu do formatu (na przykad ze skali Celsjusza do Fahrenheita lub z systemu angielskiego na metryczny), moesz ukry szczegy takiej transformacji w klasie. Moesz uy tej techniki, przekazujc dane w danym formacie do innego systemu lub transmitujc je poprzez Internet. Gdy musisz manipulowa danymi w okrelonym formacie, moesz ukry szczegy protokou w klasie manipulowania danymi.

Widoki Kady widok lub raport generowany przez system (lub w przypadku, gdy generujesz wiele raportw, kady zestaw raportw) jest kandydatem na klas. Reguy tworzenia raportu sposb gromadzenia informacji i ich przedstawiania mona ukry wewntrz klasy.

30

Urzdzenia Gdy twj system wsppracuje z urzdzeniami (takimi jak drukarki, modemy, skanery, itd.) lub operuje nimi, specyfika protokou komunikacji z urzdzeniem take powinna zosta ukryta w klasie. Take w tym przypadku, przez stworzenie klas dla interfejsu urzdzenia, moesz podcza nowe urzdzenia z nowymi protokoami, nie naruszajc przy tym adnych pozostaych czci swojego kodu; po prostu tworzysz now klas interfejsu, obsugujc ten sam (lub wyprowadzony) interfejs. I gotowe!

Model statyczny
Gdy okrelisz ju wstpny zestaw klas, pora rozpocz modelowanie powiza i interakcji pomidzy nimi. W tym rozdziale najpierw opiszemy model statyczny, a dopiero potem model dynamiczny. W rzeczywistym procesie projektowania bdziesz swobodnie przechodzi pomidzy tymi modelami, dodajc nowe klasy i, szkicujc je w miar postpu prac. Model statyczny skupia si na trzech obszarach: odpowiedzialnoci, atrybutach i powizaniach. Najwaniejszy z nich i na nim skupisz si najpierw jest zestaw odpowiedzialnoci dla kadej z klas. Najwaniejsz wytyczn bdzie teraz: Kada klasa powinna by odpowiedzialna za jedn rzecz. Nie chc przez to powiedzie, e kada klasa ma tylko jedn metod; wiele klas bdzie miao tuziny metod. Jednak wszystkie te metody musz by spjne i wzajemnie do siebie przystajce; tj. wszystkie musz by ze sob powizane i zapewnia klasie zdolno osignicia okrelonego obszaru odpowiedzialnoci. W dobrze zaprojektowanym systemie, kady obiekt jest egzemplarzem dobrze zdefiniowanej i dobrze zrozumianej klasy, odpowiedzialnej za okrelony obszar. Klasy zwykle deleguj zewntrzne odpowiedzialnoci na inne, powizane z nimi klasy. Dziki stworzeniu klas, ktre zajmuj si tylko jednym obszarem, umoliwiasz tworzenie kodu atwego do konserwacji i rozbudowy. Aby okreli obszar odpowiedzialnoci projektowanie od uycia kart CRC. swoich klas, moesz zacz

Karty CRC CRC oznacza Class, Responsibility i Collaboration (klasa, odpowiedzialno, wsppraca). Karta CRC jest tylko zwyk kartk z notatnika. To proste

31

urzdzenie umoliwia nawizanie wsppracy z innymi osobami w celu okrelenia podstawowych odpowiedzialnoci dla pocztkowego zestawu klas. W tym celu u na stole stos pustych kart CRC, a przy stole zorganizuj seri sesji CRC.

W jaki sposb przeprowadza sesj CRC Kada sesja CRC powinna odbywa si w grupie od trzech do szeciu osb; przy wikszej ich iloci staje si nieefektywna. Powiniene wyznaczy koordynatora, ktrego zadaniem bdzie zapewnienie waciwego przebiegu sesji i pomaganie jej uczestnikom w zidentyfikowaniu najwaniejszych zagadnie. Powinien by obecny co najmniej jeden dowiadczony architekt oprogramowania, najlepiej kto z duym dowiadczeniem w obiektowo zorientowanej analizie i projektowaniu. Oprcz tego, w sesji powinien wzi udzia co najmniej jeden ekspert w danej dziedzinie, rozumiejcy wymagania systemu i mogcy udzieli fachowej porady na temat dziaania systemu. Najwaniejszym elementem sesji CRC jest nieobecno menederw. Sprawia ona, e sesja jest kreatywnym, swobodnie toczcym si spotkaniem, na przebieg ktrego nie moe mie wpywu ch zrobienia wraenia na czyim szefie. Celem jej jest eksperyment, podjcie ryzyka, odkrycie wymaga klas oraz zrozumienie, w jaki sposb mog one ze sob wsppracowa. Sesj CRC rozpoczyna si od zebrania grupy przy stole, na ktrym znajduje si niewielki stos kartek. Na grze kadej karty CRC wypisuje si nazw pojedynczej klasy. Poniej narysuj pionow lini biegnc w poprzez kartki, nastpnie opisz rubryk po lewej stronie jako Odpowiedzialnoci, za po prawej stronie jako Wsppraca. Zacznij od wypenienia kart dla najwaniejszych zidentyfikowanych dotd klas. Na odwrocie kadej karty zapisz jedno lub dwuzdaniow definicj. Moesz take wskaza, jak klas specjalizuje dana klasa (o ile jest to wiadome w czasie posugiwania si kart CRC). Poniej nazwy klasy napisz po prostu Superklasa: oraz nazw klasy, od ktrej ta klasa pochodzi.

Skup si na odpowiedzialnociach Celem sesji CRC jest zidentyfikowanie odpowiedzialnoci kadej z klas. Nie zwracaj wikszej uwagi na atrybuty, wychwytuj tylko te najwaniejsze. Najwaniejszym zadaniem jest zidentyfikowanie odpowiedzialnoci. Jeli w celu

32

wypenienia odpowiedzialnoci klasa musi delegowa prac na inn klas, zapisz t informacj w rubryce Wsppraca. W miar postpu prac zwracaj uwag na list odpowiedzialnoci. Gdy na karcie CRC zabraknie miejsca, zadaj sobie pytanie, czy nie dasz od klasy zbyt wiele. Pamitaj, kada klasa powinna by odpowiedzialna za jeden oglny obszar pracy, za wymienione na karcie odpowiedzialnoci powinny by spjne i przystajce tj. powinny wspgra ze sob w celu zapewnienia oglnej odpowiedzialnoci klasy. Nie powiniene teraz skupia si na powizaniach ani na interfejsie klasy lub na tym, ktra metoda bdzie publiczna, a ktra prywatna. Postaraj si jedynie zrozumie, co robi kada z klas.

Antropomorfizacja i ukierunkowanie na przypadki uycia Kluczow cech kart CRC jest ich antropomorfizacja tj. przypisywanie kadej z klas ludzkich atrybutw. Oto sposb jej dziaania: gdy masz ju wstpny zestaw klas, wr do scenariuszy uycia. Rozdziel karty wrd uczestnikw sesji i razem przeledcie scenariusz. Na przykad, zastanwmy si nad nastpujcym scenariuszem: Klient decyduje si na wypat gotwki z rachunku osobistego. Na rachunku znajduje si wystarczajca ilo rodkw, w bankomacie jest wystarczajca ilo gotwki i papieru, dziaa take sie. Bankomat prosi klienta o podanie kwoty wypaty, za klient prosi o wypat trzystu dolarw, co jest w tym momencie moliwe. Maszyna wydaje trzysta dolarw i drukuje kwit, klient odbiera pienidze i kwit. Zamy, e w sesji uczestniczy pi osb: Amy, koordynator i projektant obiektowo zorientowanego oprogramowania; Barry, gwny programista; Charlie, klient; Dorris, ekspert w danej dziedzinie oraz Ed, programista. Amy trzyma kart CRC reprezentujc RachunekBiecy i mwi: Mwi klientowi, ile pienidzy jest dostpnych. Klient prosi mnie o wypacenie trzystu dolarw. Wysyam do dystrybutora polecenie wypacenia trzystu dolarw w gotwce. Barry podnosi swoj kart i mwi: Jestem dystrybutorem; wydaj trzysta dolarw i wysyam do Amy komunikat nakazujcy jej zmniejszenie stanu rachunku o trzysta dolarw. Komu mam powiedzie, e maszyna zawiera teraz o trzysta dolarw mniej? Czy te ja to ledz? Charlie odpowiada: Myl, e

33

potrzebujemy obiektu do ledzenia iloci gotwki w maszynie. Ed mwi: Nie, dystrybutor powinien wiedzie, ile ma gotwki; to naley do jego zada. Amy nie zgadza si z tym i mwi: Nie, kto powinien koordynowa wydawanie pienidzy. Dystrybutor musi wiedzie, czy gotwka jest dostpna i czy klient posiada wystarczajc ilo rodkw na koncie, powinien wypaci pienidze i w odpowiednim momencie zamkn szuflad. Powinien delegowa na kogo innego odpowiedzialno za ledzenie iloci dostpnej gotwki na pewien rodzaj wewntrznego rachunku. osoba, ktra zna ilo dostpnej gotwki, moe take poinformowa biuro o tym, e zasoby bankomatu powinny zosta uzupenione. W przeciwnym razie dystrybutor miaby zbyt wiele zada. Dyskusja trwa dalej. Trzymajc karty i wsppracujc z innymi, odkrywa si wymagania i moliwoci delegacji; kada klasa oywa i odkrywa swoje odpowiedzialnoci. Gdy grupa zbytnio zagbi si w projekt, koordynator moe podj decyzj o przejciu do nastpnego zagadnienia.

Ograniczenia kart CRC Cho karty CRC mog stanowi dobre narzdzie dla rozpoczcia projektowania, posiadaj one due ograniczenia. Podstawowym problemem jest to, e nie zapewniaj dobrego skalowania. W bardzo skomplikowanym projekcie posugiwanie si kartami CRC moe by trudne. Karty CRC nie odzwierciedlaj take wzajemnych relacji pomidzy klasami. Cho mona zapisa na nich zakres wsppracy, jednak nie da si nimi wymodelowa tej wsppracy. Patrzc na kart CRC, nie jeste w stanie powiedzie, czy klasa agreguje inn klas, kto kogo tworzy itd. Karty CRC nie wychwytuj take atrybutw, wic trudno jest z nich przej bezporednio do kodu. Karty CRC s statyczne; cho moesz za ich pomoc ustali interakcje midzy klasami, same karty CRC nie wychwytuj tej informacji. Karty CRC s dobre na pocztek, ale jeli chcesz stworzy stabilny i kompletny model swojego projektu, powiniene przedstawi klasy w jzyku UML. Cho przejcie do UML nie jest zbyt trudne, jest jednak operacj jednokierunkow. Gdy przeniesiesz swoje klasy do diagramw UML, nie bdzie ju odwrotu; nie wrcisz do kart CRC. Po prostu synchronizacji obu modeli jest zbyt trudna.

Przeksztacanie kart CRC na UML Kada karta CRC moe zosta przeksztacona bezporednio w klas wymodelowan w UML. Odpowiedzialnoci s przeksztacane na metody klasy,

34

ewentualnie dodawane s take wychwycone atrybuty. Definicja klasy z odwrotnej strony karty jest umieszczana w dokumentacji klasy. Rysunek 11.13 przedstawia relacj pomidzy kart CRC RachunekBiecy, a stworzon na podstawie tej karty klas UML. Rys. 11.13. Karta CRC

Klasa: RachunekBiecy Superklasa: Rachunek Odpowiedzialnoci: ledzenie stanu biecego przyjmowanie depozytw i transfery na rachunek wypisywanie czekw transfery z rachunku ledzenie dziennego limitu wypaty gotwki z bankomatu Wsppraca: inne rachunki system bankowy dystrybutor gotwki

35

Relacje pomidzy klasami


Gdy klasy zostan ju przedstawione w UML, moesz zwrci uwag na relacje pomidzy rnymi klasami. Podstawowe modelowane relacje to: - generalizacja - powizania - agregacja - kompozycja Relacja generalizacji jest w C++ implementowana poprzez publiczne dziedziczenie. Pamitajc jednak e najwaniejszy jest projekt, nie skupimy si mechanizmie dziaania relacji, ale na semantyce: co z takiej relacji wynika. Relacje sprawdzilimy ju w fazie analizy, teraz skupimy si nie tylko na obiektach dziedziny, ale take na obiektach w naszym projekcie. Naszym zadaniem jest okrelenie wsplnej funkcjonalnoci w powizanych ze sob klasach i wydzielenie z nich klas bazowych, obejmujcych te wsplne waciwoci. Gdy okrelisz wspln funkcjonalno, powiniene przenie j z klas specjalizowanych do klasy bardziej oglnej. Gdy zauwaymy, e oba rachunki, biecy i lokaty, potrzebuj metod do transferu pienidzy do i z rachunku, metod Transferrodkw() przeniesiemy do klasy bazowej Rachunek. Im wicej funkcji przeniesiemy z klas potomnych, tym bardziej polimorficzny stanie si projekt. Jedn z moliwoci dostpnych w C++, lecz niedostpnych w Javie, jest wielokrotne dziedziczenie (Java ma podobn, cho ograniczon, moliwo posiadania wielu interfejsw). Wielokrotne dziedziczenie pozwala klasie na dziedziczenie po wicej ni jednej klasie bazowej, wprowadzajc skadowe i metody z dwch lub wicej klas. Dowiadczenie wykazao, e wielokrotne dziedziczenie powinno by uywane rozwanie, gdy moe skomplikowa zarwno projekt, jak i implementacj. Wiele problemw rozwizywanych dawniej poprzez wielokrotne dziedziczenie obecnie rozwizuje si poprzez agregacj. Naley jednak pamita, e wielokrotne dziedziczenie jest uytecznym narzdziem, za projekt moe

36

wymaga, by pojedyncza klasa specjalizowaa zachowanie dwch lub wicej innych klas.

Wielokrotne dziedziczenie a zawieranie Czy obiekt jest sum swoich czci? Czy ma sens modelowanie obiektu Samochd jako specjalizacji Kierownicy, Drzwi i K, tak jak pokazano na rysunku 11.14? Rys. 11.14. Faszywe dziedziczenie

Trzeba powrci do rde: publiczne dziedziczenie powinno zawsze modelowa generalizacj. Oglnie przyjtym wyraeniem tej reguy jest stwierdzenie, e dziedziczenie powinno modelowa relacj jest czym. Jeli chcesz wymodelowa relacj posiada (na przykad samochd posiada kierownic), powiniene uy agregacji, jak pokazano na rysunku 11.15. Rys. 11.15. Agregacja

37

Diagram z rysunku 11.15 wskazuje, e samochd posiada kierownic, cztery koa oraz od dwch do piciu drzwi. Jest to waciwy model relacji pomidzy samochodem a jego elementami. Zwr uwag, e romby na rysunku nie s wypenione; rysujemy je w ten sposb, aby wskaza, e modelujemy agregacj, a nie kompozycj. Kompozycja implikuje kontrol czasu ycia obiektu. Cho samochd posiada koa i drzwi, mog one istnie jako elementy samochodu, a take jako samodzielne obiekty. Rysunek 11.16 modeluje kompozycj. Ten model pokazuje, e ciao jest nie tylko agregacj gowy, dwch rk i dwch ng, ale take, e obiekty te (gowa, rce, nogi) s tworzone w momencie tworzenia ciaa i znikaj w chwili, gdy znika ciao. Nie mog istnie niezalenie; ciao jest zoone z tych rzeczy, a czas ich istnienia jest powizany. Rys. 11.16. Kompozycja

38

Cechy i gwne typy Jak zaprojektowa klasy potrzebne do zaprezentowania rnych linii modelowych typowego producenta samochodw? Przypumy, e zostae wynajty do zaprojektowania systemu dla Acme Motors, ktry aktualnie produkuje pi modeli: Pluto (powolny, kompaktowy samochd z niewielkim silnikiem), Venus (czterodrzwiowy sedan ze rednim silnikiem), Mars (sportowe coupe z najwikszym silnikiem, opracowanym w celu uzyskiwania rekordowych szybkoci), Jupiter (minwan z takim samym silnikiem, jak w sportowym coupe, lecz z moliwoci zmiany biegw przy niszych obrotach oraz dostosowaniemm do napdzania wikszej masy) oraz Earth (furgonetka z niewielkim silnikiem o wysokich obrotach). Moesz zacz od stworzenia podtypw samochodw, odzwierciedlajcych rne modele, po czym tworzy egzemplarze kadego z modelu w miar ich schodzenia z linii montaowej, tak jak pokazano na rysunku 11.17. Rys. 11.17. Modelowanie podtypw

Czym rni si te modele? Typem nadwozia oraz rozmiarem i charakterystykami silnika. Te elementy mog by czone i dopasowywane w celu stworzenia rnych modeli. Moemy to wymodelowa w UML za pomoc stereotypu cecha, tak jak pokazano na rysunku 11.18. Rys. 11.18. Modelowanie cech

39

Diagram z rysunku 11.18 wskazuje, e klasy mog by wyprowadzone z klasy samochd dziki mieszaniu i dopasowaniu trzech atrybutw cech. Rozmiar silnika okrela si pojazdu, za charakterystyka wydajnoci okrela, czy samochd jest pojazdem sportowym, czy rodzinnym. Dziki temu moemy stworzy siln, sportow furgonetk, saby rodzinny sedan, itd. Kady atrybut moe by zaimplementowany za pomoc zwykego wyliczenia. Typ nadwozia mona zaimplementowa w kodzie za pomoc poniszego wyliczenia:
enum TypNadwozia = { sedan, coupe, minivan, furgonetka };

Moe si jednak okaza, e pojedyncza warto nie wystarcza do wymodelowania okrelonej cechy. Na przykad, charakterystyka wydajnoci moe by raczej zoona. W takim przypadku cecha moe zosta wymodelowana jako klasa, za okrelona cecha obiektu moe istnie jako konkretny egzemplarz tej klasy. Model samochodu moe modelowa charakterystyk wydajnoci, np. typ wydajno, zawierajcy informacje o tym, w ktrym momencie silnik zmienia bieg i jak wysokie obroty moe osign. Stereotyp UML dla klasy obejmujcej cech klasa ta moe suy do tworzenia egzemplarzy klasy (Samochd) nalecej logicznie do innego typu (np. SamochdSportowy i SamochdLuksusowy) to <<typ gwny>>. W tym przypadku, klasa Wydajno jest typem gwnym dla klasy Samochd. Gdy tworzymy egzemplarz klasy Samochd, jednoczenie tworzymy obiekt Wydajno, wic go z danym obiektem Samochd, jak pokazano na rysunku 11.19.

40

Rys. 11.19. Cecha jako typ gwny

Typy gwne umoliwiaj tworzenie rnorodnych typw logicznych, bez potrzeby uywania dziedziczenia. Dziki temu mona obsuy duy i zoony zestaw typw, bez gwatownego wzrostu zoonoci klas, jaki mgby nastpi przy uywaniu samego dziedziczenia. W C++ typy gwne s najczciej implementowane za pomoc wskanikw. W tym przypadku klasa Samochd zawiera wskanik do egzemplarza klasy CharakterystykaWydajnoci (patrz rysunek 11.20). Zamian cech nadwozia i silnika w typy gwne pozostawi ambitnym czytelnikom. Rys. 11.20. Relacja pomidzy obiektem Samochd a jego typem gwnym

41

Class Samochod : public Pojazd { public: Samochod(); ~Samochod(); // inne publiczne metody private: CharakterystykaWydajnosci * pWydajnosc; };

I jeszcze jedna uwaga. Typy gwne umoliwiaj tworzenie nowych typw (a nie tylko egzemplarzy) w czasie dziaania programu. Poniewa typy logiczne rni si od siebie jedynie atrybutami powizanego z nim typu gwnego, atrybuty te mog by parametrami konstruktora typu gwnego. Oznacza to, e w czasie dziaania programu moesz na bieco tworzy nowe typy samochodw. Przekazujc rne rozmiary silnika i rne punkty zmiany biegw do typu gwnego, moesz efektywnie tworzy nowe charakterystyki wydajnoci. Przypisujc te charakterystyki rnym samochodom, moesz zwiksza zestaw typw samochodw w czasie dziaania programu.

Model dynamiczny
Oprcz modelowania relacji pomidzy klasami, bardzo wane jest take wymodelowanie sposobu wsppracy pomidzy klasami. Na przykad, klasy RachunekBiecy, Bankomat oraz Kwit mog wsppracowa z klas Klient, wypeniajc przypadek uycia Wypata gotwki. Wkrtce wrcimy do diagramw sekwencji uywanych wczeniej w fazie analizy, ale tym razem, na podstawie metod opracowanych dla klas, wypenimy je szczegami, tak jak na rysunku 11.21. Rys. 11.21. Diagram sekwencji

42

Ten prosty diagram interakcji pokazuje wspdziaanie pomidzy klasami projektowymi oraz ich nastpstwo w czasie. Sugeruje, e klasa Bankomat deleguje na klas RachunekBiecy ca odpowiedzialno za zarzdzanie stanem rachunku, za klasa RachunekBiecy przenosi na klas Bankomat zadanie wywietlania informacji dla uytkownika. Diagramy interakcji wystpuj w dwch odmianach. Odmiana pokazana na rysunku 11.21 jest nazywana diagramem sekwencji. Diagramy wsppracy dostarczaj innego widoku tych samych informacji. Diagramy sekwencji kad nacisk na kolejno zdarze, za diagramy wsppracy obrazuj wspdziaanie pomidzy klasami. Diagram wsppracy mona stworzy bezporednio z diagramu sekwencji; programy takie jak Rational Rose potrafi stworzy taki diagram po jednym klikniciu na przycisku (patrz rysunek 11.22). Rys. 11.22. Diagram wsppracy

43

Diagramy zmian stanu Przechodzc do zagadnienia interakcji pomidzy obiektami, musimy pozna rne moliwe stany kadego z obiektw. Przejcia pomidzy stanami moemy wymodelowa na diagramie stanu (lub diagramie zmian stanw). Rysunek 12.23 przedstawia rne stany obiektu RachunekBiecy w czasie, gdy klient jest zalogowany do systemu. Rys. 11.23. Stan rachunku klienta

44

Kady diagram stanu rozpoczyna si od stanu start, a koczy na stanie koniec. Poszczeglne stany posiadaj nazwy, za zmiany stanw mog by opisane za pomoc etykiet. Stranik wskazuje warunek, ktry musi by speniony, aby obiekt mg przej ze stanu do stanu.

Superstany Klient moe w kadej chwili zmieni zamiar i zrezygnowa z logowania si. Moe to uczyni po woeniu karty w celu zidentyfikowania swojego rachunku lub ju po wprowadzeniu kodu PIN. W obu przypadkach system musi zaakceptowa jego danie anulowania operacji i powrci do stanu nie zalogowany (patrz rysunek 11.24). Rys. 11.24. Uytkownik moe zrezygnowa

45

Jak wida, w bardziej skomplikowanych diagramach, stan Anulowany szybko zaczyna przeszkadza. Jest to szczeglnie irytujce, gdy anulowanie jest stanem wyjtkowym, ktry nie powinien dominowa w diagramie. Moemy uproci ten diagram, uywajc superstanu, tak jak pokazano na rysunku 11.25. Rys. 11.25. Superstan

46

Diagram z rysunku 11.25 dostarcza takich samych informacji, jak diagram z rysunku 11.24, lecz jest duo bardziej przejrzysty i atwiejszy do odczytania. Od momentu rozpoczcia logowania, a do chwili jego zakoczenia przez system, moesz ten proces anulowa. Gdy to uczynisz, powrcisz do stanu Nie zalogowany.

47

Rozdzia 12. Dziedziczenie


W poprzednim rozdziale poznae wiele relacji zwizanych z projektowaniem obiektowym, m.in. relacj specjalizacji/generalizacji. Jzyk C++ implementuje j poprzez dziedziczenie. Z tego rozdziau dowiesz si: czym jest dziedziczenie, w jaki sposb wyprowadza klas z innej klasy, czym jest dostp chroniony i jak z niego korzysta, czym s funkcje wirtualne.

Usunito: zagadnie Usunito: wcznie Usunito: z Usunito: e Usunito: Usunito: Usunito: i Usunito: Usunito: e Usunito: W Usunito: ym Usunito: le Usunito: C Usunito: . Usunito: W Usunito: . Usunito: C Usunito: chroniony Usunito: . Usunito: C Usunito: na subie Usunito: a Usunito: dziaajce Usunito: Wanie ten ostatni przypadek interesuje nas w tym momencie Usunito: Usunito: wok Usunito: Tak wic s

Czym jest dziedziczenie?


Czym jest pies? Co widzisz, gdy patrzysz na swoje zwierz? Ja widz cztery apy i pysk. Biolodzy widz sie interesujcych organw, fizycy atomy i rnorodne siy, za taksonom widzi przedstawiciela gatunku canine domesticus. Skupmy si na tym ostatnim przypadku. Pies jest przedstawicielem psowatych, psowate s przedstawicielami ssakw, i tak dalej. Taksonomowie dziel wiat ywych stworze na krlestwa, typy, klasy, rzdy, rodziny, rodzaje i gatunki. Hierarchia specjalizacji/generalizacji ustanawia relacj typu jest-czym. Homo sapiens jest przedstawicielem naczelnych. Tak relacj widzimy wszdzie: wz kempingowy jest rodzajem samochodu, ktry z kolei jest rodzajem pojazdu. Budy jest rodzajem deseru, ktry jest rodzajem poywienia. Gdy mwimy, e co jest rodzajem czego innego, zakadamym e stanowi to specjalizacj tej rzeczy. Samochd jest zatemspecjalnym rodzajem pojazdu.

Dziedziczenie i wyprowadzanie
Pies dziedziczy to jest, automatycznie otrzymuje wszystkie cechy ssaka. Poniewa jest ssakiem, porusza si i oddycha powietrzem. Wszystkie ssaki, z definicji, poruszaj si i oddychaj. Pies wzbogaca te elementy o cechy takie jak, szczekanie, machanie ogonem, zjadanie dopiero co ukoczonego rozdziau mojej ksiki, warczenie, gdy prbuj zasn... Przepraszam. Gdzie skoczyem? A, wiem: Psy moemy podzieli na psy przeznaczone do pracy, psy do sportw i teriery, za psy do sportw moemy podzieli na psy myliwskie, spaniele i tak dalej. Mona take dokonywa dalszego podziau, na przykad psy myliwskie mona podzieli na labradory czy goldeny. Golden jest rodzajem psa myliwskiego, ktry jest psem do sportw, nalecego do rodzaju psw, czyli bdcego ssakiem, a wic zwierzciem, czyli rzecz yw. T hierarchi przedstawia rysunek 12.1. Rys. 12.1. Hierarchia zwierzt
Usunito: wiemy e Usunito: oraz

Usunito: L Usunito: G Usunito: bdcego Usunito: ym

C++ prbuje reprezentowa te relacje, umoliwiajc nam definiowanie klas, ktre s wyprowadzane z innych klas. Wyprowadzanie jest sposobem wyraenia relacji typu jest-czym. Now klas, Dog (pies), mona wyprowadzi z klasy Mammal (ssak). Nie musimy wtedy wyranie mwi, e pies porusza si, gdy dziedziczy t cech od ssakw. Klasa dodajca nowe waciwoci do istniejcej ju klasy jest wyprowadzona z klasy pierwotnej. Ta pierwotna klasa jest nazywana klas bazow.
Usunito: oryginalnej Usunito: oryginalna

Jeli klasa Dog jest wyprowadzona z klasy Mammal, oznacza to, e klasa Mammal jest klas bazow (nadrzdn) klasy Dog. Klasy wyprowadzone (pochodne) stanow nadzbir swoich klas bazowych. Pies przejmuje swoje cechy od ssakw, tak klasa Dog przejmie pewne metody lub dane klasy Mammal. Zwykle klasa bazowa posiada wicej ni jedn klas pochodn. Poniewa zarwno psy, jak i koty oraz konie s ssakami, wic ich zostay byy wyprowadzone z klasy Mammal.

Usunito: Tak jak p

Usunito: klasy

Krlestwo zwierzt
Aby uatwi przedstawienie procesu dziedziczenia i wyprowadzania, w tym rozdziale skupimy si na zwizkach pomidzy rnymi klasami reprezentujcymi zwierzta. Moesz sobie wyobrazi, e bawimy si w dziecic gr symulacj farmy. Opracujemy cay zestaw zwierzt, obejmujcy konie, krowy, psy, koty, owce, itd. Stworzymy metody dla tych klas, aby zwierzta mogy funkcjonowa tak, jak oczekiwaoby tego dziecko, ale na razie kad z tych metod zastpimy zwyk instrukcj wydruku. Minimalizowanie funkcji (czyli pozostawienie tylko jej szkieletu) oznacza, e napiszemy tylko tyle kodu, ile wystarczy do pokazania, e funkcja zostaa wywoana. Szczegy pozostawimy na pniej, gdy bdziemy mie wicej czasu. Jeli tylko masz ochot, moesz wzbogaci minimalny kod zaprezentowany w tym rozdziale i sprawi, by zwierzta zachowyway si bardziej realistycznie.
Usunito: Z czasem o Usunito: tak Usunito: Zastpowanie Usunito: Zminimalizowanie Usunito: pnia Usunito: Usunito: aby Usunito: Usunito: , Usunito: pozostawiajc s Usunito: , Usunito: ajc

Skadnia wyprowadzania
Gdy deklarujesz klas, moesz wskaza klas, od ktrej pochodzi, zapisujc po nazwie tworzonej klasy dwukropek, rodzaj wyprowadzania (publiczny lub inny) oraz klas bazow. Oto przykad:
class Dog : public Mammal

Rodzaj wyprowadzania opiszemy w dalszej czci rozdziau. Na razie bdziemy uywa wyprowadzania publicznego (oznaczonego sowem kluczowym public). Klasa bazowa musi by zdefiniowana wczeniej, gdy w przeciwnym razie kompilator zgosi bd. Listing 12.1 ilustruje sposb deklarowania klasy Dog, wyprowadzonej z klasy Mammal. Listing 12.1. Proste dziedziczenie
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: //Listing 12.1 Proste dziedziczenie #include <iostream> using namespace std; enum BREED { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB }; class Mammal { public: // konstruktory Mammal(); ~Mammal();

Usunito: omwimy Usunito: zawsze Usunito: wczeniej Usunito:

13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48:

//akcesory int GetAge() const; void SetAge(int); int GetWeight() const; void SetWeight(); //inne metody void Speak() const; void Sleep() const; protected: int itsAge; int itsWeight; }; class Dog : public Mammal { public: // konstruktory Dog(); ~Dog(); // akcesory BREED GetBreed() const; void SetBreed(BREED); // inne metody WagTail(); BegForFood(); protected: BREED itsBreed; };

Ten kod nie wywietla wynikw, gdy zawiera jedynie zestaw deklaracji klas (bez ich implementacji). Mimo to mona w nim wiele zobaczy. Analiza W liniach od 7. do 28. deklarowana jest klasa Mammal (ssak). Zwr uwag, e w tym przykadzie klasa Mammal nie jest wyprowadzana z adnej innej klasy. W realnym wiecie ssaki pochodz od (to znaczy, e s rodzajem) zwierzt. W programie C++ moesz zaprezentowa jedynie cz informacji, ktre posiadasz na temat danego obiektu. Rzeczywisto jest zdecydowanie zbyt skomplikowana, aby uchwyci j w caoci, wic kada hierarchia w C++ jest umown reprezentacj dostpnych danych. Sztuka dobrego projektowania polega na reprezentowaniu interesujcych nas obszarw tak, aby reprezentoway rzeczywisto w moliwie najlepszy sposb. Hierarchia musi si gdzie zaczyna; w tym programie rozpoczyna si od klasy Mammal. Z powodu tego, pewne zmienne skadowe, ktre mogy nalee do wyszej klasy, nie s tu reprezentowane. Wszystkie zwierzta z pewnoci posiadaj na przykad wag i wiek, wic jeli klasa Mammal byaby wyprowadzona z klasy Animal (zwierz), moglibymy oczekiwa, e dziedziczy te atrybuty. Jednak w naszym przykadzie atrybuty te wystpuj w klasie Mammal.

Usunito: jest Usunito: do Usunito: enia Usunito: : Usunito: rzeczywistym

Usunito: wystarczajco dobry Usunito: reprezentoway rzeczywisto Usunito: j decyzji Usunito: atrybuty

Aby zachowa niewielk i spjn posta programu, w klasie Mammal zostao umieszczonych jedynie sze metod cztery akcesory oraz metody Speak() (mwienie) i Sleep() (spanie). Klasa Dog (pies) dziedziczy po klasie Mammal, co wskazuje linia 30. Kady obiekt typu Dog bdzie posiada trzy zmienne skadowe: itsAge (wiek), itsWeight (waga) oraz itsBread (rasa). Zwr uwag, e deklaracja klasy Dog nie obejmuje zmiennych skadowych itsAge oraz itsWeight. Obiekty klasy Dog dziedzicz te zmienne od klasy Mammal, razem z metodami tej klasy (z wyjtkiem operatora kopiujcego oraz destruktora i konstruktora).

Usunito: utrzyma

Usunito: i

Prywatne kontra chronione


By moe w liniach 25. i 46. listingu 12.1 zauwaye nowe sowo kluczowe dostpu, protected (chronione). Wczeniej dane klasy byy deklarowane jako prywatne. Jednak prywatne skadowe nie s dostpne dla klas pochodnych. Moglibymy uczyni zmienne itsAge i itsWeight skadowymi publicznymi, ale nie byoby to podane. Nie chcemy, by inne klasy mogy bezporednio odwoywa si do tych danych skadowych.
Usunito: argument

UWAGA Istnieje powd, by wszystkie dane skadowe klasy oznacza jako prywatne, a nie jako chronione. Powd ten przedstawi Stroustrup (twrca jzyka C++) w swojej ksice The Design and Evolution of C++, ISBN 0-201-543330-3, Addison Wesley, 1994. Metody chronione nie s jednak uwaane za kopotliwe i mog by bardzo uyteczne.

Usunito: i Usunito: gdy Usunito: Argument Usunito: problematyczne Usunito: C Usunito: metody Usunito: To, czego p Usunito: , Usunito: to Usunito: e Usunito: y Usunito: Oglnie, i Usunito: a Usunito: jeli Usunito: Usunito: by Usunito: te Usunito: Z Usunito: dziedziczenie Usunito: do Usunito: klasy Usunito: klasy

Potrzebujemy teraz oznaczenia, ktre mwi: Uczy te zmienne widocznymi dla tej klasy i dla klas z niej wyprowadzonych. Takim oznaczeniem jest wanie sowo kluczowe protected chroniony. Chronione funkcje i dane skadowe s w peni widoczne dla klas pochodnych i nie s dostpne dla innych klas. Istniej trzy specyfikatory dostpu: publiczny (public), chroniony (protected) oraz private (prywatny). Jeli funkcja posiada obiekt twojej klasy, moe odwoywa si do wszystkich jej publicznych funkcji i danych skadowych. Z kolei funkcje skadowe klasy mog odwoywa si do wszystkich prywatnych funkcji i danych skadowych swojej wasnej klasy, oraz do wszystkich chronionych funkcji i danych skadowych wszystkich klas, z ktrych ich klasa jest wyprowadzona. Tak wic, funkcja Dog::WagTail() (macha ogonem) moe odwoywa si do prywatnej danej itsBreed oraz do prywatnych danych klasy Mammal. Nawet gdyby pomidzy klas Mammal, a klas Dog wystpoway inne klasy (na przykad DomesticAnimals zwierzta domowe), klasa Dog w dalszym cigu mogaby si odwoywa do prywatnych skadowych klasy Mammal, zakadajc e wszystkie inne klasy uywayby dziedziczenia publicznego. Dziedziczenie prywatne zostanie omwione w rozdziale 16., Dziedziczenie zaawansowane. Listing 12.2 przedstawia sposb tworzenia obiektw typu Dog oraz dostpu do danych i funkcji zawartych w tym typie. Listing 12.2. Uycie klasy wyprowadzonej
0: 1: 2: //Listing 12.2 Uycie klasy wyprowadzonej #include <iostream>

3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57:

using std::cout; enum BREED { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB }; class Mammal { public: // konstruktory Mammal():itsAge(2), itsWeight(5){} ~Mammal(){} //akcesory int GetAge() const { return itsAge; } void SetAge(int age) { itsAge = age; } int GetWeight() const { return itsWeight; } void SetWeight(int weight) { itsWeight = weight; } //inne metody void Speak()const { cout << "Dzwiek ssaka!\n"; } void Sleep()const { cout << "Ciiiicho. Wlasnie spie.\n"; } protected: int itsAge; int itsWeight; }; class Dog : public Mammal { public: // konstruktory Dog():itsBreed(GOLDEN){} ~Dog(){} // akcesory BREED GetBreed() const { return itsBreed; } void SetBreed(BREED breed) { itsBreed = breed; } // inne metody void WagTail() const { cout << "Macham ogonem...\n"; } void BegForFood() const {cout << "Prosze o jedzenie...\n"; } private: BREED itsBreed; }; int main() { Dog fido; fido.Speak(); fido.WagTail(); cout << "Fido ma " << fido.GetAge() << " lat(a)\n"; return 0; } Usunito: :

Wynik
Dzwiek ssaka! Macham ogonem...

Fido ma 2 lat(a)

Analiza W liniach od 7. do 28. jest deklarowana klasa Mammal (wszystkie jej funkcje s funkcjami inline w celu zaoszczdzenia miejsca na wydruku). W liniach od 30. do 48. deklarowana jest klasa Dog, bdca klas pochodn klasy Mammal. Zatem, z definicji, wszystkie obiekty typu Dog posiadaj wiek, wag i ras. W linii 52. deklarowany jest obiekt typu Dog o nazwie fido. Obiekt fido dziedziczy wszystkie atrybuty klasy Mammal oraz posiada wasne atrybuty klasy Dog. Tak wic fido potrafi macha ogonem (metoda WagTail()), ale potrafi take wydawa dwiki (Speak()) oraz spa (Sleep()).

Usunito: : Usunito: s typu Usunito: w Usunito: Tak wic

Konstruktory i destruktory
Obiekty klasy Dog s obiektami klasy Mammal. Na tym wanie polega relacja jest-czym. Gdy tworzony jest obiekt fido, najpierw wywoywany jest jego konstruktor bazowy, tworzcy cz Mammal-ow nowego obiektu. Nastpnie wywoywany jest konstruktor klasy Dog, uzupeniajcy tworzenie obiektu klasy Dog. Poniewa nie podalimy obiektowi adnych parametrw, w tym przypadku jest wywoywany konstruktor domylny. Obiekt fido nie istnieje do chwili cakowitego zakoczenia tworzenia go, co oznacza, e musi zosta skonstruowana zarwno jego cz Mammal, jak i cz Dog, czyli musz by wywoane oba konstruktory. Gdy obiekt fido jest niszczony, najpierw wywoywany jest destruktor klasy Dog, a dopiero potem destruktor klasy Mammal. Kady destruktor ma okazj uporzdkowa wasn cz obiektu. Pamitaj, aby posprzta po swoim psie! Demonstruje to listing 12.3. Listing 12.3. Wywoywane konstruktory i destruktory
0: //Listing 12.3 Wywoywane konstruktory i destruktory 1: 2: #include <iostream> 3: enum BREED { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB }; 4: 5: class Mammal 6: { 7: public: 8: // konstruktory 9: Mammal(); 10: ~Mammal(); 11: 12: // akcesory 13: int GetAge() const { return itsAge; } 14: void SetAge(int age) { itsAge = age; } 15: int GetWeight() const { return itsWeight; } 16: void SetWeight(int weight) { itsWeight = weight; } 17: 18: // inne metody 19: void Speak()const { std::cout << "Dzwiek ssaka!\n"; } 20: void Sleep()const { std::cout << "Ciiiicho. Wlasnie spie.\n"; } 21: 22: Usunito: . Usunito: Taka jest esencja Usunito: i Usunito: klas Usunito: .

Usunito: . Usunito: To znaczy Usunito: ,

23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: }

protected: int itsAge; int itsWeight; }; class Dog : public Mammal { public: // konstruktory Dog(); ~Dog(); // akcesory BREED GetBreed() const { return itsBreed; } void SetBreed(BREED breed) { itsBreed = breed; } // inne metody void WagTail() const { std::cout << "Macham ogonkiem...\n";

42: void BegForFood() const { std::cout << "Prosze o jedzenie...\n"; } 43: 44: private: 45: BREED itsBreed; 46: }; 47: 48: Mammal::Mammal(): 49: itsAge(1), 50: itsWeight(5) 51: { 52: std::cout << "Konstruktor klasy Mammal...\n"; 53: } 54: 55: Mammal::~Mammal() 56: { 57: std::cout << "Destruktor klasy Mammal...\n"; 58: } 59: 60: Dog::Dog(): 61: itsBreed(GOLDEN) 62: { 63: std::cout << "Konstruktor klasy Dog...\n"; 64: } 65: 66: Dog::~Dog() 67: { 68: std::cout << "Destruktor klasy Dog...\n"; 69: } 70: int main() 71: { 72: Dog fido; 73: fido.Speak(); 74: fido.WagTail(); 75: std::cout << "Fido ma " << fido.GetAge() << " lat(a)\n"; 76: return 0; 77: } Usunito: :

Wynik

Konstruktor klasy Mammal... Konstruktor klasy Dog... Dzwiek ssaka! Macham ogonkiem... Fido ma 1 lat(a) Destruktor klasy Dog... Destruktor klasy Mammal...

Analiza Listing 12.3 jest podobny do listingu 12.2, z t rnic, e konstruktory i destruktory wypisuj teraz komunikaty na ekranie. Wywoywany jest najpierw konstruktor klasy Mammal, nastpnie konstruktor klasy Dog. W tym momencie obiekt klasy Dog ju istnieje i mona wywoywa jego metody. Gdy fido wychodzi z zakresu, wywoywany jest jego destruktor klasy Dog, a nastpnie destruktor klasy Mammal.

Usunito: : Usunito: tym Usunito: w peni

Przekazywanie argumentw do konstruktorw bazowych


Moe si zdarzy, e zechcemy przeciy konstruktor klasy Mammal tak, aby przyjmowa okrelony wiek, oraz e zechcemy przeciy konstruktor klasy Dog tak, aby przyjmowa ras. W jaki sposb moemy przesa waciwe parametry wieku i wagi do odpowiedniego konstruktora klasy Mammal? Co zrobi, gdy obiekty klasy Dog chc inicjalizowa swoj wag, a obiekty klasy Mammal nie? Inicjalizacja klasy bazowej moe by wykonana podczas inicjalizacji klasy pochodnej, przez zapisanie nazwy klasy bazowej, po ktrej nastpuj parametry oczekiwane przez t klas. Demonstruje to listing 12.4. Listing 12.4. Przecione konstruktory w wyprowadzonych klasach
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: //Listing 12.4 Przecione konstruktory w wyprowadzonych klasach #include <iostream> using namespace std; enum BREED { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB }; class Mammal { public: // konstruktory Mammal(); Mammal(int age); ~Mammal(); //akcesory int GetAge() const { return itsAge; } void SetAge(int age) { itsAge = age; } int GetWeight() const { return itsWeight; } void SetWeight(int weight) { itsWeight = weight; } // inne metody void Speak()const { cout << "Dzwiek ssaka!\n"; } void Sleep()const { cout << "Ciiiicho. Wlasnie spie.\n"; } Usunito: Istnieje moliwo

25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: } 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84:

protected: int itsAge; int itsWeight; }; class Dog : public Mammal { public: // konstruktory Dog(); Dog(int age); Dog(int age, int weight); Dog(int age, BREED breed); Dog(int age, int weight, BREED breed); ~Dog(); // akcesory BREED GetBreed() const { return itsBreed; } void SetBreed(BREED breed) { itsBreed = breed; } // inne metody void WagTail() const { cout << "Macham ogonem...\n"; } void BegForFood() const { cout << "Prosze o jedzenie...\n"; private: BREED itsBreed; }; Mammal::Mammal(): itsAge(1), itsWeight(5) { cout << "Konstruktor klasy Mammal...\n"; } Mammal::Mammal(int age): itsAge(age), itsWeight(5) { cout << "Konstruktor klasy Mammal(int)...\n"; } Mammal::~Mammal() { cout << "Destruktor klasy Mammal...\n"; } Dog::Dog(): Mammal(), itsBreed(GOLDEN) { cout << "Konstruktor klasy Dog...\n"; } Dog::Dog(int age): Mammal(age), itsBreed(GOLDEN) {

85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128:

cout << "Konstruktor klasy Dog(int)...\n"; } Dog::Dog(int age, int weight): Mammal(age), itsBreed(GOLDEN) { itsWeight = weight; cout << "Konstruktor klasy Dog(int, int)...\n"; } Dog::Dog(int age, int weight, BREED breed): Mammal(age), itsBreed(breed) { itsWeight = weight; cout << "Konstruktor klasy Dog(int, int, BREED)...\n"; } Dog::Dog(int age, BREED breed): Mammal(age), itsBreed(breed) { cout << "Konstruktor klasy Dog(int, BREED)...\n"; } Dog::~Dog() { cout << "Destruktor klasy Dog...\n"; } int main() { Dog fido; Dog rover(5); Dog buster(6,8); Dog yorkie (3,GOLDEN); Dog dobbie (4,20,DOBERMAN); fido.Speak(); rover.WagTail(); cout << "Yorkie ma " << yorkie.GetAge() << " lat(a)\n"; cout << "Dobbie wazy "; cout << dobbie.GetWeight() << " funtow\n"; return 0; } Usunito: ,

UWAGA Linie wyniku zostay ponumerowane tak, aby mona si byo do nich odwoywa w analizie.

Wynik
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: Konstruktor klasy Konstruktor klasy Konstruktor klasy Konstruktor klasy Konstruktor klasy Konstruktor klasy Konstruktor klasy Konstruktor klasy Konstruktor klasy Konstruktor klasy Dzwiek ssaka! Mammal... Dog... Mammal(int)... Dog(int)... Mammal(int)... Dog(int, int)... Mammal(int)... Dog(int, BREED)... Mammal(int)... Dog(int, int, BREED)...

Usunito: :

12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24:

Macham ogonem... Yorkie ma 3 lat(a) Dobbie wazy 20 funtow Destruktor klasy Dog... Destruktor klasy Mammal... Destruktor klasy Dog... Destruktor klasy Mammal... Destruktor klasy Dog... Destruktor klasy Mammal... Destruktor klasy Dog... Destruktor klasy Mammal... Destruktor klasy Dog... Destruktor klasy Mammal... Usunito: : Usunito: 1

Analiza Na listingu 12.4, w linii 12., konstruktor klasy Mammal zosta przeciony tak, e przyjmuje warto cakowit, okrelajc wiek ssaka. Implementacja w liniach od 62. do 67. inicjalizuje skadow itsAge wartoci przekazan do tego konstruktora, za skadow itsWeight inicjalizuje za pomoc wartoci 5. Klasa Dog posiada pi przecionych konstruktorw, zadeklarowanych w liniach od 36. do 40. Pierwszym z nich jest konstruktor domylny. Drugi konstruktor otrzymuje wiek, bdcy tym samym parametrem, co parametr konstruktora klasy Mammal. Trzeci konstruktor otrzymuje zarwno wiek, jak i wag, czwarty otrzymuje wiek i ras, za pity otrzymuje wiek, wag oraz ras. Zwr uwag na lini 75., w ktrej domylny konstruktor klasy Dog wywouje domylny konstruktor klasy Mammal. Cho wywoanie to nie jest konieczne, dokumentuje, e mielimy zamiar wywoa konstruktor bazowy, nie posiadajcy adnych parametrw. Konstruktor bazowy zostaby wywoany w kadym przypadku, ale w ten sposb wyraniej przedstawiamy nasze intencje. Implementacja konstruktora klasy Dog, przyjmujcego warto cakowit znajduje si w liniach od 81. do 86. W swojej fazie inicjalizacji (linie 82. i 83.), obiekt Dog inicjalizuje swoj klas bazow, przekazujc jej parametr, po czym inicjalizuje swoj ras. Inny konstruktor klasy Dog jest zawarty w liniach od 88. do 94. Ten konstruktor otrzymuje dwa parametry. Take w tym przypadku inicjalizuje on swoj klas bazow, wywoujc jej odpowiedni konstruktor, lecz tym razem dodatkowo przypisuje warto zmiennej itsWeight w klasie bazowej. Zauwa, e nie mona tu przypisywa wartoci zmiennym klasy bazowej w fazie inicjalizacji. Poniewa klasa Mammal nie posiada konstruktora przyjmujcego ten parametr, musimy uczyni to wewntrz ciaa konstruktora klasy Dog. Przejrzyj pozostae konstruktory, aby upewni si, e zrozumiae sposb ich dziaania. Zwr uwag, co jest inicjalizowane, a co musi poczeka na przejcie do ciaa konstruktora. Linie wynikw dziaania tego programu zostay ponumerowane tak, aby mona byo odwoywa si do nich podczas analizy. Pierwsze dwie linie wynikw reprezentuj tworzenie obiektu fido, za pomoc domylnego konstruktora. Linie 3. i 4. wynikw reprezentuj tworzenie obiektu rover. Linie 5. i 6. odnosz si do obiektu buster. Zwr uwag, e wywoywany konstruktor klasy Mammal jest konstruktorem przyjmujcym jedn warto cakowit, mimo i wywoywanym konstruktorem klasy Dog jest konstruktor przyjmujcy dwie wartoci cakowite.

Usunito:

Usunito: to bezwzgldnie wymagane Usunito: suy jako Usunito: acja Usunito: y Usunito:

Usunito: poje Usunito: si Usunito: uyciem

Wszystkie stworzone obiekty s uywane w programie, po czym wychodz poza zakres. Podczas niszczenia kadego obiektu najpierw wywoywany jest destruktor klasy Dog, a nastpnie destruktor klasy Mammal (cznie po pi razy).

Usunito: Po stworzeniu w Usunito: ch Usunito: zostaj Usunito: te

Przesanianie funkcji
Obiekt klasy Dog posiada dostp do wszystkich funkcji skadowych klasy Mammal, a take do wszystkich funkcji skadowych, na przykad takich jak WagTail(), ktre mogyby zosta dodane w deklaracji klasy Dog. Moe take przesania funkcje klasy bazowej. Przesonicie (ang. override) funkcji oznacza zmian implementacji funkcji klasy bazowej w klasie z niej wyprowadzonej. Gdy tworzysz obiekt klasy wyprowadzonej, wywoywana jest waciwa funkcja. Gdy klasa wyprowadzona tworzy funkcj o tym samym zwracanym typie i sygnaturze, co funkcja skadowa w klasie bazowej, ale z inn implementacj, mwimy, e funkcja klasy bazowej zostaa przesonita. Gdy przesaniasz funkcj, jej sygnatura musi si zgadza z sygnatur tej funkcji w klasie bazowej. Sygnatura jest innym prototypem funkcji ni typ zwracany; zawiera nazw, list parametrw oraz sowo kluczowe const (o ile jest uywane). Zwracane typy mog si od siebie rni. Listing 12.5 pokazuje, co si stanie, gdy w klasie Dog przesonimy metod Speak() klasy Mammal. Dla zaoszczdzenia miejsca, z tych klas zostay usunite akcesory. Listing 12.5. Przesanianie metod klasy bazowej w klasie potomnej
0: //Listing 12.5 Przesanianie metod klasy bazowej w klasie potomnej 1: 2: #include <iostream> 3: using std::cout; 4: 5: enum BREED { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB }; 6: 7: class Mammal 8: { 9: public: 10: // konstruktory 11: Mammal() { cout << "Konstruktor klasy Mammal...\n"; } 12: ~Mammal() { cout << "Destruktor klasy Mammal...\n"; } 13: 14: // inne metody 15: void Speak()const { cout << "Dzwiek ssaka!\n"; } 16: void Sleep()const { cout << "Ciiiicho. Wlasnie spie.\n"; } 17: 18: 19: protected: 20: int itsAge; 21: int itsWeight; 22: }; 23: 24: class Dog : public Mammal 25: { 26: public: 27: 28: // konstruktory

Usunito: i

Usunito: przez Usunito:

Usunito: ta

29: 30: 31: 32: 33: 34: } 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48:

Dog(){ cout << "Konstruktor klasy Dog...\n"; } ~Dog(){ cout << "Destruktor klasy Dog...\n"; } // inne metody void WagTail() const { cout << "Macham ogonkiem...\n"; } void BegForFood() const { cout << "Prosze o jedzenie...\n"; void Speak() const { cout << "Hau!\n"; } private: BREED itsBreed; }; int main() { Mammal bigAnimal; Dog fido; bigAnimal.Speak(); fido.Speak(); return 0; }

Wynik
Konstruktor klasy Mammal... Konstruktor klasy Mammal... Konstruktor klasy Dog... Dzwiek ssaka! Hau! Destruktor klasy Dog... Destruktor klasy Mammal... Destruktor klasy Mammal...

Analiza W linii 35., klasa Dog przesania metod Speak() z klasy Mammal, co powoduje, e obiekty klasy Dog w momencie wywoania tej metody wypisuj komunikat Hau!. W linii 43. tworzony jest obiekt klasy Mammal, o nazwie bigAnimal (due zwierz), powodujc wypisanie pierwszej linii wynikw (w efekcie wywoania konstruktora klasy Mammal). W linii 44. tworzony jest obiekt klasy Dog, o nazwie fido, powodujc wypisanie dwch nastpnych linii wynikw (powstajcych w efekcie wywoania konstruktora klasy Mammal, a nastpnie konstruktora klasy Dog). W linii 45. obiekt klasy Mammal wywouje swoj metod Speak(), za w linii 46. swoj metod Speak() wywouje obiekt klasy Dog. Jak wida na wydruku, wywoane zostay odpowiednie metody z obu klas. Na zakoczenie, oba obiekty wychodz z zakresu, wic s wywoywane ich destruktory.
Przesanianie a przecianie
Usunito: w Usunito: ie Usunito: zostaje Usunito: onita Usunito: a Usunito: , Usunito: w wyniku Usunito: yw Usunito: s

Te okrelenia s do siebie podobne i odnosz si do podobnych rzeczy. Gdy przeciasz metod, tworzysz kilka funkcji o tej samej nazwie, ale z innymi sygnaturami. Gdy przesaniasz metod, tworzysz metod w klasie wyprowadzonej; posiada ona tak sam nazw i sygnatur, jak przesaniana metoda w klasie bazowej.

Ukrywanie metod klasy bazowej


W poprzednim listingu metoda Speak() klasy Dog ukrya metod klasy bazowej. Wanie tego wtedy potrzebowalimy, ale w innych sytuacjach metoda ta moe dawa nieoczekiwane rezultaty. Gdyby klasa Mammal posiadaa przecion metod Move() (ruszaj), za klasa Dog przesoniaby t metod, wtedy metoda klasy Dog ukryaby w klasie Mammal wszystkie metody o tej nazwie. Jeli klasa Mammal posiada trzy przecione metody o nazwie Move() jedn bez parametrw, drug z parametrem w postaci liczby cakowitej oraz trzeci z parametrem cakowitym i kierunkowym za klasa Dog przesania jedynie metod Move() bez parametrw, wtedy dostp do pozostaych dwch metod poprzez obiekt klasy Dog nie bdzie atwy. Problem ten ilustruje listing 12.6. Listing 12.6. Ukrywanie metod
0: //Listing 12.6 Ukrywanie metod 1: 2: #include <iostream> 3: 4: 5: class Mammal 6: { 7: public: 8: void Move() const { std::cout << "Mammal przeszedl jeden krok\n"; } 9: void Move(int distance) const 10: { 11: std::cout << "Mammal przeszedl "; 12: std::cout << distance <<" kroki.\n"; 13: } 14: protected: 15: int itsAge; 16: int itsWeight; 17: }; 18: 19: class Dog : public Mammal 20: { 21: public: 22: // Moesz zauway ostrzeenie, e ukrywasz funkcj! 23: void Move() const { std::cout << "Dog przeszedl 5 krokow.\n"; } 24: }; 25: 26: int main() 27: { 28: Mammal bigAnimal; 29: Dog fido; 30: bigAnimal.Move(); 31: bigAnimal.Move(2); 32: fido.Move(); 33: // fido.Move(10); 34: return 0; 35: } Usunito: wartoci Usunito: iem Usunito: jest

Wynik
Mammal przeszedl jeden krok Mammal przeszedl 2 kroki.

Dog przeszedl 5 krokow.

Analiza Z tych klas zostay usunite wszystkie dodatkowe metody i dane. W liniach 8. i 9. klasa Mammal deklaruje przecione metody Move(). W linii 23. klasa Dog przesania wersj metody Move(), ktra nie posiada adnych parametrw. Metody te s wywoywane w liniach od 30. do 32., za w wynikach widzimy, e zostay one wykonane. Linia 33. zostaa jednak wykomentowana, gdy powoduje powstanie bdu kompilacji. Klasa Dog mogaby wywoywa metod Move(int), gdyby nie przesonia wersji metody Move() bez parametrw. Poniewa jednak metoda ta zostaa przesonita, konieczne jest przesonicie obu wersji. W przeciwnym razie metody, ktre nie zostay przesonite, s ukrywane. Przypomina to regu, wedug ktrej w momencie dostarczenia przez nas jakiegokolwiek konstruktora, kompilator nie dostarcza ju konstruktora domylnego. Oto obowizujca regua: Gdy przesonisz jakkolwiek z przecionych metod, wszystkie inne przecienia tej metody zostaj ukryte. Jeli nie chcesz ich ukrywa, musisz przesoni je wszystkie. Ukrycie metody klasy bazowej, gdy chcemy j przesoni, jest do czstym bdem; wynika on z tego, e nie zastosowalimy sowa kluczowego const. Sowo kluczowe const stanowi cz sygnatury i pominicie go powoduje zmian sygnatury, a wic ukrycie metody, a nie przesonicie jej.
Przesanianie a ukrywanie
Usunito: Cho Usunito: k Usunito: , Usunito: jednak p Usunito: w tym celu

W nastpnym podrozdziale zajmiemy si metodami wirtualnymi. Przesonicie metody wirtualnej umoliwia uzyskanie polimorfizmu, za ukrycie jej uniemoliwia polimorfizm. Wicej informacji na ten temat uzyskasz ju wkrtce.

Wywoywanie metod klasy bazowej


Jeli przesonie metod klasy bazowej, wci moesz j wywoywa, uywajc penej kwalifikowanej nazwy metody. W tym celu zapisuje si nazw klasy bazowej, dwa dwukropki oraz nazw metody. Na przykad: Mammal::Move(). Istnieje moliwo przepisania linii 33. z listingu 12.6 tak, aby mona j byo skompilowa:
33: fido.Mammal::Move(10);

Taka linia wywouje metod klasy Mammal w sposb jawny. Proces ten w peni ilustruje listing 12.7. Listing 12.7. Wywoywanie metody bazowej z metody przesonitej
0: 1: 2: 3: //Listing 12.7 Wywoywanie metody bazowej z metody przesonitej #include <iostream> using namespace std;

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

class Mammal { public: void Move() const { cout << "Mammal przeszedl jeden krok\n"; void Move(int distance) const { cout << "Mammal przeszedl " << distance; cout << " kroki.\n"; } protected: int itsAge; int itsWeight; }; class Dog : public Mammal { public: void Move()const; }; void Dog::Move() const { cout << "W metodzie Move klasy dog...\n"; Mammal::Move(3); } int main() { Mammal bigAnimal; Dog fido; bigAnimal.Move(2); fido.Mammal::Move(6); return 0; }

Wynik
Mammal przeszedl 2 kroki. Mammal przeszedl 6 kroki.

Analiza W linii 35. tworzony jest obiekt bigAnimal klasy Mammal, za w linii 36. jest tworzony obiekt fido klasy Dog. Metoda wywoywana w linii 37. wykonuje metod Move() klasy Mammal, przyjmujc pojedynczy argument typu int. Programista chcia wywoa metod Move(int) obiektu klasy Dog, ale mia problem. W klasie Dog zostaa przesonita metoda Move(), lecz nie zostaa przesonita wersja tej metody z parametrem typu int. Rozwiza ten problem, jawnie wywoujc metod Move(int) klasy bazowej, w linii 38. TAK NIE

Rozszerzaj funkcjonalno testowanych klas, stosujc wyprowadzanie. Zmieniaj zachowanie okrelonych funkcji w klasie wyprowadzonej przez przesanianie metod klasy bazowej.

Nie ukrywaj metod klasy bazowej przez zmian sygnatury metod w klasie pochodnej.

Metody wirtualne
W tym rozdziale sygnalizujemy fakt, i obiekt klasy Dog jest obiektem klasy Mammal. Jak dotd oznaczao to jedynie, e obiekt Dog dziedziczy atrybuty (dane) oraz moliwoci (metody) swojej klasy bazowej. Jednak w jzyku C++ relacja jest-czym siga nieco gbiej. C++ rozszerza swj polimorfizm, pozwalajc, by wskanikom do klas bazowych przypisywane byy wskaniki do obiektw klas pochodnych. Zatem mona napisa:
Mammal* pMammal = new Dog;

W ten sposb tworzymy na stercie nowy obiekt klasy Dog, za otrzymany do niego wskanik przypisujemy wskanikowi do obiektw klasy Mammal. Jest to poprawne, gdy pies (ang. dog) jest ssakiem (ang. mammal).
UWAGA Na tym wanie polega polimorfizm. Na przykad, moesz stworzy wiele rodzajw okien, np. okna dialogowe, okna przewijane lub listy, kademu z nich przydzielajc wirtualn metod draw() (rysuj). Tworzc wskanik do okna i przypisujc okna dialogowe i inne wyprowadzone typy temu wskanikowi, moesz wywoywa metod draw(); bez wzgldu na biecy typ obiektu, na ktry on wskazuje. Za kadym zostanie wywoania waciwa funkcja draw().

Moesz uy tego wskanika do wywoania dowolnej metody klasy Mammal. Z pewnoci jednak bardziej spodoba ci si, e zostan wywoane waciwe metody przesaniajce z klasy Dog. Proces ten umoliwiaj funkcje wirtualne. Mechanizm ten ilustruje listing 12.8, pokazuje on take, co si dzieje z metodami, ktre nie s wirtualne. Listing 12.8. Uywanie metod wirtualnych
0: //Listing 12.8 Uywanie metod wirtualnych 1: 2: #include <iostream> 3: using std::cout; 4: 5: class Mammal 6: { 7: public: 8: Mammal():itsAge(1) { cout << "Konstruktor klasy Mammal...\n"; }

Usunito: onite metody

9: } 10: }

virtual ~Mammal() { cout << "Destruktor klasy Mammal...\n"; void Move() const { cout << "Mammal przeszedl jeden krok\n";

11: virtual void Speak() const { cout << "Metoda Speak klasy Mammal\n"; } 12: protected: 13: int itsAge; 14: 15: }; 16: 17: class Dog : public Mammal 18: { 19: public: 20: Dog() { cout << "Konstruktor klasy Dog...\n"; } 21: virtual ~Dog() { cout << "Destruktor klasy Dog...\n"; } 22: void WagTail() { cout << "Macham ogonkiem...\n"; } 23: void Speak()const { cout << "Hau!\n"; } 24: void Move()const { cout << "Dog przeszedl 5 krokow...\n"; } 25: }; 26: 27: int main() 28: { 29: 30: Mammal *pDog = new Dog; 31: pDog->Move(); 32: pDog->Speak(); 33: 34: return 0; 35: }

Wynik
Konstruktor klasy Mammal... Konstruktor klasy Dog... Mammal przeszedl jeden krok Hau!

Analiza W linii 11. zostaa zadeklarowana wirtualna metoda klasy Mammal metoda Speak(). Projektant tej klasy sygnalizuje w ten sposb, e oczekuje, i ta klasa moe by typem bazowym innej klasy. Klasa pochodna najprawdopodobniej zechce przesoni t funkcj. W linii 30. tworzony jest wskanik (pDog) do klasy Mammal, lecz jest mu przypisywany adres nowego obiektu klasy Dog. Poniewa obiekt klasy Dog jest obiektem klasy Mammal, przypisanie to jest poprawne. Nastpnie wskanik ten jest uywany do wywoania funkcji Move(). Poniewa kompilator wie tylko, e pDog jest wskanikiem do klasy Mammal, zaglda do obiektu tej klasy w celu znalezienia metody Move(). W linii 32. za pomoc tego wskanika zostaje wywoana metoda Speak(). Poniewa metoda ta jest metod wirtualn, wywoana zostaje przesonita metoda Speak() z klasy Dog. To prawie magia. Funkcja wywoujca wie tylko, i posiada wskanik do obiektu klasy Mammal, a mimo to zostaje wywoana metoda klasy Dog. W rzeczywistoci, gdybymy mieli tablic wskanikw do klasy Mammal, a kady z nich wskazywaby obiekt klasy wyprowadzonej z tej
Usunito: przesonita w Usunito: ie

klasy, moglibymy wywoywa je po kolei, a wywoywane funkcje byyby waciwe. Proces ten ilustruje listing 12.9. Listing 12.9. Wywoywanie wielu funkcji wirtualnych
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: //Listing 12.9 Wywoywanie wielu funkcji wirtualnych #include <iostream> using namespace std; class Mammal { public: Mammal():itsAge(1) { } virtual ~Mammal() { } virtual void Speak() const { cout << "Ssak mowi!\n"; } protected: int itsAge; }; class Dog : public Mammal { public: void Speak()const { cout << "Hau!\n"; } }; class Cat : public Mammal { public: void Speak()const { cout << "Miau!\n"; } }; class Horse : public Mammal { public: void Speak()const { cout << "Ihaaa!\n"; } }; class Pig : public Mammal { public: void Speak()const { cout << "Kwik!\n"; } }; int main() { Mammal* theArray[5]; Mammal* ptr; int choice, i; for ( i = 0; i<5; i++) { cout << "(1)pies (2)kot (3)kon (4)swinia: "; cin >> choice; switch (choice) { case 1: ptr = new Dog; break; case 2: ptr = new Cat; break; case 3: ptr = new Horse;

Usunito: j Usunito: zostayby

57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68:

break; case 4: ptr = new Pig; break; default: ptr = new Mammal; break; } theArray[i] = ptr; } for (i=0;i<5;i++) theArray[i]->Speak(); return 0; }

Wynik
(1)pies (2)kot (1)pies (2)kot (1)pies (2)kot (1)pies (2)kot (1)pies (2)kot Hau! Miau! Ihaaa! Kwik! Ssak mowi! (3)kon (3)kon (3)kon (3)kon (3)kon (4)swinia: (4)swinia: (4)swinia: (4)swinia: (4)swinia: 1 2 3 4 5

Analiza Ten skrcony program, w ktrym pozostawiono jedynie najistotniejsze czci kadej z klas, przedstawia funkcje wirtualne w ich najczystszej postaci. Zadeklarowane zostay cztery klasy: Dog, Cat, Horse (ko) oraz Pig (winia), wszystkie wyprowadzone z klasy Mammal. W linii 10. funkcja Speak() klasy Mammal zostaa zadeklarowana jako metoda wirtualna. Metoda ta zostaje przesonita we wszystkich czterech klasach pochodnych, w liniach 18., 25., 32. i 38. Uytkownik jest proszony o wybranie obiektu, ktry ma zosta stworzony, po czym w liniach od 47. do 64. do tablicy zostaj dodane wskaniki.
UWAGA W czasie kompilacji nie ma moliwoci sprawdzenia, ktre obiekty zostan stworzone i ktre wersje metody Speak() zostan wywoane. Wskanik ptr jest przypisywany do obiektu ju podczas wykonywania programu. Nazywa si to wizaniem dynamicznym (dokonywanym podczas dziaania programu), w przeciwiestwie do wizania statycznego (dokonywanego podczas kompilacji).

Usunito: 5 Usunito: y Usunito: maj by

Czsto zadawane pytanie

Jeli oznacz metod skadow jako wirtualn w klasie bazowej, to czy musz oznacza j jako wirtualn take w klasach pochodnych?

Odpowied: Nie, jeli oznaczysz metod jako wirtualn, a potem przesonisz j w klasie pochodnej, pozostanie ona w dalszym cigu wirtualna. Jednak warto oznaczy j jako wirtualn (cho nie jest to konieczne) dziki temu kod jest atwiejszy do zrozumienia.

Jak dziaaj funkcje wirtualne


Gdy tworzony jest obiekt klasy pochodnej, na przykad obiekt klasy Dog, najpierw wywoywany jest konstruktor klasy bazowej, a nastpnie konstruktor klasy pochodnej. Rysunek 12.2 pokazuje, jak wyglda obiekt klasy Dog po utworzeniu go. Zwr uwag, e cz Mammal obiektu jest w pamici spjna z czci Dog. Rys. 12.2. Obiekt klasy Dog po utworzeniu

Gdy w obiekcie tworzona jest funkcja wirtualna, jest ona ledzona przez ten obiekt. Wiele kompilatorw buduje tablic funkcji wirtualnych, nazywan v-table. Dla kadego typu przechowywana jest jedna taka tablica, za kady obiekt tego typu przechowuje do niej wskanik (nazywany vptr lub v-pointer). Cho implementacje rni si od siebie, wszystkie kompilatory musz wykonywa te same czynnoci, dlatego przedstawiony poniej opis nie bdzie odbiega od rzeczywistoci. Wskanik vptr kadego z obiektw wskazuje na tablic v-table, ktra z kolei zawiera wskaniki do kadej z funkcji wirtualnych. (Uwaga: wskaniki do funkcji zostan szerzej omwione w rozdziale 15., Specjalne klasy i funkcje). Gdy tworzona jest cz Mammal klasy Dog, wskanik vptr jest inicjalizowany tak, by wskazywa waciw cz tablicy v-table, jak pokazano na rysunku 12.3. Rys. 12.3. V-table klasy Mammal

Gdy zostaje wywoany konstruktor klasy Dog i dodawana jest cz Dog tego obiektu, wskanik vptr jest modyfikowany tak, by wskazywa na przesonicia funkcji wirtualnych (o ile istniej) w klasie Dog (patrz rysunek 12.4). Rys. 12.4. V-table klasy Dog

Gdy uywany jest wskanik do klasy Mammal, wskanik vptr cay czas wskazuje waciw funkcj, w zalenoci od rzeczywistego typu obiektu. W chwili wywoania metody Speak() wywoywana jest waciwa funkcja.
Usunito: amt

Nie moesz przej std dotd


Gdyby obiekt klasy Dog mia metod (na przykad WagTail()), ktra nie wystpowaaby w klasie Mammal, w celu odwoania si do tej metody nie mgby uy wskanika do klasy Mammal (chyba e jawnie rzutowaby ten wskanik do klasy Dog). Poniewa WagTail() nie jest funkcj wirtualn i poniewa nie wystpuje ona w klasie Mammal, nie moesz jej uy, nie posiadajc obiektu klasy Dog lub wskanika do klasy Dog. Cho moesz przeksztaci wskanik do klasy Mammal we wskanik do klasy Dog, istniej duo lepsze i bezpieczniejsze sposoby wywoania metody WagTail(). C++ odradza jawne rzutowanie (konwersj) typw, gdy jest ono podatne na bdy. Ten problem zostanie omwiony przy okazji rozwaa na temat wielokrotnego dziedziczenia w rozdziale 15. oraz przy omawianiu szablonw w rozdziale 20., Wyjtki i obsuga bdw.

Usunito: tu Usunito: w

Okrajanie
Zwr uwag, e funkcje wirtualne dziaaj tylko w przypadku wskanikw i referencji. Przekazanie obiektu przez warto uniemoliwia wywoanie funkcji wirtualnych. Problem ten ilustruje listing 12.10. Listing 12.10. Okrajanie (przycicie) danych podczas przekazywania przez warto
0: //Listing 12.10 Okrajanie danych podczas przekazywania przez warto 1: 2: #include <iostream> 3: 4: class Mammal 5: { 6: public:

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

Mammal():itsAge(1) { } virtual ~Mammal() { } virtual void Speak() const { std::cout << "Ssak mowi!\n"; } protected: int itsAge; }; class Dog : public Mammal { public: void Speak()const { std::cout << "Hau!\n"; } }; class Cat : public Mammal { public: void Speak()const { std::cout << "Miau!\n"; } }; void ValueFunction (Mammal); void PtrFunction (Mammal*); void RefFunction (Mammal&); int main() { Mammal* ptr=0; int choice; while (1) { bool fQuit = false; std::cout << "(1)pies (2)kot (0)Wyjscie: "; std::cin >> choice; switch (choice) { case 0: fQuit = true; break; case 1: ptr = new Dog; break; case 2: ptr = new Cat; break; default: ptr = new Mammal; break; } if (fQuit) break; PtrFunction(ptr); RefFunction(*ptr); ValueFunction(*ptr); } return 0; } void ValueFunction (Mammal MammalValue) { MammalValue.Speak(); } void PtrFunction (Mammal * pMammal) { pMammal->Speak(); }

68: 69: 70: 71:

void RefFunction (Mammal & rMammal) { rMammal.Speak(); }

Wynik
(1)pies (2)kot (0)Wyjscie: 1 Hau! Hau! Ssak mowi! (1)pies (2)kot (0)Wyjscie: 2 Miau! Miau! Ssak mowi! (1)pies (2)kot (0)Wyjscie: 0

Analiza W liniach od 4. do 24. s deklarowane okrojone klasy Mammal, Dog oraz Cat. Deklarowane s trzy funkcje PtrFunction(), RefFunction() oraz ValueFunction(). Przyjmuj one, odpowiednio, wskanik do obiektu klasy Mammal, referencj do takiego obiektu oraz sam obiekt Mammal. Wszystkie trzy funkcje robi to samo: wywouj metod Speak(). Uytkownik jest proszony o wybranie albo Pies albo Kot i, w zalenoci od tego wyboru, w liniach od 42. do 45. tworzony jest wskanik do odpowiedniego typu (Dog lub Cat). W pierwszej linii wydruku uytkownik wybra klas Dog. Obiekt klasy Dog zosta utworzony na stercie (w linii 42.). Nastpnie nowo utworzony obiekt jest przekazywany wspomnianym wyej trzem funkcjom poprzez wskanik, referencj i warto. Wskanik i referencje wywouj funkcje wirtualne, zatem zostaje wywoana funkcja skadowa Dog::Speak(). Obrazuj to nastpne dwie linie wydruku (po wyborze dokonanym przez uytkownika). Wyuskany wskanik jest jednak przekazywany poprzez warto. Funkcja oczekuje obiektu klasy
Mammal, wic kompilator okraja obiekt klasy Dog, pozostawiajc jedynie cz stanowic obiekt klasy Mammal. W tym momencie wywoywana jest wic metoda Speak() klasy Mammal,
Usunito: klasy Dog Usunito: lub Cat Usunito: Usunito: niku

Usunito: nikw

co odzwierciedla trzecia linia wydruku (po wyborze dokonanym przez uytkownika).

Usunito: niku

Ten eksperyment zostaje nastpnie powtrzony dla klasy Cat; uzyskalimy podobne rezultaty.

Destruktory wirtualne
Dozwolone, i do czsto stosowane, jest przekazywanie wskanika do wyprowadzonego obiektu tam, gdzie oczekiwany jest wskanik do klasy bazowej. Co si dzieje, gdy taki wskanik do wyprowadzonej klasy zostaje usunity? Jeli destruktor jest wirtualny, a taki powinien by, nastpuje to, co powinno : zostaje wywoywany destruktor klasy wyprowadzonej. Poniewa destruktor klasy wyprowadzonej automatycznie wywouje destruktor klasy bazowej, cay obiekt zostanie zniszczony. Obowizuje tu nastpujca zasada: jeli jakiekolwiek funkcje w klasie s wirtualne, destruktor take powinien by wirtualny.
Usunito: dziej Usunito: si waciwe rzeczy

Wirtualne konstruktory kopiujce


Konstruktory nie mog by wirtualne, wic nie istnieje co takiego, jak wirtualny konstruktor kopiujcy. Zdarza si jednak, e musimy przekaza wskanik do obiektu bazowego i otrzyma kopi waciwie utworzonego obiektu klasy wyprowadzonej. Powszechnie stosowanym rozwizaniem tego problemu jest stworzenie metody o nazwie Clone() (klonuj) w klasie bazowej i uczynienie jej metod wirtualn. Metoda Clone() tworzy i zwraca now kopi obiektu biecej klasy. Poniewa metoda Clone() zostaje przesonita w kadej z klas pochodnych, tworzona jest kopia klasy wyprowadzonej. Ilustruje to listing 12.11. Listing 12.11. Wirtualny konstruktor kopiujcy
0: //Listing 12.11 Wirtualny konstruktor kopiujcy 1: 2: #include <iostream> 3: using namespace std; 4: 5: class Mammal 6: { 7: public: 8: Mammal():itsAge(1) { cout << "Konstruktor klasy Mammal...\n"; } 9: virtual ~Mammal() { cout << "Destruktor klasy Mammal...\n"; } 10: Mammal (const Mammal & rhs); 11: virtual void Speak() const { cout << "Ssak mowi!\n"; } 12: virtual Mammal* Clone() { return new Mammal(*this); } 13: int GetAge()const { return itsAge; } 14: protected: 15: int itsAge; 16: }; 17: 18: Mammal::Mammal (const Mammal & rhs):itsAge(rhs.GetAge()) 19: { 20: cout << "Konstruktor kopiujacy klasy Mammal...\n"; 21: } 22: 23: class Dog : public Mammal 24: { 25: public: 26: Dog() { cout << "Konstruktor klasy Dog...\n"; } 27: virtual ~Dog() { cout << "Destruktor klasy Dog...\n"; } 28: Dog (const Dog & rhs); 29: void Speak()const { cout << "Hau!\n"; } 30: virtual Mammal* Clone() { return new Dog(*this); } 31: }; 32: 33: Dog::Dog(const Dog & rhs): 34: Mammal(rhs) 35: { 36: cout << "Konstruktor kopiujacy klasy Dog...\n"; 37: } 38: 39: class Cat : public Mammal 40: { 41: public: 42: Cat() { cout << "Konstruktor klasy Cat...\n"; } 43: ~Cat() { cout << "Destruktor klasy Cat...\n"; }

Usunito: i Usunito: y Usunito: i

Usunito: i Usunito: i

Usunito: i

Usunito: i

44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86:

Cat (const Cat &); void Speak()const { cout << "Miau!\n"; } virtual Mammal* Clone() { return new Cat(*this); } }; Cat::Cat(const Cat & rhs): Mammal(rhs) { cout << "Konstruktor kopiujacy klasy Cat...\n"; } enum ANIMALS { MAMMAL, DOG, CAT}; const int NumAnimalTypes = 3; int main() { Mammal *theArray[NumAnimalTypes]; Mammal* ptr; int choice, i; for ( i = 0; i<NumAnimalTypes; i++) { cout << "(1)dog (2)cat (3)Mammal: "; cin >> choice; switch (choice) { case DOG: ptr = new Dog; break; case CAT: ptr = new Cat; break; default: ptr = new Mammal; break; } theArray[i] = ptr; } Mammal *OtherArray[NumAnimalTypes]; for (i=0;i<NumAnimalTypes;i++) { theArray[i]->Speak(); OtherArray[i] = theArray[i]->Clone(); } for (i=0;i<NumAnimalTypes;i++) OtherArray[i]->Speak(); return 0; }

Usunito: i

Wynik
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: (1)dog (2)cat (3)Mammal: 1 Konstruktor klasy Mammal... Konstruktor klasy Dog... (1)dog (2)cat (3)Mammal: 2 Konstruktor klasy Mammal... Konstruktor klasy Cat... (1)dog (2)cat (3)Mammal: 3 Konstruktor klasy Mammal... Hau! Konstruktor kopiujacy klasy Mammal... Konstruktor kopiujacy klasy Dog... Miau! Konstruktor kopiujacy klasy Mammal...

Usunito: i Usunito: i Usunito: i

14: 15: 16: 17: 18: 19:

Konstruktor kopiujacy klasy Cat... Ssak mowi! Konstruktor kopiujacy klasy Mammal... Hau! Miau! Ssak mowi!

Usunito: i Usunito: i

Analiza Listing 12.11 jest bardzo podobny do dwch poprzednich listingw, z wyjtkiem tego, i tym razem do klasy Mammal zostaa dodana nowa wirtualna metoda, o nazwie Clone(). Ta metoda zwraca wskanik do nowego obiektu klasy Mammal poprzez wywoanie konstruktora kopiujcego, przekazujc sam siebie (*this) jako sta (const) referencj. Metoda Clone() zostaa przeciona zarwno w klasie Dog, jak i w klasie Cat, w ktrych inicjalizuje dane tych klas i przekazuje kopie samych siebie do swoich wasnych konstruktorw kopiujcych. Poniewa metoda Clone() jest wirtualna, w efekcie otrzymujemy wirtualny konstruktor kopiujcy, co pokazano w linii 81. Uytkownik jest proszony o wybranie klasy Dog, Cat lub Mammal, a w liniach od 62. do 74. tworzony jest odpowiedni obiekt. Wskanik dla kadego z wyborw jest umieszczany w tablicy w linii 75. Gdy program przechodzi przez kolejne elementy tablicy, wywouje metod Speak() i Clone() kadego ze wskazywanych obiektw (w liniach 80. i 81.). Rezultatem wywoania metody Clone() jest wskanik do kopii obiektu, ktry w linii 81 jest przechowywany w drugiej tablicy. W pierwszej linii wydruku uytkownik wybiera pierwsz opcj, tworzc klas Dog. Wywoywane s konstruktory klasy Mammal oraz klasy Dog. Czynno t powtarza si dla klas Cat oraz Mammal w liniach wynikw od 4. do 8. Linia 9. wynikw reprezentuje wywoanie metody Speak() pierwszego obiektu, nalecego do klasy Dog. Wywoywana jest wirtualna metoda Speak(), zatem zostaje wywoana metoda waciwej klasy. Nastpnie wywoywana jest metoda Clone(), a poniewa ona take jest wirtualna, w efekcie zostaje wywoana metoda Clone() klasy Dog. Powoduje to wywoanie konstruktora klasy Mammal oraz konstruktora kopiujcego klasy Dog. Te same czynnoci powtarza si dla klasy Cat w liniach wynikw od 12. do 14., a nastpnie dla klasy Mammal w liniach 15. i 16. Na zakoczenie program przechodzi przez drug tablic, w ktrej dla kadego z nowych obiektw zostaje wywoana metoda Speak().
Usunito: niku

Usunito: i

Usunito: i Usunito: i

Usunito: nikw

Usunito: i

Koszt metod wirtualnych


Poniewa obiekty zawierajce metody wirtualne musz przechowywa tablice funkcji wirtualnych (v-table), z posiadaniem metod wirtualnych wie si pewne obcienie. Jeli posiadasz bardzo ma klas, z ktrej nie zamierzasz wyprowadza innych klas, by moe nie ma powodu umieszczania w niej jakichkolwiek metod wirtualnych. Gdy zadeklarujesz ktr z metod jako wirtualn, poniesiesz ju wikszo kosztw posiadania tablicy funkcji wirtualnych (cho kada z kolejnych funkcji wirtualnych take powoduje pewne niewielkie obcienie pamici). Powiniene take posiada wirtualny destruktor (zakadajc take,

e wszystkie inne metody rwnie najprawdopodobniej bd wirtualne). Dokadnie przyjrzyj si kadej z niewirtualnych metod i upewnij si e, czy wiesz, dlaczego nie s one wirtualne. TAK Gdy spodziewasz si, e bdziesz wyprowadza nowe klasy z klasy, ktr wanie tworzysz, uyj metod wirtualnych. Jeli ktrakolwiek z metod jest wirtualna, uyj take wirtualnego destruktora. NIE Nie oznaczaj konstruktora jako funkcji wirtualnej.

Rozdzia 13. Tablice i listy poczone


W poprzednich rozdziaach deklarowalimy pojedyncze obiekty typu int, char, i tym podobne. Czsto chcemy jednak deklarowa zbiory obiektw, takie jak 20 wartoci typu int czy kilka obiektw typu CAT. Z tego rozdziau dowiesz si: czym s tablice i jak si je deklaruje, czym s acuchy i jak je tworzy za pomoc tablic znakw, jaki jest zwizek pomidzy tablicami a wskanikami, w jaki sposb posugiwa si arytmetyk na wskanikach odnoszcych si do tablic.

Czym jest tablica?


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

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

Elementy tablicy
Do kadego z elementw tablicy moemy si odwoa, podajc przesunicie (ang. offset) wzgldem nazwy tablicy. Elementy tablicy s liczone od zera. Tak wic pierwszym elementem tablicy jest arrayName[0]. W przykadzie z tablic LongArray, pierwszym elementem jest LongArray[0], drugim LongArray[1], itd. Moe to by nieco mylce. Tablica SomeArray[3] zawiera trzy elementy. S to: SomeArray[0], SomeArray[1] oraz SomeArray[2]. Tablica SomeArray[n] zawiera n elementw ponumerowanych od SomeArray[0] do SomeArray[n-1]. Elementy tablicy LongArray[25] s ponumerowane od LongArray[0] do LongArray[24]. Listing 13.1 przedstawia sposb zadeklarowania tablicy piciu wartoci cakowitych i wypenienia jej wartociami. Listing 13.1. Uycie tablicy wartoci cakowitych
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: //Listing 13.1 - Tablice #include <iostream> int main() { int myArray[5]; int i; for ( i=0; i<5; i++) // 0-4 { std::cout << "Wartosc elementu myArray[" << i << "]: "; std::cin >> myArray[i]; } for (i = 0; i<5; i++) std::cout << i << ": " << myArray[i] << "\n"; return 0; } Usunito: u

Wynik
Wartosc elementu myArray[0]: 3 Wartosc elementu myArray[1]: 6 Wartosc elementu myArray[2]: 9

Wartosc elementu myArray[3]: 12 Wartosc elementu myArray[4]: 15 0: 3 1: 6 2: 9 3: 12 4: 15

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

Zapisywanie poza koniec tablicy


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

Listing 13.2. Zapis za kocem tablicy


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

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

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

0 0 0 0

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

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

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

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

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

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

stworzysz tak sam tablic, jak w poprzednim przykadzie. Jeli chcesz zna rozmiar tablicy, moesz poprosi kompilator, aby go dla ciebie obliczy. Na przykad
const USHORT IntegerArrayLength = sizeof(IntegerArray) / sizeof(IntegerArray[0]);

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

spowoduje bd kompilacji, gdy zostaa zadeklarowana tablica picioelementowa, a my prbujemy zainicjalizowa sze elementw. Mona natomiast napisa
int IntegerArray = {10, 20};

TAK Pozwl, by kompilator sam okrela rozmiar inicjalizowanych tablic. Nadawaj tablicom znaczce nazwy, tak jak wszystkim innym zmiennym. Pamitaj, e pierwszy element tablicy ma offset (przesunicie) wynoszcy zero.

NIE Nie dokonuj zapisw za kocem tablicy.

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

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

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

Wynik
Wartoscia Wtorku jest: 30

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

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

Przykad 1
int MyIntegerArray[90];

Przykad 2
long * ArrayOfPointersToLong[8];

Aby odwoa si do elementw tablicy, uyj operatora indeksu.

Przykad 1
int theNinethInteger = MyIntegerArray[8];

Przykad 2
long * pLong = ArrayOfPointersToLongs[8];

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

Tablice obiektw
W tablicach mona przechowywa dowolne obiekty, zarwno wbudowane, jak i te zdefiniowane przez uytkownika. Deklarujc tablic, informujesz kompilator o typie przechowywanych obiektw oraz iloci obiektw, dla jakiej ma zosta zaalokowane miejsce. Kompilator, na podstawie deklaracji klasy, zna ilo miejsca zajmowanego przez kady z obiektw. Klasa musi posiada domylny konstruktor nie posiadajcy argumentw (aby obiekty mogy zosta stworzone podczas definiowania tablicy). Na proces dostpu do danych skadowych w tablicy obiektw skadaj si dwa kroki. Waciwy element tablicy jest wskazywany za pomoc operatora indeksu ([]), po czym dodawany jest operator skadowej (.), wydzielajcy okrelon zmienn skadow obiektu. Listing 13.4 demonstruje sposb tworzenia i wykorzystania tablicy piciu obiektw typu CAT. Listing 13.4. Tworzenie tablicy obiektw
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: // Listing 13.4 - Tablica obiektw #include <iostream> using namespace std; class CAT { public: CAT() { itsAge = 1; itsWeight=5; } ~CAT() {} int GetAge() const { return itsAge; } int GetWeight() const { return itsWeight; } void SetAge(int age) { itsAge = age; } private: int itsAge; int itsWeight; }; int main() { CAT Litter[5]; int i; for (i = 0; i < 5; i++) Litter[i].SetAge(2*i +1); for (i = 0; i < 5; i++) { cout << "Kot nr " << i+1<< ": "; cout << Litter[i].GetAge() << endl; } return 0;

32:

Wynik
Kot Kot Kot Kot Kot nr nr nr nr nr 1: 2: 3: 4: 5: 1 3 5 7 9

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

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

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

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


SQUARE Board[64];

Nie odpowiadaoby to jednak rzeczywistej, dwuwymiarowej planszy. Na pocztku gry krl umieszczany jest na czwartej pozycji w pierwszym rzdzie; tej pozycji odpowiada:
Board[0][3];

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

Inicjalizowanie tablic wielowymiarowych


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

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

W celu uzyskania lepszej przejrzystoci, moesz pogrupowa wartoci, uywajc nawiasw klamrowych. Na przykad:
int theArray[5][3] = { { 1, 2, 3}, { 4, 5, 6}, { 7, 8, 9}, {10,11,12}, {13,14,15} };

Kompilator ignoruje wewntrzne nawiasy klamrowe (cho uatwiaj one uytkownikowi zrozumienie sposobu uoenia wartoci). Kada warto musi by oddzielona przecinkiem, bez wzgldu na stosowanie nawiasw klamrowych. Cay zestaw inicjalizacyjny musi by ujty w nawiasy klamrowe i koczy si rednikiem. Listing 13.5 tworzy tablic dwuwymiarow. Pierwszy wymiarem (czyli pierwsz kolumn) jest zestaw liczb od zera do cztery. Drugi wymiar (czyli drug kolumn) stanowi liczby o wartociach dwukrotnie wikszych ni wartoci w kolumnie pierwszej. Listing 13.5. Tworzenie tablicy wielowymiarowej
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: // Listing 13.5 - Tworzenie tablicy wielowymiarowej #include <iostream> using namespace std; int main() { int SomeArray[5][2] = { {0,0}, {1,2}, {2,4}, {3,6}, {4,8}}; for (int i = 0; i<5; i++) for (int j=0; j<2; j++) { cout << "SomeArray[" << i << "][" << j << "]: "; cout << SomeArray[i][j]<< endl; } } return 0; Usunito: m Usunito: zawiera podwojon Usunito: kadej z wartoci w pierwszym wymiarze.

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

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

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

Kilka sw na temat pamici


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

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

lepsze wykorzystanie pamici, rozmiar tablicy zosta zwikszony z piciu do piciuset elementw, za jej nazwa zostaa zmieniona z Litter (miot) na Family (rodzina). Listing 13.6. Tablica wskanikw do obiektw
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: // Listing 13.6 - Tablica wskanikw do obiektw #include <iostream> using namespace std; class CAT { public: CAT() { itsAge = 1; itsWeight=5; } ~CAT() {} // destruktor int GetAge() const { return itsAge; } int GetWeight() const { return itsWeight; } void SetAge(int age) { itsAge = age; } private: int itsAge; int itsWeight; }; int main() { CAT * Family[500]; int i; CAT * pCat; for (i = 0; i < 500; i++) { pCat = new CAT; pCat->SetAge(2*i +1); Family[i] = pCat; } for (i = 0; i < 500; i++) { cout << "Kot nr " << i+1 << ": "; cout << Family[i]->GetAge() << endl; } return 0; }

Wynik
Kot Kot Kot ... Kot Kot nr 1: 1 nr 2: 3 nr 3: 5 nr 499: 997 nr 500: 999

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

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

Deklarowane tablic na stercie


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

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

// // // //

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

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

Wskanik do tablicy a tablica wskanikw


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

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

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

Wskaniki a nazwy tablic


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

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

Uywanie nazw tablic jako staych wskanikw (i odwrotnie) jest dozwolone. Tak wic Family
+ 4 jest poprawnym sposobem odwoania si do danych w elemencie Family[4].

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

Listing 13.7. Tworzenie tablicy za pomoc operatora new


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: // Listing 13.7 - Tablica utworzona na stercie #include <iostream> class CAT { public: CAT() { itsAge = 1; itsWeight=5; } ~CAT(); int GetAge() const { return itsAge; } int GetWeight() const { return itsWeight; } void SetAge(int age) { itsAge = age; } private: int itsAge; int itsWeight; }; CAT :: ~CAT() { // cout << "Wywolano destruktor!\n"; } int main() { CAT * Family = new CAT[500]; int i; for (i = 0; i < 500; i++) { Family[i].SetAge(2*i +1); } for (i = 0; i < 500; i++) { std::cout << "Kot nr " << i+1 << ": "; std::cout << Family[i].GetAge() << std::endl; } delete [] Family; } return 0;

Wynik
Kot Kot Kot ... Kot Kot nr 1: 1 nr 2: 3 nr 3: 5 nr 499: 997 nr 500: 999

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

Usuwanie tablic ze sterty


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

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

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

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

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

Powiniene zwrci uwag na dwa elementy w tej konstrukcji: zamiast pojedynczych znakw ujtych w apostrofy, oddzielonych przecinkami i ujtych w nawiasy klamrowe, wpisuje si ujty w cudzysowy acuch w stylu C, bez przecinkw i bez nawiasw klamrowych, nie ma potrzeby doczania znaku null, gdy kompilator docza go automatycznie.

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

" << buffer <<

Wynik
Wpisz lancuch: Hello World Oto zawartosc bufora: Hello

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

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

Usunito: jest

Domylnym znakiem koczcym jest znak nowej linii. Uycie tej metody ilustruje listing 13.9. Listing 13.9. Wypenianie tablicy
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: znaku 10: 11: 12: //Listing 13.9 uycie cin.get() #include <iostream> using namespace std; int main() { char buffer[80]; cout << "Wpisz lancuch: "; cin.get(buffer, 79); // pobiera do 79 znakw lub do nowej linii cout << "Oto zawartosc bufora: " << buffer << endl; return 0; }

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

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

Usunito: dodawania Usunito: wystarczy Usunito: ej

strcpy() oraz strncpy()


C++ odziedziczyo od jzyka C bibliotek funkcji operujcych na acuchach w stylu C. Wrd wielu funkcji tego typu znajdziemy dwie funkcje suce do kopiowania jednego acucha do drugiego: strcpy() oraz strncpy(). Funkcja strcpy() kopiuje ca zawarto jednego acucha do wskazanego bufora. Jej uycie ilustruje listing 13.10.

Listing 13.10. Uycie strcpy()


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: //Listing 13.10 Uycie strcpy() #include <iostream> #include <string.h> using namespace std; int main() { char String1[] = "Zaden czlowiek nie jest samoistna wyspa"; char String2[80]; strcpy(String2,String1); cout << "String1: " << String1 << endl; cout << "String2: " << String2 << endl; return 0;

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

Analiza W linii 3. jest doczany plik nagwkowy string.h. Ten plik zawiera prototyp funkcji strcpy(). Funkcja otrzymuje dwie tablice znakw docelow oraz rdow. Gdyby tablica rdowa bya wiksza ni tablica docelowa, funkcja strcpy() dokonaaby zapisu poza koniec bufora. Aby nas przed tym zabezpieczy, biblioteka standardowa zawiera take funkcj strncpy(). Ta wersja funkcji posiada dodatkowy argument, okrelajcy maksymaln ilo znakw do skopiowania. Funkcja strncpy() kopiuje znaki, a do napotkania pierwszego znaku null albo do osignicia dozwolonej maksymalnej iloci znakw. Listing 13.11 ilustruje uycie funkcji strncpy(). Listing 13.11. Uycie funkcji strncpy()
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: //Listing 13.11 Uycie strncpy() #include <iostream> #include <string.h> int main() { const int MaxLength = 80; char String1[] = "Zaden czlowiek nie jest samoistna wyspa"; char String2[MaxLength+1]; strncpy(String2,String1,MaxLength); std::cout << "String1: " << String1 << std::endl; std::cout << "String2: " << String2 << std::endl; return 0; }

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

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

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

Dobra klasa String alokuje tylko tyle pamici, ile potrzebuje (aby zawsze wystarczao na przechowanie tego, co powinna zawiera). Jeli nie moe zaalokowa wystarczajcej iloci pamici, powinna poprawnie to zgosi. Pierwsz prb utworzenia naszej klasy String przedstawia listing 13.12. Listing 13.12. Uycie klasy String
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: //Listing 13.12 Uycie klasy String #include <iostream> #include <string.h> using namespace std; // zasadnicza klasa acucha class String { public: // konstruktory

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

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

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

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

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

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

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

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

Analiza

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

Usunito: i

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

Usunito: zielimy

Usunito: i Usunito: , Usunito: cho

Usunito: i

Usunito: i

Operator przypisania najpierw sprawdza, czy prawa strona przypisania jest taka sama, jak lewa strona. Jeli tak nie jest, biecy acuch jest usuwany, po czym w jego miejsce tworzony jest i kopiowany nowy acuch. W celu umoliwienia przypisa w rodzaju:
String1 = String2 = String3;

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

Usunito: znak

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

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

Usunito: ale

Listy poczone i inne struktury


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

W licie poczonej pojedynczo kady wze wskazuje na wze nastpny (nie wskazuje poprzedniego). Aby odszuka okrelony wze, musimy zacz od pocztku listy, tak jak w zabawie w poszukiwanie skarbw (Nastpny wze jest pod fotelem). Lista poczona

podwjnie umoliwia poruszenie si wzdu acucha wzw do przodu i do tyu. Drzewo jest zoon struktur zbudowan z wzw. Kady wze moe wskazywa w dwch lub wicej kierunkach. Te trzy podstawowe struktury przedstawia rysunek 13.5. Rys. 13.5. Listy poczone

Analiza listy poczonej


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

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

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

Copyright (C) 1998 Liberty Associates, Inc. All Rights Reserved

Demonstruje obiektowo zorientowane podejcie do list poczonych. Lista deleguje prac na wzy. Wze jest abstrakcyjnym typem danych. Uywane s trzy typy wzw: wzy gowy, wzy ogona oraz wzy wewntrzne. Tylko wzy wewntrzne przechowuj dane. Klasa data zostaa stworzona jako obiekt danych przechowywany na licie poczonej. ***********************************************

20: 21: #include <iostream> 22: using namespace std; 23: 24: enum { kIsSmaller, kIsLarger, kIsSame}; 25: 26: // Klasa danych do umieszczania w licie poczonej. 27: // Kada klasa w tej poczonej licie musi posiada dwie metody: 28: // Show (wywietla warto) a 29: // Compare (zwraca wzgldn pozycj) 30: class Data 31: { 32: public: 33: Data(int val):myValue(val){} 34: ~Data(){} 35: int Compare(const Data &); 36: void Show() { cout << myValue << endl; } 37: private: 38: int myValue; 39: }; 40: 41: // Compare jest uywane do podjcia decyzji, w ktrym 42: // miejscu listy powinien znale si dany obiekt. 43: int Data::Compare(const Data & theOtherData) 44: { 45: if (myValue < theOtherData.myValue) 46: return kIsSmaller; 47: if (myValue > theOtherData.myValue) 48: return kIsLarger; 49: else 50: return kIsSame; 51: } 52: 53: // wstpne deklaracje 54: class Node; 55: class HeadNode; 56: class TailNode; 57: class InternalNode; 58: 59: // ADT reprezentuje obiekt wza listy 60: // Kada klasa potomna musi przesoni metody Insert i Show 61: class Node 62: { 63: public: 64: Node(){} 65: virtual ~Node(){} 66: virtual Node * Insert(Data * theData)=0; 67: virtual void Show() = 0; 68: private: 69: }; 70: 71: // To jest wze przechowujcy rzeczywisty obiekt. 72: // W tym przypadku obiekt jest typu Data. 73: // Gdy poznamy szablony, dowiemy si, jak mona 74: // to uoglni. 75: class InternalNode: public Node 76: { 77: public: 78: InternalNode(Data * theData, Node * next); 79: ~InternalNode(){ delete myNext; delete myData; }

Usunito: oraz

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

139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199:

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

200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225:

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

Wynik
Jaka Jaka Jaka Jaka Jaka Jaka Jaka 2 3 5 8 9 10 wartosc? wartosc? wartosc? wartosc? wartosc? wartosc? wartosc? (0 (0 (0 (0 (0 (0 (0 aby aby aby aby aby aby aby zakonczyc): zakonczyc): zakonczyc): zakonczyc): zakonczyc): zakonczyc): zakonczyc): 5 8 3 9 2 10 0

Analiza Pierwsz rzecz, jak naley zauway, jest instrukcja wyliczeniowa definiujca trzy stae: kIsSmaller (jest mniejsze), kIsLarger (jest wiksze) oraz kIsSame (jest takie same). Kady obiekt, ktry moe by przechowywany na tej licie poczonej, musi obsugiwa metod Compare(). Te stae s zwracane wanie przez t metod. Dla przykadu, w liniach od 30. do 39. zostaa stworzona klasa Data (dane), za jej metoda Compare() zostaa zaimplementowana w liniach od 41. do 51. Obiekt typu Data przechowuje warto i moe porwnywa si z innymi obiektami Data. Oprcz tego obsuguje metod Show(), ktra wywietla warto obiektu Data. Najprostszym sposobem na zrozumienie dziaania listy poczonej jest przeanalizowanie ilustrujcego j przykadu. W linii 203. rozpoczyna si testowy program sterujcy; w linii 206.
Usunito: e Usunito: zawierajce

deklarowany jest wskanik do obiektu Data, za w linii 208. definiowana jest lokalna lista poczona. Gdy tworzona jest lista poczona, wywoywany jest jej konstruktor, zaczynajcy si w linii 192. Jedyn prac wykonywan w tym konstruktorze jest zaalokowanie obiektu HeadNode (wze gowy) i przypisanie jego adresu do wskanika przechowywanego w poczonej licie w linii 185. Tworzenie obiektu HeadNode powoduje wywoanie konstruktora klasy HeadNode zawartego w liniach od 163. do 166. To z kolei powoduje zaalokowanie obiektu TailNode (wze gowy) i przypisanie jego adresu do wskanika myNext (mj nastpny) w wle gowy. Tworzenie obiektu TailNode powoduje wywoanie konstruktora tej klasy zdefiniowanego w linii 131., ktry jest funkcj inline i nie robi nic. Dziki zwykemu zaalokowaniu listy poczonej na stosie, tworzona jest sama lista, wzy gowy i ogona oraz ustanawiane s powizania pomidzy nimi. Ilustruje to rysunek 13.6. Rys. 13.6. Lista poczona po jej utworzeniu

Usunito: mu

Linia 212. rozpoczyna ptl nieskoczon. Uytkownik jest proszony o wpisanie wartoci dodawanych do listy poczonej. Moe on doda dowoln ilo wartoci ; wpisanie wartoci 0 powoduje zakoczenie pobierania danych. Kod w linii 216. sprawdza wprowadzan warto; po natrafianiu na warto 0 powoduje on wyjcie z ptli. Jeli warto jest rna od zera, w linii 218. tworzony jest nowy obiekt typu Data, ktry w linii 219. jest wstawiany do listy. Dla zilustrowania tego przykadu zamy, e uytkownik wprowadzi warto 15. Powoduje to wywoanie metody Insert() (wstaw) w linii 198. Lista poczona (klasa LinkedList) natychmiast przenosi odpowiedzialno za wstawienie obiektu na swj wze gowy. To wywouje metod Insert() z linii 170. Z kolei wze gowy natychmiast przekazuje odpowiedzialno za wstawienie obiektu do wza wskazywanego przez skadow myNext (mj nastpny). W tym (pierwszym) przypadku, skadowa ta wskazuje na wze ogona (pamitajmy, e podczas tworzenia wza gowy zostao stworzone cze do wza ogona). Tak wic zostaje wywoana metoda Insert() z linii 142. Metoda TailNode::Insert() wie, e obiekt, ktry otrzymaa, musi by wstawiony bezporednio przed jej obiektem tj. nowy obiekt znajdzie si na licie tu przed wzem ogona. Zatem, w linii 144. tworzy ona nowy obiekt InternalNode (wze wewntrzny), przekazujc mu dane oraz wskanik do siebie. To powoduje wywoanie konstruktora klasy InternalNode, zdefiniowanego w linii 90.

Usunito: tyle wartoci, ile chce

Usunito: W Usunito: , Usunito: ona

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

Po wstawieniu pierwszego wza, sterowanie programu powraca do linii 214., gdzie wprowadzane s kolejne dane. Dla przykadu zamy, e uytkownik wpisa warto 3. Powoduje to stworzenie w linii 218. nowego obiektu typu Data, ktry jest wstawiany do listy w linii 219.

W linii 200. lista ponownie przekazuje dane do swojego wza gowy. Z kolei metoda HeadNode::Insert() przekazuje now warto do wza wskazywanego przez swj wskanik myNext. Jak wiemy, w tym momencie wskanik ten wskazuje wze zawierajcy obiekt Data o wartoci 15. Powoduje to wywoanie metody InternalNode::Insert() z linii 99. W linii 103. obiekt InternalNode uywa wskanika myData, aby dla wasnego obiektu Data (tego o wartoci 15) wywoa za pomoc otrzymanego nowego obiektu Data (tego o wartoci 3) metod Compare(). To powoduje wywoanie metody InternalNode::Compare() zdefiniowanej w linii 43. Obie wartoci zostaj porwnane, a poniewa myValue ma warto 15, za theOtherData.myValue ma warto 3, zwrcon wartoci jest kIsLarger. To powoduje, e

program przechodzi do linii 112.

Dla nowego obiektu Data tworzony jest nowy wze InternalNode. Nowy wze bdzie wskazywa na biecy obiekt InternalNode, a metoda InternalNode::Insert() zwrci do obiektu HeadNode adres nowego wza. Zatem nowy wze, ktrego warto obiektu danych jest mniejsza od wartoci obiektu danych wza biecego, zostanie wstawiony do listy przed wzem biecym. Caa lista wyglda w tym momencie tak, jak na rysunku 13.8. Rys. 13.8. Lista poczona po wstawieniu drugiego wza

W trakcie trzeciego wykonania ptli uytkownik wpisa warto 8. Jest ona wiksza od 3, ale mniejsza od 15, wic powinna by wstawiona pomidzy dwa istniejce ju wzy. Dziaanie programu bdzie podobne do przedstawionego w poprzednim przykadzie, z t rnic, e gdy dojdzie do porwnania wartoci danych z wartoci 3, zamiast zwrci sta kIsLarger, funkcja zwrci warto kIsSmaller (co oznacza, e obiekt o wartoci 3 jest mniejszy od nowego obiektu, ktrego warto wynosi 8). To spowoduje, e metoda InternalNode::Insert() przejdzie do linii 119. Zamiast tworzy nowy wze i wstawia go, obiekt InternalNode po prostu przekae nowe dane do metody Insert tego wza, na ktry wskazuje akurat jego zmienna myNext. W tym przypadku zostanie wic wywoana metoda Insert() tego obiektu InternalNode, ktrego obiektem danych jest warto 15.

Ponownie odbywa si porwnanie i tworzony jest nowy obiekt InternalNode. Bdzie wskazywa on na wze, ktrego wartoci danych jest 15, za jego adres zostanie przekazany wstecz do wza, ktrego wartoci danych jest 3 (w linii 119.). Spowoduje to, e nowy wze zostanie wstawiony we waciwe miejsce na licie. Jeli to moliwe, powiniene przeledzi w swoim debuggerze proces wstawiania kolejnych wzw. Powiniene zobaczy, jak te metody wzajemnie si wywouj i odpowiednio dostosowuj wskaniki.

Czego si nauczya, Dorotko?


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

Klasy tablic
Napisanie wasnej klasy tablicowej ma wiele zalet w porwnaniem z korzystaniem z tablic wbudowanych. Jako pocztkujcy programista, moesz zabezpieczy program przed przepenieniem tablicy. Moesz take wzi pod uwag stworzenie wasnej klasy tablicowej, dynamicznie zmieniajcej rozmiar: tu po utworzeniu mogaby ona zawiera tylko jeden element i zwiksza rozmiar w miar potrzeb, podczas dziaania programu. W przypadku, gdy zechcesz posortowa lub uporzdkowa elementy tablicy w jaki inny sposb, moesz wykorzysta kilka rnych przydatnych odmian tablic. Do najpopularniejszych z nich nale:
Usunito: y Usunito: y

zbir uporzdkowany (ordered collection): kady element jest uoony w odpowiedniej kolejnoci, zestaw (set): kady element wystpuje tylko raz, sownik (dictionary): wykorzystuje on dopasowane do siebie pary, w ktrych jedna warto peni rol klucza sucego do pobierania drugiej wartoci, rzadka tablica (sparse array): umoliwia uywanie bardzo szerokiego zakresu indeksw, ale pami zajmuj tylko te elementy, ktre rzeczywicie zostay dodane do tablicy. Moesz poprosi o element SparseArray[5] lub SparseArray[200], a mimo to pami zostanie zaalokowana tylko dla niewielkiej iloci elementw, torba (bag): nieuporzdkowany zbir, ktrego elementy s dodawane i zwracane w przypadkowej kolejnoci.

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

Rozdzia 14. Polimorfizm


Z rozdziau 12. dowiedziae si, jak pisa funkcje wirtualne w klasach wyprowadzonych. Jest to jedna z podstawowych umiejtnoci potrzebnych przy posugiwaniu si polimorfizmem, czyli moliwoci przypisywania ju podczas dziaania programu specyficznych obiektw klas pochodnych do wskanikw wskazujcych na obiekty klasy bazowej. Z tego rozdziau dowiesz si: czym jest dziedziczenie wielokrotne i jak z niego korzysta, czym jest dziedziczenie wirtualne, czym s abstrakcyjne typy danych, czym s czyste funkcje wirtualne.

Problemy z pojedynczym dziedziczeniem


Przypumy, e od pewnego czasu pracujemy z naszymi klasami zwierzt i e podzielilimy hierarchi klas na ptaki (Bird) i ssaki (Mammal). Klasa Bird posiada funkcj skadow Fly() (latanie). Klasa Mammal zostaa podzielona na rne rodzaje ssakw, midzy innymi na klas Horse (ko). Klasa Horse posiada funkcje skadowe Whinny() (renie) oraz Gallop() (galopowanie). Nagle okazuje si, e potrzebujemy obiektu pegaza (Pegasus): skrzyowania konia z ptakiem. Pegasus moe lata (metoda Fly()), ale take moe re (Whinny()) i galopowa (Gallop()). Przy dziedziczeniu pojedynczym okazuje si, e jestemy w kropce. Moemy uczyni z pegaza obiekt klasy Bird, ale wtedy nie bdzie mg re ani galopowa. Moemy zrobi z niego obiekt Horse, ale wtedy nie bdzie mg lata. Pierwsz prb rozwizania tego problemu moe by skopiowanie metody Fly() do klasy Pegasus i wyprowadzenie tej klasy z klasy Horse. Bdzie to prawidowa operacja, przeprowadzona jednak kosztem posiadania metody Fly() w dwch miejscach (w klasach Bird i Pegasus). Gdy zmienisz j w jednym miejscu, musisz pamita o wprowadzeniu modyfikacji

take w drugim. Oczywicie, programista, ktry kilka miesicy czy lat pniej sprbuje zmodyfikowa taki kod, take musi wiedzie o obu miejscach. Wkrtce jednak pojawia si nowy problem. Chcemy stworzy list obiektw typu Horse oraz list obiektw typu Bird. Chcielibymy doda obiekt klasy Pegasus do dowolnej z tych list, ale gdyby Pegasus zosta wyprowadzony z klasy Horse, nie moglibymy go doda do listy obiektw klasy Bird. Istnieje kilka rozwiza tego problemu. Moemy zmieni nazw metody Gallop() na Move() (ruch), a nastpnie przesoni metod Move() w klasie Pegasus tak, aby wykonywaa prac metody Fly(). Nastpnie przesonilibymy metod Move() innych koni tak, aby wykonywaa prac metody Gallop(). By moe pegaz byby inteligentny na tyle, by galopowa na krtkich dystansach, a lata tylko na duszych:
Pegasus::Move(long distance) { if (distance > veryFar) Fly(distance); else Gallop(distance); }

To rozwizanie posiada jednak pewne ograniczenia. By moe ktrego dnia pegaz zechce lata na krtkich dystansach lub galopowa na duszych. Nastpnym rozwizaniem mogoby by przeniesienie metody Fly() w gr, do klasy Horse, co zostao pokazane na listingu 14.1. Problem jednak polega na tym, i zwyke konie nie potrafi lata, wic w przypadku koni innych ni pegaz, ta metoda nie bdzie nic robi. Listing 14.1. Gdyby konie umiay lata...
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: class Horse { public: void Gallop(){ cout << "Galopuje...\n"; } virtual void Fly() { cout << "Konie nie potrafia latac.\n" ; } private: int itsAge; #include <iostream> using namespace std; // Listing 14.1. Gdyby konie umiay lata... // Przeniesienie metody Fly() do klasy Horse

13: 14: 15: 16: 17:

};

class Pegasus : public Horse { public:

18: virtual void Fly() {cout<<"Moge latac! Moge latac! Moge latac!\n";} 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: } } return 0; } cout << "\n"; for (i=0; i<NumberHorses; i++) { Ranch[i]->Fly(); delete Ranch[i]; else pHorse = new Horse; Ranch[i] = pHorse; const int NumberHorses = 5; int main() { Horse* Ranch[NumberHorses]; Horse* pHorse; int choice,i; for (i=0; i<NumberHorses; i++) { cout << "(1)Horse (2)Pegasus: "; cin >> choice; if (choice == 2) pHorse = new Pegasus; };

Wynik
(1)Horse (2)Pegasus: 1 (1)Horse (2)Pegasus: 2

(1)Horse (2)Pegasus: 1 (1)Horse (2)Pegasus: 2 (1)Horse (2)Pegasus: 1 Konie nie potrafia latac. Moge latac! Moge latac! Moge latac! Konie nie potrafia latac. Moge latac! Moge latac! Moge latac! Konie nie potrafia latac.

Analiza Ten program oczywicie dziaa, ale kosztem posiadania przez klas Horse metody Fly(). Metoda Fly() dla klasy Horse jest zdefiniowana w linii 10. W rzeczywistej klasie mogaby po prostu wywietla komunikat bdu lub po cichu zakoczy dziaanie. W linii 18. klasa Pegasus przesania metod Fly() tak, aby wykonywaa waciw prac, w tym przypadku polegajc na wypisywaniu radosnego komunikatu. Tablica wskanikw do klasy Horse, zadeklarowana w linii 24., suy do zademonstrowania, e waciwa metoda Fly()zostaje wywoana w zalenoci od tego, czy zosta stworzony obiekt klasy Horse lub klasy Pegasus.
UWAGA Pokazany tutaj przykad zosta bardzo okrojony, do elementw niezbdych dla zrozumienia zasad jego dziaania. Konstruktory, wirtualne destruktory i tak dalej, zostay usunite w celu uatwienia analizy kodu.

Przenoszenie w gr
Przenoszenie podanej funkcji w gr hierarchii klas jest powszechnym rozwizaniem tego typu problemw; powoduje jednak, e w klasie bazowej wystpuje wiele funkcji nadmiarowych. Istnieje niebezpieczestwo, e klasa bazowa stanie si globaln przestrzeni nazw dla wszystkich funkcji, ktre mogyby by uyte w klasach potomnych. Moe to znacznie wpyn na efektywno zarzdzania typami w C++ i powodowa zbytni rozrost i skomplikowanie klas bazowych. Chcemy przenie funkcjonalno w gr hierarchii, ale bez rwnoczesnego przenoszenia interfejsu kadej z klas. Oznacza to, e jeli dwie klasy posiadaj wspln klas bazow (na przykad klasy Horse i Bird pochodz od klasy Animal) i posiadaj wspln funkcj (zarwno konie, jak i ptaki odywiaj si), powinnimy przenie t cech w gr, do klasy bazowej i stworzy z niej funkcj wirtualn. Powinnimy unika przy tym przenoszenia interfejsu (tak, jak przeniesienie metody Fly() tam, gdzie nie powinno jej by) tylko w celu wywoywania danej funkcji w niektrych z klas wyprowadzonych.

Rzutowanie w d
Alternatyw dla przedstawionego wczeniej rozwizania (nie wykluczajc korzystania z pojedynczego dziedziczenia), jest zatrzymanie metody Fly() wewntrz klasy Pegasus i wywoywanie jej tylko wtedy, gdy wskanik do obiektu rzeczywicie wskazuje obiekt klasy Pegasus. Aby sposb ten mg dziaa, musimy mie moliwo zapytania wskanika, jaki typ faktycznie wskazuje. Nazywa si to identyfikacj typw podczas wykonywania programu (RTTI, Run Time Type Identification). Korzystanie z RTTI stao si oficjalnym elementem jzyka C++ dopiero od niedawna. Jeli kompilator nie obsuguje RTTI, moemy symulowa t obsug, umieszczajc w kadej z klas metod zwracajc jedn z wyliczeniowych staych. Moemy nastpnie sprawdza typ podczas dziaania programu i wywoywa metod Fly() tylko wtedy, gdy ta metoda zwrci sta dla typu Pegasus.
UWAGA Bd ostrony z RTTI. Korzystanie z tego mechanizmu moe by oznak saboci projektu programu. Zamiast tego uyj funkcji wirtualnych, wzorcw lub wielokrotnego dziedziczenia.

Aby mc wywoa metod Fly(), musimy dokona rzutowania wskanika, informujc kompilator, e wskazywany obiekt jest obiektem typu Pegasus, a nie obiektem typu Horse. Nazywa si to rzutowaniem w d, gdy obiekt Horse rzutujemy w d hierarchii, do typu bardziej wyprowadzonego. Dzi C++ ju oficjalnie, cho do niechtnie, obsuguje rzutowanie w d za pomoc nowego operatora dynamic_cast. Oto sposb jego dziaania: Jeli mamy wskanik do klasy bazowej, takiej jak Horse, i przypiszemy mu adres obiektu klasy wyprowadzonej, takiej jak Pegasus, moemy uywa wskanika do klasy Horse polimorficznie. Jeli chcemy nastpnie odwoa si do obiektu klasy Pegasus, tworzymy wskanik do tej klasy i w celu dokonania konwersji uywamy operatora dynamic_cast. W czasie dziaania programu nastpi sprawdzenie wskanika do klasy bazowej. Jeli konwersja bdzie waciwa, nowy wskanik do klasy Pegasus bdzie poprawny. Jeli konwersja bdzie niewaciwa (nie bdzie to wskanik do klasy Pegasus), nowy wskanik bdzie pusty (null). Ilustruje to listing 14.2. Listing 14.2. Rzutowanie w d
0: 1: 2: 3: 4: 5: 6: 7: 8: class Horse enum TYPE { HORSE, PEGASUS }; #include <iostream> using namespace std; // Listing 14.2 Uycie operatora dynamic_cast. // Using rtti

9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45:

{ public: virtual void Gallop(){ cout << "Galopuje...\n"; }

private: int itsAge; };

class Pegasus : public Horse { public:

virtual void Fly() {cout<<"Moge latac! Moge latac! Moge latac!\n";} };

const int NumberHorses = 5; int main() { Horse* Ranch[NumberHorses]; Horse* pHorse; int choice,i; for (i=0; i<NumberHorses; i++) { cout << "(1)Horse (2)Pegasus: "; cin >> choice; if (choice == 2) pHorse = new Pegasus; else pHorse = new Horse; Ranch[i] = pHorse; } cout << "\n"; for (i=0; i<NumberHorses; i++) { Pegasus *pPeg = dynamic_cast< Pegasus *> (Ranch[i]); if (pPeg) pPeg->Fly();

46: 47: 48: 49: 50: 51: 52: } }

else cout << "Po prostu kon\n";

delete Ranch[i];

return 0;

Wynik
(1)Horse (2)Pegasus: 1 (1)Horse (2)Pegasus: 2 (1)Horse (2)Pegasus: 1 (1)Horse (2)Pegasus: 2 (1)Horse (2)Pegasus: 1 Po prostu kon Moge latac! Moge latac! Moge latac! Po prostu kon Moge latac! Moge latac! Moge latac! Po prostu kon

Analiza Ten sposb rwnie okaza si dobry. Metoda Fly() zostaa utrzymana poza klas Horse i nie jest wywoywana dla obiektw typu Horse. Jednak w przypadku wywoywania jej dla obiektw klasy Pegasus, musi by stosowane rzutowanie jawne; obiekty klasy Horse nie posiadaj metody Fly(), wic musimy poinformowa kompilator, e wskanik wskazuje na klas Pegasus. Potrzeba rzutowania obiektu klasy Pegasus jest ostrzeeniem, e program moe by le zaprojektowany. Taki program znacznie obnia uyteczno polimorfizmu funkcji wirtualnych, gdy podczas dziaania jest zaleny od rzutowania obiektu do jego rzeczywistego typu.
Czsto zadawane pytanie

Podczas kompilacji za pomoc kompilatora Visual C++ Microsoftu otrzymuj ostrzeenie: warning C4541: 'dynamic_cast' used on polymorphic type 'class Horse' with /GR-; unpredictable behavior may result. Co powinienem zrobi?
Usunito: w

Odpowied: Jest to jeden z najbardziej kopotliwych komunikatw o bdach. Aby si go pozby, wykonaj nastpujce kroki:

1. W swoim projekcie wybierz polecenie Project | Settings.

2. Przejd na zakadk C++.

3. Z listy rozwijanej wybierz pozycj C++ Language.

4. Wcz opcj Enable Runtime Type Information (RTTI).

5. Zbuduj cay projekt ponownie.

Poczenie dwch list


Inny problem z podanymi wyej rozwizaniami polega na tym, e zadeklarowalimy obiekt Pegasus jako obiekt typu Horse, wic nie moemy doda obiektu Pegasus do listy obiektw Bird. Stracilimy albo poprzez przeniesienie metody Fly() w gr albo poprzez rzutowanie wskanika w d, a mimo to wci nie osignlimy penej funkcjonalnoci. Pojawia si jeszcze jedno, ostatnie rozwizanie, rwnie wykorzystujce pojedyncze dziedziczenie. Moemy umieci metody Fly(), Whinny() oraz Gallop() w klasie bazowej wsplnej zarwno dla klas Bird, jak i Horse: w klasie Animal. Teraz, zamiast osobnej listy ptakw i listy koni, moemy mie jedn, zunifikowan list zwierzt. Sposb ten dziaa, ale powoduje jeszcze wiksze przeniesienie specjalnych funkcji w gr do klas bazowych. Moemy rwnie pozostawi te metody tam, gdzie s i rzutowa w d obiekty klas Horse, Bird i
Pegasus, ale to jeszcze gorsze rozwizanie!
Usunito: tymi Usunito: oszc Usunito: Usunito: ujc

TAK Przeno funkcjonalno w gr hierarchii dziedziczenia. Unikaj przeczania na podstawie typu obiektu sprawdzanego podczas dziaania programu uywaj metod wirtualnych, wzorcw oraz wielokrotnego dziedziczenia.

NIE Nie przeno interfejsw w gr hierarchii dziedziczenia. Nie rzutuj wskanikw do obiektw bazowych w d, do obiektw wyprowadzonych.

Dziedziczenie Wielokrotne
Istnieje moliwo wyprowadzenia nowej klasy z wicej ni jednej klasy bazowej. Nazywa si to dziedziczeniem wielokrotnym. Aby wyprowadzi klas z wicej ni jednej klasy bazowej, w nagwku klasy musimy oddzieli kad z klas bazowych przecinkami. Listing 14.3 pokazuje sposb zadeklarowania klasy Pegasus jako pochodzcej zarwno od klasy Horse, jak i klasy Bird. Nastpnie program dodaje obiekty klasy Pegasus do list obu typw. Listing 14.3. Dziedziczenie wielokrotne
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: }; } private: int itsWeight; class Bird { public: Bird() { cout << "Konstruktor klasy Bird... "; } virtual ~Bird() { cout << "Destruktor klasy Bird... "; } virtual void Chirp() const { cout << "Cwir, cwir... "; virtual void Fly() const { cout << "Moge latac! Moge latac! Moge latac! "; } }; class Horse { public: Horse() { cout << "Konstruktor klasy Horse... "; } virtual ~Horse() { cout << "Destruktor klasy Horse... "; } virtual void Whinny() const { cout << "Ihaaa!... "; } private: int itsAge; #include <iostream> using std::cout; using std::cin; // Listing 14.3. Dziedziczenie wielokrotne. // Dziedziczenie wielokrotne

30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: } } for (i=0; i<MagicNumber; i++) { cout << "\n(1)Bird (2)Pegasus: "; cin >> choice; if (choice == 2) pBird = new Pegasus; else pBird = new Bird; Aviary[i] = pBird; const int MagicNumber = 2; int main() { Horse* Ranch[MagicNumber]; Bird* Aviary[MagicNumber]; Horse * pHorse; Bird * pBird; int choice,i; for (i=0; i<MagicNumber; i++) { cout << "\n(1)Horse (2)Pegasus: "; cin >> choice; if (choice == 2) pHorse = new Pegasus; else pHorse = new Horse; Ranch[i] = pHorse; }; class Pegasus : public Horse, public Bird { public: void Chirp() const { Whinny(); } Pegasus() { cout << "Konstruktor klasy Pegasus... "; } ~Pegasus() { cout << "Destruktor klasy Pegasus... "; }

67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: } } return 0; for (i=0; i<MagicNumber; i++) { cout << "\nAviary[" << i << "]: " ; Aviary[i]->Chirp(); Aviary[i]->Fly(); delete Aviary[i]; } cout << "\n"; for (i=0; i<MagicNumber; i++) { cout << "\nRanch[" << i << "]: " ; Ranch[i]->Whinny(); delete Ranch[i];

Wynik
(1)Horse (2)Pegasus: 1 Konstruktor klasy Horse... (1)Horse (2)Pegasus: 2 Konstruktor klasy Horse... Konstruktor klasy Bird... Konstruktor klasy Pegasus... (1)Bird (2)Pegasus: 1 Konstruktor klasy Bird... (1)Bird (2)Pegasus: 2 Konstruktor klasy Horse... Konstruktor klasy Bird... Konstruktor klasy Pegasus... Ranch[0]: Ihaaa!... Destruktor klasy Horse... Ranch[1]: Ihaaa!... Destruktor klasy Pegasus... Bird... Destruktor klasy Horse... Destruktor klasy

Aviary[0]: Cwir, cwir... Moge latac! Moge latac! Moge latac! Destruktor klasy Bird...

Aviary[1]: Ihaaa!... Moge latac! Moge latac! Moge latac! Destruktor klasy Pegasus... Destruktor klasy Bird... Destruktor klasy Horse...

Analiza W liniach od 7. do 15. zostaa zadeklarowana klasa Horse. Jej konstruktor i destruktor wypisuje komunikat, za metoda Whinny() wypisuje komunikat Ihaaa!. W liniach od 17. do 29. zostaa zadeklarowana klasa Bird. Oprcz konstruktora i destruktora, ta klasa posiada dwie metody: Chirp() (wierkanie) oraz Fly(). Obie te metody wypisuj odpowiednie komunikaty. W rzeczywistym programie mogyby na przykad uaktywnia gonik lub wywietla animowane sekwencje. Na zakoczenie, w liniach od 31. do 37. zostaa zadeklarowana klasa Pegasus. Dziedziczy ona zarwno po klasie Horse, jak i klasie Bird. Klasa Pegasus przesania metod Chirp() tak, aby zostaa wywoana metoda Whinny(), odziedziczona po klasie Horse. Tworzone s dwie tablice: w linii 42. tablica Ranch (ranczo) ze wskanikami do klasy Horse oraz w linii 43. tablica Aviary (ptaszarnia) ze wskanikami do klasy Bird. W liniach od 47. do 56. do tablicy Ranch s dodawane obiekty klas Horse i Pegasus. W liniach od 57. do 66. do tablicy Aviary s dodawane obiekty klas Bird i Pegasus. Wywoania metod wirtualnych zarwno dla wskanikw do obiektw klasy Bird, jak i obiektw klasy Horse, dziaaj poprawnie take dla obiektw klasy Pegasus. Na przykad, w linii 79., elementy tablicy Aviary s uywane do wywoania metody Chirp() wskazywanych przez nie obiektw. Klasa Bird deklaruje t metod jako wirtualn, wic dla kadego obiektu wywoywana jest waciwa funkcja. Zwr uwag, e za kadym razem, gdy tworzony jest obiekt Pegasus, wyniki odzwierciedlaj, e tworzone s take czci tego obiektu nalece tak do klasy Bird, jak i Horse. Gdy obiekt Pegasus jest niszczony, niszczone s take czci obiektu nalece do klas Bird oraz Horse, a to dziki temu, e destruktor take zosta zamieniony na wirtualny.
Usunito: oraz

Deklarowanie dziedziczenia wielokrotnego

Deklarowanie obiektu dziedziczcego z wicej ni jednej klasy bazowej polega na umieszczeniu po nazwie tworzonej klasy dwukropka i rozdzielonej przecinkami listy klas bazowych.

Przykad 1
class Pegasus : public Horse, public Bird

Przykad 2
class Schnoodle : Public Schnauzer, public Poodle

Czci obiektu z dziedziczeniem wielokrotnym


Gdy w pamici tworzony jest obiekt Pegasus, na cz tego obiektu skadaj si obie klasy bazowe, co ilustruje rysunek 14.1. Rys. 14.1. Obiekt klasy z wielokrotnym dziedziczeniem

W przypadku obiektw posiadajcych kilka klas bazowych, pojawia si kilka zagadnie. Na przykad, co si stanie, gdy dwie klasy bazowe maj dane lub funkcje wirtualne o tych samych nazwach? Jak s inicjalizowane konstruktory klas bazowych? Co si dzieje, gdy rne klasy bazowe dziedzicz z tej samej klasy? Na te pytania odpowiemy w nastpnych podrozdziaach i pokaemy take, jak dziedziczenie wielokrotne mona wykorzysta do pracy.

Konstruktory w obiektach dziedziczonych wielokrotnie


Jeli klasa Pegasus jest wyprowadzona z klas Horse oraz Bird, a kada z nich posiada konstruktory wymagajce parametrw, klasa Pegasus inicjalizuje te konstruktory po kolei. Ilustruje to listing 14.4. Listing 14.4. Wywoywanie wielu konstruktorw
0: 1: 2: 3: 4: 5: 6: 7: typedef int HANDS; enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown } ; #include <iostream> using namespace std; // Listing 14.4 // Wywoywanie wielu konstruktorw

8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: }; private: COLOR itsColor; bool itsMigration; } virtual COLOR GetColor()const { return itsColor; } virtual bool GetMigration() const { return itsMigration; } class Bird { public: Bird(COLOR color, bool migrates); virtual ~Bird() {cout << "Destruktor klasy Bird...\n"; virtual void Chirp()const { cout << "Cwir, cwir... "; virtual void Fly()const { cout << "Moge latac! Moge latac! Moge latac! "; } } } Horse::Horse(COLOR color, HANDS height): itsColor(color),itsHeight(height) { cout << "Konstruktor klasy Horse...\n"; }; class Horse { public: Horse(COLOR color, HANDS height); virtual ~Horse() { cout << "Destruktor klasy Horse...\n"; } virtual void Whinny()const { cout << "Ihaaa!... "; } virtual HANDS GetHeight() const { return itsHeight; } virtual COLOR GetColor() const { return itsColor; } private: HANDS itsHeight; COLOR itsColor;

45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: int main() { Pegasus *pPeg = new Pegasus(Red, 5, true, 10); { cout << "Konstruktor klasy Pegasus...\n"; } Pegasus::Pegasus( COLOR aColor, HANDS height, bool migrates, long NumBelieve): Horse(aColor, height), Bird(aColor, migrates), itsNumberBelievers(NumBelieve) }; private: long itsNumberBelievers; } class Pegasus : public Horse, public Bird { public: void Chirp()const { Whinny(); } Pegasus(COLOR, HANDS, bool,long); ~Pegasus() {cout << "Destruktor klasy Pegasus...\n";} virtual long GetNumberBelievers() const { return itsNumberBelievers; } Bird::Bird(COLOR color, bool migrates): itsColor(color), itsMigration(migrates) { cout << "Konstruktor klasy Bird...\n";

82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: }

pPeg->Fly(); pPeg->Whinny(); cout << "\nTwoj Pegaz ma " << pPeg->GetHeight(); cout << " dloni wysokosci i "; if (pPeg->GetMigration()) cout << "migruje."; else cout << "nie migruje."; cout << "\nLacznie " << pPeg->GetNumberBelievers(); cout << " osob wierzy ze on istnieje.\n"; delete pPeg; return 0;

Wynik
Konstruktor klasy Horse... Konstruktor klasy Bird... Konstruktor klasy Pegasus... Moge latac! Moge latac! Moge latac! Ihaaa!... Twoj Pegaz ma 5 dloni wysokosci i migruje. Lacznie 10 osob wierzy ze on istnieje. Destruktor klasy Pegasus... Destruktor klasy Bird... Destruktor klasy Horse...

Analiza W liniach od 9. do 20. zostaa zadeklarowana klasa Horse. Jej konstruktor wymaga dwch parametrw: jednym z nich jest staa wyliczeniowa zadeklarowana w linii 7., a drugim typ zadeklarowany w linii 6. Implementacja konstruktora zawarta w liniach od 22. do 26. po prostu inicjalizuje zmienne skadowe i wypisuje komunikat. W liniach od 28. do 44. jest deklarowana klasa Bird, za implementacja jej konstruktora znajduje si w liniach od 46. do 50. Take konstruktor tej klasy wymaga podania dwch parametrw. Co ciekawe, konstruktor klasy Horse wymaga podania koloru (dziki czemu moemy reprezentowa konie o rnych kolorach), tak jak wymaga go konstruktor klasy Bird (co pozwala mu na reprezentowanie ptakw o rnym kolorze upierzenia). W momencie zapytania pegaza o jego kolor powstaje problem, co zreszt zobaczymy w nastpnym przykadzie. Sama klasa Pegasus zostaa zadeklarowana w liniach od 52. do 56., za jej konstruktor znajduje si w liniach od 67. do 77. Inicjalizacja obiektu tej klasy skada si z trzech instrukcji. Po pierwsze, konstruktor klasy Horse jest inicjalizowany kolorem i wysokoci. Po drugie,

konstruktor klasy Bird jest inicjalizowany kolorem i wartoci logiczn. Na koniec inicjalizowana jest zmienna skadowa klasy Pegasus, itsNumberBelievers (ilo wierzcych). Gdy proces ten zostanie zakoczony, wywoywane jest cia konstruktora obiektu Pegasus. W funkcji main() tworzony jest wskanik do obiektu klasy Pegasus i zostaje on uyty do wywoania funkcji skadowych klas bazowych tego obiektu.

Eliminowanie niejednoznacznoci
Na listingu 14.4 zarwno klasa Horse, jak i klasa Bird posiaday metod GetColor() (pobierz kolor). Moemy poprosi obiekt Pegasus o podanie swojego koloru, ale bdziemy mieli wtedy problem klasa Pegasus dziedziczy zarwno po klasie Bird, jak i klasie Horse. Obie te klasy posiadaj kolor, za metody odczytywania koloru tych klas maj te same nazwy i sygnatury. To powoduje niejednoznaczno, niezrozumia dla kompilatora. Sprbujemy rozwiza ten problem. Jeli napiszemy po prostu:
COLOR currentColor = pPeg->GetColor();

otrzymamy bd kompilatora:
Member is ambiguous: 'Horse::GetColor' and 'Bird::GetColor' (skadowa jest niejednoznaczna: 'Horse::GetColor' i 'Bird::GetColor')

Moemy zlikwidowa t niejednoznaczno przez jawne wywoanie funkcji, ktrej chcemy uy:
COLOR currentColor = pPeg->Horse::GetColor();

Za kadym razem, gdy chcemy okreli klas uywanej funkcji lub danej skadowej, moemy uy penej kwalifikowanej nazwy, poprzedzajc nazw skadowej nazw klasy bazowej. Zwr uwag, e gdyby klasa Pegasus przesonia t funkcj, pytanie o kolor zostaoby przesunite tak, jak powinno, do funkcji skadowej klasy Pegasus:
virtual COLOR GetColor()const { return Horse::GetColor(); } Usunito: problem Usunito: y

To powoduje ukrycie problemu przed klientami klasy Pegasus i ukrywa wewntrz tej klasy wiedz o tym, z ktrej klasy bazowej obiekt chce odczyta swj kolor. Klient jednak w dalszym cigu moe wymusi odczytanie koloru z danej klasy, piszc:

COLOR currentColor = pPeg->Bird::GetColor();

Dziedziczenie ze wsplnej klasy bazowej


Co si stanie, gdy zarwno klasa Bird, jak i klasa Horse dziedziczy ze wsplnej klasy bazowej, takiej jak klasa Animal? Sytuacja wyglda podobnie do przedstawionej na rysunku 14.2. Rys. 14.2. Wsplne klasy bazowe

Jak wida na rysunku 14.2, istniej dwa obiekty klasy bazowej. Gdy nastpuje odwoanie do danej lub metody we wsplnej klasie bazowej, powstaje kolejna niejednoznaczno. Na przykad, jeli klasa Animal deklaruje itsAge jako swoj zmienn skadow i GetAge() jako swoj funkcj skadow, i jeli wywoamy pPeg->GetAge(), to czy chodzi nam o wywoanie funkcji GetAge() odziedziczonej od klasy Animal poprzez klas Horse, czy poprzez klas Bird? T niejednoznaczno take musimy usun, tak jak ilustruje listing 14.5. Listing 14.5. Wsplne klasy bazowe
0: 1: 2: // Listing 14.5 // Wsplne klasy bazowe

Usunito: po czym

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

#include <iostream> using namespace std;

typedef int HANDS; enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown } ;

class Animal { public: Animal(int);

// wsplna klasa dla klasy Bird i Horse

virtual ~Animal() { cout << "Destruktor klasy Animal...\n"; } virtual int GetAge() const { return itsAge; } virtual void SetAge(int age) { itsAge = age; } private: int itsAge; };

Animal::Animal(int age): itsAge(age) { cout << "Konstruktor klasy Animal...\n"; }

class Horse : public Animal { public: Horse(COLOR color, HANDS height, int age); virtual ~Horse() { cout << "Destruktor klasy Horse...\n"; } virtual void Whinny()const { cout << "Ihaaa!... "; } virtual HANDS GetHeight() const { return itsHeight; } virtual COLOR GetColor() const { return itsColor; } protected: HANDS itsHeight; COLOR itsColor; };

Horse::Horse(COLOR color, HANDS height, int age):

40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76:

Animal(age), itsColor(color),itsHeight(height) { cout << "Konstruktor klasy Horse...\n"; }

class Bird : public Animal { public: Bird(COLOR color, bool migrates, int age); virtual ~Bird() {cout << "Destruktor klasy Bird...\n"; virtual void Chirp()const { cout << "Cwir, cwir... "; virtual void Fly()const { cout << "Moge latac! Moge latac! Moge latac! "; } virtual COLOR GetColor()const { return itsColor; } virtual bool GetMigration() const { return itsMigration; } protected: COLOR itsColor; bool itsMigration; }; } }

Bird::Bird(COLOR color, bool migrates, int age): Animal(age), itsColor(color), itsMigration(migrates) { cout << "Konstruktor klasy Bird...\n"; }

class Pegasus : public Horse, public Bird { public: void Chirp()const { Whinny(); } Pegasus(COLOR, HANDS, bool, long, int); virtual ~Pegasus() {cout << "Destruktor klasy Pegasus...\n";} virtual long GetNumberBelievers() const { return itsNumberBelievers; }

virtual COLOR GetColor()const { return Horse::itsColor; }

77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: } } { };

virtual int GetAge() const { return Horse::GetAge(); } private: long itsNumberBelievers;

Pegasus::Pegasus( COLOR aColor, HANDS height, bool migrates, long NumBelieve, int age): Horse(aColor, height,age), Bird(aColor, migrates,age), itsNumberBelievers(NumBelieve)

cout << "Konstruktor klasy Pegasus...\n";

int main() { Pegasus *pPeg = new Pegasus(Red, 5, true, 10, 2); int age = pPeg->GetAge(); cout << "Ten pegaz ma " << age << " lat.\n"; delete pPeg; return 0;

Wynik
Konstruktor klasy Animal... Konstruktor klasy Horse... Konstruktor klasy Animal... Konstruktor klasy Bird... Konstruktor klasy Pegasus... Ten pegaz ma 2 lat. Destruktor klasy Pegasus... Destruktor klasy Bird...

Destruktor klasy Animal... Destruktor klasy Horse... Destruktor klasy Animal...

Analiza Na tym listingu wystpuje kilka ciekawych elementw. W liniach od 9. do 18. zostaa zadeklarowana klasa Animal. Klasa ta posiada wasn zmienn skadow, itsAge, oraz dwa akcesory przeznaczone do jej odczytywania i ustawiania: GetAge() i SetAge(). W linii 26. rozpoczyna si deklaracja klasy Horse, wyprowadzonej z klasy Animal. Obecnie konstruktor klasy Horse posiada trzeci parametr, age (wiek), ktry przekazuje do swojej klasy bazowej. Zwrmy uwag, e klasa Horse nie przesania metody GetAge(), lecz po prostu j dziedziczy. W linii 46. rozpoczyna si deklaracja klasy Bird, take wyprowadzonej z klasy Animal. Jej konstruktor take otrzymuje wiek i uywa go do inicjalizacji swojej klasy bazowej. Oprcz tego, take w tej klasie nie jest przesaniana metoda GetAge(). Klasa Pegasus dziedziczy po klasach Bird i Horse, wic w swoim acuchu dziedziczenia posiada dwie klasy Animal. Gdybymy zechcieli wywoa funkcj GetAge() dla obiektu klasy Pegasus, musielibymy usun niejednoznaczno, czyli uy penej kwalifikowanej nazwy metody, ktrej chcielibymy uy. Problem ten zostaje rozwizany w linii 77., w ktrej klasa Pegasus przesania metod GetAge() tak, aby nie robia niczego poza przekazaniem wywoania w gr, do ktrej z metod w klasach bazowych. Przekazywanie w gr odbywa si z dwch powodw: albo w celu rozwizania niejednoznacznoci wywoania metody klasy bazowej, tak jak w tym przypadku, albo w celu wykonania pewnej pracy, a nastpnie pozwolenia, by klasa bazowa wykonaa dodatkow prac. Czasem zechcemy wykona prac i dopiero potem wywoa metod klasy bazowej, a czasem zechcemy najpierw wywoa metod klasy bazowej, a dopiero po powrocie z niej wykona swoj prac. Konstruktor klasy Pegasus posiada pi parametrw: jego kolor, wysoko (w DONIACH), zmienn okrelajc, czy migruje, ilo wierzcych w niego osb oraz wiek. Konstruktor inicjalizuje cz Horse obiektu kolorem, wysokoci i wiekiem (w linii 89.). Cz Bird inicjalizowana jest kolorem, okreleniem, czy migruje oraz wiekiem (w linii 88.). Na zakoczenie, w linii 90., konstruktor inicjalizuje swoj skadow itsNumberBelievers. Wywoanie konstruktora klasy Horse w linii 88. powoduje wywoanie jego implementacji zawartej w linii 39. Konstruktor klasy Horse uywa parametru age do zainicjalizowania czci Animal obiektu klasy Pagasus. Nastpnie inicjalizuje dwie skadowe klasy Horse itsColor oraz itsHeight. Wywoanie konstruktora klasy Bird w linii 89. wywouje jego implementacj zawart w linii 61. Take w tym przypadku parametr age jest uywany do zainicjalizowania czci Animal obiektu. Zwr uwag, e parametr koloru przekazany konstruktorowi klasy Pegasus jest uywany do zainicjalizowania zmiennych skadowych zarwno w klasie Bird, jak i klasie Horse. Zauwa

take. e parametr age jest uywany do zainicjalizowania wieku (zmiennej itsAge) w klasie Animal, otrzymanej od klasy Bird oraz w klasie Animal, otrzymanej od klasy Horse.

Dziedziczenie wirtualne
Na listingu 14.5 pokazano, jak klasa Pegasus musiaa usuwa pewne niejednoznacznoci zwizane z tym, ktra z bazowych klas Animal miaa by uyta. W wikszoci przypadkw taka decyzja moe by dowolna w kocu klasy Horse i Bird maj t sam klas bazow. Istnieje moliwo poinformowania C++, e nie chcemy mie dwch kopii wsplnej klasy bazowej, tak jak widzielimy na rysunku 14.2, ale e chcemy mie pojedyncz wspln klas bazow, tak jak pokazano na rysunku 14.3. Rys. 14.3. Dziedziczenie wirtualne

Osigniemy to, czynic z klasy Animal wirtualn klas bazow zarwno dla klasy Horse, jak i klasy Bird. Klasa Animal nie ulega adnej zmianie. W deklaracjach klas Horse i Bird przed nazw klasy Animal dodawane jest jedynie sowo kluczowe virtual. Jednak klasa Pegasus podlega ju wikszym modyfikacjom. Normalnie konstruktor klasy inicjalizuje tylko swoje wasne zmienne i swoj klas bazow. Klasy bazowe dziedziczone wirtualnie s jednak wyjtkiem. S one inicjalizowane przez swoje najbardziej wyprowadzone klasy. Tak wic klasa Animal nie bdzie inicjalizowana przez klasy

Horse i Bird, ale przez klas Pegasus. Klasy Horse i Bird musz w swoich konstruktorach inicjalizowa klas Animal, ale w przypadku tworzenia obiektu klasy Pegasus te inicjalizacje s ignorowane.

Listing 14.6 zawiera zmodyfikowan wersj listingu 14.5, wykorzystujc zalety dziedziczenia wirtualnego. Listing 14.6. Przykad uycia dziedziczenia wirtualnego
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: class Horse : virtual public Animal { public: Horse(COLOR color, HANDS height, int age); virtual ~Horse() { cout << "Destruktor klasy Horse...\n"; } virtual void Whinny()const { cout << "Ihaaa!... "; } } Animal::Animal(int age): itsAge(age) { cout << "Konstruktor klasy Animal...\n"; }; class Animal { public: Animal(int); virtual ~Animal() { cout << "Destruktor klasy Animal...\n"; } virtual int GetAge() const { return itsAge; } virtual void SetAge(int age) { itsAge = age; } private: int itsAge; // wsplna klasa dla klasy Bird i Horse typedef int HANDS; enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown } ; // Listing 14.6 // Dziedziczenie wirtualne #include <iostream> using namespace std;

31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: } }; } };

virtual HANDS GetHeight() const { return itsHeight; } virtual COLOR GetColor() const { return itsColor; } protected: HANDS itsHeight; COLOR itsColor;

Horse::Horse(COLOR color, HANDS height, int age): Animal(age), itsColor(color),itsHeight(height) { cout << "Konstruktor klasy Horse...\n";

class Bird : virtual public Animal { public: Bird(COLOR color, bool migrates, int age); virtual ~Bird() {cout << "Destruktor klasy Bird...\n"; virtual void Chirp()const { cout << "Cwir, cwir... "; virtual void Fly()const { cout << "Moge latac! Moge latac! Moge latac! "; } virtual COLOR GetColor()const { return itsColor; } virtual bool GetMigration() const { return itsMigration; } protected: COLOR itsColor; bool itsMigration; } }

Bird::Bird(COLOR color, bool migrates, int age): Animal(age), itsColor(color), itsMigration(migrates) { cout << "Konstruktor klasy Bird...\n";

class Pegasus : public Horse, public Bird

68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101:

{ public: void Chirp()const { Whinny(); } Pegasus(COLOR, HANDS, bool, long, int); virtual ~Pegasus() {cout << "Destruktor klasy Pegasus...\n";} virtual long GetNumberBelievers() const { return itsNumberBelievers; }

virtual COLOR GetColor()const { return Horse::itsColor; } private: long itsNumberBelievers; };

Pegasus::Pegasus( COLOR aColor, HANDS height, bool migrates, long NumBelieve, int age): Horse(aColor, height,age), Bird(aColor, migrates,age), Animal(age*2), itsNumberBelievers(NumBelieve) { cout << "Konstruktor klasy Pegasus...\n"; }

int main() { Pegasus *pPeg = new Pegasus(Red, 5, true, 10, 2); int age = pPeg->GetAge(); cout << "Ten pegaz ma " << age << " lat.\n"; delete pPeg; return 0; }

Wynik
Konstruktor klasy Animal...

Konstruktor klasy Horse... Konstruktor klasy Bird... Konstruktor klasy Pegasus... Ten pegaz ma 4 lat. Destruktor klasy Pegasus... Destruktor klasy Bird... Destruktor klasy Horse... Destruktor klasy Animal...

Analiza W linii 25. klasa Horse deklaruje, e dziedziczy wirtualnie po klasie Animal.T sam deklaracj zgasza klasa Bird w linii 45. Zauwa, e konstruktory obu tych klas nadal inicjalizuj obiekt Animal. Klasa Pegasus dziedziczy po klasach Bird i Horse, i z tej racji, jako najbardziej wyprowadzona klasa, musi inicjalizowa klas Animal. Podczas inicjalizacji obiektu klasy Pegasus, wywoania konstruktora klasy Animal w konstruktorach klas Bird i Horse s ignorowane. Moemy to zobaczy, gdy do konstruktora przekazywana jest warto 2, ktra w klasach Horse i Bird byaby przekazywana dalej (do Animal), natomiast w klasie Pegasus jest podwajana. Rezultat, warto 4, zosta odzwierciedlony w komunikacie wypisywanym w linii 98. i widocznym w wynikach. Klasa Pegasus nie musi ju usuwa niejednoznacznoci wywoania metody GetAge() i moe po prostu odziedziczy t funkcj od klasy Animal. Zauwa jednak, e klasa Pegasus musi w dalszym cigu usuwa niejednoznaczno wywoania metody GetColor(), gdy ta funkcja wystpuje w obu klasach bazowych, a nie w klasie Animal.
Deklarowanie klas dla dziedziczenia wirtualnego
Usunito: , za w linii 45 klasa Bird zgasza t

Usunito: za Usunito: w Usunito: jest

Aby zapewni, e wyprowadzone klasy maj tylko jeden egzemplarz wsplnej klasy bazowej, zadeklaruj klasy porednie tak, aby dziedziczyy wirtualnie po klasie bazowej.

Usunito: wirtualnie

Przykad 1
class Horse : virtual public Animal class Bird : virtual public Animal class Pegasus : public Horse, public Bird

Przykad 2
class Schnauzer : virtual public Dog class Poodle : virtual public Dog

class Schnoodle : public Schnauzer, public Poodle

Problemy z dziedziczeniem wielokrotnym


Cho dziedziczenie wielokrotne w porwniu do dziedziczenia pojedynczego posiada kilka zalet, jednak wielu programistw C++ korzysta z niego niechtnie. Tumacz to faktem, i wiele kompilatorw jeszcze go nie obsuguje, e utrudnia ono debuggowanie oraz e prawie wszystko to, co mona uzyska dziki dziedziczeniu wielokrotnemu, da si uzyska take bez niego. S to wakie powody i sam powiniene uwaa, by nie komplikowa niepotrzebnie swoich programw. Niektre debuggery nie radz sobie z dziedziczeniem wielokrotnym; zdarza si te, e wprowadzenie wielokrotnego dziedziczenia niepotrzebnie komplikuje projekt programu. TAK NIE

Uywaj dziedziczenia wielokrotnego, jeli nowa Nie uywaj dziedziczenia wielokrotnego tam, klasa wymaga funkcji i cech pochodzcych z gdzie wystarczy dziedziczenie pojedyncze. wicej ni jednej klasy bazowej. Uywaj dziedziczenia wirtualnego wtedy, gdy najbardziej wyprowadzona klasa musi posiada tylko jeden egzemplarz wsplnej klasy bazowej. Uywajc wirtualnych klas bazowych, inicjalizuj wspln klas bazow w klasie najbardziej wyprowadzonej.

Usunito: z klas.

Mixiny i klasy metod


Jednym ze sposobw uzyskania efektu poredniego pomidzy dziedziczeniem wielokrotnym a pojedynczym jest uycie tak zwanych mixinw. Moemy wic wyprowadzi klas Horse z klasy Animal oraz z klasy Displayable (moliwy do wywietlenia). Klasa Displayable bdzie posiadaa jedynie kilka metod sucych do wywietlenia dowolnego obiektu na ekranie. Mixin, czyli klasa metod, jest klas zwikszajc funkcjonalno, ale nie posiadajc wasnych danych lub posiadajc ich bardzo niewiele. Klasy metod s czone (miksowane std nazwa) z klasami wyprowadzanymi w taki sam sposb, jak wszystkie inne klasy: przez zadeklarowanie klasy wyprowadzonej jako klasy publicznie po nich dziedziczcej. Jedyn rnic midzy klas metod a inn klas jest to, e klasa metod zwykle nie zawiera danych. Jest to oczywicie rozrnienie do dowolne, ktre przypomina jedynie, e czasem jedyn rzecz, jakiej potrzebujemy, jest doczenie pewnych dodatkowych moliwoci bez komplikowania klasy wyprowadzonej.
Usunito: dodajc

Usunito: a Usunito: a Usunito: po

W przypadku pewnych debuggerw atwiej jest pracowa z mixinami ni z bardziej zoonymi obiektami dziedziczonymi wielokrotnie. Oprcz tego prawdopodobiestwo wystpienia niejednoznacznoci przy dostpie do danych w innych podstawowych klasach bazowych jest mniejsze. Na przykad, gdyby klasa Horse dziedziczya po klasach Animal i Displayable, wtedy klasa Displayable nie zawieraaby adnych danych. Klasa Animal wygldaaby tak jak zwykle, wic wszystkie dane w klasie Horse pochodziyby z klasy Animal, za funkcje skadowe pochodziyby z obu klas bazowych. Okrelenie mixin narodzio si w pewnej lodziarni w Sommerville w stanie Massachusetts, w ktrej z podstawowymi smakami lodw miksowano rne ciastka i sodycze. Dla odwiedzajcych lodziarni programistw zorientowanych obiektowo stanowio to dobr metafor, szczeglnie wtedy, gdy pracowali nad obiektowo zorientowanym jzykiem SCOOPS.

Abstrakcyjne typy danych


Czsto hierarchie klas tworzone s wsplnie. Na przykad, moemy stworzy klas Shape (ksztat), a z niej wyprowadzi klasy Rectangle (prostokt) i Circle (okrg). Z klasy Rectangle moemy wyprowadzi klas Square (kwadrat), stanowic specjalny przypadek prostokta. W kadej z wyprowadzonych klas zostanie przesonita metoda Draw() (rysuj), GetArea() (pobierz obszar), i tak dalej. Listing 14.7 ilustruje podstawowy szkielet implementacji klasy Shape i wyprowadzonych z niej klas Circle i Rectangle. Listing 14.7. Klasy ksztatw
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: class Shape { public: Shape(){} virtual ~Shape(){} virtual long GetArea() { return -1; } // bd virtual long GetPerim() { return -1; } virtual void Draw() {} #include <iostream> using std::cout; using std::cin; using std::endl; //Listing 14.7. Klasy ksztatw

15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51:

private: };

class Circle : public Shape { public: Circle(int radius):itsRadius(radius){} ~Circle(){} long GetArea() { return 3 * itsRadius * itsRadius; } long GetPerim() { return 6 * itsRadius; } void Draw(); private: int itsRadius; int itsCircumference; };

void Circle::Draw() { cout << "Procedura rysowania okregu!\n"; }

class Rectangle : public Shape { public: Rectangle(int len, int width): itsLength(len), itsWidth(width){} virtual ~Rectangle(){} virtual long GetArea() { return itsLength * itsWidth; } virtual long GetPerim() {return 2*itsLength + 2*itsWidth; } virtual int GetLength() { return itsLength; } virtual int GetWidth() { return itsWidth; } virtual void Draw(); private: int itsWidth; int itsLength; };

52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: int main() { int choice; bool fQuit = false; Shape * sp; } Square::Square(int len, int width): Rectangle(len,width) { if (GetLength() != GetWidth()) cout << "Blad, nie Square... Moze Rectangle??\n"; Square::Square(int len): Rectangle(len,len) {} }; class Square : public Rectangle { public: Square(int len); Square(int len, int width); ~Square(){} long GetPerim() {return 4 * GetLength();} } cout << "\n"; } void Rectangle::Draw() { for (int i = 0; i<itsLength; i++) { for (int j = 0; j<itsWidth; j++) cout << "x ";

89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: } } return 0; } if( !fQuit ) sp->Draw(); delete sp; sp = 0; cout << "\n"; switch (choice) { case 0: fQuit = true; while ( !fQuit ) { cout << "(1)Circle (2)Rectangle (3)Square (0)Wyjscie: "; cin >> choice;

break; case 1: sp = new Circle(5); break; case 2: sp = new Rectangle(4,6); break; case 3: sp = new Square(5); break; default: cout<<"Wpisz liczbe pomiedzy 0 a 3"<<endl; continue; break;

Wynik
(1)Circle (2)Rectangle (3)Square (0)Wyjscie: 2 x x x x x x x x x x x x x x x x x x x x x x x x

(1)Circle (2)Rectangle (3)Square (0)Wyjscie: 3 x x x x x x x x x x x x x x x x x x x x x x x x x (1)Circle (2)Rectangle (3)Square (0)Wyjscie: 0

Analiza W liniach od 7. do 16. tworzona jest klasa Shape. Metody GetArea() i GetPerim() (pobierz obwd) zwracaj warto, bdc kodem bdu, za metoda Draw() nie robi niczego. Waciwie, co to znaczy: narysowa ksztat? Mona rysowa jedynie rodzaje ksztatw (okrgi, prostokty, itd.); ksztat jako taki jest abstrakcj, ktrej nie mona narysowa. Klasa Circle jest wyprowadzona z klasy Shape i przesania jej trzy metody wirtualne. Zauwa, e nie ma powodu do stosowania sowa kluczowego virtual, gdy jest to cz ich dziedziczenia. Nie zaszkodzi jednak tego uczyni, tak jak pokazano w klasie Rectangle w liniach 43., 44. oraz 47. Dobrym nawykiem jest stosowanie sowa kluczowego virtual w celu przypomnienia (jako formy dokumentacji). Klasa Square jest wyprowadzona z klasy Rectangle; zostaa w niej przesonita metoda GetPerim(), za inne metody zostay odziedziczone od klasy Rectangle. Kopoty mog pojawi si, gdy klient sprbuje stworzy egzemplarz obiektu klasy Shape, naley mu to uniemoliwi. Klasa Shape istnieje tylko po to, by dostarcza interfejsu dla wyprowadzonych z niej klas; jako taka jest abstrakcyjnym typem danych, czyli ADT (abstract data type). Abstrakcyjny typ danych reprezentuje koncepcj (tak jak ksztat), a nie sam obiekt (na przykad okrg). W C++ ADT jest zawsze klas bazow innych klas; tworzenie egzemplarzy obiektw klas abstrakcyjnych nie jest moliwe.
Usunito: y

Czyste funkcje wirtualne


C++ obsuguje tworzenie abstrakcyjnych typw danych poprzez czyste funkcje wirtualne. Funkcja wirtualna staje si czysta, gdy zainicjalizujemy j wartoci zero, tak jak:
virtual void Draw() = 0;

Kada klasa, zawierajca jedn lub wicej czystych funkcji wirtualnych, staje si abstrakcyjnym typem danych i nie jest moliwe tworzenie jej obiektw. Prba stworzenia obiektu klasy abstrakcyjnej powoduje bd kompilacji. Umieszczenie w klasie czystej funkcji wirtualnej sygnalizuje aplikacjom-klientom tej klasy dwie rzeczy:

aby nie tworzyy obiektw tej klasy, a jedynie wyprowadzay z niej nowe klasy; aby zapewniy, e czysta funkcja wirtualna jest przesaniana.
Usunito: jeli chce Usunito: y

Kada klasa wyprowadzona z klasy abstrakcyjnej dziedziczy czyst funkcj wirtualn jako czyst, dlatego, aby mc tworzy egzemplarze obiektw, musi przesoni kad czyst funkcj wirtualn. Zatem, gdyby klasa Rectangle dziedziczya po klasie Shape i klasa Shape miaa trzy czyste funkcje wirtualne, wtedy klasa Rectangle musiaaby przesoni wszystkie trzy funkcje, gdy w przeciwnym razie sama staaby si klas abstrakcyjn. Listing 14.8 zosta przepisany tak, aby klasa Shape staa si klas abstrakcyjn. Aby zaoszczdzi miejsca, nie zostaa tu pokazana pozostaa cz listingu 14.7. Zamie deklaracj klasy Shape na listingu 14.7, linie od 7. do 16., deklaracj klasy Shape z listingu 14.8, po czym uruchom program ponownie. Listing 14.8. Abstrakcyjne typy danych
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: class Shape { public: Shape(){} ~Shape(){} virtual long GetArea() = 0; virtual long GetPerim()= 0; virtual void Draw() = 0; private: }; //Listing 14.8 Abstrakcyjne typy danych

Usunito: // bd

Wynik
(1)Circle (2)Rectangle (3)Square (0)Wyjscie: 2 x x x x x x x x x x x x x x x x x x x x x x x x (1)Circle (2)Rectangle (3)Square (0)Wyjscie: 3 x x x x x x x x x x x x x x x x x x x x x x x x x

(1)Circle (2)Rectangle (3)Square (0)Wyjscie: 0

Analiza Jak wida, dziaanie programu nie zmienio si. Jedyn rnic jest to, e teraz nie jest moliwe stworzenie obiektu klasy Shape.
Abstrakcyjne typy danych

Klasa abstrakcyjna powstaje wtedy, gdy w jej deklaracji znajdzie si jedna lub wicej czystych funkcji wirtualnych. Funkcja wirtualna staje si czysta, gdy do jej deklaracji dopiszemy = 0.

Przykad
class Shape { virtual void Draw() = 0; // czysta funkcja wirtualna };

Implementowanie czystych funkcji wirtualnych


Zwykle czyste funkcje wirtualne nie s implementowane w abstrakcyjnej klasie bazowej. Poniewa nigdy nie s tworzone obiekty tego typu, nie ma powodu do dostarczania implementacji; klasa abstrakcyjna funkcjonuje wycznie jako definicja interfejsu dla obiektw z niej wyprowadzanych. Czyste funkcje wirtualne mog by jednak implementowane. Taka funkcja moe by wywoywana przez klasy wyprowadzone z klasy abstrakcyjnej, na przykad w celu zapewnienia wsplnej funkcjoalnoci wszystkich funkcji przesonitych. Listing 14.9 stanowi reprodukcj listingu 14.7, w ktrym klasa Shape jest tym razem klas abstrakcyjn, zawierajc implementacj czystej funkcji wirtualnej Draw(). Klasa Circle przesania funkcj Draw(), gdy musi, ale potem przekazuje wywoanie do klasy bazowej, w celu skorzystania z dodatkowej funkcjonalnoci. W tym przykadzie dodatkowa funkcjonalno polega po prostu na wypisaniu dodatkowego komunikatu, ale mona sobie wyobrazi, e klasa bazowa mogaby zawiera wsplny mechanizm rysowania, ktry mgby pomc w przygotowaniu okna uywanego przez wszystkie wyprowadzone klasy. Listing 14.9. Implementowanie czystych funkcji wirtualnych
0: 1: 2: #include <iostream> //Listing 14.9 Implementowanie czystych funkcji wirtualnych

Usunito: funkcji Usunito: to

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

using namespace std;

class Shape { public: Shape(){} virtual ~Shape(){} virtual long GetArea() = 0; // bd virtual long GetPerim()= 0; virtual void Draw() = 0; private: };

void Shape::Draw() { cout << "Abstrakcyjny mechanizm rysowania!\n"; }

class Circle : public Shape { public: Circle(int radius):itsRadius(radius){} virtual ~Circle(){} long GetArea() { return 3 * itsRadius * itsRadius; } long GetPerim() { return 9 * itsRadius; } void Draw(); private: int itsRadius; int itsCircumference; };

void Circle::Draw() { cout << "Procedura rysowania okregu!\n"; Shape::Draw(); }

40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: class Square : public Rectangle { public: Square(int len); Square(int len, int width); virtual ~Square(){} long GetPerim() {return 4 * GetLength();} } cout << "\n"; } Shape::Draw(); void Rectangle::Draw() { for (int i = 0; i<itsLength; i++) { for (int j = 0; j<itsWidth; j++) cout << "x "; }; class Rectangle : public Shape { public: Rectangle(int len, int width): itsLength(len), itsWidth(width){} virtual ~Rectangle(){} long GetArea() { return itsLength * itsWidth; } long GetPerim() {return 2*itsLength + 2*itsWidth; } virtual int GetLength() { return itsLength; } virtual int GetWidth() { return itsWidth; } void Draw(); private: int itsWidth; int itsLength;

77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113:

};

Square::Square(int len): Rectangle(len,len) {}

Square::Square(int len, int width): Rectangle(len,width)

{ if (GetLength() != GetWidth()) cout << "Blad, nie Square... moze Rectangle??\n"; }

int main() { int choice; bool fQuit = false; Shape * sp;

while (1) { cout << "(1)Circle (2)Rectangle (3)Square (0)Wyjscie: "; cin >> choice;

switch (choice) { case 1: sp = new Circle(5); break; case 2: sp = new Rectangle(4,6); break; case 3: sp = new Square (5); break; default: fQuit = true; break; } if (fQuit)

114: 115: 116: 117: 118: 119: 120: 121: } }

break;

sp->Draw(); delete sp; cout << "\n";

return 0;

Wynik
(1)Circle (2)Rectangle (3)Square (0)Wyjscie: 2 x x x x x x x x x x x x x x x x x x x x x x x x Abstrakcyjny mechanizm rysowania! (1)Circle (2)Rectangle (3)Square (0)Wyjscie: 3 x x x x x x x x x x x x x x x x x x x x x x x x x Abstrakcyjny mechanizm rysowania! (1)Circle (2)Rectangle (3)Square (0)Wyjscie: 0

Analiza W liniach od 5. do 14. zostaa zadeklarowana abstrakcyjna klasa Shape, ktrej wszystkie trzy metody uytkowe zostay zadeklarowane jako czyste funkcje wirtualne. Zwr uwag, e nie jest to konieczne. Gdyby ktrakolwiek z tych metod zostaaby zadeklarowana jako czysta, i tak caa klasa byaby traktowana jako abstrakcyjna. Metody GetArea() oraz GetPerim() nie zostay zaimplementowane (w przeciwiestwie do metody Draw()). W klasach Circle i Rectangle metoda Draw() zostaa przesonita, jednake w obu przypadkach, w przesonitych wersjach jest wywoywana take metoda klasy bazowej, w celu dodatkowego skorzystania ze wsplnej funkcjonalnoci.

Usunito: i

Zoone hierarchie abstrakcji


Czasem zdarza si, e wyprowadzamy klasy abstrakcyjne z innych klas abstrakcyjnych. By moe zechcemy zmieni niektre z odziedziczonych czystych funkcji wirtualnych w zwyke funkcje, za inne pozostawi jako czyste. Jeli stworzymy klas Animal, to wszystkie metody typu Eat() (jedzenie), Sleep() (spanie), Move() (poruszanie si) i Reproduce() (rozmnaanie) moemy zmieni na czyste funkcje wirtualne. By moe, zechcesz nastpnie wyprowadzi z klasy Animal na przykad klas Mammal (ssak) lub Fish (ryba). Po duszej analizie hierarchii, moemy zdecydowa, e kady ssak bdzie rozmnaa si w ten sam sposb, wic metod Mammal::Reproduce() zamienimy w zwyk funkcj, pozostawiajc metody Eat(), Sleep() i Move() w postaci czystych funkcji wirtualnych. Z klasy Mammal wyprowadzimy klas Dog (pies), w ktrej musimy przesoni i zaimplementowa trzy pozostae czyste funkcje wirtualne tak, aby mc tworzy egzemplarze obiektw tej klasy. Jako projektanci klasy stwierdzamy fakt, e nie mona tworzy egzemplarzy klas Animal i Mammal, i e wszystkie obiekty klasy Mammal mog dziedziczy dostarczon metod Reproduce() bez koniecznoci jej przesaniania. Technik t ilustruje listing 14.10; zastosowano w nim jedynie szkieletow implementacj omawianych klas. Listing 14.10. Wyprowadzanie klas abstrakcyjnych z innych klas abstrakcyjnych
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: class Animal // wsplna klasa bazowa dla klas Mammal i Fish { public: Animal(int); virtual ~Animal() { cout << "Destruktor klasy Animal...\n"; } virtual int GetAge() const { return itsAge; } virtual void SetAge(int age) { itsAge = age; } virtual void Sleep() const = 0; virtual void Eat() const = 0; virtual void Reproduce() const = 0; virtual void Move() const = 0; virtual void Speak() const = 0; enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown } ; // Listing 14.10 // Wyprowadzanie klas abstrakcyjnych z innych klas abstrakcyjnych #include <iostream> using namespace std; Usunito: y

Usunito: wszystkie Usunito: N Usunito: moemy Usunito: wyprowadzi Usunito: y

19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55:

private: int itsAge; };

Animal::Animal(int age): itsAge(age) { cout << "Konstruktor klasy Animal...\n"; }

class Mammal : public Animal { public: Mammal(int age):Animal(age) { cout << "Konstruktor klasy Mammal...\n";} virtual ~Mammal() { cout << "Destruktor klasy Mammal...\n";} virtual void Reproduce() const { cout << "Rozmnazanie dla klasy Mammal...\n"; } };

class Fish : public Animal { public: Fish(int age):Animal(age) { cout << "Konstruktor klasy Fish...\n";} virtual ~Fish() {cout << "Destruktor klasy Fish...\n"; virtual void Sleep() const { cout << "Ryba spi...\n"; } virtual void Eat() const { cout << "Ryba zeruje...\n"; } virtual void Reproduce() const { cout << "Ryba sklada jaja...\n"; } virtual void Move() const { cout << "Ryba plywa...\n"; virtual void Speak() const { } }; } }

class Horse : public Mammal {

56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92:

public: Horse(int age, COLOR color ): Mammal(age), itsColor(color) { cout << "Konstruktor klasy Horse...\n"; } virtual ~Horse() { cout << "Destruktor klasy Horse...\n"; } virtual void Speak()const { cout << "Ihaaa!... \n"; } virtual COLOR GetItsColor() const { return itsColor; } virtual void Sleep() const { cout << "Kon spi...\n"; } virtual void Eat() const { cout << "Kon sie pasie...\n"; } virtual void Move() const { cout << "Kon biegnie...\n";}

protected: COLOR itsColor; };

class Dog : public Mammal { public: Dog(int age, COLOR color ): Mammal(age), itsColor(color) { cout << "Konstruktor klasy Dog...\n"; } virtual ~Dog() { cout << "Destruktor klasy Dog...\n"; } virtual void Speak()const { cout << "Hau, hau!... \n"; } virtual void Sleep() const { cout << "Pies chrapie...\n"; } virtual void Eat() const { cout << "Pies je...\n"; } virtual void Move() const { cout << "Pies biegnie...\n"; }

virtual void Reproduce() const { cout << "Pies sie rozmnaza...\n"; }

protected: COLOR itsColor; };

int main() { Animal *pAnimal=0;

93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: }

int choice; bool fQuit = false;

while (1) { cout << "(1)Dog (2)Horse (3)Fish (0)Quit: "; cin >> choice;

switch (choice) { case 1: pAnimal = new Dog(5,Brown); break; case 2: pAnimal = new Horse(4,Black); break; case 3: pAnimal = new Fish (5); break; default: fQuit = true; break; } if (fQuit) break;

pAnimal->Speak(); pAnimal->Eat(); pAnimal->Reproduce(); pAnimal->Move(); pAnimal->Sleep(); delete pAnimal; cout << "\n"; } return 0;

Wynik
(1)Dog (2)Horse (3)Fish (0)Quit: 1 Konstruktor klasy Animal... Konstruktor klasy Mammal...

Konstruktor klasy Dog... Hau, hau!... Pies je... Pies sie rozmnaza... Pies biegnie... Pies chrapie... Destruktor klasy Dog... Destruktor klasy Mammal... Destruktor klasy Animal... (1)Dog (2)Horse (3)Fish (0)Quit: 0

Analiza W liniach od 7. do 21. zostaa zadeklarowana abstrakcyjna klasa Animal. Klasa ta posiada zwyke wirtualne akcesory do swojej skadowej itsAge, wspuzytkowane przez wszystkie obiekty tej klasy. Oprcz tego posiada pi czystych funkcji wirtualnych: Sleep(), Eat(), Reproduce(), Move() i Speak() (mwienie). Klasa Mammal, zadeklarowana w liniach od 29. do 37., zostaa wyprowadzona z klasy Animal. Nie posiada ona adnych wasnych danych. Przesania jednak funkcj Reproduce(), zapewniajc wspln form rozmnaania dla wszystkich ssakw. W klasie Fish funkcja Reproduce() musi zosta przesonita, gdy ta klasa musi dziedziczy bezporednio po klasie Animal i nie moe skorzysta z rozmnaania na wzr ssakw (i dobrze!). Klasy ssakw nie musz ju przesania funkcji Reproduce(), ale jeli chc, mog to zrobi, na przykad tak, jak klasa Dog w linii 83. Klasy Fish, Horse i Dog przesaniaj pozostae czyste funkcje wirtualne, dziki czemu mona tworzy specyficzne dla nich egzemplarze obiektw. Znajdujcy si w ciele programu wskanik do klasy Animal jest uywany do kolejnego wskazywania obiektw rnych wyprowadzonych klas. Wywoywane s metody wirtualne i, w zalenoci od powiza tworzonych dynamicznie, z waciwych klas pochodnych wywoywane s waciwe funkcje. Prba stworzenia egzemplarza klasy Animal lub Mammal spowodowaaby bd kompilacji, gdy obie te klasy s klasami abstrakcyjnymi.
Usunito: y Usunito: y Usunito: la Usunito: j Usunito: funkcji

Usunito: wszystkie Usunito: ich

Ktre typy s abstrakcyjne?


Klasa Animal jest abstrakcyjna w jednym programie, a w innym nie. Co decyduje o tym, e jak klas czynimy abstrakcyjn? Odpowied na to pytanie nie zaley od adnego czynnika zewntrznego, ale od tego, co ma sens dla danego programu. Jeli piszesz program opisujcy farm lub ogrd zoologiczny, moesz zdecydowa, by klasa Animal bya klas abstrakcyjn, a klasa Dog bya klas, ktrej obiekty mgby samodzielnie tworzy.
Usunito: sprawia Usunito: a Usunito: jest Usunito: a Usunito: okrelonego

Z drugiej strony, gdyby tworzy animowane schronisko dla psw, mgby zdecydowa, by klasa Dog bya abstrakcyjnym typem danych i tworzy jedynie egzemplarze ras psw: jamniki, teriery i tak dalej. Poziom abstrakcji zaley od tego, jak precyzyjnie chcesz rozrnia swoje typy. TAK W celu zapewnienia wsplnej funkcjonalnoci licznym powizanym ze sob klasom, uywaj typw abstrakcyjnych. Przesaniaj wszystkie czyste funkcje wirtualne. Jeli funkcja musi zosta przesonita, zamie j w czyst funkcj wirtualn. NIE Nie prbuj tworzy egzemplarzy obiektw klas abstrakcyjnych.

Program podsumowujcy wiadomoci


{uwaga korekta: to jest zawarto rozdziau Week 2 In Review } W tym programie zebrano w w cao wiele omawianych w poprzednich rozdziaach zagadnie. Przedstawiona poniej demonstracja poczonych list wykorzystuje funkcje wirtualne, czyste funkcje wirtualne, przesanianie funkcji, polimorfizm, dziedziczenie publiczne, przecianie funkcji, ptle nieskoczone, wskaniki, referencje i inne elementy. Naley zwrci uwag, e ta lista poczona rni si od listy opisywanej wczeniej; w C++ zwykle ten sam efekt mona uzyska na kilka sposobw. Celem programu jest utworzenie listy poczonej. Wzy listy s zaprojektowane w celu przechowywania czci samochodowych, takich, jakie mogyby by uywane w fabryce. Cho nie jest to ostateczna wersja programu, stanowi jednak dobry przykad do zaawansowanej struktury danych. Kod ma prawie trzysta linii; zanim przystpisz do przeczytania analizy przedstawionej po wynikach jego dziaania, sprbuj przeanalizowa go samodzielnie. Listing 14.11. Program podsumowujcy wiadomoci
0: 1: 2: 3: 4: 5: 6: 7: // ************************************************** // // Tytu: Program podsumowujcy numer 2 // // Plik: // // Opis: // Program demonstruje tworzenie listy poczonej 4eList1411

8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44:

// Klasy: PART - zawiera numery czci oraz ewentualnie inne // // // // // // // // // ************************************************** PartsList - dostarcza mechanizmu dla poczonej listy obiektw PartNode PartNode - peni rol wza w licie PartsList informacje na ich temat

#include <iostream> using namespace std;

// **************** Part ************

// Abstrakcyjna klasa bazowa czci class Part { public: Part():itsPartNumber(1) {} Part(int PartNumber):itsPartNumber(PartNumber){} virtual ~Part(){}; int GetPartNumber() const { return itsPartNumber; } virtual void Display() const =0; private: int itsPartNumber; }; // musi by przesonite

// implementacja czystej funkcji wirtualnej, dziki czemu // mog z niej korzysta klasy pochodne void Part::Display() const { cout << "\nNumer czesci: " << itsPartNumber << endl; }

45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: } private: int itsEngineNumber; { Part::Display(); cout << "Nr silnika.: "; cout << itsEngineNumber << endl; class AirPlanePart : public Part { public: AirPlanePart():itsEngineNumber(1){}; AirPlanePart(int EngineNumber, int PartNumber); virtual void Display() const // **************** Cz samolotu ************ {} CarPart::CarPart(int year, int partNumber): itsModelYear(year), Part(partNumber) }; } private: int itsModelYear; { Part::Display(); cout << "Rok modelu: "; cout << itsModelYear << endl; class CarPart : public Part { public: CarPart():itsModelYear(94){} CarPart(int year, int partNumber); virtual void Display() const // **************** Cz samochodu ************

82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118:

};

AirPlanePart::AirPlanePart(int EngineNumber, int PartNumber): itsEngineNumber(EngineNumber), Part(PartNumber) {}

// **************** Wze czci ************ class PartNode { public: PartNode (Part*); ~PartNode(); void SetNext(PartNode * node) { itsNext = node; } PartNode * GetNext() const; Part * GetPart() const; private: Part *itsPart; PartNode * itsNext; };

// Implementacje klasy PartNode

PartNode::PartNode(Part* pPart): itsPart(pPart), itsNext(0) {}

PartNode::~PartNode() { delete itsPart; itsPart = 0; delete itsNext; itsNext = 0; }

// Gdy nie ma nastpnego wza, zwraca NULL

119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155:

PartNode * PartNode::GetNext() const { return itsNext; }

Part * PartNode::GetPart() const { if (itsPart) return itsPart; else return NULL; //bd }

// **************** Klasa PartList ************ class PartsList { public: PartsList(); ~PartsList(); // wymaga konstruktora kopiujacego i operatora porwnania! Part* int Part* void void Part* private: PartNode * pHead; int itsCount; }; Find(int & position, int PartNumber) GetCount() const { return itsCount; } GetFirst() const; Insert(Part *); Iterate() const; operator[](int) const; const; Usunito: i Usunito: rzy

// Implementacje dla list...

PartsList::PartsList(): pHead(0), itsCount(0) {}

156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: { Part* { PartNode * pNode = 0; for (pNode = pHead, position = 0; pNode!=NULL; pNode = pNode->GetNext(), position++) PartsList::Find(int & position, int PartNumber) const } return pNode->GetPart(); for (int i=0;i<offSet; i++) pNode = pNode->GetNext(); if (offSet > itsCount) return NULL; // bd if (!pHead) return NULL; // w celu wykrycia bdu Part * { PartNode* pNode = pHead; PartsList::operator[](int offSet) const } Part* { if (pHead) return pHead->GetPart(); else return NULL; // w celu wykrycia bdu PartsList::GetFirst() const } PartsList::~PartsList() { delete pHead;

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

if (pNode->GetPart()->GetPartNumber() == PartNumber) break;

if (pNode == NULL) return NULL; else return pNode->GetPart();

void PartsList::Iterate() const { if (!pHead) return; PartNode* pNode = pHead; do pNode->GetPart()->Display(); while (pNode = pNode->GetNext());

void PartsList::Insert(Part* pPart) { PartNode * pNode = new PartNode(pPart); PartNode * pCurrent = pHead; PartNode * pNext = 0;

int New =

pPart->GetPartNumber();

int Next = 0; itsCount++;

if (!pHead) { pHead = pNode; return;

// jeli ten wze jest mniejszy ni gowa, // wtedy staje si now gow

230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: }

if (pHead->GetPart()->GetPartNumber() > New) { pNode->SetNext(pHead); pHead = pNode; return; }

for (;;) { // jeli nie ma nastpnego, doczamy ten nowy if (!pCurrent->GetNext()) { pCurrent->SetNext(pNode); return; }

// jeli trafia pomidzy biecy a nastepny, // wstawiamy go tu; w przeciwnym razie bierzemy nastpny pNext = pCurrent->GetNext(); Next = pNext->GetPart()->GetPartNumber(); if (Next > New) { pCurrent->SetNext(pNode); pNode->SetNext(pNext); return; } pCurrent = pNext; } Usunito: o

int main() { PartsList pl;

Part * pPart = 0; int PartNumber; int value;

267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: }

int choice;

while (1) { cout << "(0)Wyjscie (1)Samochod (2)Samolot: "; cin >> choice;

if (!choice) break;

cout << "Nowy numer czesci?: "; cin >> PartNumber;

if (choice == 1) { cout << "Model?: "; cin >> value; pPart = new CarPart(value,PartNumber); } else { cout << "Numer silnika?: "; cin >> value; pPart = new AirPlanePart(value,PartNumber); }

pl.Insert(pPart); } pl.Iterate(); return 0;

Wynik
(0)Wyjscie (1)Samochod (2)Samolot: 1 Nowy numer czesci?: 2837 Model?: 90 (0)Wyjscie (1)Samochod (2)Samolot: 2

Nowy numer czesci?: 378 Numer silnika?: 4938 (0)Wyjscie (1)Samochod (2)Samolot: 1 Nowy numer czesci?: 4499 Model?: 94 (0)Wyjscie (1)Samochod (2)Samolot: 1 Nowy numer czesci?: 3000 Model?: 93 (0)Wyjscie (1)Samochod (2)Samolot: 0 Numer czesci: 378 Nr silnika.: 4938 Numer czesci: 2837 Rok modelu: 90 Numer czesci: 3000 Rok modelu: 93 Numer czesci: 4499 Rok modelu: 94

Analiza Przedstawiony tu listing zawiera implementacj listy poczonej, przechowujcej obiekty klasy Part (cz). Lista poczona jest dynamiczn struktur danych, mogc dostosowywa swoje rozmiary do potrzeb programu. Ta konkretna lista poczona zostaa zaprojektowana w celu przechowywania obiektw klasy Part, przy czym klasa Part jest klas abstrakcyjn, penic rol klasy bazowej dla wszystkich obiektw posiadajcych numer czci. W tym przykadzie z klasy Part zostay wydzielone dwie podklasy: CarPart (cz samochodowa) oraz AirPlanePart (cz samolotu). Klasa Part jest deklarowana w liniach od 27. do 37. i skada si z numeru czci oraz kilku akcesorw. W rzeczywistym programie klasa ta mogaby zawiera dodatkowe informacje o czciach, na przykad na temat komponentw, z jakich si skadaj, ile takich czci znajduje si w magazynie i tak dalej. Klasa Part jest abstrakcyjnym typem danych, wymuszonym przez czyst funkcj wirtualn Display() (wywietl). Zauwamy, e metoda Display() posiada w liniach od 41. do 44. swoj implementacj. W ten sposb zostaa wyraona intencja projektanta kodu; chcia on by klasy pochodne tworzyy wasne

implementacje metody Display(), a jednoczenie mogy w nich korzysta z metody Display() w klasie bazowej. Dwie proste klasy pochodne, CarPart oraz AirPlanePart zostay zadeklarowane w liniach od 48. do 66. oraz od 70. do 88. Kada z nich zawiera przesonit metod Display(), ktra jednak w obu przypadkach wywouje metod Display() klasy bazowej. Klasa PartNode (wze czci) peni rol interfejsu pomidzy klas Part a klas PartsList (lista czci). Zawiera ona wskanik do czci oraz wskanik do nastpnego wza na licie. Jej jedynymi metodami s metody przeznaczone do ustawiania i odczytywania nastpnego wza listy oraz do zwracania wskazywanego przez wze obiektu Part. Caa tajemnica dziaania listy kryje si w klasie PartsList, zadeklarowanej w liniach od 133. do 148. Klasa PartsList przechowuje wskanik do pierwszego elementu listy (pHead) i uywa go we wszystkich metodach przetwarzajcych t list. Przetworzenie listy oznacza odpytanie kadego z wzw o nastpny wze, a do czasu osignicia wza, ktrego wskanik itsNext wynosi NULL. Jest to implementacja czciowa; w peni zaprojektowana lista oferowywaaby albo lepszy dostp do swojego pierwszego i ostatniego wza, albo dostarczaaby obiektu iteratora, pozwalajcego klientom na atwe poruszanie si po licie. Jednak klasa PartsList i tak posiada kilka interesujcych metod, opisanych poniej w kolejnoci alfabetycznej. Zwykle dobrze jest zachowywa tak kolejno, gdy uatwia ona odszukanie funkcji. Metoda Find() otrzymuje numer czci oraz referencj do typu int. Jeli zostanie znaleziona cz zgodna z PartNumber (numerem czci), funkcja zwraca wskanik do obiektu Part i wypenia referencj pozycj tej czci na licie. Jeli szukany numer czci nie zostanie znaleziony, funkcja zwraca warto NULL i numer pozycji nie ma znaczenia. Metoda GetCount() zwraca ilo elementw na licie. Klasa PartsList przechowuje t warto jako swoj zmienn skadow, itsCount, cho oczywicie mogaby j oblicza, przetwarzajc list. Metoda GetFirst() zwraca wskanik do pierwszego obiektu Part na licie; w przypadku, gdy lista jest pusta, zwraca warto NULL. Metoda Insert() otrzymuje wskanik do obiektu klasy Part, tworzy dla niego obiekt PartNode, po czym dodaje go do listy, zgodnie z kolejnoci wyznaczan przez PartNumber. Metoda Iterate() otrzymuje wskanik do funkcji skadowej klasy Part, ktra nie posiada parametrw, zwraca void i jest funkcj const. Wywouje t funkcj dla kadego obiektu klasy Part na licie. W naszym przykadowym programie zostaje wywoana funkcja Display(), bdca funkcj wirtualn, wic w kadym przypadku zostaje wywoana metoda odpowiedniej podklasy klasy Part. pooeniu na licie. Wykonywane jest przy tym podstawowe sprawdzanie zakresw; jeli lista jest pusta lub podane pooenie wykracza poza rozmiar listy, jako warto bdu zostaje zwrcona warto NULL.
Operator[] umoliwia bezporedni dostp do obiektu Part, znajdujcego si we wskazanym
Usunito: Usunito: 0

Zwr uwag, e w rzeczywistym programie takie opisy funkcji zostayby zapisane w deklaracji klasy. Program sterujcy jest zawarty w liniach od 260. do 298. Obiekt PartsList jest tworzony w linii 263. W linii 278. uytkownik jest proszony o wybranie, czy cz powinna by wprowadzana dla samochodu, czy dla samolotu. W zalenoci od tego wyboru tworzona jest odpowiednia cz, ktra nastpnie jest wstawiana do listy w linii 294. Implementacja metody Insert() klasy PartsList znajduje si w liniach od 212. do 258. Gdy zostaje wprowadzony numer pierwszej czci, 2837, zostaje stworzony obiekt CarPar o tym numerze czci i modelu 90, ktry jest przekazywany do metody LinkedList::Insert(). W linii 214. dla tej czci jest tworzony nowy obiekt PartNode, za zmienna New jest inicjalizowana numerem czci. Zmienna itsCount klasy PartsList jest inkrementowana w linii 220. W linii 222., w tecie sprawdzajcym, czy pHead ma warto NULL, otrzymujemy warto TRUE. Poniewa jest to pierwszy wze, wskanik pHead listy na nic jeszcze nie wskazuje. Zatem w linii 224. wskanik pHead jest ustawiany tak, aby wskazywa nowy wze, po czym nastpuje powrt z funkcji. Uytkownik jest proszony o wybranie kolejnej czci; tym razem zostaje wybrana cz do samolotu, o numerze czci 378 i numerze silnika 4938. Ponownie jest wywoywana metoda PartsList::Insert(), a pNode jest ponownie inicjalizowane nowym wzem. Statyczna zmienna skadowa itsCount zostaje zwikszona do dwch i nastpuje sprawdzenie wskanika pHead. Poniewa ostatnim razem temu wskanikowi zostaa przypisana warto, nie zawiera on ju wartoci NULL i test zwraca warto FALSE. W linii 230. numer czci wskazywanej przez pHead, 2837, jest porwnywany z numerem biecej czci, 378. Poniewa numer nowej czci jest mniejszy od numeru czci wskazywanej przez pHead, nowa cz musi sta si nowym pierwszym elementem listy, wic test w linii 230. zwraca warto TRUE. W linii 232. nowy wze jest ustawiany tak, by wskazywa na wze aktualnie wskazywany przez wskanik pHead. Zwr uwag, e nowy wze nie wskazuje na pHead, ale na wze wskazywany przez pHead! W linii 233. pHead jest ustawiany tak, by wskazywa na nowy wze. Przy trzecim wykonaniu ptli uytkownik wybra cz samochodow o numerze 4499 i modelu 94. Nastpuje zwikszenie licznika i tym razem numer czci nie jest mniejszy od numeru wskazywanego przez pHead, wic przechodzimy do ptli for, rozpoczynajcej si w linii 237. Wartoci wskazywan przez pHead jest 378. Wartoci wskazywan przez nastpny wze jest 2837. Biec wartoci jest 4499. Wskanik pCurrent wskazuje na ten sam wze, co pHead, wic posiada nastpn warto; pCurrent wskazuje na drugi wze, wic test w linii 240 zwraca warto FALSE. Wskanik pCurrent jest ustawiany tak, by wskazywa nastpny wze, po czym nastpuje ponowne wykonanie ptli. Tym razem test w linii 240. zwraca warto TRUE. Nie ma nastpnego elementu, wic w linii 242. biecy wze zostaje ustawiony tak, by wskazywa nowy wze, po czym proces wstawiania si koczy.

Za czwartym razem wprowadzona zostaje cz o numerze 3000. Jej wstawianie jest bardzo podobne do przypadku opisanego powyej, ale tym razem, gdy biecy wze wskazuje numer 2837, a nastpny 4499, test w linii 250. zwraca warto TRUE i nowy wze jest wstawiany wanie w tej pozycji. Gdy uytkownik w kocu wybierze zero, test w linii 275. zwraca warto TRUE i nastpuje wyjcie z ptli while(1). Wykonanie programu przechodzi do linii 296., w ktrej wywoywana jest funkcja Iterate(). Wykonanie przechodzi do linii 202., a w linii 208. wskanik PNode jest wykorzystywany do uzyskania dostpu do obiektu Part i wywoania dla niego metody Display.

Rozdzia 15. Specjalne klasy i funkcje


C++ oferuje kilka sposobw na ograniczenie zakresu i oddziaywania zmiennych i wskanikw. Do tej pory, dowiedzielimy si, jak tworzy zmienne globalne, lokalne zmienne funkcji, wskaniki do zmiennych oraz zmienne skadowe klas. Z tego rozdziau dowiesz si: czym s zmienne statyczne i funkcje skadowe, jak uywa zmiennych statycznych i statycznych funkcji skadowych, jak tworzy i operowa wskanikami do funkcji i wskanikami do funkcji skadowych, jak pracowa z tablicami wskanikw do funkcji.

Statyczne dane skadowe


Prawdopodobnie do tej pory uwaae dane kadego obiektu za unikalne dla tego obiektu (i nie wspuytkowane pomidzy obiektami klasy). Gdyby mia na przykad pi obiektw klasy Cat, kady z nich miaby swj wiek, wag, itp. Wiek jednego kota nie wpywa na wiek innego. Czasem zdarza si jednak, e chcemy ledzi pul danych. Na przykad, moemy chcie wiedzie, ile obiektw danej klasy zostao stworzonych w programie, a take ile z nich istnieje nadal. Statyczne zmienne skadowe s wspuytkowane przez wszystkie egzemplarze obiektw klasy. Stanowi one kompromis pomidzy danymi globalnymi, ktre s dostpne dla wszystkich czci programu, a danymi skadowymi, ktre zwykle s dostpne tylko dla konkretnego obiektu. Statyczne skadowe mona traktowa jako nalece do caej klasy, a nie tylko do pojedynczego obiektu. Zwyka dana skadowa odnosi si do pojedynczego obiektu, a dana skadowa statyczna odnosi si do caej klasy. Listing 15.1 deklaruje obiekt Cat, zawierajcy statyczn skadow HowManyCats (ile kotw). Ta zmienna ledzi, ile obiektw klasy Cat zostao utworzonych. ledzenie odbywa si poprzez inkrementacj statycznej zmiennej HowManyCats w konstruktorze klasy i dekrementowanie jej w destruktorze. Listing 15.1. Statyczne dane skadowe

Usunito: e Usunito: e Usunito: e Usunito: przechowywane s po jednej dla kadego Usunito: e Usunito: e Usunito: s przechowywane po jednej dla

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

//Listing 15.1 Statyczne dane skadowe

#include <iostream> using namespace std;

class Cat { public: Cat(int age):itsAge(age){HowManyCats++; } virtual ~Cat() { HowManyCats--; } virtual int GetAge() { return itsAge; } virtual void SetAge(int age) { itsAge = age; } static int HowManyCats;

private: int itsAge;

};

int Cat::HowManyCats = 0;

int main() { const int MaxCats = 5; int i; Cat *CatHouse[MaxCats]; for (i = 0; i<MaxCats; i++) CatHouse[i] = new Cat(i);

for (i = 0; i<MaxCats; i++) { cout << "Zostalo kotow: "; cout << Cat::HowManyCats; cout << "\n"; cout << "Usuwamy kota, ktory ma "; cout << CatHouse[i]->GetAge(); cout << " lat\n"; delete CatHouse[i];

37: 38: 39: 40: } }

CatHouse[i] = 0;

return 0;

Wynik
Zostalo kotow: 5 Usuwamy kota, ktory ma 0 lat Zostalo kotow: 4 Usuwamy kota, ktory ma 1 lat Zostalo kotow: 3 Usuwamy kota, ktory ma 2 lat Zostalo kotow: 2 Usuwamy kota, ktory ma 3 lat Zostalo kotow: 1 Usuwamy kota, ktory ma 4 lat

Analiza W liniach od 5. do 17. zostaa zadeklarowana uproszczona klasa Cat. W linii 12. zmienna HowManyCats zostaa zadeklarowana jako statyczna zmienna skadowa typu int. Sama deklaracja zmiennej HowManyCats nie definiuje wartoci cakowitej i nie jest dla niej rezerwowane miejsce w pamici. W odrnieniu od zwykych zmiennych skadowych, w momencie tworzenia egzemplarzy obiektw klasy Cat, nie jest tworzone miejsce dla tej zmiennej statycznej, gdy nie znajduje si ona w obiekcie. W zwizku z tym musielimy zdefiniowa i zainicjalizowa t zmienn w linii 19.. Programistom bardzo czsto zdarza si zapomnie o zdefiniowaniu statycznych zmiennych skadowych klasy. Nie pozwl, by przydarzao si to tobie! Oczywicie, gdy si przydarzy, linker zgosi komunikat bdu, informujcy o niezdefiniowanym symbolu, na przykad taki jak poniszy:
undefined symbol Cat::HowManyCats (niezdefiniowany symbol Cat::HowManyCats)

Nie musimy definiowa zmiennej itsAge, gdy nie jest statyczn zmienn skadow i w zwizku z tym jest definiowana za kadym razem, gdy tworzymy obiekt klasy Cat (w tym przypadku w linii 26. programu). Konstruktor klasy Cat w linii 8. inkrementuje statyczn zmienn skadow. Destruktor (zawarty w linii 9.) dekrementuje j. Zatem zmienna HowManyCats przez cay czas zawiera waciw ilo obiektw Cat, ktre zostay utworzone i jeszcze nie zniszczone.

Program sterujcy. zawarty w liniach od 21. do 40., tworzy pi egzemplarzy obiektw klasy Cat i umieszcza je w pamici. Powoduje to piciokrotne wywoanie konstruktora, w zwizku z czym nastpuje piciokrotne inkrementowanie zmiennej HowManyCats od jej pocztkowej wartoci 0. Nastpnie program w ptli przechodzi przez wszystkie pi elementw tablicy i przed usuniciem kolejnego wskanika do obiektu Cat wypisuje warto zmiennej HowManyCats. Wydruk pokazuje to, e wartoci pocztkow jest 5 (gdy zostao skonstruowanych pi obiektw) i e przy kadym wykonaniu ptli pozostaje o jeden obiekt Cat mniej. Zwr uwag, e zmienna HowManyCats jest publiczna i jest uywana bezporednio w funkcji main(). Nie ma powodu do udostpniania zmiennej skadowej w ten sposb. Najlepsz metod

Usunito: tablicy Usunito: des Usunito: biecego

jest uczynienie z niej prywatnej skadowej i udostpnienie publicznego akcesora (o ile ma by ona dostpna wycznie poprzez egzemplarze klasy Cat). Z drugiej strony, gdybymy chcieli korzysta z tej danej bezporednio, niekoniecznie posiadajc obiekt klasy Cat, mamy do wyboru dwie opcje: moemy zadeklarowa t zmienn jako publiczn, tak jak pokazano na listingu 15.2, albo dostarczy akcesor w postaci statycznej funkcji skadowej, co zostanie omwione w dalszej czci rozdziau. Listing 15.2. Dostp do statycznych danych skadowych bez obiektu
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: void TelepathicFunction(); int Cat::HowManyCats = 0; }; private: int itsAge; class Cat { public: Cat(int age):itsAge(age){HowManyCats++; } virtual ~Cat() { HowManyCats--; } virtual int GetAge() { return itsAge; } virtual void SetAge(int age) { itsAge = age; } static int HowManyCats; #include <iostream> using namespace std; //Listing 15.2 Statyczne dane skadowe

23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45:

int main() { const int MaxCats = 5; int i; Cat *CatHouse[MaxCats]; for (i = 0; i<MaxCats; i++) { CatHouse[i] = new Cat(i); TelepathicFunction(); }

for ( i = 0; i<MaxCats; i++) { delete CatHouse[i]; TelepathicFunction(); } return 0; }

void TelepathicFunction() { cout << "Zostalo jeszcze zywych kotow: "; cout << Cat::HowManyCats << "\n"; }

Wynik
Zostalo jeszcze zywych kotow: 1 Zostalo jeszcze zywych kotow: 2 Zostalo jeszcze zywych kotow: 3 Zostalo jeszcze zywych kotow: 4 Zostalo jeszcze zywych kotow: 5 Zostalo jeszcze zywych kotow: 4 Zostalo jeszcze zywych kotow: 3 Zostalo jeszcze zywych kotow: 2 Zostalo jeszcze zywych kotow: 1 Zostalo jeszcze zywych kotow: 0

Analiza

Listing 15.2 przypomina listing 15.1, z wyjtkiem nowej funkcji, TelepathicFunction() (funkcja telepatyczna). Ta funkcja nie tworzy obiektu Cat ani nie otrzymuje obiektu Cat jako parametru, a mimo to moe odwoywa si do zmiennej skadowej HowManyCats. Naley pamita, e ta zmienna skadowa nie naley do adnego konkretnego obiektu; znajduje si w klasie i, o ile jest publiczna, moe by wykorzystywana przez kad funkcj w programie. Alternatyw dla tej zmiennej publicznej moe by zmienna prywatna. Gdy skorzystamy z niej, moemy to uczyni poprzez funkcj skadow, ale wtedy musimy posiada obiekt tej klasy. Takie rozwizanie przedstawia listing 15.3. Alternatywne rozwizanie, z wykorzystaniem statycznej funkcji skadowej, zostanie omwione bezporednio po analizie listingu 15.3. Listing 15.3. Dostp do statycznych skadowych za pomoc zwykych funkcji skadowych
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: int main() { const int MaxCats = 5; int i; Cat *CatHouse[MaxCats]; for (i = 0; i<MaxCats; i++) CatHouse[i] = new Cat(i); int Cat::HowManyCats = 0; }; private: int itsAge; static int HowManyCats; class Cat { public: Cat(int age):itsAge(age){HowManyCats++; } virtual ~Cat() { HowManyCats--; } virtual int GetAge() { return itsAge; } virtual void SetAge(int age) { itsAge = age; } virtual int GetHowMany() { return HowManyCats; } #include <iostream> using std::cout; //Listing 15.3 prywatne statyczne dane skadowe

28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: } } return 0; for (i = 0; i<MaxCats; i++) { cout << "Zostalo jeszcze "; cout << CatHouse[i]->GetHowMany(); cout << " kotow!\n"; cout << "Usuwamy kota, ktory ma "; cout << CatHouse[i]->GetAge()+2; cout << " lat\n"; delete CatHouse[i]; CatHouse[i] = 0;

Wynik
Zostalo jeszcze 5 kotow! Usuwamy kota, ktory ma 2 lat Zostalo jeszcze 4 kotow! Usuwamy kota, ktory ma 3 lat Zostalo jeszcze 3 kotow! Usuwamy kota, ktory ma 4 lat Zostalo jeszcze 2 kotow! Usuwamy kota, ktory ma 5 lat Zostalo jeszcze 1 kotow! Usuwamy kota, ktory ma 6 lat

Analiza W linii 17. statyczna zmienna skadowa HowManyCats zostaa zadeklarowana jako skadowa prywatna. Nie moemy wic odwoywa si do niej z funkcji innych ni skadowe, na przykad takich, jak TelepathicFunction() z poprzedniego listingu. Jednak mimo, i zmienna HowManyCats jest statyczna, nadal znajduje si w zakresie klasy. Moe si do niej odwoywa dowolna funkcja skadowa klasy, na przykad GetHowMany(), podobnie jak do wszystkich innych danych skadowych. Jednak, aby zewntrzna funkcja moga wywoa metod GetHowMany(), musi posiada obiekt klasy Cat. TAK NIE

W celu wspuytkowania danych pomidzy wszystkimi egzemplarzami klasy uywaj statycznych zmiennych skadowych Jeli chcesz ograniczy dostp do statycznych zmiennych skadowych, uczy je skadowymi prywatnymi lub chronionymi.

Nie uywaj statycznych zmiennych skadowych do przechowywania danych nalecych do pojedynczego obiektu. Statyczne dane skadowe s wspuytkowane przez wszystkie obiekty klasy.

Statyczne funkcje skadowe


Statyczne funkcje skadowe dziaaj podobnie do statycznych zmiennych skadowych: istniej nie w obiekcie, ale w zakresie klasy. W zwizku z tym mog by wywoywane bez posiadania obiektu swojej klasy, co ilustruje listing 15.4. Listing 15.4. Statyczne funkcje skadowe
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: int main() { void TelepathicFunction(); int Cat::HowManyCats = 0; }; class Cat { public: Cat(int age):itsAge(age){HowManyCats++; } virtual ~Cat() { HowManyCats--; } virtual int GetAge() { return itsAge; } virtual void SetAge(int age) { itsAge = age; } static int GetHowMany() { return HowManyCats; } private: int itsAge; static int HowManyCats; #include <iostream> //Listing 15.4 statyczne funkcje skadowe

23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: }

const int MaxCats = 5; Cat *CatHouse[MaxCats]; int i; for (i = 0; i<MaxCats; i++) { CatHouse[i] = new Cat(i); TelepathicFunction(); }

for ( i = 0; i<MaxCats; i++) { delete CatHouse[i]; TelepathicFunction(); } return 0;

void TelepathicFunction() {

41: std::cout<<"Zostalo jeszcze "<< Cat::GetHowMany()<<" zywych kotow!\n"; 42: }

Wynik
Zostalo jeszcze 1 zywych kotow! Zostalo jeszcze 2 zywych kotow! Zostalo jeszcze 3 zywych kotow! Zostalo jeszcze 4 zywych kotow! Zostalo jeszcze 5 zywych kotow! Zostalo jeszcze 4 zywych kotow! Zostalo jeszcze 3 zywych kotow! Zostalo jeszcze 2 zywych kotow! Zostalo jeszcze 1 zywych kotow! Zostalo jeszcze 0 zywych kotow!

Analiza W linii 14., w klasie Cat zostaa zadeklarowana statyczna prywatna zmienna skadowa HowManyCats. Publiczny akcesor, GetHowMany(), zosta w linii 11. zadeklarowany zarwno jako metoda publiczna, jak i statyczna.

Poniewa metoda GetHowMany() jest publiczna, moe by uywana w kadej funkcji zewntrznej, a poniewa jest te statyczna, moe by wywoana bez obiektu klasy Cat. Zatem, w linii 41., funkcja TelepathicFunction() jest w stanie uy publicznego statycznego akcesora, nie posiadajc dostpu do obiektu klasy Cat. Oczywicie, moglibymy wywoa funkcj GetHowMany() dla obiektw klasy Cat dostpnych w funkcji main(), tak samo jak w przypadku innych akcesorw.
UWAGA Statyczne funkcje skadowe nie posiadaj wskanika this. W zwizku z tym nie mog by deklarowane jako const. Ponadto, poniewa zmienne skadowe s dostpne w funkcjach skadowych poprzez wskanik this, statyczne funkcje skadowe nie mog korzysta z adnych zmiennych skadowych, nie bdcych skadowymi statycznymi!

Statyczne funkcje skadowe

Moesz korzysta ze statycznych funkcji skadowych, wywoujc je dla obiektu klasy, tak samo jak w przypadku innych funkcji skadowych, moesz te wywoywa je bez obiektu klasy, stosujc pen kwalifikowan nazw klasy i funkcji.

Przykad
class Cat { public: static int GetHowMany() { return HowManyCats; } private: static int HowManyCats; }; int Cat::HowManyCats = 0; int main() { int howMany; Cat theCat; // definiuje obiekt klasy Cat

howMany = theCat.GetHowMany(); // dostp poprzez obiekt howMany = Cat::GetHowMany(); } // dostp bez obiektu

Wskaniki do funkcji
Nazwa tablicy jest wskanikiem const do pierwszego elementu tej tablicy, a nazwa funkcji jest wskanikiem const do funkcji. Istnieje moliwo zadeklarowania zmiennej wskanikowej wskazujcej na funkcj i wywoywania tej funkcji za pomoc tej zmiennej. Moe to by bardzo przydatne; umoliwia tworzenie programw, ktre decyduj (na podstawie wprowadzonych danych) o tym, ktra funkcja ma zosta wywoana. Jedyny problem ze wskanikami do funkcji polega na zrozumieniu typu wskazywanego obiektu. Wskanik do int wskazuje zmienn cakowit, za wskanik do funkcji musi wskazywa funkcj o okrelonym zwracanym typie i sygnaturze. W deklaracji:
long (* funcPtr) (int);

funcPtr jest deklarowane jako wskanik (zwr uwag na gwiazdk przed nazw), wskazujcy funkcj otrzymujc parametr typu int i zwracajc warto typu long. Nawiasy wok * funcPtr s konieczne, gdy nawiasy wok int wi cilej; tj. maj priorytet nad operatorem dostpu poredniego (*). Bez zastosowania pierwszych nawiasw zostaaby zadeklarowana funkcja otrzymujca parametr typu int i zwracajca wskanik do typu long. (Pamitaj, e spacje nie maj tu znaczenia.)

Usunito: bardziej

Przyjrzyjmy si dwm poniszym deklaracjom:


long * Function (int); long (* funcPtr) (int);

Pierwsza, Function(), jest funkcj otrzymujc parametr typu int i zwracajc wskanik do zmiennej typu long. Druga, funcPtr, jest wskanikiem do funkcji otrzymujcej warto typu int i zwracajcej zmienn typu long. Deklaracja wskanika do funkcji zawsze zawiera zwracany typ oraz, w nawiasach, typ parametrw (o ile wystpuj). Listing 15.5 ilustruje deklarowanie i uywanie wskanikw do funkcji. Listing 15.5. Wskaniki do funkcji
0: 1: 2: 3: 4: 5: 6: void Square (int&,int&); void Cube (int&, int&); // do kwadratu // do trzeciej potegi #include <iostream> using namespace std; // Listing 15.5 Uycie wskanikw do funkcji.

7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:

void Swap (int&, int &);

// zamiana

void GetVals(int&, int&); // zmiana void PrintVals(int, int);

int main() { void (* pFunc) (int &, int &); bool fQuit = false;

int valOne=1, valTwo=2; int choice; while (fQuit == false) {

20: cout << "(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: "; 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: void PrintVals(int x, int y) { cout << "x: " << x << " y: " << y << endl; } } return 0; PrintVals(valOne, valTwo); pFunc(valOne, valTwo); PrintVals(valOne, valTwo); if (fQuit) break; cin >> choice; switch (choice) { case 1: pFunc = GetVals; break; case 2: pFunc = Square; break; case 3: pFunc = Cube; break; case 4: pFunc = Swap; break; default : fQuit = true; break; }

44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79:

void Square (int & rX, int & rY) { rX *= rX; rY *= rY; }

void Cube (int & rX, int & rY) { int tmp;

tmp = rX; rX *= rX; rX = rX * tmp;

tmp = rY; rY *= rY; rY = rY * tmp; }

void Swap(int & rX, int & rY) { int temp; temp = rX; rX = rY; rY = temp; }

void GetVals (int & rValOne, int & rValTwo) { cout << "Nowa wartosc dla ValOne: "; cin >> rValOne; cout << "Nowa wartosc dla ValTwo: "; cin >> rValTwo; }

Wynik
(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 1 x: 1 y: 2 Nowa wartosc dla ValOne: 2 Nowa wartosc dla ValTwo: 3 x: 2 y: 3 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 3 x: 2 y: 3 x: 8 y: 27 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 2 x: 8 y: 27 x: 64 y: 729 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 4 x: 64 y: 729 x: 729 y: 64 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 0

Analiza W liniach od 5. do 8. zostay zadeklarowane cztery funkcje, wszystkie o tych samych zwracanych typach i sygnaturach, zwracajce void i przyjmujce referencje do dwch zmiennych cakowitych. W linii 13. zmienna pFunc jest deklarowana jako wskanik do funkcji zwracajcej void i przyjmujcej referencje do dwch zmiennych cakowitych. Wskanik pFunc moe wic wskazywa na dowoln z zadeklarowanych wczeniej czterech funkcji. Uytkownik ma moliwo wyboru funkcji, ktra powinna zosta wybrana, a wskanik pFunc jest ustawiany zgodnie z tym wyborem. W liniach od 34. do 36. s wypisywane biece wartoci obu zmiennych cakowitych, wywoywana jest aktualnie przypisana funkcja, po czym ponownie wypisywane s wartoci obu zmiennych.

Wskanik do funkcji

Wskanik do funkcji jest wywoywany tak samo jak funkcja, ktr wskazuje, z wyjtkiem tego, e zamiast nazwy funkcji, uywana jest nazwa wskanika do tej funkcji.

Przypisanie wskanikowi konkretnej funkcji odbywa si przez przypisanie mu nazwy funkcji bez nawiasw. Nazwa funkcji jest wskanikiem const do samej funkcji. Wskanika do funkcji mona wic uywa tak samo, jak nazwy funkcji. Zwracany typ oraz sygnatura wskanika do funkcji musz by zgodne z przypisywan mu funkcj.

Przykad
long (*pFuncOne) (int, int); long SomeFunction (int, int); pFuncOne = SomeFunction; pFuncOne(5,7);

Dlaczego warto uywa wskanikw do funkcji?


Listing 15.5 z pewnoci mgby zosta napisany bez uycia wskanikw do funkcji, ale uycie takich wskanikw jawnie okrela przeznaczenie programu: wybr funkcji z listy, a nastpnie jej wywoanie. Listing 15.6 wykorzystuje prototypy i definicje funkcji z listingu 15.5, ale ciao programu nie korzysta ze wskanikw do funkcji. Przyjrzyj si rnicom dzielcym te dwa listingi. Listing 15.6. Listing 15.5 przepisany bez uywania wskanikw do funkcji
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: int main() { bool fQuit = false; int valOne=1, valTwo=2; int choice; while (fQuit == false) { void Square (int&,int&); void Cube (int&, int&); void Swap (int&, int &); // do kwadratu // do trzeciej potegi // zamiana #include <iostream> using namespace std; // Listing 15.6 bez wskanikw do funkcji

void GetVals(int&, int&); // zmiana void PrintVals(int, int);

18: cout << "(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: "; 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: } return 0; if (fQuit) break; } default : fQuit = true; break; case 4: PrintVals(valOne, valTwo); Swap(valOne, valTwo); PrintVals(valOne, valTwo); break; case 3: PrintVals(valOne, valTwo); Cube(valOne, valTwo); PrintVals(valOne, valTwo); break; case 2: PrintVals(valOne, valTwo); Square(valOne,valTwo); PrintVals(valOne, valTwo); break; cin >> choice; switch (choice) { case 1: PrintVals(valOne, valTwo); GetVals(valOne, valTwo); PrintVals(valOne, valTwo); break;

55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91:

void PrintVals(int x, int y) { cout << "x: " << x << " y: " << y << endl; }

void Square (int & rX, int & rY) { rX *= rX; rY *= rY; }

void Cube (int & rX, int & rY) { int tmp;

tmp = rX; rX *= rX; rX = rX * tmp;

tmp = rY; rY *= rY; rY = rY * tmp; }

void Swap(int & rX, int & rY) { int temp; temp = rX; rX = rY; rY = temp; }

void GetVals (int & rValOne, int & rValTwo) { cout << "Nowa wartosc dla ValOne: ";

92: 93: 94: 95: }

cin >> rValOne; cout << "Nowa wartosc dla ValTwo: "; cin >> rValTwo;

Wynik
(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 1 x: 1 y: 2 Nowa wartosc dla ValOne: 2 Nowa wartosc dla ValTwo: 3 x: 2 y: 3 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 3 x: 2 y: 3 x: 8 y: 27 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 2 x: 8 y: 27 x: 64 y: 729 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 4 x: 64 y: 729 x: 729 y: 64 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 0

Analiza Kusio mnie, by umieci wywoanie funkcji PrintVals() na pocztku i na kocu ptli while, a nie w kadej z instrukcji case. Spowodowaoby to jednak wywoanie tej funkcji take dla opcji wyjcia z programu, co nie zgadzaoby si z jego specyfikacj. Poza wzrostem objtoci kodu i powtrzonych wywoa tej samej funkcji, znacznie zmniejszya si take oglna przejrzysto programu. Ten przypadek zosta jednak stworzony w celu zilustrowania dziaania wskanikw do funkcji. W rzeczywistoci zalety wskanikw do funkcji s jeszcze wiksze: wskaniki do funkcji mog eliminowa powtrzenia kodu, sprawiaj, e program staje si bardziej przejrzysty i umoliwiaj stosowanie tablic funkcji wywoywanych w zalenoci od sytuacji powstaych podczas dziaania programu.

Skrcone wywoanie

Wskanik do funkcji nie musi by wyuskiwany, cho oczywicie mona przeprowadzi t operacj. Zatem, jeli pFunc jest wskanikiem do funkcji przyjmujcej warto cakowit i zwracajcej zmienn typu long, i gdy do pFunc przypiszemy odpowiedni dla niego funkcj, moemy wywoa t funkcj, piszc albo:
pFunc(x);

Usunito: e

albo:
(*pFunc)(x);

Obie te formy dziaaj identycznie. Pierwsza jest jedynie skrcon wersj drugiej.

Tablice wskanikw do funkcji


Mona deklarowa nie tylko tablice wskanikw do wartoci cakowitych, ale rwnie tablice wskanikw do funkcji zwracajcych okrelony typ i posiadajcych okrelon sygnatur. Listing 15.7 stanowi zmodyfikowan wersj listingu 15.5, tym razem wykorzystujc tablic do wywoywania wszystkich opcji naraz. Listing 15.7. Demonstruje uycie tablicy wskanikw do funkcji
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: int main() { int valOne=1, valTwo=2; int choice, i; void Square (int&,int&); void Cube (int&, int&); void Swap (int&, int &); // do kwadratu // do trzeciej potegi // zamiana #include <iostream> using namespace std; // Listing 15.7 //Demonstruje uycie tablicy wskanikw do funkcji

void GetVals(int&, int&); // zmiana void PrintVals(int, int);

16: 17: 18: 19: 20:

const MaxArray = 5; void (*pFuncArray[MaxArray])(int&, int&);

for (i=0;i<MaxArray;i++) {

21: cout << "(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: "; 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: } void Square (int & rX, int & rY) { rX *= rX; rY *= rY; } void PrintVals(int x, int y) { cout << "x: " << x << " y: " << y << endl; } } return 0; for (i=0;i<MaxArray; i++) { if ( pFuncArray[i] == 0 ) continue; pFuncArray[i](valOne,valTwo); PrintVals(valOne,valTwo); } cin >> choice; switch (choice) { case 1: case 2: case 3: case 4: pFuncArray[i] = GetVals; break; pFuncArray[i] = Square; break; pFuncArray[i] = Cube; break; pFuncArray[i] = Swap; break;

default:pFuncArray[i] = 0; }

53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: } void GetVals (int & rValOne, int & rValTwo) { cout << "Nowa wartosc dla ValOne: "; cin >> rValOne; cout << "Nowa wartosc dla ValTwo: "; cin >> rValTwo; } void Swap(int & rX, int & rY) { int temp; temp = rX; rX = rY; rY = temp; } tmp = rY; rY *= rY; rY = rY * tmp; tmp = rX; rX *= rX; rX = rX * tmp; void Cube (int & rX, int & rY) { int tmp;

Wynik
(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 1 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 2 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 3

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 4 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 2 Nowa wartosc dla ValOne: 2 Nowa wartosc dla ValTwo: 3 x: 2 y: 3 x: 4 y: 9 x: 64 y: 729 x: 729 y: 64 x: 531441 y: 4096

Analiza W linii 17. tablica pFuncArray jest deklarowana jako tablica piciu wskanikw do funkcji zwracajcych void i przyjmujcych po dwie referencje do wartoci cakowitych. W liniach od 19. do 31. uytkownik jest proszony o wybranie funkcji przeznaczonej do wywoania, po czym kademu elementowi tablicy jest przypisywany adres odpowiedniej funkcji. W liniach od 33. do 39. wywoywane s funkcje wskazywane przez kolejne elementy tablicy. Po kadym wywoaniu wypisywane s wyniki.
Usunito: do innych funkcji

Przekazywanie wskanikw do funkcji innym funkcjom


Wskaniki do funkcji (a take tablice wskanikw do funkcji) mog by przekazywane do innych funkcji, ktre mog wykonywa obliczenia i na podstawie ich wyniku, dziki otrzymanym wskanikom, wywoywa odpowiednie funkcje. Moemy na przykad poprawi listing 15.5, przekazujc wskanik do wybranej funkcji do innej funkcji (poza main()), ktra wypisuje wartoci, wywouje funkcj i ponownie wypisuje wartoci. Ten wariant dziaania przedstawia listing 15.8. Listing 15.8. Przekazywanie wskanikw do funkcji jako argumentw funkcji
0: 1: 2: 3: 4: 5: 6: 7: 8: void Square (int&,int&); void Cube (int&, int&); void Swap (int&, int &); // do kwadratu // do trzeciej potegi // zamiana #include <iostream> using namespace std; // Listing 15.8 Przekazywanie wskanikw do funkcji

void GetVals(int&, int&); // zmiana

9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:

void PrintVals(void (*)(int&, int&),int&, int&);

int main() { int valOne=1, valTwo=2; int choice; bool fQuit = false;

void (*pFunc)(int&, int&);

while (fQuit == false) {

21: cout << "(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: "; 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: } void PrintVals( void (*pFunc)(int&, int&),int& x, int& y) { cout << "x: " << x << " y: " << y << endl; pFunc(x,y); cout << "x: " << x << " y: " << y << endl; } return 0; } cin >> choice; switch (choice) { case 1: case 2: case 3: case 4: pFunc = GetVals; break; pFunc = Square; break; pFunc = Cube; break; pFunc = Swap; break;

default:fQuit = true; break; } if (fQuit == true) break; PrintVals ( pFunc, valOne, valTwo);

46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79:

void Square (int & rX, int & rY) { rX *= rX; rY *= rY; }

void Cube (int & rX, int & rY) { int tmp;

tmp = rX; rX *= rX; rX = rX * tmp;

tmp = rY; rY *= rY; rY = rY * tmp; }

void Swap(int & rX, int & rY) { int temp; temp = rX; rX = rY; rY = temp; }

void GetVals (int & rValOne, int & rValTwo) { cout << "Nowa wartosc dla ValOne: "; cin >> rValOne; cout << "Nowa wartosc dla ValTwo: "; cin >> rValTwo; }

Wynik

(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 1 x: 1 y: 2 Nowa wartosc dla ValOne: 2 Nowa wartosc dla ValTwo: 3 x: 2 y: 3 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 3 x: 2 y: 3 x: 8 y: 27 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 2 x: 8 y: 27 x: 64 y: 729 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 4 x: 64 y: 729 x: 729 y: 64 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 0

Analiza W linii 17. zmienna pFunc zostaje zadeklarowana jako wskanik do funkcji zwracajcej void i przyjmujcej dwa parametry, bdce referencjami do wartoci typu int. W linii 9. zostaa zadeklarowana funkcja PrintVals przyjmujca trzy parametry. Pierwszym z nich jest wskanik do funkcji zwracajcej void i przyjmujcej dwie referencje do typu int, a drugim i trzecim s referencje do zmiennych typu int. Take w tym programie uytkownik jest proszony o wybranie funkcji do wywoania, po czym w linii 33. wywoywana jest funkcja PrintVals. Znajd jakiego programist C++ i zapytaj go, co oznacza ponisza deklaracja:
void PrintVals(void (*)(int&, int&), int&, int&); Usunito: , Usunito: e Usunito: wartoci

Jest to niezbyt czsto uywany rodzaj deklaracji; prawdopodobnie zajrzysz do ksiki za kadym razem, gdy sprbujesz z niej skorzysta. Moe ona jednak ocali twj program w tych rzadkich przypadkach, gdy niezbdna bdzie taka wanie konstrukcja.

Usunito: bdzie dokadnie t Usunito: Usunito: , jaka jest wymagana.

Uycie instrukcji typedef ze wskanikami do funkcji


Konstrukcja void (*)(int&, int&) jest co najmniej niezrozumiaa. Aby j uproci, moemy uy instrukcji typedef, deklarujc typ (w tym przypadku nazwiemy go VPF) jako wskanik do funkcji zwracajcej void i przyjmujcej dwie referencje do typu int. Listing 15.9 stanowi nieco zmodyfikowan wersj listingu 15.8. w ktrym zastosowano instrukcj typedef. Listing 15.9. Uycie instrukcji typedef w celu uproszczenia deklaracji wskanika do funkcji
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: while (fQuit == false) { VPF pFunc; int main() { int valOne=1, valTwo=2; int choice; bool fQuit = false; void Square (int&,int&); void Cube (int&, int&); void Swap (int&, int &); // do kwadratu // do trzeciej potegi // zamiana #include <iostream> using namespace std; // Listing 15.9. // Uycie typedef w celu uproszczenia deklaracji wskanika do funkcji

void GetVals(int&, int&); // zmiana typedef void (*VPF) (int&, int&) ;

void PrintVals(VPF,int&, int&);

23: cout << "(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: "; 24: 25: 26: 27: 28: 29: cin >> choice; switch (choice) { case 1: case 2: case 3: pFunc = GetVals; break; pFunc = Square; break; pFunc = Cube; break;

30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: } } } } }

case 4:

pFunc = Swap; break;

default:fQuit = true; break; } if (fQuit == true) break; PrintVals ( pFunc, valOne, valTwo);

return 0;

void PrintVals( VPF pFunc,int& x, int& y) { cout << "x: " << x << " y: " << y << endl; pFunc(x,y); cout << "x: " << x << " y: " << y << endl;

void Square (int & rX, int & rY) { rX *= rX; rY *= rY;

void Cube (int & rX, int & rY) { int tmp;

tmp = rX; rX *= rX; rX = rX * tmp;

tmp = rY; rY *= rY; rY = rY * tmp;

void Swap(int & rX, int & rY)

67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80:

{ int temp; temp = rX; rX = rY; rY = temp; }

void GetVals (int & rValOne, int & rValTwo) { cout << "Nowa wartosc dla ValOne: "; cin >> rValOne; cout << "Nowa wartosc dla ValTwo: "; cin >> rValTwo; }

Wynik
(0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 1 x: 1 y: 2 Nowa wartosc dla ValOne: 2 Nowa wartosc dla ValTwo: 3 x: 2 y: 3 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 3 x: 2 y: 3 x: 8 y: 27 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 2 x: 8 y: 27 x: 64 y: 729 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 4 x: 64 y: 729 x: 729 y: 64 (0)Koniec (1)Zmiana (2)Do kwadratu (3)Do trzeciej potegi (4)Zamiana: 0

Analiza W linii 10. instrukcja typedef zostaa uyta do zadeklarowania VPF jako typu wskanik do funkcji zwracajcej void i przyjmujcej dwa parametry w postaci referencji do wartoci typu int. W linii 11. zostaa zadeklarowana funkcja PrintVals() przyjmujca trzy parametry: VPF i dwie referencje do wartoci typu int. W tym programie wskanik pFunc zosta zadeklarowany jako zmienna typu VPF (w linii 19.). Po zdefiniowaniu typu VPF wszystkie nastpne konstrukcje z uyciem pFunc i PrintVals() staj si duo bardziej przejrzyste. Jak wida, dziaanie programu nie ulega zmianie.

Wskaniki do funkcji skadowych


Do tej pory wszystkie wskaniki do funkcji odnosiy si do funkcji oglnych, nie bdcych funkcjami skadowymi. Istnieje jednak moliwo tworzenia wskanikw do funkcji, bdcych skadowymi klas. Aby stworzy wskanik do funkcji skadowej, musimy uy tej samej skadni, jak w przypadku zwykego wskanika do funkcji, ale z zastosowaniem nazwy klasy i operatora zakresu (::). Jeli pFunc ma wskazywa na funkcj skadow klasy Shape zwracajc void i przyjmujc dwie wartoci cakowite, wtedy deklaracja tej zmiennej powinna wyglda nastpujco:
void (Shape::*pFunc) (int, int);

Wskaniki do funkcji skadowych s uywane tak samo, jak wskaniki do zwykych funkcji, z tym e w celu wywoania ich potrzebny jest obiekt waciwej klasy. Listing 15.10 ilustruje uycie wskanikw do funkcji skadowych. Listing 15.10. Wskaniki do funkcji skadowych
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: class Mammal { public: Mammal():itsAge(1) { virtual ~Mammal() { } virtual void Speak() const = 0; virtual void Move() const = 0; } #include <iostream> using namespace std; //Listing 15.10 Wskaniki do funkcji skadowych

12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48:

protected: int itsAge; };

class Dog : public Mammal { public: void Speak()const { cout << "Hau!\n"; } void Move() const { cout << "Gonie w pietke...\n"; } };

class Cat : public Mammal { public: void Speak()const { cout << "Miau!\n"; } void Move() const { cout << "Skradam sie...\n"; } };

class Horse : public Mammal { public: void Speak()const { cout << "Ihaaa!\n"; } void Move() const { cout << "Galopuje...\n"; } };

int main() { void (Mammal::*pFunc)() const =0; Mammal* ptr =0; int Animal; int Method; bool fQuit = false;

while (fQuit == false)

49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: }

{ cout << "(0)Wyjscie (1)pies (2)kot (3)kon: "; cin >> Animal; switch (Animal) { case 1: ptr = new Dog; break;

case 2: ptr = new Cat; break; case 3: ptr = new Horse; break; default: fQuit = true; break; } if (fQuit) break;

cout << "(1)Speak! cin >> Method; switch (Method) {

(2)Move: ";

case 1: pFunc = Mammal::Speak; break; default: pFunc = Mammal::Move; break; }

(ptr->*pFunc)(); delete ptr; } return 0;

Wynik
(0)Wyjscie (1)pies (2)kot (3)kon: 1 (1)Speak Hau! (0)Wyjscie (1)pies (2)kot (3)kon: 2 (1)Speak Miau! (0)Wyjscie (1)pies (2)kot (3)kon: 3 (1)Speak (2)Move: 2 (2)Move: 1 (2)Move: 1

Galopuje... (0)Wyjscie (1)pies (2)kot (3)kon: 0

Analiza W liniach od 5. do 14. zostaa zadeklarowana abstrakcyjna klasa Mammal, zawierajca dwie czyste metody wirtualne: Speak() (daj gos) oraz Move() (ruszaj si). Z klasy Mammal zostay wyprowadzone klasy Dog, Cat i Horse, z ktrych kada przesania metody Speak() i Move(). Program sterujcy (funkcja main()) prosi uytkownika o wybranie zwierzcia, ktre ma zosta stworzone, po czym w liniach od 54. do 56. na stercie tworzony jest nowy obiekt klasy pochodnej; jego adres zostaje przypisany wskanikowi ptr. Nastpnie uytkownik jest proszony o wybranie metody, ktra ma zosta wywoana; wybrana metoda jest przypisywana do wskanika pFunc. W linii 70. wywoywana jest metoda wybrana dla stworzonego wczeniej obiektu. Wywoywana jest ona poprzez uycie wskanika ptr (w celu uzyskania dostpu do obiektu) i wskanika pFunc (w celu wywoania jego metody). Na zakoczenie, w linii 71., za pomoc operatora delete zostaje usunity obiekt wskazywany przez ptr (w celu zwolnienia pamici na stercie). Zauwa, e nie ma powodu wywoywania delete dla wskanika pFunc, gdy jest to wskanik do kodu, a nie do obiektu na stercie. W rzeczywistoci taka prba zakoczyaby si wypisaniem bdu kompilacji.

Tablice wskanikw do funkcji skadowych


Podobnie jak w przypadku wskanikw do zwykych funkcji, w tablicach mona przechowywa take wskaniki do funkcji skadowych. Tablica moe zosta zainicjalizowana adresami rnych funkcji skadowych, ktre potem mog by wywoywane dla poszczeglnych elementw tablicy. Technik t ilustruje listing 15.11. Listing 15.11. Tablica wskanikw do funkcji skadowych
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: class Dog { public: void Speak()const { cout << "Hau!\n"; } void Move() const { cout << "Gonie w pietke...\n"; } void Eat() const { cout << "Jem...\n"; } void Growl() const { cout << "Warcze\n"; } void Whimper() const { cout << "Wyje...\n"; } void RollOver() const { cout << "Tarzam sie...\n"; } #include <iostream> using std::cout; //Listing 15.11 Tablica wskanikw do funkcji skadowych

14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: };

void PlayDead() const { cout << "Koniec Malego Cezara?\n"; }

typedef void (Dog::*PDF)()const ; int main() { const int MaxFuncs = 7; PDF DogFunctions[MaxFuncs] = {Dog::Speak, Dog::Move, Dog::Eat, Dog::Growl, Dog::Whimper, Dog::RollOver, Dog::PlayDead };

Dog* pDog =0; int Method; bool fQuit = false;

while (!fQuit) { cout << "(0)Wyjscie (1)Daj glos (2)Ruszaj sie (3)Jedz (4)Warcz"; cout << " (5)Wyj (6)Tarzaj sie (7)Zdechl pies: "; std::cin >> Method; if (Method == 0) { fQuit = true; } else { pDog = new Dog; (pDog->*DogFunctions[Method-1])(); delete pDog; } } return 0;

51:

Wynik
(0)Wyjscie (1)Daj glos (2)Ruszaj sie (3)Jedz (4)Warcz (5)Wyj (6)Tarzaj sie (7)Zdechl pies: 1 Hau! (0)Wyjscie (1)Daj glos (2)Ruszaj sie (3)Jedz (4)Warcz (5)Wyj (6)Tarzaj sie (7)Zdechl pies: 4 Warcze (0)Wyjscie (1)Daj glos (2)Ruszaj sie (3)Jedz (4)Warcz (5)Wyj (6)Tarzaj sie (7)Zdechl pies: 7 Koniec Malego Cezara? (0)Wyjscie (1)Daj glos (2)Ruszaj sie (3)Jedz (4)Warcz (5)Wyj (6)Tarzaj sie (7)Zdechl pies: 0

Analiza W liniach od 5. do 15. zostaa stworzona klasa Dog, zawierajca siedem funkcji skadowych, kad o tym samym zwracanym typie i sygnaturze. W linii 17. instrukcja typedef deklaruje PDF jako wskanik do funkcji skadowej klasy Dog, ktra nie przyjmuje adnych parametrw i nie zwraca adnej wartoci, a ponadto jest funkcj const typ ten jest zgodny z sygnatur wszystkich siedmiu funkcji skadowych klasy Dog. W liniach od 21. do 28. zostaa zadeklarowana tablica DogFunctions, przechowujca wskaniki do siedmiu funkcji skadowych; jest ona inicjalizowana adresami tych funkcji. W liniach 36. i 37. uytkownik jest proszony o wybranie metody. Do momentu wybrania opcji Wyjcie, na stercie za kadym razem jest tworzony obiekt klasy Dog, nastpnie w linii 46. jest dla niego wywoywana odpowiednia funkcja z tablicy. Oto kolejna linia, ktr warto pokaza zarozumiaym programistom z twojej firmy; zapytaj ich, do czego suy:
(pDog->*DogFunctions[Method-1])(); Usunito: 7 Usunito: 8 Usunito:

Take ta konstrukcja moe wydawa si niezrozumiaa, ale zbudowana ze wskanikw do funkcji skadowych tablica moe znacznie uatwi konstruowanie i analiz programu. TAK Wywouj wskaniki do funkcji skadowych dla konkretnych obiektw klasy. W celu uproszczenia deklaracji wskanikw do funkcji skadowych uywaj instrukcji typedef. NIE Nie uywaj wskanikw do funkcji skadowych, gdy mona zastosowa prostsze rozwizanie.

Rozdzia 16. Dziedziczenie zaawansowane


Do tej pory uywalimy dziedziczenia pojedynczego i wielokrotnego w celu stworzenia relacji typu jest-czym. Z tego rozdziau dowiesz si: czym jest zawieranie i jak je modelowa, czym jest delegowanie i jak je modelowa, jak zaimplementowa dan klas poprzez inn, jak uywa dziedziczenia prywatnego.

Usunito: Z Usunito: dziedziczenie

Usunito: Jak dotd Usunito: korzystalimy z Usunito: pojedynczego i wielokrotnego Usunito: W Usunito: tym Usunito: le Usunito: C Usunito: za Usunito: .

Zawieranie
Jak pokazalimy w poprzednich przykadach, moliwe jest, by dane skadowe jednej klasy obejmoway obiekty innych klas. Programici C++ mwi wtedy, e klasa zewntrzna zawiera klas wewntrzn. Tak wic klasa Employee (pracownik) moe zawiera na przykad obiekt typu acucha (przechowujcy nazwisko pracownika) oraz skadowe cakowite (zawierajce jego pensj i inne dane). Listing 16.1 opisuje niekompletn, jednak uyteczn klas String, do podobn do klasy String zadeklarowanej w rozdziale 13. Ten listing nie generuje adnego wydruku. Bdzie on jednak wykorzystywany razem z dalszymi listingami. Listing 16.1. Klasa String
0: 1: 2: 3: #include <iostream> #include <string.h> // Listing 16.1 Klasa String

Usunito: C Usunito: za Usunito: . Usunito: J Usunito: . Usunito: J Usunito: dziedziczenia Usunito: widzielimy Usunito: cho wci Usunito: owo Usunito: daje Usunito: wyniku Usunito: Zamiast tego zostanie Usunito: an

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

using namespace std;

class String { public: // konstruktory String(); String(const char *const); String(const String &); ~String();

// przecione operatory char & operator[](int offset); char operator[](int offset) const; String operator+(const String&); void operator+=(const String&); String & operator= (const String &),

// oglne akcesory int GetLen()const { return itsLen; } const char * GetString() const { return itsString; } static int ConstructorCount;

private: String (int); char * itsString; unsigned short itsLen; // prywatny konstruktor

}; Usunito: zera

// domylny konstruktor tworzcy cig pusty (0 bajtw) String::String() { itsString = new char[1]; itsString[0] = '\0'; itsLen=0; // cout << "\tDomyslny konstruktor lancucha\n";

41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: } } }

// ConstructorCount++;

// prywatny (pomocniczy) konstruktor, uywany tylko przez // metody klasy przy tworzeniu nowego, wypenionego zerowymi // bajtami, acucha o zadanej dugoci String::String(int len) { itsString = new char[len+1]; for (int i = 0; i<=len; i++) itsString[i] = '\0'; itsLen=len; // cout << "\tKonstruktor String(int)\n"; // ConstructorCount++;

// Zamienia tablice znakw w typ String String::String(const char * const cString) { itsLen = strlen(cString); itsString = new char[itsLen+1]; for (int i = 0; i<itsLen; i++) itsString[i] = cString[i]; itsString[itsLen]='\0'; // cout << "\tKonstruktor String(char*)\n"; // ConstructorCount++;

// konstruktor kopiujcy String::String (const String & rhs) { itsLen=rhs.GetLen(); itsString = new char[itsLen+1]; for (int i = 0; i<itsLen;i++) itsString[i] = rhs[i]; itsString[itsLen] = '\0'; // cout << "\tKonstruktor String(String&)\n";

Usunito: i

78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: } } } }

// ConstructorCount++;

// destruktor, zwalnia zaalokowan pami String::~String () { delete [] itsString; itsLen = 0; // cout << "\tDestruktor klasy String\n";

// operator rwnoci, zwalnia istniejc pami, // po czym kopiuje acuch i rozmiar String& String::operator=(const String & rhs) { if (this == &rhs) return *this; delete [] itsString; itsLen=rhs.GetLen(); itsString = new char[itsLen+1]; for (int i = 0; i<itsLen;i++) itsString[i] = rhs[i]; itsString[itsLen] = '\0'; return *this; // cout << "\toperator= klasy String\n";

//nie const operator indeksu, zwraca // referencj do znaku, wic mona go // zmieni! char & String::operator[](int offset) { if (offset > itsLen) return itsString[itsLen-1]; else return itsString[offset];

115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: // zmienia biecy acuch, nie zwraca nic void String::operator+=(const String& rhs) { unsigned short rhsLen = rhs.GetLen(); unsigned short totalLen = itsLen + rhsLen; String temp(totalLen); } // tworzy nowy acuch przez dodanie do rhs // biecego acucha String String::operator+(const String& rhs) { int totalLen = itsLen + rhs.GetLen(); } // const operator indeksu do uywania z obiektami // const (patrz konstruktor kopiujcy!) char String::operator[](int offset) const { if (offset > itsLen) return itsString[itsLen-1]; else return itsString[offset]; Usunito: i

String temp(totalLen); int i, j; for (i = 0; i<itsLen; i++) temp[i] = itsString[i]; for (j = 0; j<rhs.GetLen(); j++, i++) temp[i] = rhs[j]; temp[totalLen]='\0'; return temp;

int i, j; for (i = 0; i<itsLen; i++) temp[i] = itsString[i]; for (j = 0; j<rhs.GetLen(); j++, i++) temp[i] = rhs[i-itsLen];

152: 153: 154: 155: 156: }

temp[totalLen]='\0'; *this = temp;

// int String::ConstructorCount = 0;

Usunito: K

UWAGA Umie kod z listingu 16.1 w pliku o nazwie String.hpp. Za kadym razem, gdy bdziesz potrzebowa klasy String, bdziesz mg doczy listing 16.1 (uywajc instrukcji #include "String.hpp";, tak jak to robimy w dalszych listingach przedstawionych w tym rozdziale).

Usunito: umie Usunito: Wtedy z

Wynik Brak

Usunito: :

Analiza Listing 16.1 zawiera klas String, bardzo podobn do klasy String z listingu 13.12 przedstawionego w rozdziale trzynastym, Tablice i listy poczone. Jednake konstruktory i inne funkcje z listingu 13.12 zawieray instrukcje wypisujce na ekranie komunikaty; w listingu 16.1 instrukcje te zostay wykomentowane. Z funkcji tych skorzystamy w nastpnych przykadach. W linii 25. zostaa zadeklarowana statyczna zmienna skadowa ConstructorCount (licznik konstruktorw), ktra jest inicjalizowana w linii 156. Ta zmienna podlega inkrementacji w kadym konstruktorze klasy. Wszystko to zostao na razie wykomentowane; skorzystamy z tego dopiero w nastpnych listingach. Listing 16.2 przedstawia klas Employee (pracownik), zawierajc trzy obiekty typu String. Listing 16.2. Klasa Employee i program sterujcy
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: class Employee { public: Employee(); Employee(char *, char *, char *, long); ~Employee(); Employee(const Employee&); Employee & operator= (const Employee &); // Listing 16.2 Klasa Employee i program sterujcy #include "String.hpp"

Usunito: :

Usunito: Najwaniejsz rnic jest to, e Usunito: na ekranie Usunito: jest Usunito: inicjalizowana

11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: Employee::Employee(const Employee & rhs): {} Employee::Employee(char * firstName, char * lastName, char * address, long salary): itsFirstName(firstName), itsLastName(lastName), itsAddress(address), itsSalary(salary) {} Employee::Employee(): itsFirstName(""), itsLastName(""), itsAddress(""), itsSalary(0) }; void SetFirstName(const String & fName) { itsFirstName = fName; } void SetLastName(const String & lName) { itsLastName = lName; } void SetAddress(const String & address) { itsAddress = address; } void SetSalary(long salary) { itsSalary = salary; } private: String String String long itsFirstName; itsLastName; itsAddress; itsSalary; const String & GetFirstName() const { return itsFirstName; } const String & GetLastName() const { return itsLastName; } const String & GetAddress() const { return itsAddress; } long GetSalary() const { return itsSalary; }

48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: } {}

itsFirstName(rhs.GetFirstName()), itsLastName(rhs.GetLastName()), itsAddress(rhs.GetAddress()), itsSalary(rhs.GetSalary())

Employee::~Employee() {}

Employee & Employee::operator= (const Employee & rhs) { if (this == &rhs) return *this;

itsFirstName = rhs.GetFirstName(); itsLastName = rhs.GetLastName(); itsAddress = rhs.GetAddress(); itsSalary = rhs.GetSalary();

return *this;

int main() { Employee Edie("Jane","Doe","1461 Shore Parkway", 20000); Edie.SetSalary(50000); String LastName("Levine"); Edie.SetLastName(LastName); Edie.SetFirstName("Edythe");

cout << "Imie i nazwisko: "; cout << Edie.GetFirstName().GetString(); cout << " " << Edie.GetLastName().GetString(); cout << ".\nAdres: "; cout << Edie.GetAddress().GetString(); cout << ".\nPensja: " ; cout << Edie.GetSalary(); return 0;

85:

Dla wygody uytkownika, implementacja klasy String zostaa umieszczona w pliku wraz z deklaracj. W rzeczywistym programie, deklaracja tej klasy zostaaby umieszczona w pliku String.hpp, za jej implementacja w pliku String.cpp. Wtedy moglibymy doda do projektu plik String.cpp (za pomoc polece w menu kompilatora lub za pomoc pliku makefile). Na pocztku pliku String.cpp musiaaby si znale instrukcja #include "String.hpp". W rzeczywistym programie uylibymy klasy String ze standardowej biblioteki C++, a nie klasy stworzonej przez nas samych.

Usunito: plik String.cpp Usunito: przy Usunito: y Usunito: przy Usunito: y Usunito: Jednak przede wszystkim Usunito: Ale, oczywicie, w Usunito: acucha Usunito: siebie Usunito: : Usunito: : Usunito: w Usunito: s Usunito: przy czym Usunito: za Usunito: zostaj Usunito: ane Usunito: przy Usunito: y Usunito: a Usunito: si Usunito: Usunito: ej Usunito: ej Usunito: a

Wynik
Imie i nazwisko: Edythe Levine. Adres: 1461 Shore Parkway. Pensja: 50000

Analiza Listing 16.2 przedstawia klas Employee zawierajc trzy obiekty acuchowe: itsFirstName (imi), itsLastName (nazwisko) oraz itsAddress (adres). W linii 71. zostaje utworzony obiekt klasy Employee, a do jego inicjalizacji s wykorzystywane cztery wartoci. W linii 72. zostaje wywoany akcesor SetSalary() (ustaw pensj), ze sta wartoci 50000. Zwr uwag e, w rzeczywistym programie byaby to albo warto dynamiczna (ustawiana podczas dziaania programu), albo zdefiniowana staa. W linii 73. zostaje utworzony acuch, inicjalizowany za pomoc staej acuchowej w stylu C++. Otrzymany obiekt acuchowy jest nastpnie uywany jako argument funkcji SetLastName() (ustaw nazwisko) w linii 74. W linii 75. wywoana zostaje funkcja SetFirstName() (ustaw imi) klasy Employee, ktrej przekazywana jest inna staa acuchowa. Jeli jednak bliej si jej przyjrzysz, zauwaysz, e klasa Employee nie posiada funkcji SetFirstName(), przyjmujcej jako argument sta acuchow w stylu C; zamiast tego funkcja SetFirstName() wymaga referencji do staego acucha. Kompilator potrafi rozwika to wywoanie, gdy wie, w jaki sposb moe stworzy obiekt acuchowy ze staej acuchowej.. Wie, gdy poinformowalimy go o tym w linii 11. listingu 16.1.
Czsto zadawane pytanie

Usunito: w stylu C Usunito: kopoczemy

Dlaczego uciekamy si do wywoania funkcji GetString() w liniach 78., 79. i 81?


78: cout << Edie.GetFirstName().GetString();

Usunito: em

Odpowied: Metoda GetFirstName() obiektu Edie zwraca obiekt klasy String. Niestety, nasza klasa String jeszcze nie obsuguje operatora << dla cout. Aby wic usatysfakcjonowa cout, musimy zwrci acuch znakw w stylu C. Taki acuch zwraca metoda GetString() naszej klasy String. Problem ten rozwiemy nieco pniej.
Usunito: klasy

Dostp do skadowych klasy zawieranej


Obiekty Employee nie posiadaj specjalnych uprawnie dostpu do zmiennych skadowych klasy String. Gdyby obiekt klasy Employee, Edie (Edyta) prbowa odwoa si do skadowej itsLen w swojej wasnej zmiennej itsFirstName, wystpiby bd kompilacji. Nie stanowi to jednak wikszego problemu, bowiem akcesory tworz interfejs dla klasy String i klasa Employee nie musi si martwi o szczegy implementacji obiektw acuchowych bardziej ni o szczegy implementacji swojej skadowej cakowitej itsSalary.
Usunito: Jeli Usunito: by Usunito: jest Usunito: wikszym Usunito: problemem

Filtrowany dostp do skadowych zawieranych


Zwr uwag, e klasa String posiada operator+. Projektant klasy Employee zablokowa dostp do operatora+ wywoywanego dla obiektw Employee. Uczyni to, deklarujc, e wszystkie akcesory zwracajce acuch, takie jak GetFirstName(), zwracaj sta referencj. Poniewa operator+ nie jest (i nie moe by) funkcj const (gdy zmienia obiekt, dla ktrego jest wywoywany), prba napisania poniszej linii zakoczy si bdem kompilacji:
String buffer = Edie.GetFirstName() + Edie.GetLastName();

Usunito: . Usunito: Interfejs dla klasy String tworz Usunito: B Usunito: , Usunito: wic Usunito: w Usunito: , Usunito: ie Usunito: u

GetFirstName() zwraca stay obiekt String, a nie mona uywa operatora+ dla staych

Usunito: skadowych Usunito: dla staych obiektw Usunito: .

obiektw.

Aby temu zaradzi, przeciamy metod GetFirstName() jako nie const:


const String & GetFirstName() const { return itsFirstName; } String & GetFirstName() { return itsFirstName; }

Zwr uwag, e warto zwracana nie jest const oraz, e sama funkcja nie jest ju const. Zmiana samej wartoci zwracanej nie wystarcza do przecienia nazwy funkcji; musimy take zmieni stao samej funkcji.

Usunito: z Usunito: zarwno Usunito: warto Usunito: jak i Usunito: oraz e Usunito: wartoci

Koszt zawierania
Naley zdawa sobie spraw, e uytkownik klasy Employee ponosi koszty tworzenia i przechowywania obiektw String za kadym razem, gdy tworzony lub kopiowany jest obiekt klasy Employee. Odkomentowanie kilku instrukcji cout w listingu 16.1 ujawni, jak czsto wywoywane s konstruktory klasy String. Listing 16.3 zawiera now wersj programu sterujcego, zawierajc komunikaty wskazujce momenty tworzenia obiektw i wywoywania dla nich metod.
UWAGA Aby skompilowa ten listing, odkomentuj linie 40., 53., 65., 77., 86. oraz 102. na listingu 16.1.
Usunito: Wane jest by Usunito: paci cen Usunito: jest tworzony lub kopiowany Usunito: pokazuje Usunito: przepisan Usunito: na Usunito: o Usunito: napisan

Listing 16.3. Konstruktory klasy zawieranej


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: void SetFirstName(const String & fName) { itsFirstName = fName; } void SetLastName(const String & lName) { itsLastName = lName; } void SetAddress(const String & address) { itsAddress = address; } void SetSalary(long salary) { itsSalary = salary; } private: const String & GetFirstName() const { return itsFirstName; } const String & GetLastName() const { return itsLastName; } const String & GetAddress() const { return itsAddress; } long GetSalary() const { return itsSalary; } class Employee { public: Employee(); Employee(char *, char *, char *, long), ~Employee(); Employee(const Employee&); Employee & operator= (const Employee &); //Listing 16.3 Konstruktory klasy zawieranej #include "String.hpp"

Usunito: u Usunito: jego Usunito: klasy Usunito: klasy

26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: {} {} {} };

String String String long

itsFirstName; itsLastName; itsAddress; itsSalary;

Employee::Employee(): itsFirstName(""), itsLastName(""), itsAddress(""), itsSalary(0)

Employee::Employee(char * firstName, char * lastName, char * address, long salary): itsFirstName(firstName), itsLastName(lastName), itsAddress(address), itsSalary(salary)

Employee::Employee(const Employee & rhs): itsFirstName(rhs.GetFirstName()), itsLastName(rhs.GetLastName()), itsAddress(rhs.GetAddress()), itsSalary(rhs.GetSalary())

Employee::~Employee() {}

Employee & Employee::operator= (const Employee & rhs) { if (this == &rhs) return *this;

itsFirstName = rhs.GetFirstName(); itsLastName = rhs.GetLastName();

63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: } }

itsAddress = rhs.GetAddress(); itsSalary = rhs.GetSalary();

return *this;

int main() { cout << "Tworzenie obiektu Edie...\n"; Employee Edie("Jane","Doe","1461 Shore Parkway", 20000); Edie.SetSalary(20000); cout << "Wywolanie SetFirstName z parametrem char *...\n"; Edie.SetFirstName("Edythe"); cout << "Tworzenie tymczasowego lancucha LastName...\n"; String LastName("Levine"); Edie.SetLastName(LastName);

cout << "Imie i nazwisko: "; cout << Edie.GetFirstName().GetString(); cout << " " << Edie.GetLastName().GetString(); cout << "\nAdres: "; cout << Edie.GetAddress().GetString(); cout << "\nPensja: " ; cout << Edie.GetSalary(); cout << endl; return 0;

Wynik
1: 2: 3: 4: 5: 6: 7: Tworzenie obiektu Edie... Konstruktor String(char*) Konstruktor String(char*) Konstruktor String(char*) Wywolanie SetFirstName z parametrem char *... Konstruktor String(char*) Destruktor klasy String

Usunito: :

8: 9: 10: 11: 12: 13: 14: 15: 16:

Tworzenie tymczasowego lancucha LastName... Konstruktor String(char*) Imie i nazwisko: Edythe Levine Adres: 1461 Shore Parkway Pensja: 20000 Destruktor klasy String Destruktor klasy String Destruktor klasy String Destruktor klasy String
Usunito: : Usunito: Usunito: Oprcz tego

Analiza Listing 16.3 wykorzystuje t sam deklaracj klasy String, co listingi 16.1 i 16.2. Jednak tym razem instrukcje cout w implementacji klasy String zostay odkomentowane. Poza tym, dla uatwienia analizy dziaania, linie wynikw programu zostay ponumerowane. W linii 71. listingu 16.3 zostaje wypisany komunikat Tworzenie obiektu Edie..., co pokazuje pierwsza linia wynikw. W linii 72. zostaje utworzony obiekt klasy Employee o nazwie Edie; konstruktorowi zostaj przekazane cztery parametry. Tak jak mona byo oczekiwa, konstruktor klasy String zosta wywoany trzykrotnie. Linia 74. wypisuje informacyjny komunikat, po czym w linii 75. zostaje wykonana instrukcja Edie.SetFirstName("Edythe");. Ta instrukcja powoduje utworzenie z acucha znakw "Edythe" tymczasowego obiektu klasy String, co odzwierciedlaj linie 5. i 6. wynikw. Zwr uwag, e tymczasowy obiekt jest niszczony natychmiast po uyciu go w instrukcji przypisania. W linii 77. w ciele programu zostaje utworzony obiekt klasy String. W tym przypadku programista wykonuje jawnie to, co kompilator zrobi w poprzedniej instrukcji niejawnie. Tym razem w smej linii wynikw widzimy wywoanie konstruktora, nie ma jednak destruktora. Ten obiekt nie jest niszczony a do chwili, kiedy wyjdzie poza zakres (czyli za koniec funkcji). W liniach od 81. do 87. niszczone s obiekty acuchowe zawarte w klasie Employee, gdy obiekt Edie wychodzi poza zakres funkcji main(). Z tego te powodu niszczony jest take obiekt acuchowy LastName, stworzony wczeniej w linii 77.

Usunito: odzwierciedla Usunito: Wyniki pokazuj e, t

Usunito: jawnie Usunito: niejawnie Usunito: koczcy si wraz Usunito: kocem Usunito: w Usunito: ych

Kopiowanie przez warto


Listing 16.3 pokazuje, e stworzenie jednego obiektu Employee powoduje pi wywoa konstruktora klasy String. Listing 16.4 zawiera kolejn wersj programu sterujcego. Tym razem nie s wypisywane komunikaty o tworzeniu obiektu, lecz zostaje uyta (odkomentowana w linii 156.) statyczna zmienna skadowa ConstructorCount klasy String. Gdy przyjrzysz si listingowi 16.1, zauwaysz, e zmienna ConstructorCount jest (po odkomentowaniu w konstruktorach) inkrementowana za kadym razem, gdy zostaje wywoany konstruktor. Program sterujcy z listingu 16.4 wywouje funkcje wypisujce, przekazujc im obiekt Employee najpierw poprzez referencj, a nastpnie poprzez warto. Zmienna

Usunito: a

ConstructorCount przechowuje aktualn liczb obiektw String, tworzonych podczas przekazywania obiektu Employee jako parametru. UWAGA Aby skompilowa ten listing, pozostaw bez zmian linie, ktre odkomentowae w celu skompilowania listingu 16.3. Natomiast w listingu 16.1 odkomentuj linie 41., 54., 66., 78. oraz 156.

Usunito: Usunito: s

Usunito: Oprcz tego

Listing 16.4. Przekazywanie przez warto


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: void SetFirstName(const String & fName) { itsFirstName = fName; } void SetLastName(const String & lName) { itsLastName = lName; } void SetAddress(const String & address) { itsAddress = address; } void SetSalary(long salary) { itsSalary = salary; } private: String String String long itsFirstName; itsLastName; itsAddress; itsSalary; const String & GetFirstName() const { return itsFirstName; } const String & GetLastName() const { return itsLastName; } const String & GetAddress() const { return itsAddress; } long GetSalary() const { return itsSalary; } class Employee { public: Employee(); Employee(char *, char *, char *, long); ~Employee(); Employee(const Employee&); Employee & operator= (const Employee &); // Listing 16.4 Przekazywanie przez warto #include "String.hpp"

30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66:

};

Employee::Employee(): itsFirstName(""), itsLastName(""), itsAddress(""), itsSalary(0) {}

Employee::Employee(char * firstName, char * lastName, char * address, long salary): itsFirstName(firstName), itsLastName(lastName), itsAddress(address), itsSalary(salary) {}

Employee::Employee(const Employee & rhs): itsFirstName(rhs.GetFirstName()), itsLastName(rhs.GetLastName()), itsAddress(rhs.GetAddress()), itsSalary(rhs.GetSalary()) {}

Employee::~Employee() {}

Employee & Employee::operator= (const Employee & rhs) { if (this == &rhs) return *this;

itsFirstName = rhs.GetFirstName(); itsLastName = rhs.GetLastName(); itsAddress = rhs.GetAddress(); itsSalary = rhs.GetSalary();

return *this;

67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103:

void PrintFunc(Employee); void rPrintFunc(const Employee&);

int main() { Employee Edie("Jane","Doe","1461 Shore Parkway", 20000); Edie.SetSalary(20000); Edie.SetFirstName("Edythe"); String LastName("Levine"); Edie.SetLastName(LastName);

cout << "Ilosc konstruktorow: " ; cout << String::ConstructorCount << endl; rPrintFunc(Edie); cout << "Ilosc konstruktorow: "; cout << String::ConstructorCount << endl; PrintFunc(Edie); cout << "Ilosc konstruktorow: "; cout << String::ConstructorCount << endl; return 0; } void PrintFunc (Employee Edie) { cout << "Imie i nazwisko: "; cout << Edie.GetFirstName().GetString(); cout << " " << Edie.GetLastName().GetString(); cout << ".\nAdres: "; cout << Edie.GetAddress().GetString(); cout << ".\nPensja: " ; cout << Edie.GetSalary(); cout << endl; }

void rPrintFunc (const Employee& Edie) {

104: 105: 106: 107: 108: 109: 110: 111: 112: }

cout << "Imie i nazwisko: "; cout << Edie.GetFirstName().GetString(); cout << " " << Edie.GetLastName().GetString(); cout << "\nAdres: "; cout << Edie.GetAddress().GetString(); cout << "\nPensja: " ; cout << Edie.GetSalary(); cout << endl;

Wynik
Konstruktor String(char*) Konstruktor String(char*) Konstruktor String(char*) Konstruktor String(char*) Destruktor klasy String Konstruktor String(char*) Ilosc konstruktorow: 5 Imie i nazwisko: Edythe Levine Adres: 1461 Shore Parkway Pensja: 20000 Ilosc konstruktorow: 5 Konstruktor String(String&) Konstruktor String(String&) Konstruktor String(String&) Imie i nazwisko: Edythe Levine. Adres: 1461 Shore Parkway. Pensja: 20000 Destruktor klasy String Destruktor klasy String Destruktor klasy String Ilosc konstruktorow: 8 Destruktor klasy String Destruktor klasy String Destruktor klasy String

Usunito: :

Destruktor klasy String

Analiza Wynik pokazuje, e przy okazji tworzenia jednego obiektu klasy Employee jest tworzonych pi obiektw klasy String. Gdy obiekt klasy Employee jest przekazywany funkcji rPrintFunc() przez referencj, nie s tworzone adne dodatkowe obiekty tej klasy, wic nie s tworzone take adne dodatkowe obiekty klasy String (take przekazywane poprzez referencj). Gdy w linii 85. obiekt klasy Employee jest przekazywany do funkcji PrintFunc() poprzez warto, tworzona jest kopia obiektu, oznacza to, e powstaj take trzy kolejne obiekty klasy String (w wyniku wywoania konstruktora kopiujcego).

Usunito: : Usunito: u Usunito: one Usunito: s Usunito: Jednak g Usunito: wic Usunito: i

Implementowanie poprzez dziedziczenie i zawieranie oraz poprzez delegacj


Czasem zdarza si, e jedna klasa chce uy jakich atrybutw innej klasy. Na przykad, przypumy, e musimy stworzy klas PartsCatalog (katalog czci). Z otrzymanej specyfikacji wynika, e klasa PartsCatalog ma by zbiorem czci; kada cz posiada unikalny numer. Klasa PartsCatalog nie pozwala, by w zbiorze wystpoway takie same pozycje, natomiast umoliwia dostp do czci na podstawie jej numeru. Podsumowujcy wiadomoci listing w rozdziale 14. zawiera klas PartsList. Zostaa dobrze przetestowana, wic moemy z niej skorzysta tworzc klas PartsCatalog (bez koniecznoci wymylania wszystkiego od pocztku). Moglibymy stworzy now klas PartsCatalog i umieci w niej klas PartsList. Klasa PartsCatalog mogaby delegowa (przekaza) zarzdzanie poczon list do zawartego w niej obiektu klasy PartsList. Alternatywnym rozwizaniem mogoby by wyprowadzenie klasy PartsCatalog z klasy PartsList i przejcie w ten sposb waciwoci klasy PartsList. Pamitajmy jednak, e dziedziczenie publiczne przedstawia relacj typu jest-czym, wic powinnimy sobie zada pytanie, czy obiekt PartsCatalog rzeczywicie jest obiektem PartsList. Jednym ze sposobw odpowiedzi na pytanie, czy obiekt PartsCatalog, jest obiektem PartsList jest zaoenie, e PartsList jest klas bazow, a PartsCatalog jest klas wyprowadzon, i zadanie poniszych pyta: 1. Czy w klasie bazowej jest cokolwiek, co nie powinno by w klasie wyprowadzonej? Na przykad, czy klasa bazowa PartsList posiada funkcje nieodpowiednie dla klasy PartsCatalog? Jeli tak, prawdopodobnie nie powinnimy stosowa dziedziczenia publicznego. Czy klasa, ktr tworzymy, ma wicej ni jedn baz? Na przykad, czy klasa PartsCatalog wymaga dla kadego swojego obiektu dwch obiektw PartsList? Jeli tak, prawie z ca pewnoci powinnimy uy zawierania.
Usunito: kolekcj Usunito: kolekcji Usunito: oraz Usunito: Listing p Usunito: Klasa PartsList z Usunito: i zrozumiana Usunito: chcemy z niej skorzysta, Usunito: Usunito: zawrze Usunito: na Usunito: y Usunito: Usunito: przejcie Usunito: publiczne Usunito: Usunito: a Usunito: nastpnie Usunito: a Usunito: dziedziczenia Usunito: dwch obiektw PartsList

2.

3.

Czy musimy dziedziczy po klasie bazowej tak, aby mc skorzysta z funkcji wirtualnych lub z dostpu do skadowych chronionych? Jeli tak, musimy uy dziedziczenia, publicznego lub prywatnego.

Usunito: skadowych Usunito: Na podstawie Usunito: albo Usunito: co Usunito: albo Usunito: O Usunito: jest zawierany przez t klas Usunito: Uycie Usunito: klasy w celu wykonania Usunito: B

Uwzgldniajc odpowiedzi na te pytania, musimy dokona wyboru pomidzy dziedziczeniem publicznym (relacj jest-czym) a dziedziczeniem prywatnym (ktrego zasady wyjanimy w dalszej czci rozdziau) lub zawieraniem. Zawieranie obiekt zadeklarowany jako skadowa innej klasy zawartej w tej klasie. Delegacja uycie atrybutw klasy zawieranej dla realizacji funkcji niedostpnych w klasie zawierajcej. Implementacja poprzez budowanie jednej klasy w oparciu o moliwoci innej klasy, bez korzystania z publicznego dziedziczenia.

Delegacja
Dlaczego nie powinnimy wyprowadza klasy PartsCatalog z klasy PartsList? Obiekt PartsCatlog nie jest obiektem PartsList poniewa, obiekty PartsList s uporzdkowanymi zbiorami, ktrych elementy mog si powtarza. Klasa PartsCatalog ma zawiera unikalne pozycje, ktre nie musz by uporzdkowane. Pity element obiektu PartsCatalog nie musi by czci o numerze pi. Oczywicie, istnieje moliwoci publicznego dziedziczenia po klasie PartsList, a nastpnie przesonicia metody Insert() i operatora indeksu ([]) tak, aby dziaay zgodnie z nasz specyfikacj, ale w ten sposb wpynlibymy na istot dziaania tej klasy. Zamiast tego zbudujemy klas PartsCatalog, ktra nie posiada operatora indeksu i nie pozwala na powtarzanie elementw, za w celu czenia dwch zestaww zdefiniujemy operator+. W pierwszym przypadku wykorzystamy zawieranie. Klasa PartsCatalog bdzie delegowa zarzdzanie list na zawarty w niej obiekt klasy PartsList. To rozwizanie ilustruje listing 16.5. Listing 16.5. Delegowanie na zawierany obiekt klasy PartsList
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: // Abstrakcyjna klasa bazowa czci class Part { public: Part():itsPartNumber(1) {} // **************** Cz ************ #include <iostream> using namespace std; // Listing 16.5 Delegowanie na zawierany obiekt klasy PartsList

Usunito: kolekcjami

Usunito: Usunito: sam esencj Usunito: Usunito: jc Usunito: , Usunito: jc Usunito: rzone Usunito: y Usunito: do Usunito: podejciu

12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: }; } };

Part(int PartNumber): itsPartNumber(PartNumber){} virtual ~Part(){} int GetPartNumber() const { return itsPartNumber; } virtual void Display() const =0; private: int itsPartNumber;

// implementacja czystej funkcji wirtualnej, dziki temu // mog z niej korzysta klasy pochodne void Part::Display() const { cout << "\nNumer czesci: " << itsPartNumber << endl;

Usunito: czemu Usunito: mog z niej korzysta

// **************** Cz samochodu ************

class CarPart : public Part { public: CarPart():itsModelYear(94){} CarPart(int year, int partNumber); virtual void Display() const { Part::Display(); cout << "Rok modelu: "; cout << itsModelYear << endl; } private: int itsModelYear;

CarPart::CarPart(int year, int partNumber): itsModelYear(year), Part(partNumber)

49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85:

{}

// **************** Cz samolotu ************

class AirPlanePart : public Part { public: AirPlanePart():itsEngineNumber(1){}; AirPlanePart (int EngineNumber, int PartNumber); virtual void Display() const { Part::Display(); cout << "Nr silnika: "; cout << itsEngineNumber << endl; } private: int itsEngineNumber; };

AirPlanePart::AirPlanePart (int EngineNumber, int PartNumber): itsEngineNumber(EngineNumber), Part(PartNumber) {}

// **************** Wze czci ************ class PartNode { public: PartNode (Part*); ~PartNode(); void SetNext(PartNode * node) { itsNext = node; } PartNode * GetNext() const; Part * GetPart() const;

86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122:

private: Part *itsPart; PartNode * itsNext; }; // Implementacje klasy PartNode...

PartNode::PartNode(Part* pPart): itsPart(pPart), itsNext(0) {}

PartNode::~PartNode() { delete itsPart; itsPart = 0; delete itsNext; itsNext = 0; }

// Gdy nie ma nastpnego wza czci, zwraca NULL PartNode * PartNode::GetNext() const { return itsNext; }

Part * PartNode::GetPart() const { if (itsPart) return itsPart; else return NULL; //bd }

// **************** Klasa PartList ************ class PartsList

123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159:

{ public: PartsList(); ~PartsList(); // wymaga konstruktora kopiujcego i operatora porwnania! void Part* Part* void Part* int static { return } private: PartNode * pHead; int itsCount; static PartsList GlobalPartsList; }; GlobalPartsList; Iterate(void (Part::*f)()const) const; Find(int & position, int PartNumber) GetFirst() const; Insert(Part *); operator[](int) const; GetCount() const { return itsCount; } PartsList& GetGlobalPartsList() const; Usunito: i Usunito: rzy Usunito: rzy

PartsList PartsList::GlobalPartsList;

PartsList::PartsList(): pHead(0), itsCount(0) {}

PartsList::~PartsList() { delete pHead; }

Part* {

PartsList::GetFirst() const

if (pHead)

160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: } { { Part* } Part * { }

return pHead->GetPart(); else return NULL; // w celu wykrycia bdu

PartsList::operator[](int offSet) const

PartNode* pNode = pHead;

if (!pHead) return NULL; // w celu wykrycia bdu

if (offSet > itsCount) return NULL; // bd

for (int i=0;i<offSet; i++) pNode = pNode->GetNext();

return

pNode->GetPart();

PartsList::Find(

int & position, int PartNumber) const

PartNode * pNode = 0; for (pNode = pHead, position = 0; pNode!=NULL; pNode = pNode->GetNext(), position++)

if (pNode->GetPart()->GetPartNumber() == PartNumber) break;

if (pNode == NULL) return NULL; else return pNode->GetPart();

197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233:

void PartsList::Iterate(void (Part::*func)()const) const { if (!pHead) return; PartNode* pNode = pHead; do (pNode->GetPart()->*func)(); while (pNode = pNode->GetNext()); }

void PartsList::Insert(Part* pPart) { PartNode * pNode = new PartNode(pPart); PartNode * pCurrent = pHead; PartNode * pNext = 0;

int New =

pPart->GetPartNumber();

int Next = 0; itsCount++;

if (!pHead) { pHead = pNode; return; }

// jeli ten wze jest mniejszy ni gowa, // staje si now gow if (pHead->GetPart()->GetPartNumber() > New) { pNode->SetNext(pHead); pHead = pNode; return; }

234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: }; }

for (;;) { // jeli nie ma nastpnego, doczamy nowy if (!pCurrent->GetNext()) { pCurrent->SetNext(pNode); return; }

// jeli trafia pomidzy biecy a nastepny, // wstawiamy go tu; w przeciwnym razie bierzemy nastpny pNext = pCurrent->GetNext(); Next = pNext->GetPart()->GetPartNumber(); if (Next > New) { pCurrent->SetNext(pNode); pNode->SetNext(pNext); return; } pCurrent = pNext; } Usunito: o

class PartsCatalog { public: void Insert(Part *); int Exists(int PartNumber); Part * Get(int PartNumber); operator+(const PartsCatalog &); void ShowAll() { thePartsList.Iterate(Part::Display); } private: PartsList thePartsList;

271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307:

void PartsCatalog::Insert(Part * newPart) { int partNumber = int offset; newPart->GetPartNumber();

if (!thePartsList.Find(offset, partNumber)) thePartsList.Insert(newPart); else { cout << partNumber << " byl "; switch (offset) { case 0: case 1: case 2: cout << "pierwsza "; break; cout << "druga "; break; cout << "trzecia "; break;

default: cout << offset+1 << "th "; } cout << "pozycja. Odrzucony!\n"; } }

int PartsCatalog::Exists(int PartNumber) { int offset; thePartsList.Find(offset,PartNumber); return offset; }

Part * PartsCatalog::Get(int PartNumber) { int offset; Part * thePart = thePartsList.Find(offset, PartNumber); return thePart; }

int main()

308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342:

{ PartsCatalog pc; Part * pPart = 0; int PartNumber; int value; int choice;

while (1) { cout << "(0)Wyjscie (1)Samochod (2)Samolot: "; cin >> choice;

if (!choice) break;

cout << "Nowy numer czesci?: "; cin >> PartNumber;

if (choice == 1) { cout << "Model?: "; cin >> value; pPart = new CarPart(value,PartNumber); } else { cout << "Numer silnika?: "; cin >> value; pPart = new AirPlanePart(value,PartNumber); } pc.Insert(pPart); } pc.ShowAll(); return 0; }

Wynik

(0)Wyjscie (1)Samochod (2)Samolot: 1 Nowy numer czesci?: 1234 Model?: 94 (0)Wyjscie (1)Samochod (2)Samolot: 1 Nowy numer czesci?: 4434 Model?: 93 (0)Wyjscie (1)Samochod (2)Samolot: 1 Nowy numer czesci?: 1234 Model?: 94 1234 byl pierwsza pozycja. Odrzucony! (0)Wyjscie (1)Samochod (2)Samolot: 1 Nowy numer czesci?: 2345 Model?: 93 (0)Wyjscie (1)Samochod (2)Samolot: 0 Numer czesci: 1234 Rok modelu: 94 Numer czesci: 2345 Rok modelu: 93 Numer czesci: 4434 Rok modelu: 93 UWAGA Niektre kompilatory nie potrafi skompilowa linii 266., mimo i w C++ jest ona poprawna. Jeli zdarzy si to w przypadku twojego kompilatora, zmie t lini na:
266: void ShowAll() { thePartsList.Iterate(&Part::Display); }

(Chodzi o dopisanie znaku ampersand (&) przed Part::Display.) Jeli rozwie to problem, natychmiast zadzwo do twrcy swojego kompilatora i poskar si.

Analiza Listing 16.5 zawiera klasy Part, PartNode oraz PartsList z listingu podsumowujcego wiadomoci (na kocu rozdziau czternastego). W liniach od 259. do 269. zostaa zadeklarowana nowa klasa, PartsCatalog (katalog czci). Jedn ze skadowych tej klasy jest obiekt klasy PartsList; wanie do tej klasy jest delegowane

zarzdzanie list. Mona take powiedzie, e klasa PartsCatalog jest zaimplementowana poprzez klas PartsList. Zwr uwag, e klienci klasy PartsCatalog nie maj bezporedniego dostpu do klasy PartsList. Interfejsem dla niej jest klasa PartsCatalog i w zwizku z tym dziaanie klasy PartsList ulego duej zmianie. Na przykad, metoda PartsCatalog::Insert() nie pozwala, by w licie PartsList pojawiy si elementy powielone. Implementacja metody PartsCatalog::Insert() rozpoczyna si w linii 271. Obiekt Part, ktry jest przekazywany jako parametr, jest pytany o warto swojej zmiennej skadowej itsPartNumber. Ta warto jest przekazywana do metody PartsList::Find() (znajd) i jeli nie zostanie znaleziona pasujca cz, element jest wstawiany do listy; w przeciwnym razie wypisywany jest komunikat informacyjny. Zwr uwag, e klasa PartsCatalog wstawia elementy, wywoujc metod Insert() dla swojej zmiennej skadowej thePartsList, ktra jest obiektem klasy PartsList. Mechanizm wstawiania i zarzdzania list poczon, a take wyszukiwanie i pobieranie jej elementw, naley wycznie do obiektu klasy PartsList, zawartego w klasie PartsCatalog. Nie ma powodu, by klasa PartsCatalog powielaa ten kod, gdy moe skorzysta z dobrze zdefiniowanego interfejsu. Na tym wanie polega idea ponownego wykorzystania klas w C++: klasa PartsCatalog moe ponownie skorzysta z kodu klasy PartsList, a projektant klasy PartsCatalog moe po prostu zignorowa szczegy implementacji klasy PartsList. Interfejs klasy PartsList (to jest deklaracja tej klasy) dostarcza wszystkich informacji potrzebnych projektantowi klasy PartsCatalog.
Usunito: pl Usunito: j

Usunito: Catalog

Dziedziczenie prywatne
Gdyby klasa PartsCatalog musiaa mie dostp do chronionych skadowych klasy PartsList (w tym przypadku skadowe te nie wystpuj) lub musiaa przesania ktr z metod tej klasy, wtedy klasa PartsCatalog musiaaby zosta wyprowadzona z klasy PartsList. Poniewa klasa PartsCatalog nie jest obiektem PartsList i poniewa nie chcemy udostpnia klientom klasy PartsCatalog caego zestawu funkcji klasy PartsList, musimy uy dziedziczenia prywatnego. Naley wiedzie, i wszystkie zmienne i funkcje skadowe klasy bazowej s traktowane tak, jakby byy zadeklarowane jako prywatne, bez wzgldu na ich rzeczywiste deklaracje w klasie bazowej. Tak wic adna funkcja, ktra nie jest funkcj skadow klasy PartsCatalog, nie ma dostpu do adnej skadowej klasy PartsList. Obowizuje tu nastpujca zasada: dziedziczenie prywatne nie wie si z dziedziczeniem interfejsu, a jedynie z implementacj. Klasa PartsList jest niewidoczna dla klientw klasy PartsCatalog. Nie jest dla nich dostpny aden z jej interfejsw: nie mog wywoywa adnych z jej metod. Mog jednak wywoywa metody klasy PartsCatalog; metody tej klasy mog z kolei odwoywa si do skadowych klasy PartsList (gdy klasa PartsCatalog jest z niej wyprowadzona). Wany jest tu fakt, e klasa PartsCatalog nie jest klas PartsList, tak jak w przypadku dziedziczenia publicznego. Klasa

Usunito: prywatnego

Usunito: nie Usunito: i

PartsCatalog jest zaimplementowana poprzez klas PartsList, tak jak miao to miejsce w

przypadku zawierania. Dziedziczenie prywatne stanowi jedynie uatwienie.

Listing 16.6 demonstruje uycie dziedziczenia prywatnego; w tym przykadzie klasa


PartsCatalog zostaa przepisana jako dziedziczca prywatnie po klasie PartsList.

Listing 16.6. Dziedziczenie prywatne


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: class CarPart : public Part // **************** Cz samochodu ************ } // implementacja czystej funkcji wirtualnej, dziki temu // mog z niej korzysta klasy pochodne void Part::Display() const { cout << "\nNumer czesci: " << itsPartNumber << endl; }; // Abstrakcyjna klasa bazowa czci class Part { public: Part():itsPartNumber(1) {} Part(int PartNumber): itsPartNumber(PartNumber){} virtual ~Part(){} int GetPartNumber() const { return itsPartNumber; } virtual void Display() const =0; private: int itsPartNumber; // **************** Cz ************ //Listing 16.6 demonstruje dziedziczenie prywatne #include <iostream> using namespace std;

31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67:

{ public: CarPart():itsModelYear(94){} CarPart(int year, int partNumber); virtual void Display() const { Part::Display(); cout << "Rok modelu: "; cout << itsModelYear << endl; } private: int itsModelYear; };

CarPart::CarPart(int year, int partNumber): itsModelYear(year), Part(partNumber) {}

// **************** Cz samolotu ************

class AirPlanePart : public Part { public: AirPlanePart():itsEngineNumber(1){}; AirPlanePart(int EngineNumber, int PartNumber); virtual void Display() const { Part::Display(); cout << "Nr silnika: "; cout << itsEngineNumber << endl; } private: int itsEngineNumber; };

68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104:

AirPlanePart::AirPlanePart (int EngineNumber, int PartNumber): itsEngineNumber(EngineNumber), Part(PartNumber) {}

// **************** Wze czci ************ class PartNode { public: PartNode (Part*); ~PartNode(); void SetNext(PartNode * node) { itsNext = node; } PartNode * GetNext() const; Part * GetPart() const; private: Part *itsPart; PartNode * itsNext; }; // Implementacje klasy PartNode...

PartNode::PartNode(Part* pPart): itsPart(pPart), itsNext(0) {}

PartNode::~PartNode() { delete itsPart; itsPart = 0; delete itsNext; itsNext = 0; }

// Gdy nie ma nastpnego wza czci, zwraca NULL PartNode * PartNode::GetNext() const

105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141:

{ return itsNext; }

Part * PartNode::GetPart() const { if (itsPart) return itsPart; else return NULL; //bd }

// **************** Klasa PartList ************ class PartsList { public: PartsList(); ~PartsList(); // wymaga konstruktora kopiujcego i operatora porwnania! void Part* Part* void Part* int static { return } private: PartNode * pHead; int itsCount; static PartsList GlobalPartsList; }; GlobalPartsList; Iterate(void (Part::*f)()const) const; Find(int & position, int PartNumber) GetFirst() const; Insert(Part *); operator[](int) const; GetCount() const { return itsCount; } PartsList& GetGlobalPartsList() const; Usunito: i Usunito: rzy

142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178:

PartsList PartsList::GlobalPartsList;

PartsList::PartsList(): pHead(0), itsCount(0) {}

PartsList::~PartsList() { delete pHead; }

Part* {

PartsList::GetFirst() const

if (pHead) return pHead->GetPart(); else return NULL; } // w celu wykrycia bdu

Part * {

PartsList::operator[](int offSet) const

PartNode* pNode = pHead;

if (!pHead) return NULL; // w celu wykrycia bdu

if (offSet > itsCount) return NULL; // bd

for (int i=0;i<offSet; i++) pNode = pNode->GetNext();

return }

pNode->GetPart();

179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215:

Part* {

PartsList::Find(int & position, int PartNumber)

const

PartNode * pNode = 0; for (pNode = pHead, position = 0; pNode!=NULL; pNode = pNode->GetNext(), position++) { if (pNode->GetPart()->GetPartNumber() == PartNumber) break; } if (pNode == NULL) return NULL; else return pNode->GetPart(); }

void PartsList::Iterate(void (Part::*func)()const) const { if (!pHead) return; PartNode* pNode = pHead; do (pNode->GetPart()->*func)(); while (pNode = pNode->GetNext()); }

void PartsList::Insert(Part* pPart) { PartNode * pNode = new PartNode(pPart); PartNode * pCurrent = pHead; PartNode * pNext = 0;

int New =

pPart->GetPartNumber();

int Next = 0; itsCount++;

if (!pHead)

216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: }

{ pHead = pNode; return; }

// jeli ten wze jest mniejszy ni gowa, // staje si now gow if (pHead->GetPart()->GetPartNumber() > New) { pNode->SetNext(pHead); pHead = pNode; return; }

for (;;) { // jeli nie ma nastpnego, doczamy nowy if (!pCurrent->GetNext()) { pCurrent->SetNext(pNode); return; }

// jeli trafia pomidzy biecy a nastepny, // wstawiamy go tu; w przeciwnym razie bierzemy nastpny pNext = pCurrent->GetNext(); Next = pNext->GetPart()->GetPartNumber(); if (Next > New) { pCurrent->SetNext(pNode); pNode->SetNext(pNext); return; } pCurrent = pNext; }

253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: int PartsCatalog::Exists(int PartNumber) { int offset; } } if (!Find(offset, partNumber)) PartsList::Insert(newPart); else { cout << partNumber << " byl "; switch (offset) { case 0: case 1: case 2: cout << "pierwsza "; break; cout << "druga "; break; cout << "trzecia "; break; void PartsCatalog::Insert(Part * newPart) { int partNumber = int offset; newPart->GetPartNumber(); class PartsCatalog : private PartsList { public: void Insert(Part *); int Exists(int PartNumber); Part * Get(int PartNumber); operator+(const PartsCatalog &); void ShowAll() { Iterate(Part::Display); } private: };

default: cout << offset+1 << "th "; } cout << "pozycja. Odrzucony!\n";

290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: } }

Find(offset,PartNumber); return offset;

Part * PartsCatalog::Get(int PartNumber) { int offset; return (Find(offset, PartNumber));

int main() { PartsCatalog pc; Part * pPart = 0; int PartNumber; int value; int choice;

while (1) { cout << "(0)Wyjscie (1)Samochod (2)Samolot: "; cin >> choice;

if (!choice) break;

cout << "Nowy numer czesci?: "; cin >> PartNumber;

if (choice == 1) { cout << "Model?: "; cin >> value; pPart = new CarPart(value,PartNumber); } else

327: 328: 329: 330: 331: 332: 333: 334: 335: 336: } }

{ cout << "Numer silnika?: "; cin >> value; pPart = new AirPlanePart(value,PartNumber); } pc.Insert(pPart);

pc.ShowAll(); return 0;

Wynik
(0)Wyjscie (1)Samochod (2)Samolot: 1 Nowy numer czesci?: 1234 Model?: 94 (0)Wyjscie (1)Samochod (2)Samolot: 1 Nowy numer czesci?: 4434 Model?: 93 (0)Wyjscie (1)Samochod (2)Samolot: 1 Nowy numer czesci?: 1234 Model?: 94 1234 byl pierwsza pozycja. Odrzucony! (0)Wyjscie (1)Samochod (2)Samolot: 1 Nowy numer czesci?: 2345 Model?: 93 (0)Wyjscie (1)Samochod (2)Samolot: 0 Numer czesci: 1234 Rok modelu: 94 Numer czesci: 2345 Rok modelu: 93 Numer czesci: 4434 Rok modelu: 93

Analiza Listing 16.6 pokazuje zmieniony interfejs klasy PartsCatalog oraz przepisany program sterujcy. Interfejsy innych klas nie zmieniy si w stosunku do listingu 16.5. W linii 255. listingu 16.6 klasa PartsCatalog zostaa zadeklarowana jako dziedziczca prywatnie po klasie PartsList. Interfejs klasy PartsCatalog pozosta taki sam jak na listingu 16.5, jednak obecnie w tej klasie nie korzystamy ju z obiektu klasy PartsList jako zmiennej skadowej. Funkcja PartsCatalog::ShowAll() wywouje metod PartsList::Iterate(), przekazujc jej odpowiedni wskanik do funkcji skadowej klasy Part. Metoda ShowAll() peni rol publicznego interfejsu do metody Iterate(), dostarczajc poprawnych informacji, ale nie pozwala klasom klientw na bezporednie wywoywanie tej metody. Cho klasa PartsList mogaby pozwoli na przekazywanie innych funkcji do metody Iterate(), jednak nie pozwala na to klasa PartsCatalog. Zmianie ulega take sama funkcja Insert(). Zauwa, e w linii 271. metoda Find() jest teraz wywoywana bezporednio, gdy zostaa odziedziczona po klasie bazowej. Wywoanie metody Insert() w linii 272. musi oczywicie korzysta z penej nazwy kwalifikowanej, gdy w przeciwnym razie mielibymy do czynienia z rekurencj. Podsumowujc, gdy metody klasy PartsCatalog chc wywoa metod klasy PartsList, mog uczyni to bezporednio. Jedyny wyjtek stanowi sytuacja, w ktrej klasa PartsCatalog przesania metod, a potrzebna jest jej wersja z klasy PartsList. W takim przypadku konieczne jest uycie penej nazwy kwalifikowanej. Dziedziczenie prywatne pozwala, by klasa PartsCatalog dziedziczya to, czego moe uy i wci zapewniaa klasom klientw poredni dostp do metody Insert() i innych metod, do ktrych te klasy nie powinny mie bezporedniego dostpu. TAK Uywaj dziedziczenia publicznego, gdy wyprowadzany obiekt jest rodzajem obiektu bazowego. Uywaj zawierania wtedy, gdy chcesz delegowa funkcjonalno na inn klas i nie potrzebujesz dostpu do jej skadowych chronionych. Uywaj dziedziczenia prywatnego wtedy, gdy musisz zaimplementowa jedn klas poprzez drug i chcesz mie dostp do jej chronionych skadowych. NIE Nie uywaj dziedziczenia prywatnego, gdy musisz uy wicej ni jednej klasy bazowej. W takiej sytuacji musisz uy zawierania. Na przykad, gdyby klasa PartsCatalog potrzebowaa dwch obiektw PartsList, nie mgby uy dziedziczenia prywatnego. Nie uywaj dziedziczenia publicznego, gdy skadowe klasy bazowej nie powinny by dostpne dla klientw klasy pochodnej.
Usunito: skadowych

Usunito: oczywicie

Usunito: zabezpieczajc Usunito: y Usunito: przed Usunito: m Usunito: m Usunito: 3

Usunito: chronionych

Klasy zaprzyjanione
Czasem klasy tworzy si cznie, jako zestaw. Na przykad, klasy PartNode i PartsList s ze sob cile powizane i byoby wygodnie, gdyby klasa PartsList moga bezporednio odczytywa wskanik do klasy Part z klasy PartNode, czyli mie bezporedni dostp do jej zmiennej skadowej itsPart. Nie chcielibymy, by skadowa itsPart bya skadow publiczn, ani nawet skadow chronion, gdy jest ona szczegem implementacji klasy PartNode, ktry powinien pozosta prywatny. Chcemy jednak udostpni j klasie PartsList. Jeli chcesz udostpni swoje prywatne dane lub funkcje skadowe innej klasie, musisz zadeklarowa t klas jako zaprzyjanion. To rozszerza interfejs twojej klasy o interfejs klasy zaprzyjanionej. Gdy klasa PartNode zadeklaruje klas PartsList jako zaprzyjanion, wszystkie dane i funkcje skadowe klasy PartNode s dla klasy PartsList dostpne jako skadowe publiczne. Naley pamita, e takie zaprzyjanienie nie moe by przekazywane dalej. Cho ty jeste moim przyjacielem, a Joe jest twoim przyjacielem, nie oznacza to, e Joe jest moim przyjacielem. Przyja nie jest take dziedziczona. Cho jeste moim przyjacielem i mam zamiar wyjawi ci swoj tajemnic, nie oznacza to, e mam zamiar wyjawi j twoim dzieciom. Zaprzyjanienie nie dziaa zwrotnie. Zadeklarowanie klasy ClassOne jako klasy zaprzyjanionej klasy ClassTwo nie sprawia, e klasa ClassTwo jest klas zaprzyjanion klasy ClassOne. By moe chcesz wyjawi mi swoje sekrety, ale to nie oznacza, e ja chc wyjawi ci moje. Listing 16.7 ilustruje zastosowanie klasy zaprzyjanionej. Ten przykad to zmodyfikowana wersja listingu 16.6, w ktrej klasa PartsList jest klas zaprzyjanion klasy PartNode. Zwr uwag, e to nie czyni z klasy PartNode klasy zaprzyjanionej klasy PartsList. Listing 16.7. Przykad klasy zaprzyjanionej
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: // Abstrakcyjna klasa bazowa czci class Part { public: Part():itsPartNumber(1) {} Part(int PartNumber): // **************** Cz ************ #include <iostream> using namespace std; //Listing 16.7 Przykad klasy zaprzyjanionej

Usunito: Funkcje

13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: }; } };

itsPartNumber(PartNumber){} virtual ~Part(){} int GetPartNumber() const { return itsPartNumber; } virtual void Display() const =0; private: int itsPartNumber;

// implementacja czystej funkcji wirtualnej, dziki temu // mog z niej korzysta klasy pochodne void Part::Display() const { cout << "\nNumer czesci: "; cout << itsPartNumber << endl;

// **************** Cz samochodu ************

class CarPart : public Part { public: CarPart():itsModelYear(94){} CarPart(int year, int partNumber); virtual void Display() const { Part::Display(); cout << "Rok modelu: "; cout << itsModelYear << endl; } private: int itsModelYear;

CarPart::CarPart(int year, int partNumber): itsModelYear(year), Part(partNumber)

50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86:

{}

// **************** Cz samolotu ************

class AirPlanePart : public Part { public: AirPlanePart():itsEngineNumber(1){}; AirPlanePart(int EngineNumber, int PartNumber); virtual void Display() const { Part::Display(); cout << "Nr silnika: "; cout << itsEngineNumber << endl; } private: int itsEngineNumber; };

AirPlanePart::AirPlanePart(int EngineNumber, int PartNumber): itsEngineNumber(EngineNumber), Part(PartNumber) {}

// **************** Wze czci ************ class PartNode { public: friend class PartsList; PartNode (Part*); ~PartNode(); void SetNext(PartNode * node) { itsNext = node; } PartNode * GetNext() const; Part * GetPart() const; private:

87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: } } } {} };

Part *itsPart; PartNode * itsNext;

PartNode::PartNode(Part* pPart): itsPart(pPart), itsNext(0)

PartNode::~PartNode() { delete itsPart; itsPart = 0; delete itsNext; itsNext = 0;

// Gdy nie ma nastpnego wza czci, zwraca NULL PartNode * PartNode::GetNext() const { return itsNext;

Part * PartNode::GetPart() const { if (itsPart) return itsPart; else return NULL; //bd

// **************** Klasa PartList ************ class PartsList { public:

124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: } {} };

PartsList(); ~PartsList(); // wymaga konstruktora kopiujcego i operatora porwnania! void Part* Part* void Part* int static { return } private: PartNode * pHead; int itsCount; static PartsList GlobalPartsList; GlobalPartsList; Iterate(void (Part::*f)()const) const; Find(int & position, int PartNumber) const; GetFirst() const; Insert(Part *); operator[](int) const; GetCount() const { return itsCount; } PartsList& GetGlobalPartsList() Usunito: i

PartsList PartsList::GlobalPartsList;

// Implementacje list...

PartsList::PartsList(): pHead(0), itsCount(0)

PartsList::~PartsList() { delete pHead;

Part* {

PartsList::GetFirst() const

if (pHead) return pHead->itsPart;

161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: } } }

else return NULL; // w celu wykrycia bdu

Part * PartsList::operator[](int offSet) const { PartNode* pNode = pHead;

if (!pHead) return NULL; // w celu wykrycia bdu

if (offSet > itsCount) return NULL; // bd

for (int i=0;i<offSet; i++) pNode = pNode->itsNext;

return

pNode->itsPart;

Part* PartsList::Find(int & position, int PartNumber) const { PartNode * pNode = 0; for (pNode = pHead, position = 0; pNode!=NULL; pNode = pNode->itsNext, position++) { if (pNode->itsPart->GetPartNumber() == PartNumber) break; } if (pNode == NULL) return NULL; else return pNode->itsPart;

void PartsList::Iterate(void (Part::*func)()const) const

198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234:

{ if (!pHead) return; PartNode* pNode = pHead; do (pNode->itsPart->*func)(); while (pNode = pNode->itsNext); }

void PartsList::Insert(Part* pPart) { PartNode * pNode = new PartNode(pPart); PartNode * pCurrent = pHead; PartNode * pNext = 0;

int New =

pPart->GetPartNumber();

int Next = 0; itsCount++;

if (!pHead) { pHead = pNode; return; }

// jeli ten wze jest mniejszy ni gowa, // staje si now gow if (pHead->itsPart->GetPartNumber() > New) { pNode->itsNext = pHead; pHead = pNode; return; }

for (;;) { // jeli nie ma nastpnego, doczamy ten nowy

235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: } }

if (!pCurrent->itsNext) { pCurrent->itsNext = pNode; return; }

// jeli trafia pomidzy biecy a nastepny, // wstawiamy go tu; w przeciwnym razie bierzemy nastpny pNext = pCurrent->itsNext; Next = pNext->itsPart->GetPartNumber(); if (Next > New) { pCurrent->itsNext = pNode; pNode->itsNext = pNext; return; } pCurrent = pNext;

class PartsCatalog : private PartsList { public: void Insert(Part *); int Exists(int PartNumber); Part * Get(int PartNumber); operator+(const PartsCatalog &); void ShowAll() { Iterate(Part::Display); } private: };

void PartsCatalog::Insert(Part * newPart) { int partNumber = int offset; newPart->GetPartNumber();

if (!Find(offset, partNumber))

272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: } } } }

PartsList::Insert(newPart); else { cout << partNumber << " byl "; switch (offset) { case 0: case 1: case 2: cout << "pierwsza "; break; cout << "druga "; break; cout << "trzecia "; break;

default: cout << offset+1 << "-ta "; } cout << "pozycja. Odrzucony!\n";

int PartsCatalog::Exists(int PartNumber) { int offset; Find(offset,PartNumber); return offset;

Part * PartsCatalog::Get(int PartNumber) { int offset; return (Find(offset, PartNumber));

int main() { PartsCatalog pc; Part * pPart = 0; int PartNumber; int value; int choice;

while (1)

309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: }

{ cout << "(0)Wyjscie (1)Samochod (2)Samolot: "; cin >> choice;

if (!choice) break;

cout << "Nowy numer czesci?: "; cin >> PartNumber;

if (choice == 1) { cout << "Model?: "; cin >> value; pPart = new CarPart(value,PartNumber); } else { cout << "Numer silnika?: "; cin >> value; pPart = new AirPlanePart(value,PartNumber); } pc.Insert(pPart); } pc.ShowAll(); return 0;

Wynik
(0)Wyjscie (1)Samochod (2)Samolot: 1 Nowy numer czesci?: 1234 Model?: 94 (0)Wyjscie (1)Samochod (2)Samolot: 1 Nowy numer czesci?: 4434 Model?: 93 (0)Wyjscie (1)Samochod (2)Samolot: 1

Nowy numer czesci?: 1234 Model?: 94 1234 byl pierwsza pozycja. Odrzucony! (0)Wyjscie (1)Samochod (2)Samolot: 1 Nowy numer czesci?: 2345 Model?: 93 (0)Wyjscie (1)Samochod (2)Samolot: 0 Numer czesci: 1234 Rok modelu: 94 Numer czesci: 2345 Rok modelu: 93 Numer czesci: 4434 Rok modelu: 93

Analiza W linii 79. klasa PartsList zostaa zadeklarowana jako klasa zaprzyjaniona klasy PartNode. Na listingu deklaracj klasy zaprzyjanionej umieszczono w sekcji publicznej, ale nie jest to niezbdne; klasa zaprzyjaniona moe by zadeklarowana w dowolnym miejscu deklaracji klasy, bez koniecznoci zmiany znaczenia instrukcji zaprzyjanienia (friend). Dziki tej instrukcji wszystkie prywatne funkcje i dane skadowe klasy PartNode staj si dostpne dla wszystkich funkcji skadowych klasy PartsList. Zmian t odzwierciedla w linii 157. implementacja funkcji GetFirst(). Zamiast zwraca pHead->GetPart, obecnie funkcja ta moe zwrci (wczeniej bdc prywatn) zmienn skadow pHead->itsPart. Metoda Insert() moe teraz uy pNode->itsNext (zamiast wywoywa pNode->SetNext(pHead)). Oczywicie, zmiany te s kosmetyczne i nie byo istotnego powodu, by uczyni z klasy
PartsList klas zaprzyjanion klasy PartNode, ale zmiany te posuyy do zilustrowania dziaania sowa kluczowego friend (przyjaciel).
Usunito: mog Usunito: Usunito: la

Klasy zaprzyjanione powinny by deklarowane z du rozwag. Deklaracja taka przydaje si, gdy dwie klasy s ze sob cile powizane i czsto korzystaj ze swoich skadowych. Jednak uywaj jej rozsdnie; czsto rwnie atwe okazuje si zastosowanie publicznych akcesorw, dziki ktrym mona modyfikowa jedn z klas bez koniecznoci modyfikowania drugiej.
UWAGA Czsto syszy si, e pocztkujcy programici C++ narzekaj, e deklaracja klasy zaprzyjanionej pomniejsza znaczenia kapsukowania, tak wanego dla obiektowo zorientowanego programowania. Mwic szczerze, to nonsens. Deklaracja friend czyni z

zadeklarowanej klasy zaprzyjanionej cz interfejsu klasy i nie pomniejsza znaczenia kapsukowania bardziej ni publiczne dziedziczenie.

Usunito: podkopuje e

Klasa zaprzyjaniona

Aby zadeklarowa klas zaprzyjanion innej klasy, przed nazw klasy, ktrej chcesz przydzieli dostp, umie sowo kluczowe friend. W ten sposb ja mog zadeklarowa, e jeste moim przyjacielem, ale ty sam nie moesz tego zadeklarowa.

Przykad:
class PartNode { public: friend class PartsList; // deklaruje klas PartsList jako zaprzyjanion };

Funkcje zaprzyjanione
Czasem zdarza si, e chcemy nada ten poziom dostpu nie caej klasie, ale tylko jednej czy dwm jej funkcjom. Moemy to uczyni, deklarujc jako zaprzyjanion funkcj innej klasy (a nie ca t klas). W rzeczywistoci, jako zaprzyjanion moemy zadeklarowa dowoln funkcj, nie tylko funkcj skadow innej klasy.

Funkcje zaprzyjanione i przecianie operatorw


Listing 16.1 zawiera klas String, w ktrej zosta przeciony operator+. Oprcz tego, klasa ta zawieraa konstruktor przyjmujcy stay wskanik do znakw, dziki czemu mona byo tworzy obiekty acuchw z acuchw znakw w stylu C. Pozwalao to na tworzenie obiektu acucha i dodawanie do niego acucha w stylu C.
UWAGA acuchy w stylu C s tablicami znakw zakoczonymi znakiem null, takimi jak char myString[] = "Witaj wiecie".

Nie moglimy jednak stworzy acucha w stylu C (tablicy znakw) i doda do niego obiektu acuchowego, tak jak w poniszym przykadzie:
char cString[] = {"Witaj"}; String sString(" Swiecie"); String sStringTwo = cString + sString; // bd!

Usunito: u Usunito: a Usunito: ch Usunito: liniach

acuchy w stylu C nie posiadaj przecionego operatora+. Jak wspominalimy w rozdziale 10., Funkcje zaawansowane, gdy piszemy cString + sString;, w rzeczywistoci wywoujemy cString.operator+(sString). Poniewa acuchy w stylu C nie posiadaj operatora+(), powoduje to bd kompilacji. Moemy rozwiza ten problem, deklarujc w klasie String funkcj zaprzyjanion, ktra przeciy operator+, lecz przyjmie dwa obiekty acuchw. acuch w stylu C zostanie zamieniony przez odpowiedni konstruktor na obiekt acucha, po czym zostanie wywoany operator+ uywajcy dwch obiektw acuchw. Listing 16.8. Zaprzyjaniony operator+
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: // przecione operatory char & operator[](int offset); char operator[](int offset) const; String operator+(const String&); friend String operator+(const String&, const String&); void operator+=(const String&); // Zasadnicza klasa obiektu acuchowego class String { public: // konstruktory String(); String(const char *const); String(const String &); ~String(); Usunito: a #include <iostream> #include <string.h> using namespace std; //Listing 16.8 - zaprzyjanione operatory

22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: } } };

String & operator= (const String &);

// oglne akcesory int GetLen()const { return itsLen; } const char * GetString() const { return itsString; }

private: String (int); char * itsString; unsigned short itsLen; // prywatny konstruktor

// domylny konstruktor tworzcy cig pusty (0 bajtw) String::String() { itsString = new char[1]; itsString[0] = '\0'; itsLen=0; // cout << "\tDomyslny konstruktor lancucha\n"; // ConstructorCount++;

Usunito: zera

// prywatny (pomocniczy) konstruktor, uywany tylko przez // metody klasy przy tworzeniu nowego, wypenionego zerowymi // bajtami, acucha o danej dugoci String::String(int len) { itsString = new char[len+1]; for (int i = 0; i<=len; i++) itsString[i] = '\0'; itsLen=len; // cout << "\tKonstruktor String(int)\n"; // ConstructorCount++;

// Zamienia tablice znakow w typ String String::String(const char * const cString)

59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95:

{ itsLen = strlen(cString); itsString = new char[itsLen+1]; for (int i = 0; i<itsLen; i++) itsString[i] = cString[i]; itsString[itsLen]='\0'; // cout << "\tKonstruktor String(char*)\n"; // ConstructorCount++; } Usunito: i

// konstruktor kopiujcy String::String (const String & rhs) { itsLen=rhs.GetLen(); itsString = new char[itsLen+1]; for (int i = 0; i<itsLen;i++) itsString[i] = rhs[i]; itsString[itsLen] = '\0'; // cout << "\tKonstruktor String(String&)\n"; // ConstructorCount++; }

// destruktor, zwalnia zaalokowan pami String::~String () { delete [] itsString; itsLen = 0; // cout << "\tDestruktor klasy String\n"; }

// operator rwnoci, zwalnia istniejc pami, // po czym kopiuje acuch i rozmiar String& String::operator=(const String & rhs) { if (this == &rhs) return *this; delete [] itsString;

96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: } } }

itsLen=rhs.GetLen(); itsString = new char[itsLen+1]; for (int i = 0; i<itsLen;i++) itsString[i] = rhs[i]; itsString[itsLen] = '\0'; return *this; // cout << "\toperator= klasy String\n";

//nie const operator indeksu, zwraca // referencj do znaku, wic mona go // zmieni! char & String::operator[](int offset) { if (offset > itsLen) return itsString[itsLen-1]; else return itsString[offset];

// const operator indeksu do uywania z obiektami // const (patrz konstruktor kopiujcy!) char String::operator[](int offset) const { if (offset > itsLen) return itsString[itsLen-1]; else return itsString[offset]; Usunito: i

// tworzy nowy acuch przez dodanie // biecego acucha do rhs String String::operator+(const String& rhs) { int totalLen = itsLen + rhs.GetLen();

String temp(totalLen); int i, j;

133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: } }

for (i = 0; i<itsLen; i++) temp[i] = itsString[i]; for (j = 0, i = itsLen; j<rhs.GetLen(); j++, i++) temp[i] = rhs[j]; temp[totalLen]='\0'; return temp;

// tworzy nowy acuch przez dodanie // jednego acucha do drugiego String operator+(const String& lhs, const String& rhs) { int totalLen = lhs.GetLen() + rhs.GetLen();

String temp(totalLen); int i, j; for (i = 0; i<lhs.GetLen(); i++) temp[i] = lhs[i]; for (j = 0, i = lhs.GetLen(); j<rhs.GetLen(); j++, i++) temp[i] = rhs[j]; temp[totalLen]='\0'; return temp;

int main() { String s1("Lancuch Jeden "); String s2("Lancuch Dwa "); char *c1 = { "Lancuch-C Jeden " } ; String s3; String s4; String s5;

cout << "s1: " << s1.GetString() << endl; cout << "s2: " << s2.GetString() << endl; cout << "c1: " << c1 << endl; s3 = s1 + s2; cout << "s3: " << s3.GetString() << endl;

170: 171: 172: 173: 174: 175: }

s4 = s1 + c1; cout << "s4: " << s4.GetString() << endl; s5 = c1 + s2; cout << "s5: " << s5.GetString() << endl; return 0;

Wynik
s1: Lancuch Jeden s2: Lancuch Dwa c1: Lancuch-C Jeden s3: Lancuch Jeden Lancuch Dwa s4: Lancuch Jeden Lancuch-C Jeden s5: Lancuch-C Jeden Lancuch Dwa

Analiza Z wyjtkiem metody operator+, wszystkie inne metody pozostay takie same jak na listingu 16.1. W linii 21. nowy operator, operator+, zosta przeciony tak, aby przyjmowa dwie stae referencje do acuchw i zwraca acuch; metoda ta zostaa zadeklarowana jako zaprzyjaniona. Zwr uwag, e operator+ nie jest funkcj skadow tej, ani adnej innej klasy. W klasie String jest ona deklarowana tylko po to, aby uczyni j zaprzyjanion dla tej klasy, ale poniewa jest zadeklarowana, nie potrzebujemy ju innego prototypu. Implementacja funkcji operator+ jest zawarta w liniach od 143. do 154. Zwr uwag, e jest ona podobna do wczeniejszej wersji operatora+, ale przyjmuje dwa acuchy i odwouje si do nich poprzez publiczne akcesory. Program sterujcy demonstruje uycie tej funkcji w linii 172., w ktrej operator+ moe by teraz wywoany dla acucha w stylu C.
Usunito: ujcy Usunito: jcy

Funkcje zaprzyjanione

Funkcj zaprzyjanion deklaruje si, uywajc sowa kluczowego friend oraz penej specyfikacji funkcji. Zadeklarowanie funkcji jako zaprzyjanionej nie umoliwia jej dostpu do wskanika this klasy, ale udostpnia jej wszystkie prywatne i chronione funkcje i dane skadowe.

Przykad:
class PartNode

// ... // deklarujemy jako zaprzyjanion funkcj innej klasy friend void PartsList::Insert(Part *); // deklarujemy jako zaprzyjanion funkcj globaln friend int SomeFunction(); // ...

};

Przecianie operatora wstawiania


Jestemy ju gotowi do nadania naszej klasie String moliwoci korzystania z cout w taki sam sposb, w jaki czyni to kady inny typ. Do tej pory, gdy chcielimy wypisa acuch, musielimy robi to nastpujco:
cout << theString.GetString(); Usunito: o Usunito: e Usunito: y

My natomiast chcemy mie moliwo pisania:


cout << theString;

Aby to osign, musimy przeciy operator<<(). W rozdziale 17. zajmiemy si plusami i minusami pracy z obiektem iostream; na razie jedynie zobaczmy, jak na listingu 16.9 zosta przeciony operator<< z wykorzystaniem funkcji zaprzyjanionej. Listing 16.9. Przecianie operatora<<()
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: class String { public: // konstruktory String(); #include <iostream> #include <string.h> using namespace std; // Listing 16.9 Przecianie operatora<<()

11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: } };

String(const char *const); String(const String &); ~String();

// przecione operatory char & operator[](int offset); char operator[](int offset) const; String operator+(const String&); void operator+=(const String&); String & operator= (const String &); friend ostream& operator<< ( ostream& theStream,String& theString); // oglne akcesory int GetLen()const { return itsLen; } const char * GetString() const { return itsString; }

private: String (int); char * itsString; unsigned short itsLen; // prywatny konstruktor

// domylny konstruktor tworzcy cig zera bajtw String::String() { itsString = new char[1]; itsString[0] = '\0'; itsLen=0; // cout << "\tDomyslny konstruktor lancucha\n"; // ConstructorCount++;

// prywatny (pomocniczy) konstruktor, uywany tylko przez // metody klasy przy tworzeniu nowego, wypenionego zerowymi // bajtami, acucha o danej dugoci String::String(int len)

48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84:

{ itsString = new char[len+1]; for (int i = 0; i<=len; i++) itsString[i] = '\0'; itsLen=len; // cout << "\tKonstruktor String(int)\n"; // ConstructorCount++; }

// Zamienia tablice znakw w typ String String::String(const char * const cString) { itsLen = strlen(cString); itsString = new char[itsLen+1]; for (int i = 0; i<itsLen; i++) itsString[i] = cString[i]; itsString[itsLen]='\0'; // cout << "\tKonstruktor String(char*)\n"; // ConstructorCount++; } Usunito: i

// konstruktor kopiujcy String::String (const String & rhs) { itsLen=rhs.GetLen(); itsString = new char[itsLen+1]; for (int i = 0; i<itsLen;i++) itsString[i] = rhs[i]; itsString[itsLen] = '\0'; // cout << "\tKonstruktor String(String&)\n"; // ConstructorCount++; }

// destruktor, zwalnia zaalokowan pami String::~String () { delete [] itsString;

85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: } } }

itsLen = 0; // cout << "\tDestruktor klasy String\n";

// operator rwnoci, zwalnia istniejc pami, // po czym kopiuje acuch i rozmiar String& String::operator=(const String & rhs) { if (this == &rhs) return *this; delete [] itsString; itsLen=rhs.GetLen(); itsString = new char[itsLen+1]; for (int i = 0; i<itsLen;i++) itsString[i] = rhs[i]; itsString[itsLen] = '\0'; return *this; // cout << "\toperator= klasy String\n";

// nie const operator indeksu, zwraca // referencj do znaku, wic mona go // zmieni! char & String::operator[](int offset) { if (offset > itsLen) return itsString[itsLen-1]; else return itsString[offset];

// const operator indeksu do uywania z obiektami // const (patrz konstruktor kopiujcy!) char String::operator[](int offset) const { if (offset > itsLen) return itsString[itsLen-1]; Usunito: i

122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: } } }

else return itsString[offset];

// tworzy nowy acuch przez dodanie // biecego acucha do rhs String String::operator+(const String& rhs) { int totalLen = itsLen + rhs.GetLen();

String temp(totalLen); int i, j; for (i = 0; i<itsLen; i++) temp[i] = itsString[i]; for (j = 0; j<rhs.GetLen(); j++, i++) temp[i] = rhs[j]; temp[totalLen]='\0'; return temp;

// zmienia biecy acuch, nie zwraca nic void String::operator+=(const String& rhs) { unsigned short rhsLen = rhs.GetLen(); unsigned short totalLen = itsLen + rhsLen; String temp(totalLen);

int i, j; for (i = 0; i<itsLen; i++) temp[i] = itsString[i]; for (j = 0, i = 0; j<rhs.GetLen(); j++, i++) temp[i] = rhs[i-itsLen]; temp[totalLen]='\0'; *this = temp;

// int String::ConstructorCount = ostream& operator<< ( ostream& theStream,String& theString) {

159: 160: 161: 162: 163: 164: 165: 166: 167: 168: } }

theStream << theString.itsString; return theStream;

int main() { String theString("Witaj swiecie."); cout << theString; return 0;

Wynik
Witaj swiecie.

Analiza W linii 21. operator<< zosta zadeklarowany jako funkcja zaprzyjaniona, przyjmujca referencj do ostream oraz referencj do obiektu klasy String i zwracajca referencj do ostream. Zauwa, e nie jest to funkcja skadowa klasy String. Zwraca ona referencj do ostream, wic moemy czy wywoania operatora<<, na przykad tak jak w poniszej linii:
cout << "Mam: " << itsAge << " lat.";

Implementacja samej funkcji zaprzyjanionej jest zawarta w liniach od 157. do 161. W rzeczywistoci ukrywa ona tylko szczegy przekazywania acucha do ostream (to wanie powinna robi). Wicej informacji na temat przeciania tego operatora oraz operatora>> znajdziesz w rozdziale 17.

Rozdzia 17. Strumienie


Do tej pory uywalimy cout do wypisywania tekstu na ekranie, za cin do odczytywania klawiatury, wykorzystywalimy je bez penego zrozumienia ich dziaania. Z tego rozdziau dowiesz si: czym s strumienie i jak si ich uywa, jak zarzdza wejciem i wyjciem, wykorzystujc strumienie, jak odczytywa i zapisywa pliki za pomoc strumieni.

Przegld strumieni
C++ nie okrela, w jaki sposb dane s wypisywane na ekranie lub zapisywane do pliku, ani w jaki sposb s one odczytywane przez program. Operacje te stanowi jednak podstawow cz pracy z C++, wic biblioteka standardowa C++ zawiera bibliotek iostream, ktra obsuguje wejcie i wyjcie (I/O, input-output). Zalet oddzielenia funkcji wejcia-wyjcia od jzyka i obsugiwania ich w bibliotekach jest moliwo atwiejszego przenoszenia programw pomidzy rnymi platformami. Dziki temu mona napisa program na komputerze PC, a nastpnie przekompilowa go i uruchomi na stacji roboczej Sun. Producent kompilatora dostarcza odpowiedni bibliotek i wszystko dziaa. Przynajmniej w teorii.

UWAGA Biblioteka jest zbiorem plikw .obj, ktre mog by poczone z programem w celu zapewnienia dodatkowej funkcjonalnoci. Jest to najbardziej podstawowa forma wielokrotnego wykorzystywania kodu, i uywana od czasw pierwszych programistw, ryjcych zera i jedynki na cianach jaski.

Usunito: ponownego

Kapsukowanie
Klasy biblioteki iostream postrzegaj przepyw danych z programu na ekran jako strumie, pyncy bajt po bajcie. Jeli punktem docelowym strumienia jest plik lub ekran, wtedy jego rdem jest zwykle jaka cz programu. Gdy strumie jest odwrcony, dane mog pochodzi z klawiatury lub z dysku i mog wypenia zmienne w programie. Jednym z podstawowych zada strumieni jest kapsukowanie problemu pobierania danych z wejcia (na przykad dysku) i wysyania ich do wyjcia (na przykad na ekran). Po stworzeniu strumienia program dziaa z tym strumieniem, przy czym strumie zajmuje si wszystkimi szczegami. T podstawow ide ilustruje rysunek 17.1. Rys. 17.1. Kapsukowanie poprzez strumienie

Usunito: ktry

Buforowanie
Zapis na dysk (i w mniejszym stopniu take na ekran) jest bardzo kosztowny. Zapis danych na dysk lub odczyt ich z dysku zajmuje (stosunkowo) duo czasu, a podczas operacji zapisu i odczytu dziaanie programu zwykle jest zablokowane. Aby rozwiza ten problem, strumienie oferuj buforowanie. Dane s zapisywane do strumienia, ale nie s natychmiast zapisywane na dysk. Zamiast tego bufor strumienia wypenia si danymi; gdy si cakowicie wypeni, dane s zapisywane na dysk w ramach pojedynczej operacji. Wyobramy sobie wod wpywajc do zbiornika przez grny zawr i wypeniajc go, lecz nie wypywajc z niego poprzez dolny zawr. Ilustruje to rysunek 17.2. Rys. 17.2. Wypenianie bufora

Gdy woda (dane) cakowicie wypeni zbiornik, zawr si otwiera i caa zawarto gwatownie wypywa. Pokazuje to rysunek 17.3. Rys. 17.3. Oprnianie bufora

Gdy bufor si oprni, dolny zawr zostaje zamknity, otwiera si grny zawr i do zbiornikabufora zaczyna napywa woda. Przedstawia to rysunek 17.4. Rys. 17.4. Ponowne napenianie bufora

Czsto zdarza si, e chcemy wypuci wod ze zbiornika jeszcze zanim cakowicie si wypeni. Nazywa si to zrzucaniem bufora. Ilustruje to rysunek 17.5. Rys. 17.5. Zrzucanie bufora

Strumienie i bufory
Jak mona byo oczekiwa, C++ implementuje strumienie i bufory w sposb obiektowy. Klasa streambuf zarzdza buforem, za jej funkcje skadowe oferuj moliwo wypeniania, oprniania, zrzucania i innych sposobw manipulowania buforem. Klasa ios jest bazow klas dla klas wejcia-wyjcia z uyciem strumieni. Jedn ze zmiennych skadowych tej klasy jest obiekt klasy streambuf. Klasy istream i ostream s wyprowadzone z klasy ios i specjalizuj dziaanie strumieni wejciowych i wyjciowych. Klasa iostream jest wyprowadzona zarwno z klasy istream, jak i ostream i dostarcza metod wejcia-wyjcia do wypisywania danych na ekranie i odczytywania ich z klawiatury. Klasa fstream zapewnia wejcie i wyjcie z oraz do plikw.

Standardowe obiekty wejcia-wyjcia


Gdy program C++, zawierajcy klas iostream, rozpoczyna dziaanie, tworzy i inicjalizuje cztery obiekty:

UWAGA Biblioteka klasy iostream jest dodawana przez kompilator do programu automatycznie. Aby uy jej funkcji, musisz doczy do pocztku kodu swojego programu odpowiedni instrukcj #include.

cin (wymawiane jako si-in) obsuguje wprowadzanie danych ze standardowego wejcia, czyli klawiatury. cout (wymawiane jako si-aut) obsuguje wyprowadzanie danych na standardowe wyjcie,

czyli ekran.

standardowe urzdzenie wyjcia dla wydruku bdw, czyli ekran. Poniewa wyjcie bdw nie jest buforowane, wszystko co zostanie wysane do cerr, jest wypisywane na standardowym urzdzeniu bdw natychmiast, bez oczekiwania na wypenienie bufora lub nadejcie polecenia zrzutu.
clog (wymawiane jako si-log) obsuguje buforowane wyprowadzanie danych na standardowe wyjcie bdw, czyli ekran. Do czsto to wyjcie jest przekierowywane do pliku log dziennika, co zostanie opisane w nastpnym podrozdziale.

cerr (wymawiane jako si-err) obsuguje niebuforowane wyprowadzanie danych na

Usunito: Usunito: e

Przekierowywanie
Kade ze standardowych urzdze, wejcia, wyjcia oraz bdw, moe zosta przekierowane na inne urzdzenie. Standardowe wyjcie bdw czsto jest przekierowywane do pliku, za standardowe wejcie i wyjcie mog zosta poczone potokowo z plikami danych wejciowych i wyjciowych. Mona uzyska ten efekt za pomoc polece systemu operacyjnego. Przekierowanie oznacza powizanie wyjcia (lub wejcia) z miejscem innym ni domylne. Operatory przekierowania dla DOS-a i UNIKS-a to: < dla przekierowania wejcia oraz > dla przekierowania wyjcia. Potok oznacza wykorzystanie danych wyjciowych jednego programu jako danych wejciowych drugiego. DOS zapewnia jedynie podstawowe polecenia przekierowywania, takie jak przekierowane wyjcie (>) i przekierowane wejcie (<). UNIX posiada bardziej zaawansowane moliwoci przekierowywania, ale rzdzca nimi zasada jest ta sama: zapisz wyniki skierowane na ekran do pliku lub przeka je potokiem do innego programu. Podobnie, dane wejciowe dla programu mog by pobierane z pliku, a nie z domylnej klawiatury. Przekierowywanie jest raczej funkcj systemu operacyjnego ni bibliotek iostream. C++ zapewnia jedynie dostp do czterech urzdze standardowych; przekierowanie tych urzdze w odpowiednie miejsca naley do uytkownika.

Wejcie z uyciem cin


Globalny obiekt cin odpowiada za wejcie i jest dostpny dla programu po doczeniu biblioteki iostream. We wczeniejszych przykadach, w celu umieszczania danych w zmiennych programu, uywalimy przecionego operatora ekstrakcji (>>). Jak to dziaa? Skadnia, jak by moe pamitasz, jest nastpujca:
int someVariable; cout << "Wpisz liczbe: "; cin >> someVariable;

Globalny obiekt cout zostanie opisany w dalszej czci rozdziau; na razie skupmy si na trzeciej linii, cin >> someVariable;. Czego moemy dowiedzie si na temat cin? Oczywicie, musi to by obiekt globalny, gdy nie zdefiniowalimy go w naszym kodzie. Z dowiadczenia wiemy, e cin posiada przeciony operator ekstrakcji (>>) i e efektem tego jest wypenienie naszej lokalnej zmiennej someVariable danymi z bufora cin. Nie od razu moemy domyli si, e cin przecia operator ekstrakcji dla bardzo rnych typw parametrw, midzy innymi int&, short&, long&, double&, float&, char* i tak dalej. Gdy

piszemy cin >> someVariable;, analizowany jest typ zmiennej someVariable. W poprzednim przykadzie zmienna ta bya typu int, wic zostaa wywoana nastpujca funkcja:
istream & operator>> (int &)

Zauwa, e poniewa parametr jest przekazywany poprzez referencj, operator ekstrakcji moe dziaa na pierwotnej zmiennej. Listing 17.1 ilustruje uycie obiektu cin. Listing 17.1. Obiekt cin obsuguje rne typy danych
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: //Listing 17.1 - uycie obiektu cin #include <iostream> using namespace std; int main() { int myInt; long myLong; double myDouble; float myFloat; unsigned int myUnsigned; cout << "int: "; cin >> myInt; cout << "long: "; cin >> myLong; cout << "double: "; cin >> myDouble; cout << "float: "; cin >> myFloat; cout << "unsigned: "; cin >> myUnsigned; cout << "\n\nint:\t" << myInt << endl; cout << "long:\t" << myLong << endl; cout << "double:\t" << myDouble << endl; cout << "float:\t" << myFloat << endl; cout << "unsigned:\t" << myUnsigned << endl; return 0; }

Wynik
int: 2 long: 70000 double: 987654321 float: 3.33 unsigned: 25 int: long: double: float: 2 70000 9.87654e+008 3.33

unsigned:

25

Analiza W liniach od 7. do 11. s deklarowane zmienne rnych typw. W liniach od 13. do 22. uytkownik jest proszony o wprowadzenie wartoci dla tych zmiennych, po czym w liniach od 24. do 28. wypisywane s (z uyciem cout) wyniki. Wynik odzwierciedla fakt, e dane zostay umieszczone w zmiennych odpowiedniego rodzaju i e program dziaa tak, jak moglimy tego oczekiwa.

acuchy
Obiekt cin moe take obsugiwa argumenty w postaci acuchw do znakw (char*); tak wic moemy stworzy bufor znakw i uy cin do jego wypenienia. Na przykad, moemy napisa:
char YourName[50]; cout << "Wpisz swoje imie: "; cin >> YourName;

Gdy wpiszesz Jesse, zmienna YourName zostanie wypeniona znakami J, e, s, s, \0. Ostatni znak to null; cin automatycznie koczy nim acuch, dlatego w buforze musi by wystarczajca ilo miejsca na pomieszczenie caego acucha oraz koczcego go znaku null. Dla funkcji biblioteki standardowej znak null oznacza koniec acucha. Funkcje biblioteki standardowej zostan omwione w rozdziale 21., Co dalej.

Problemy z acuchami
Pamitajc o wszystkich zaletach obiektu cin, moesz by zaskoczony, prbujc wpisa do acucha swoje pene imi i nazwisko. Obiekt cin traktuje biae spacje jako separatory. Gdy natrafia na spacj lub znak nowej linii, zakada, e dane wejciowe dla parametru s kompletne i, w przypadku acuchw, dodaje na ich kocu znak null. Problem ten ilustruje listing 17.2. Listing 17.2. Prba wpisania do cin wicej ni jednego sowa
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: //Listing 17.2 - cin i acuchy znakw #include <iostream> int main() { char YourName[50]; std::cout << "Podaj imie: "; std::cin >> YourName; std::cout << "Masz na imie: " << YourName << std::endl; std::cout << "Podaj imie i nazwisko: "; std::cin >> YourName;

12: 13: 14:

std::cout << "Nazywasz sie: " << YourName << std::endl; return 0; }

Wynik
Podaj imie: Jesse Masz na imie: Jesse Podaj imie i nazwisko: Jesse Liberty Nazywasz sie: Jesse

Analiza W linii 6. zostaa stworzona tablica znakw (w celu przechowania danych wprowadzanych przez uytkownika). W linii 7. uytkownik jest proszony o wpisanie jedynie imienia; imi to jest przechowywane prawidowo, co odzwierciedla wynik. W linii 10. uytkownik jest proszony o podanie caego nazwiska. Obiekt cin odczytuje dane wejciowe i gdy natrafia na spacj pomidzy wyrazami, umieszcza po pierwszym sowie znak null i koczy odczytywanie danych. Nie tego oczekiwalimy. Aby zrozumie, dlaczego dziaa to w ten sposb, przeanalizuj listing 17.3. Wywietla on dane wprowadzone do kilku pl. Listing 17.3 Wejcie wielokrotne
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: //Listing 17.3 - dziaanie cin #include <iostream> using namespace std; int main() { int myInt; long myLong; double myDouble; float myFloat; unsigned int myUnsigned; char myWord[50]; cout << "int: "; cin >> myInt; cout << "long: "; cin >> myLong; cout << "double: "; cin >> myDouble; cout << "float: "; cin >> myFloat; cout << "slowo: "; cin >> myWord; cout << "unsigned: "; cin >> myUnsigned; cout << "\n\nint:\t" << myInt << endl; cout << "long:\t" << myLong << endl; cout << "double:\t" << myDouble << endl;

30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46:

cout << "float:\t" << myFloat << endl; cout << "slowo: \t" << myWord << endl; cout << "unsigned:\t" << myUnsigned << endl; cout << "\n\nint, long, double, float, slowo, unsigned: "; cin >> myInt >> myLong >> myDouble; cin >> myFloat >> myWord >> myUnsigned; cout << "\n\nint:\t" << myInt << endl; cout << "long:\t" << myLong << endl; cout << "double:\t" << myDouble << endl; cout << "float:\t" << myFloat << endl; cout << "slowo: \t" << myWord << endl; cout << "unsigned:\t" << myUnsigned << endl; return 0; }

Wynik
int: 2 long: 30303 double: 393939397834 float: 3.33 slowo: Hello unsigned: 85 int: 2 long: 30303 double: 3.93939e+011 float: 3.33 slowo: Hello unsigned: 85 int, long, double, float, word, unsigned: 3 304938 393847473 6.66 bye -2 int: 3 long: 304938 double: 3.93847e+008 float: 6.66 slowo: bye unsigned: 4294967294

Analiza Take w tym przykadzie zostao stworzonych kilka zmiennych, tym razem obejmujcych take tablic znakw. Uytkownik jest proszony o wprowadzenie danych, ktre nastpnie zostaj wypisane. W linii 34. uytkownik jest proszony i wpisanie wszystkich danych jednoczenie, po czym kade sowo danych wejciowych jest przypisywane odpowiedniej zmiennej. W przypadku takiego

wielokrotnego przypisania, obiekt cin musi potraktowa kade sowo jako pene dane dla kadej ze zmiennych. Gdyby obiekt cin potraktowa wszystkie dane jako skierowane do jednej zmiennej, wtedy takie poczone wprowadzanie danych nie byoby moliwe. Zwr uwag, e w linii 42. ostatnim zadanym obiektem bya liczba cakowita bez znaku, lecz uytkownik wpisa warto 2. Poniewa cin wierzy, e odczytuje liczb cakowit bez znaku, wzorzec bitowy wartoci 2 zosta odczytany jako liczba bez znaku. Warto ta, wypisana przez cout, to 4294967294. Warto cakowita bez znaku 4294967294 ma dokadnie ten sam wzr bitw, co warto cakowita ze znakiem rwna 2. W dalszej czci rozdziau zobaczymy, jak wpisa do bufora cay acuch z wieloma sowami. Na razie jednak pojawia si pytanie: w jaki sposb operator ekstrakcji daje sobie rad z czeniem wartoci?

operator>> zwraca referencj do obiektu istream


Wartoci zwracan przez cin jest referencja do obiektu istream. Poniewa samo cin jest obiektem klasy istream, wic zwracana warto jednej ekstrakcji moe by wejciem dla nastpnej.
int varOne, varTwo, varThree; cout << "Wpisz trzy liczby: "; cin >> varOne >> varTwo >> varThree;

Gdy piszemy cin >> varOne >> varTwo >> varThree;, wtedy pierwsza ekstrakcja jest obliczana jako (cin >> varOne). Otrzymana warto jest kolejnym obiektem istream i operator ekstrakcji tego obiektu otrzymuje zmienn varTwo. Wyglda to tak, jakbymy napisali:
((cin >> varOne) >> varTwo) >> varThree;

Do techniki tej wrcimy pniej, przy omawianiu dziaania obiektu cout.


Usunito: klasy

Inne funkcje skadowe w dyspozycji cin


W dyspozycji obiektu cin pozostaje nie tylko operator >>, ale take inne funkcje skadowe. S one uywane wtedy, gdy jest wymagana bardziej precyzyjna kontrola wprowadzania danych.
Usunito: Oprcz przecionego operatora>>, obiekt cin posiada

Wprowadzanie pojedynczych znakw


operator>> przyjmujcy referencj do znaku moe by uyty do pobierania pojedynczego znaku ze standardowego wejcia. Do pobrania pojedynczego znaku mona take uy funkcji skadowej get(), i to na dwa sposoby: funkcja get() moe zosta wywoana bez parametrw(wtedy wykorzystywana jest warto zwracana) lub z referencj do znaku.

Uycie get() bez parametrw


Pierwsza forma funkcji get() nie przyjmuje adnych parametrw. Zwraca ona odczytany znak lub znak EOF (znak koca pliku, end of file) w chwili dojcia do koca pliku. Funkcja get() bez parametrw nie jest uywana zbyt czsto. Nie ma moliwoci czenia tej funkcji z wielokrotnym wprowadzaniem, gdy zwracan wartoci nie jest obiekt klasy iostream. Dlatego nie zadziaa ponisza linia kodu:
cin.get() >> myVarOne >> myVarTwo; // niedozwolone

Wartoci zwracan przez cin.get() >> myVarOne jest warto cakowita, a nie obiekt klasy iostream. Najczstsze zastosowanie funkcji get() bez parametrw przedstawia listing 17.4. Listing 17.4. Uycie funkcji get() bez parametrw
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: // Listing 17.4 - Using get() with no parameters #include <iostream> int main() { char ch; while ( (ch = std::cin.get()) != EOF) { std::cout << "ch: " << ch << std::endl; } std::cout << "\nGotowe!\n"; return 0; }

Wynik
Hello ch: H ch: e ch: l ch: l ch: o ch: World

ch: ch: ch: ch: ch: ch:

W o r l d

(ctrl+z) Gotowe!

Analiza W linii 6. zostaa zadeklarowana lokalna zmienna znakowa ch. Ptla while przypisuje dane otrzymane od funkcji get.cin() do ch, i gdy nie jest to znak koca pliku, wypisywany jest acuch. Wypisywane dane s buforowane a do chwili osignicia koca linii. Gdy zostanie napotkany znak EOF (wprowadzony w wyniku nacinicia kombinacji klawiszy Ctrl+Z w DOS-ie lub Ctrl+D w UNIKS-ie), nastpuje wyjcie z ptli. Pamitaj, e nie kada implementacja klasy istream obsuguje t wersj funkcji get(), mimo i obecnie stanowi ona cz standardu ANSI/ISO.

Uycie funkcji get() z parametrem w postaci referencji do znaku


Gdy jako parametr funkcji get() zostanie uyty znak, jest on wypeniany nastpnym znakiem ze strumienia wejciowego. Zwracan wartoci jest obiekt iostream, wic wywoanie tej funkcji get() moe by czone, co ilustruje listing 17.5. Listing 17.5. Uycie funkcji get() z parametrem
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: // Listing 17.5 - Uycie funkcji get() z parametrem #include <iostream> int main() { char a, b, c; std::cout << "Wpisz trzy litery: "; std::cin.get(a).get(b).get(c); std::cout << "a: " << a << "\nb: "; std::cout << b << "\nc: " << c << std::endl; return 0; }

Wynik
Wpisz trzy litery: raz

a: r b: a c: z

Analiza W linii 6. zostay zadeklarowane trzy zmienne znakowe. W linii 10. zostaje trzykrotnie wywoana, w sposb poczony, funkcja cin.get(). Najpierw jest wywoywana cin.get(a). To powoduje umieszczenie pierwszej litery w zmiennej a i zwrcenie obiektu cin. W rezultacie, po powrocie z wywoania, zostaje wywoane cin.get(b), a w zmiennej b jest umieszczana nastpna litera. Ostatecznie wywoane zostaje cin.get(c), a w zmiennej c zostaje umieszczona trzecia litera. Poniewa cin.get(a) zwraca obiekt cin, moglibymy napisa:
cin.gat(a) >> b;

W tej formie cin.get(a) zwraca obiekt cin, wic drug fraz jest cin >> b;. TAK Uywaj operatora ekstrakcji (>>)wtedy, gdy chcesz pomin biae spacje. Uywaj funkcji get() z parametrem wtedy, gdy chcesz sprawdzi kady znak, wcznie z biaymi spacjami. NIE

Odczytywanie acuchw z wejcia standardowego


Operator ekstrakcji (>>) moe by uywany do wypeniania tablicy znakw, podobnie jak funkcje get() i getline(). Ostatnia forma funkcji get() przyjmuje trzy parametry. Pierwszym z nich jest wskanik do tablicy znakw, drugim parametrem jest maksymalna ilo znakw do odczytania plus jeden, za trzecim parametrem jest znak koczcy. Gdy jako drugi parametr podasz warto 20, funkcja get() odczyta dziewitnacie znakw, doda znak null, po czym umieci cao w buforze wskazywanym przez pierwszy parametr. Trzeci parametr, znak koczcy, jest domylnie znakiem nowej linii ('\n'). Gdy znak koczcy zostanie napotkany przed odczytaniem maksymalnej iloci znakw, do acucha zostaje dodany znak null, a znak koczcy pozostaje w buforze wejciowym. Sposb uycia tej formy funkcji get() przedstawia listing 17.6.

Listing 17.6. Uycie funkcji get() z tablic znakw


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: // Listing 17.6 - Uycie funkcji get() z tablic znakw #include <iostream> using namespace std; int main() { char stringOne[256]; char stringTwo[256]; cout << "Wpisz pierwszy lancuch: "; cin.get(stringOne,256); cout << "Pierwszy lancuch: " << stringOne << endl; cout << "Wpisz drugi lancuch: "; cin >> stringTwo; cout << "Drugi lancuch: " << stringTwo << endl; return 0; }

Wynik
Wpisz pierwszy lancuch: Dobry zart Pierwszy lancuch: Dobry zart Wpisz drugi lancuch: tynfa wart Drugi lancuch: tynfa

Analiza W liniach 7. i 8. zostay zadeklarowane dwie tablice znakw. W linii 10. uytkownik jest proszony o wprowadzenie acucha, po czym w linii 11. zostaje wywoana funkcja cin.get(). Pierwszym parametrem jest bufor do wypenienia, za drugim jest zwikszona o jeden maksymalna ilo znakw, jak funkcja get() moe przyj (dodatkowa pozycja jest zarezerwowana dla znaku null, '\0'). Domylnym, trzecim parametrem jest znak nowej linii. Uytkownik wpisuje Dobry art. Poniewa fraza ta koczy si znakiem nowej linii, jest ona umieszczana wraz z koczcym znakiem null w buforze stringOne. W linii 14. uytkownik jest proszony o wpisanie kolejnego acucha; tym razem do odczytania go zosta uyty operator ekstrakcji. Poniewa operator ekstrakcji odczytuje znaki tylko do chwili napotkania biaej spacji, w drugim buforze zostaje umieszczony wyraz tynfa, wraz z kocowym znakiem null. Oczywicie, nie tego chcielimy. Innym sposobem rozwizania tego problemu jest uycie funkcji getline(), co ilustruje listing 17.7. Listing 17.7. Uycie funkcji getline()
0: 1: 2: 3: 4: // Listing 17.7 - Uycie funkcji getline() #include <iostream> using namespace std; Usunito: a

5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:

int main() { char stringOne[256]; char stringTwo[256]; char stringThree[256]; cout << "Wpisz pierwszy lancuch: "; cin.getline(stringOne,256); cout << "Pierwszy lancuch: " << stringOne << endl; cout << "Wpisz drugi lancuch: "; cin >> stringTwo; cout << "Drugi lancuch: " << stringTwo << endl; cout << "Wpisz trzeci lancuch: "; cin.getline(stringThree,256); cout << "Trzeci lancuch: " << stringThree << endl; return 0; }

Wynik
Wpisz pierwszy lancuch: raz dwa trzy Pierwszy lancuch: raz dwa trzy Wpisz drugi lancuch: cztery piec szesc Drugi lancuch: cztery Wpisz trzeci lancuch: Trzeci lancuch: piec szesc

Analiza Ten przykad wymaga dokadnego przeanalizowania, gdy moe sprawi kilka niespodzianek. W liniach od 7. do 9. zostay zadeklarowane trzy tablice znakw. W linii 11. uytkownik jest proszony o wprowadzenie acucha; ten acuch jest odczytywany za pomoc funkcji getline(). Podobnie jak funkcja get(), funkcja getline() przyjmuje bufor i maksymaln liczb znakw. Jednak w odrnieniu od funkcji get(), kocowy znak nowej linii jest odczytywany i odrzucany. Funkcja get() nie odrzuca kocowego znaku nowej linii, ale pozostawia go w buforze wejciowym. W linii 15. uytkownik jest ponownie proszony o wpisanie acucha; tym razem do jego odczytania zostaje uyty operator ekstrakcji. Uytkownik wpisuje cztery pi sze, a w tablicy stringTwo umieszczane jest pierwsze sowo cztery. Nastpnie wywietlany jest komunikat Wpisz trzeci acuch: i ponownie zostaje wywoana funkcja getline(). Poniewa w buforze wejciowym nadal znajduj si sowa pi sze, zostaj one natychmiast odczytane, a do znaku nowej linii; dopiero wtedy funkcja getline() koczy dziaanie i acuch z bufora stringThree zostaje wypisany na ekranie (w 21. linii kodu). Uytkownik nie ma moliwoci wpisania trzeciego acucha, gdy drugie wywoanie funkcji getline() zostaje zaspokojone przez dane pozostajce jeszcze w buforze wejciowym po wywoaniu operatora ekstrakcji w linii 16. Operator ekstrakcji (>>) odczytuje znaki a do chwili napotkania pierwszego biaego znaku, wtedy umieszcza odczytane sowo w tablicy znakw.

Usunito: buforze

Usunito: spenione

Funkcja skadowa get() jest przeciona. W pierwszej wersji nie przyjmuje adnych parametrw i zwraca znak pobrany z bufora wejciowego. W drugiej wersji przyjmuje referencj do pojedynczego znaku i poprzez referencj zwraca obiekt klasy istream. W trzeciej i ostatniej wersji funkcja get() przyjmuje tablic znakw, ilo znakw do pobrania oraz znak koczcy (ktrym domylnie jest znak nowej linii). Ta wersja funkcji get() odczytuje znaki do tablicy a do chwili odczytania maksymalnej ich iloci (mniejszej o jeden od wartoci podanej jako jej parametr) lub do natrafienia na znak kocowy. Gdy funkcja get() natrafi na znak kocowy, przestaje odczytywa dalsze znaki, a znak kocowy pozostawia w buforze. Funkcja skadowa getline() take przyjmuje trzy parametry: bufor do wypenienia, zwikszon o jeden maksymaln ilo znakw, jak moe odczyta, oraz znak kocowy. Funkcja getline() dziaa tak samo, jak funkcja get() z takimi samymi parametrami, z wyjtkiem tego, e funkcja getline() odrzuca znak kocowy.

Usunito: y Usunito: y Usunito: y

Uycie cin.ignore()
Czasem zdarza si, e chcemy pomin znaki, ktre pozostay na kocu linii (do znaku EOL, end of line) lub do koca pliku (EOF, end of file). Suy do tego funkcja skadowa ignore(). Funkcja ta przyjmuje dwa parametry: maksymaln ilo znakw do pominicia oraz znak kocowy. Gdybymy napisali ignore(80,'\n'), zostaoby odrzuconych do osiemdziesiciu znakw. Znaleziony znak nowej linii zostaby odrzucony i funkcja zakoczyaby dziaanie. Uycie funkcji ignore() ilustruje listing 17.8. Listing 17.8. Uycie funkcji ignore()
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: // Listing 17.8 - Uycie funkcji ignore() #include <iostream> using namespace std; int main() { char stringOne[255]; char stringTwo[255]; cout << "Wpisz pierwszy lancuch: "; cin.get(stringOne,255); cout << "Pierwszy lancuch: " << stringOne << endl; cout << "Wpisz drugi lancuch: "; cin.getline(stringTwo,255); cout << "Drugi lancuch: " << stringTwo << endl; cout << "\n\nA teraz sprobuj ponownie...\n"; cout << "Wpisz pierwszy lancuch: "; cin.get(stringOne,255); cout << "Pierwszy lancuch: " << stringOne<< endl; cin.ignore(255,'\n'); cout << "Wpisz drugi lancuch: ";

26: 27: 28: 29:

cin.getline(stringTwo,255); cout << "Drugi lancuch: " << stringTwo<< endl; return 0; }

Wynik
Wpisz pierwszy lancuch: dawno temu Pierwszy lancuch: dawno temu Wpisz drugi lancuch: Drugi lancuch: A teraz sprobuj ponownie... Wpisz pierwszy lancuch: dawno temu Pierwszy lancuch: dawno temu Wpisz drugi lancuch: byl sobie... Drugi lancuch: byl sobie...

Analiza W liniach 6. i 7. zostay stworzone dwie tablice znakw. W linii 9. uytkownik jest proszony o wprowadzenie acucha, wic wpisuje sowa dawno temu, po ktrych naciska klawisz Enter. Do odczytania acucha zostaje w linii 10. wywoana funkcja get(). Funkcja get() wypenia tablic stringOne i koczy dziaanie na znaku nowej linii, ale nie odrzuca go, lecz pozostawia w buforze wejciowym. W linii 13. uytkownik jest ponownie proszony o wpisanie acucha znakw, ale funkcja getline() w linii 14. odczytuje pozostay w buforze znak nowej linii i natychmiast po tym koczy dziaanie (zanim uytkownik moe wpisa jakikolwiek znak). W linii 19. uytkownik jest jeszcze raz proszony o dokonanie wpisu i wpisuje te same sowa, co na pocztku. Jednak tym razem, w linii 23., zostaje uyta funkcja ignore(), ktra zjada pozostajcy znak nowej linii. W zwizku z tym, gdy w linii 26. zostaje wywoana funkcja getline(), bufor wejciowy jest ju pusty i uytkownik moe wprowadzi nastpn lini historyjki.

peek() oraz putback()


Obiekt strumienia wejciowego cin posiada dwie dodatkowe metody, ktre mog okaza si cakiem przydatne: funkcj peek(), ktra sprawdza, czy w buforze jest dostpny znak, lecz nie pobiera go, oraz funkcj putback(), ktra wstawia znak do strumienia wejciowego. Listing 17.9 pokazuje, w jaki sposb mogyby zosta uyte te funkcje. Listing 17.9. Uycie funkcji peek() i putback()
0: 1: 2: 3: // Listing 17.9 - Uycie funkcji peek() i putback() #include <iostream> using namespace std;

4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:

int main() { char ch; cout << "Wpisz fraze: "; while ( cin.get(ch) ) { if (ch == '!') cin.putback('$'); else cout << ch; while (cin.peek() == '#') cin.ignore(1,'#'); } return 0; }

Wynik
Wpisz fraze: Nadszedl!czas#na!dobra#zabawe! Nadszedl$czasna$dobrazabawe$

Analiza W linii 6. zostaa zadeklarowana zmienna znakowa ch, a w linii 7. uytkownik jest proszony o wpisanie frazy. Zadaniem tego programu jest zamiana kadego wykrzyknika (!) na znak dolara ($) oraz usunicie kadego znaku hash (#). Program dziaa w ptli a do napotkania znaku koca pliku (Ctrl+C w Windows, Ctrl+Z lub Ctrl+D w innych systemach operacyjnych). Pamitajmy, e funkcja cin.get() zwraca warto 0 dla zasygnalizowania koca pliku. Jeli biecy znak jest wykrzyknikiem, zostaje odrzucony i do bufora wejciowego jest wstawiany znak dolara; zostanie on odczytany jako nastpny znak acucha. Jeli biecy znak nie jest wykrzyknikiem, zostaje wydrukowany. Kady znak w buforze jest sprawdzany za pomoc funkcji peek() i jeli jest to znak #, zostaje usunity. Nie jest to najbardziej efektywny sposb wykonania tego zadania (nie usunie znaku #, gdy bdzie on pierwszym znakiem wprowadzonego acucha), ale ilustruje sposb dziaania tych metod. Nie s one wykorzystywane zbyt czsto i nie am sobie gowy, prbujc na si wymyli jakie ich zastosowanie. Potraktuj je jako swego rodzaju sztuczki; by moe kiedy do czego si przydadz.
Usunito: C Usunito: dla koca pliku

Usunito:

RADA Funkcje peek() i putback() s zwykle uywane do przetwarzania acuchw i innych danych, na przykad wtedy, gdy chcemy stworzy kompilator.

Wyjcie poprzez cout


Obiektu cout, wraz z przecionym operatorem wstawiania (<<), uywalimy do wypisywania na ekranie acuchw, liczb cakowitych i innych danych. Istnieje take moliwo formatowania

wypisywanych danych, wyrwnywania kolumn i wypisywania danych numerycznych w postaci dziesitnej i szesnastkowej. Wszystkie te zagadnienia opiszemy w tym podrozdziale.

Zrzucanie zawartoci bufora


Przekonalimy si, e uycie endl powoduje zrzucenie zawartoci bufora. endl wywouje funkcj skadow flush() obiektu cout, ktra wypisuje wszystkie dane znajdujce si w buforze. Funkcj flush() mona wywoywa bezporednio, albo poprzez wywoanie metody obiektu, albo poprzez napisanie:
cout << flush

Przydaje si to wtedy, gdy chcemy mie pewno, e bufor wyjciowy jest pusty i e jego zawarto zostaa wypisana na ekranie.

Powizane funkcje
Dziaanie operatora ekstrakcji moe by rozszerzone funkcjami get() i getline(); operator wstawiania take moe by uzupeniony funkcjami put() oraz write(). Funkcja put() suy do wysyania pojedynczego znaku do urzdzenia wyjciowego. Poniewa funkcja ta zwraca referencj do obiektu klasy ostream i poniewa cout jest obiektem tej klasy, moemy czy wywoanie funkcji put() z wywoaniami operatora wstawiania. Ilustruje to listing 17.10. Listing 17.10. Uycie funkcji put()
0: // Listing 17.10 - Uycie funkcji put() 1: 2: #include <iostream> 3: 4: int main() 5: { 6: std::cout.put('H').put('e').put('l').put('l').put('o').put('\n'); 7: return 0; 8: }

Wynik
Hello

UWAGA Niektre kompilatory maj problem z wypisywaniem znakw za pomoc powyszego kodu. Jeli twj kompilator nie wypisze sowa Hello, moesz pomin ten listing.

Usunito: a

Analiza Linia 6. jest przetwarzana w nastpujcy sposb: std::cout.put('H') wypisuje na ekranie liter H i zwraca obiekt cout. To pozostawia nam:
cout.put('e').put('l').put('l').put('o').put('\n');

Wypisana zostaje litera e, pozostawiajc cout.put('l'). Proces si powtarza: wypisane zostaj litery i zwracany jest obiekt cout, a do chwili wypisania kocowego znaku ('\n'), po czym funkcja koczy dziaanie. Funkcja write() dziaa podobnie jak operator wstawiania (<<), ale przyjmuje parametr okrelajcy maksymaln ilo znakw, jaka moe zosta wypisana. Jej uycie pokazuje listing 17.11. Listing 17.11. Uycie funkcji write()
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: // Listing 17.11 - Uycie funkcji write() #include <iostream> #include <string.h> using namespace std; int main() { char One[] = "O jeden most za daleko"; int fullLength = strlen(One); int tooShort = fullLength -4; int tooLong = fullLength + 6; cout.write(One,fullLength) << "\n"; cout.write(One,tooShort) << "\n"; cout.write(One,tooLong) << "\n"; return 0; }

Wynik
O jeden most za daleko O jeden most za da O jeden most za daleko UWAGA W twoim komputerze wynik moe wyglda nieco inaczej.

Analiza

W linii 7. jest tworzona pojedyncza fraza. W linii 9. zmienna fullLength (pena dugo) zostaje ustawiona na dugo frazy, zmienna tooShort (zbyt krtka) zostaje ustawiona na dugo pomniejszon o cztery, za zmienna tooLong (zbyt duga) zostaje ustawiona na warto fullLength plus sze. W linii 13., za pomoc funkcji write(), zostaje wypisana caa fraza. Dugo zostaa ustawiona zgodnie z faktyczn dugoci frazy, wic wydruk jest poprawny. W linii 14. fraza jest wypisywana ponownie, lecz tym razem jest o cztery znaki krtsza ni pena wersja, co odzwierciedla kolejna linia wyniku. W linii 15. fraza jest wypisywana jeszcze raz, ale tym razem funkcja write() ma wypisa o sze znakw za duo. Po wypisaniu frazy wypisane zostaj znaki odpowiadajce wartociom szeciu bajtw z pamici pooonej bezporednio za fraz.

Manipulatory, znaczniki oraz instrukcje formatowania


Strumie wyjciowy posiada kilka znacznikw stanu, okrelajcych aktualnie uywany system liczbowy (dziesitny lub szesnastkowy), szeroko wypisywanych pl oraz znak uywany do wypeniania pl. Znacznik stanu jest bajtem, ktrego poszczeglne bity posiadaj okrelone znaczenia. Sposb operowania bitami zostanie opisany w rozdziale 21. Kady ze znacznikw klasy ostream moe by ustawiany za pomoc funkcji skadowych i manipulatorw.

Uycie cout.width()
Domylna szeroko wydruku umoliwia zmieszczenie w jej obrbie wypisywanej liczby, znaku lub acucha znajdujcego si w buforze wyjciowym. Mona j zmieni, uywajc funkcji width(). Poniewa width() jest funkcj skadow, musi by wywoywana z uyciem obiektu cout. Zmienia ona szeroko jedynie nastpnego wypisywanego pola, po czym natychmiast przywraca ustawienia domylne. Jej uycie ilustruje listing 17.12. Listing 17.12. Dostosowywanie szerokoci wydruku
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: // Listing 17.12 - Dostosowywanie szerokoci wydruku #include <iostream> using namespace std; int main() { cout << "Start >"; cout.width(25); cout << 123 << "< Koniec\n"; cout << "Start >"; Usunito: niku

Usunito: niku

11: 12: 13: 14: 15: 16: 17: 18: 19: 20:

cout.width(25); cout << 123<< "< Nastepny >"; cout << 456 << "< Koniec\n"; cout << "Start >"; cout.width(4); cout << 123456 << "< Koniec\n"; return 0; }

Wynik
Start > Start > Start >123456< Koniec 123< Koniec 123< Nastepny >456< Koniec

Analiza Pierwsze wyjcie (linie od 6. do 8. kodu) wypisuje liczb 123 w polu, ktrego szeroko zostaa ustawiona na 25 w linii 7.. Odzwierciedla to pierwsza linia wyniku. Drugie wyjcie najpierw wypisuje liczb 123 w polu o szerokoci ustawionej na 25 znakw, po czym wypisuje warto 456. Zauwa, e warto 456 jest wypisywana w polu o szerokoci umoliwiajcej precyzyjne zmieszczenie tej liczby; jak wspomniano, funkcja width() odnosi si wycznie do nastpnego wypisywanego pola. Ostatnia linia wyniku pokazuje, i ustawienie szerokoci mniejszej ni wymagana oznacza ustawienie szerokoci wystarczajcej na zmieszczenie wypisywanego acucha.
Usunito: u

Ustawianie znakw wypenienia


Zwykle obiekt cout wypenia puste pola (powstae w wyniku wywoania funkcji width()) spacjami, tak jak widzielimy w poprzednim przykadzie. Czasem zdarza si jednak, e chcemy wypeni ten obszar innymi znakami, na przykad gwiazdkami. W tym celu moemy wywoa funkcj fill(), jako argument przekazujc jej znak, ktrego chcemy uy jako znaku wypenienia. Pokazuje to listing 17.13. Listing 17.13. Uycie funkcji fill()
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: // Listing 17.13 - Uycie funkcji fill() #include <iostream> using namespace std; int main() { cout << "Start >"; cout.width(25); cout << 123 << "< Koniec\n";

Usunito: a

Usunito: a

11: 12: 13: 14: 15: 16: 17:

cout << "Start >"; cout.width(25); cout.fill('*'); cout << 123 << "< Koniec\n"; return 0; }

Wynik
Start > 123< Koniec Start >**********************123< Koniec

Analiza Linie od 7. do 9. stanowi powtrzenie poprzedniego przykadu. Linie od 12. do 15. take stanowi powtrzenie, ale tym razem, w linii 14., jako znak wypeniania zostaje wybrana gwiazdka, co odzwierciedla druga linia wyniku.

Funkcja setf()
Obiekt iostream pamita swj stan dziki przechowywanym znacznikom. Moemy je ustawia, wywoujc funkcj setf() i przekazujc jej jedn z predefiniowanych staych wyliczeniowych. Obiekty posiadaj stan wtedy, gdy ktra lub wszystkie z ich danych reprezentuj warunki, ktre mog ulega zmianom podczas dziaania programu. Na przykad, moemy okreli, czy maj by wywietlane zera kocowee (tak by 20.00 nie zostao obcite do 20). Aby wyczy zera kocowe, wywoaj setf(ios::showpoint). Stae wyliczeniowe nale do zakresu klasy iostream (ios) i w zwizku z tym s stosowane z pen kwalifikowan nazw w postaci ios::nazwa_znacznika, na przykad ios::showpoint. Uywajc ios::showpos mona wczy wywietlanie znaku plus (+) przed liczbami dodatnimi. Z kolei do wyrwnywania wyniku su stae ios::left (do lewej strony), ios::right (do prawej) lub ios::internal (znak wypenienia jest wstawiany midzy przedrostkiem oznaczajcym system a liczb). Ustawiajc znaczniki, moemy take ustawi system dla wypisywanych liczb jako dziesitny (staa ios::dec), semkowy (o podstawie osiem, staa ios::oct) lub szesnastkowy (o podstawie szesnacie, staa ios::hex). Te znaczniki mog by take czone z operatorem wstawiania. Ustawienia te ilustruje listing 17.14. Dodatkowo, listing ten pokazuje take zastosowanie manipulatora setw, ktry ustawia szeroko pola, ale moe by przy tym czony z operatorem wstawiania. Listing 17.14. Uycie funkcji setf()
0: 1: 2: 3: 4: // Listing 17.14 - Uycie funkcji setf() #include <iostream> #include <iomanip> using namespace std;

Usunito: a Usunito: prze znakiem Usunito: wybra

5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31:

int main() { const int number = 185; cout << "Liczba to " << number << endl; cout << "Liczba to " << hex << cout.setf(ios::showbase); cout << "Liczba to " << hex << cout << "Liczba to " ; cout.width(10); cout << hex << number << endl; cout << "Liczba to " ; cout.width(10); cout.setf(ios::left); cout << hex << number << endl; cout << "Liczba to " ; cout.width(10); cout.setf(ios::internal); cout << hex << number << endl; cout << "Liczba to:" << setw(10) << hex << number << endl; return 0; number << endl; number << endl;

Wynik
Liczba Liczba Liczba Liczba Liczba Liczba Liczba to 185 to b9 to 0xb9 to to 0xb9 to 0x to:0x

0xb9 b9 b9

Analiza W linii 7. staa typu int zostaje zainicjalizowana wartoci 185. Ta warto zostaje wypisana w linii 8. Warto jest wywietlana ponownie w linii 10., ale tym razem w poczeniu z manipulatorem hex, ktry powoduje, e warto ta jest wypisywana w systemie szesnastkowym (jako b9). (Szesnastkowa cyfra b odpowiada dziesitnej liczbie 11. Jedenacie razy szesnacie rwna si 176; po dodaniu 9 otrzymujemy warto 185.) W linii 12. zostaje ustawiony znacznik showbase. Powoduje on, e do wszystkich liczb szesnastkowych zostaje dodany przedrostek 0x (co wida w kolejnych liniach wyniku). W linii 16. szeroko pola zostaje ustawiona na dziesi znakw, a wypisywana warto jest wyrwnywana do prawej strony pola. W linii 20. szeroko take zostaje ustawiona na dziesi znakw, ale tym razem wypisywana liczba zostaje wyrwnana do lewej strony.

W linii 25. szeroko take zostaje ustawiona na dziesi znakw, ale tym razem zostaje zastosowany znacznik internal. W zwizku z tym przedrostek (0x) jest wypisywany po lewej stronie, a sama warto (b9) po prawej. Na koniec, w linii 29. do ustawienia szerokoci dziesiciu znakw zostaje uyty operator czenia (konkatenacji) setw(), po czym warto jest wypisywana ponownie.
Usunito: poczony

Strumienie kontra funkcja printf()


Wikszo implementacji C++ posiada standardowe biblioteki wejcia-wyjcia z jzyka C, zawierajce midzy innymi funkcj printf(). Cho funkcja ta moe by atwiejsza w uyciu ni obiekt cout, jednak jej uycie nie jest zalecane. Funkcja printf() nie zapewnia bezpieczestwa typw, wic atwo jest przypadkowo nakaza jej wypisanie wartoci cakowitej jako znaku, i odwrotnie. Funkcja ta nie obsuguje klas, wic nie ma moliwoci poinformowania jej jak ma wypisywa dane klasy; trzeba jej przekazywa kolejno wszystkie wartoci, ktre maj zosta wypisane. Poniewa korzysta z tej funkcji dua ilo starszego kodu, w tym podrozdziale pokrtce omwimy jej dziaanie. Aby mc skorzysta z tej funkcji, musisz pamita o doczeniu do kodu pliku nagwkowego stdio.h. W swojej najprostszej formie funkcja printf() przyjmuje jako pierwszy parametr acuch formatujcy, za jako nastpne parametry seri wartoci. acuch formatujcy jest ujtym w cudzysowy acuchem znakw, zawierajcym specyfikatory konwersji. Wszystkie specyfikatory konwersji musz zaczyna si od symbolu procentw (%). Najczciej stosowane specyfikatory zostay zebrane w tabeli 17.1. Tabela 17.1. Powszechnie stosowane specyfikatory konwersji Specyfikator
%s %d %ld %lg %f

Zastosowanie Wypisywanie acuchw. Wypisywanie liczb cakowitych. Wypisywanie dugich liczb cakowitych. Wypisywanie wartoci typu double. Wypisywanie wartoci typu float.
Usunito: d

Kady ze specyfikatorw konwersji moe zawiera pole szerokoci oraz pole precyzji, przedstawiane jako warto zmiennoprzecinkowa, w ktrej cyfry po lewej stronie kropki dziesitnej oznaczaj cakowit szeroko pola, a cyfry po prawej stronie punktu dziesitnego oznaczaj dokadno zapisu liczb zmiennopozycyjnych. Tak wic specyfikator %5d okrela szerok na pi cyfr liczb cakowit, a %15.5f jest specyfikatorem dla szerokiej na pitnacie

cyfr wartoci typu float, w ktrej na czci dziesitn ma zosta przeznaczone pi ostatnich cyfr. Rne zastosowania funkcji printf() przedstawia listing 17.15. Listing 17.15. Drukowanie z uyciem funkcji printf()
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: //17.15 Drukowanie z uyciem funkcji printf() #include <stdio.h> int main() { printf("%s","Witaj swiecie\n"); char *phrase = "Witaj ponownie!\n"; printf("%s",phrase); int x = 5; printf("%d\n",x); char *phraseTwo = "Oto kilka wartosci: "; char *phraseThree = " i jeszcze te: "; int y = 7, z = 35; long longVar = 98456; float floatVar = 8.8f; printf("%s %d %d",phraseTwo,y,z); printf("%s %ld %f\n",phraseThree,longVar,floatVar); char *phraseFour = "Sformatowane: "; printf("%s %5d %10d %10.5f\n",phraseFour,y,z,floatVar); return 0; }

Wynik
Witaj swiecie Witaj ponownie! 5 Oto kilka wartosci: 7 35 i jeszcze te: 98456 8.800000 Sformatowane: 7 35 8.80000

Analiza Pierwsza instrukcja printf(), zawarta w linii 5., uywa formy standardowej: nazwy printf, po ktrej nastpuje ujty w cudzysowy acuch ze specyfikatorem konwersji (w tym przypadku %s), a po nim warto, ktra ma zosta wstawiona w miejsce specyfikatora konwersji. Specyfikator %s wskazuje, e jest to acuch, a wartoci acucha w tym przypadku jest ujte w cudzysowy "Witaj swiecie". Druga instrukcja printf() jest podobna do pierwszej, ale tym razem jako drugi parametr funkcji zosta uyty wskanik do typu char, a nie staa acuchowa. Trzecia instrukcja printf(), w linii 11., uywa specyfikatora konwersji dla liczb cakowitych, za wstawian wartoci jest zmienna cakowita x. Czwarta instrukcja printf(), w linii 19., jest

ju bardziej skomplikowana. S w niej czone trzy wartoci. Dla kadej z nich istnieje specyfikator konwersji, a odpowiednie wartoci s przekazywane jako rozdzielone przecinkami kolejne parametry funkcji. Na koniec, w linii 23. specyfikatory konwersji zostaj uyte do okrelenia szerokoci i dokadnoci. Jak wida, jest to nieco atwiejsze ni uywanie manipulatorw. Wspomnielimy wczeniej o ograniczenie funkcji printf(), ktre polega na braku kontroli typw. Oprcz tego, funkcji printf() nie mona zadeklarowa jako funkcji zaprzyjanionej lub funkcji skadowej klasy. W zwizku z tym, gdy chcemy wypisywa rne dane skadowe klasy, musimy jawnie przekazywa kady z akcesorw klasy do funkcji printf().

Czsto zadawane pytanie

Czy moesz podsumowa sposoby manipulowania wyjciem?

Odpowied (specjalne podzikowania dla Roberta Francisa) Aby sformatowa wyjcie w C++, naley uy kombinacji znakw specjalnych, manipulatorw wyjcia oraz znacznikw.

Ponisze znaki specjalne s doczane do wyjciowego acucha wysyanego do cout za pomoc operatora wstawiania:

\n nowa linia

\r powrt karetki

\t tabulator

\\ lewy ukonik
Usunito: d

\odd (liczba semkowa) znak ASCII

\a alarm (sygna dwikowy).

Na przykad:

cout << "\aWystpi bd!\t"

powoduje powstanie sygnau dwikowego, wypisanie komunikatu bdu oraz przejcie do nastpnego tabulatora. Manipulatory s uywane z obiektem cout. Manipulatory przyjmujce argumenty wymagaj doczenia pliku nagwkowego iomanip do pliku standardowego.

Usunito: n

Oto lista manipulatorw, ktre nie wymagaj pliku iomanip:

flush zrzuca bufor wyjciowy

endl wstawia znak nowej linii i zrzuca bufor wyjciowy

oct ustawia system wypisywanych liczb na semkowy

dec ustawia system wypisywanych liczb na dziesitny

hex ustawia system wypisywanych liczb na szesnastkowy.

Oto lista manipulatorw wymagajcych pliku iomanip:


Usunito:

setbase(base) ustawia system liczbowy (0 = dziesitny, 8 = semkowy, 10 = dziesitny, 16 = szesnastkowy)

Usunito: e

setw(width) ustawia minimaln szeroko pola

setfill(ch) ustawia znak wypeniania pustych obszarw pola

setprecision(p) ustawia dokadno wypisywania liczb zmiennopozycyjnych

setiosflags(f) ustawia jeden lub wicej znacznikw ios

resetiosflags(f) przywraca stan jednego lub wicej znacznikw ios.

Na przykad:
cout << setw(12) << setfill('#') << hex << x << endl;

ustawia szeroko pola na dwanacie znakw, ustawia znak wypeniania na '#', nakazuje wypisywanie liczb w systemie szesnastkowym, wypisuje warto zmiennej x, umieszcza w buforze znak nowej linii i zrzuca zawarto bufora. Wszystkie manipulatory, z wyjtkiem flush, endl oraz setw, obowizuj a do wprowadzenia jawnej zmiany lub koca programu. Domylna warto manipulatora setw jest przywracana po wykonaniu biecego cout.

Wraz z manipulatorami setiosflags oraz resetiosflags mog by uywane ponisze znaczniki ios:

ios::left wyrwnuje wynik do lewej strony pola

ios::right wyrwnuje wynik do prawej strony pola

ios::internal znak lub przedrostek zostaje wyrwnany do lewej, a liczba do prawej

ios::dec dziesitny system liczbowy

ios::oct semkowy system liczbowy

ios::hex szesnastkowy system liczbowy

ios::showbase dodaje przedrostek 0x do liczb szesnastkowych i przedrostek 0 do liczb semkowych

ios::showpoint dodaje zera kocowe, zgodnie z wymagan dokadnoci


Usunito: inynierskim

ios::uppercase litery w liczbach szesnastkowych i w wartociach wywietlanych w zapisie wykadniczym s wywietlane jako wielkie

ios::showpos przed wartociami dodatnimi jest wywietlany znak plus


Usunito: ozycyjne Usunito: inynierskim

ios::scientific liczby zmiennoprzecinkowe s wywietlane w zapisie wykadniczym

Usunito: ozycyjne

ios::fixed liczby zmiennoprzecinkowe s wywietlane w zapisie dziesitnym.

Dodatkowe informacje na ten temat mona znale w pliku ios oraz w dokumentacji kompilatora.

Wejcie i wyjcie z uyciem plikw


Strumienie zapewniaj jednolity sposb obsugi danych przychodzcych z klawiatury lub z twardego dysku oraz wychodzcych na ekran lub do twardego dysku. W kadym z powyszych przypadkw moemy korzysta z operatorw wstawiania i ekstrakcji lub innych, powizanych z nimi funkcji i manipulatorw. Do otwarcia i zamknicia pliku naley uy obiektw typu ifstream i ofstream, ktrych dziaanie omwimy w kilku nastpnych podrozdziaach.
Usunito: y

ofstream
Poszczeglne obiekty uywane do odczytu z pliku lub zapisu do pliku s nazywane obiektami ofstream. S one wyprowadzone z uywanych przez nas dotd obiektw iostream. Aby rozpocz zapis do pliku, musimy najpierw stworzy obiekt typu ofstream, a nastpnie powiza go z konkretnym plikiem na dysku. Aby mc uywa obiektw typu ofstream, do kodu naley doczy plik nagwkowy fstream.h.

UWAGA Poniewa plik fstream.h docza plik iostream.h, nie ma potrzeby jawnego doczania tego drugiego pliku.

Stany strumieni
Obiekty typu iostream przechowuj znaczniki okrelajce stan wejcia i wyjcia. Moemy je sprawdza za pomoc logicznych funkcji eof(), bad(), fail() oraz good(). Funkcja eof() zwraca warto true, gdy obiekt typu iostream napotka koniec pliku. Funkcja bad() zwraca warto true, gdy prbujemy wykona niedozwolon operacj. Funkcja fail()zwraca warto

true, gdy funkcja bad() zwraca warto true lub operacja si nie powioda. Na koniec, funkcja good() zwraca warto true, gdy wszystkie trzy pozostae funkcje zwracaj warto false.

Otwieranie plikw dla wejcia i wyjcia


Aby otworzy plik myfile.cpp z obiektem typu ofstream, deklarujemy egzemplarz obiektu typu ofstream i jako parametr przekazujemy mu nazw pliku:
ofstream fout("myfile.cpp");

Otwarcie tego pliku do odczytu wyglda podobnie, ale wykorzystujemy w tym celu obiekt ifstream:
ifstream fin("myfile.cpp");

Zwr uwag na uyte nazwy, fout oraz fin; nazwa fout zostaa uyta w celu zwrcenia uwagi na podobiestwo z cout, a nazwa fin w celu wykazania podobiestwa do nazwy cin. Jedn z wanych funkcji strumieni zwizanych z plikami, jest funkcja close(). Kady tworzony obiekt strumienia otwiera plik albo do zapisu, albo do odczytu, albo do obu tych operacji. Po zakoczeniu zapisywania lub odczytywania danych naley zamkn plik funkcj close(); to zapewnia, e plik nie zostanie uszkodzony i e zawarte w buforze dane zostan zrzucone na dysk. Gdy obiekty strumieni plikw zostan ju powizane z plikami na dysku, mog zosta uyte tak samo jak wszystkie inne obiekty strumieni. Ilustruje to listing 17.16. Listing 17.16. Otwieranie plikw do odczytu i zapisu
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: pliku 16: przez 17: //Listing 17.16 Otwieranie plikw do odczytu i zapisu #include <fstream> #include <iostream> using namespace std; int main() { char fileName[80]; char buffer[255]; // dla danych uytkownika cout << "Nazwa pliku: "; cin >> fileName; ofstream fout(fileName); // otwieramy do zapisu fout << "Ta linia jest zapisana bezposrednio do pliku...\n"; cout << "Wpisz tekst dla pliku: "; cin.ignore(1,'\n'); // odrzucamy znak nowej linii po nazwie cin.getline(buffer,255); uytkownika fout << buffer << "\n"; // odczytujemy dane wprowadzone // i zapisujemy je do pliku

Usunito: e

18: fout.close(); // zamykamy plik; bdzie gotw do ponownego otwarcia 19: 20: ifstream fin(fileName); // ponownie otwieramy plik do odczytu 21: cout << "Oto zawartosc pliku:\n"; 22: char ch; 23: while (fin.get(ch)) 24: cout << ch; 25: 26: cout << "\n***Koniec zawartosci pliku.***\n"; 27: 28: fin.close(); // dbao zawsze popaca 29: return 0; 30: }

Wynik
Nazwa pliku: test1 Wpisz tekst dla pliku: Ten tekst zostal zapisany do pliku! Oto zawartosc pliku: Ta linia jest zapisana bezposrednio do pliku... Ten tekst zostal zapisany do pliku! ***Koniec zawartosci pliku.***

Analiza W linii 7. zostaje przygotowany bufor przeznaczony na nazw pliku, za w linii 8. zosta przygotowany bufor przeznaczony na dane wprowadzane przez uytkownika. W linii 9. uytkownik jest proszony o podanie nazwy pliku, a wprowadzona przez niego nazwa jest umieszczana w buforze fileName. W linii 12. zostaje stworzony obiekt typu ofstream o nazwie fout, ktry jest wizany z now nazw pliku. To powoduje otwarcie pliku; jeli plik istnia ju wczeniej, jego zawarto zostaje usunita. W linii 13. do pliku zostaje bezporednio zapisany acuch znakw. W linii 14. uytkownik jest proszony o wpisanie danych. Znak koca linii pozostay po wpisaniu przez uytkownika nazwy pliku jest odrzucany w linii 15., po czym w linii 16. nastpne dane wpisane przez uytkownika s umieszczane w buforze. Dane te s w linii 17. zapisywane do pliku wraz ze znakiem nowej linii, po czym w linii 18. nastpuje zamknicie pliku. W linii 20. plik jest otwierany ponownie, tym razem w trybie do odczytu danych, po czym w liniach 23. i 24. jego zwarto jest odczytywana i wypisywana na ekranie (kady znak wypisywany jest osobno).

Zmiana domylnego zachowania obiektu ofstream w trakcie otwierania pliku

Domylnym dziaaniem wykonywanym przy otwieraniu pliku jest stworzenie pliku, gdy jeszcze nie istnieje, lub obcicie pliku (tj. usunicie caej jego zawartoci), gdy ju istnieje. Gdy takie zachowanie domylne nie jest podane, moemy jawnie przekaza drugi argument do konstruktora obiektu typu ofstream. Dostpne argumenty to:
ios::app powoduje doczenie danych do koca istniejcego pliku, bez usuwania istniejcych w pliku danych. ios::ate umieszcza nas na kocu pliku, ale moemy zapisywa dane w dowolnym miejscu pliku. ios::trunc argument domylny. Powoduje usunicie zawartoci istniejcego pliku. ios::nocreate jeli plik nie istnieje, otwarcie si nie powiedzie. ios::noreplace jeli plik ju istnieje, otwarcie si nie powiedzie.

Dla ciekawych: nazwa app pochodzi od sowa append (docz), ate od at end (na kocu) oraz trunc od truncate (obetnij). Listing 17.17 ilustruje uycie doczania (append) przez otwarcie pliku stworzonego w listingu 17.16 i dopisanie do niego dalszej treci. Listing 17.17. Dopisywanie do koca pliku
0: //Listing 17.17 Dopisywanie do koca pliku 1: #include <fstream> 2: #include <iostream> 3: using namespace std; 4: 5: int main() // zwraca 1 w przypadku bdu 6: { 7: char fileName[80]; 8: char buffer[255]; 9: cout << "Prosze ponownie wpisac nazwe pliku: "; 10: cin >> fileName; 11: 12: ifstream fin(fileName); 13: if (fin) // czy ju istnieje? 14: { 15: cout << "Biezaca zawartosc pliku:\n"; 16: char ch; 17: while (fin.get(ch)) 18: cout << ch; 19: cout << "\n***Koniec zawartosci pliku.***\n"; 20: } 21: fin.close(); 22: 23: cout << "\nOtwieranie " << fileName << " w trybie dopisywania...\n"; 24: 25: ofstream fout(fileName,ios::app); 26: if (!fout) 27: { 28: cout << "Nie mozna otworzyc " << fileName << " do dopisywania.\n";

29: return(1); 30: } 31: 32: cout << "\nWpisz tekst dla pliku: "; 33: cin.ignore(1,'\n'); 34: cin.getline(buffer,255); 35: fout << buffer << "\n"; 36: fout.close(); 37: 38: fin.open(fileName); // ponownie przypisujemy istniejacy obiekt fin! 39: if (!fin) 40: { 41: cout << "Nie mozna otworzyc " << fileName << " do odczytu.\n"; 42: return(1); 43: } 44: cout << "\nOto zawartosc pliku:\n"; 45: char ch; 46: while (fin.get(ch)) 47: cout << ch; 48: cout << "\n***Koniec zawartosci pliku.***\n"; 49: fin.close(); 50: return 0; 51: }

Wynik
Prosze ponownie wpisac nazwe pliku: test1 Biezaca zawartosc pliku: Ta linia jest zapisana bezposrednio do pliku... Ten tekst zostal zapisany do pliku! ***Koniec zawartosci pliku.*** Otwieranie test1 w trybie dopisywania... Wpisz tekst dla pliku: Wiecej tekstu dla pliku! Oto zawartosc pliku: Ta linia jest zapisana bezposrednio do pliku... Ten tekst zostal zapisany do pliku! Wiecej tekstu dla pliku! ***Koniec zawartosci pliku.***

Analiza Uytkownik jest proszony o ponowne wpisanie nazwy pliku. Tym razem w linii 12. jest tworzony obiekt strumienia wejciowego z pliku. Wynik otwierania jest sprawdzany w linii 13. i jeli plik istnieje, jego zawarto jest wypisywana w liniach od 15. do 19. Zwr uwag, e if(fin) jest synonimem if(fin.good()). Nastpnie zamykany jest plik wejciowy; ten sam plik zostaje otwarty ponownie w linii 25., tym razem w trybie dopisywania. Po tym otwarciu (i wszystkich innych) sprawdzamy, czy plik zosta otwarty poprawnie. Zwr uwag, e if(!fout) jest tym samym, co if(fout.fail()).

Uytkownik jest proszony o wpisanie tekstu, ktry jest dopisywany do pliku. W linii 36. plik zostaje ponownie zamknity. Na koniec, tak jak w listingu 17.16, plik zostaje ponownie otwarty w trybie do odczytu. Jednak tym razem obiekt fin nie musi by deklarowany ponownie, jest mu po prostu przypisywana ta sama nazwa pliku. W linii 39. ponownie nastpuje sprawdzenie poprawnoci otwarcia i gdy wszystko jest w porzdku, zawarto pliku zostaje wypisana na ekranie, a sam plik jest ostatecznie zamykany. TAK Sprawdzaj kade otwarcie pliku, aby mie pewno e zosta on poprawnie otwarty. Ponownie wykorzystuj istniejce obiekty
ifstream lub ofstream.
Usunito: o

NIE Nie prbuj zamyka lub ponownie przypisywa


cin i cout.

Po wykorzystaniu obiektw ofstream zamykaj je

Pliki binarne a pliki tekstowe


Niektre systemy operacyjne, takie jak DOS, dokonuj rozrnienia pomidzy plikami tekstowymi a plikami binarnymi. Pliki tekstowe (jak mona si domyla) przechowuj wszystko jako tekst, wic due liczby, takie jak 54 325 s przechowywane jako acuchy cyfr (5, 4, 3, 2, 5). To moe by nieefektywne, ale ma t zalet, e tekst moe zosta odczytany przez proste programy, takie jak DOS-owy program type. Aby dopomc systemowi plikw w odrnieniu pliku tekstowego od binarnego, C++ udostpnia znacznik ios::binary. W wielu systemach ten znacznik jest ignorowany, gdy wszystkie dane s przechowywane w postaci binarnej. W jeszcze innych systemach znacznik ten jest niedozwolony i uniemoliwia skompilowanie programu! Pliki binarne mog przechowywa nie tylko liczby i acuchy, ale take cae struktury danych. Mona do nich zapisywa wszystkie dane jednoczenie, uywajc metody write() klasy fstream. Gdy uyjemy metody write(),do odczytania zapisanych danych moemy uy funkcji read(). Kada z tych funkcji oczekuje wskanika do znaku, wic adres obiektu trzeba rzutowa na wskanik do znaku. Drugim argumentem obu funkcji jest ilo znakw do zapisu lub odczytu, ktr mona wyznaczy za pomoc funkcji sizeof(). Pamitaj, e zapisywane s tylko dane klasy, a nie jej metody. Odczytywane s take tylko dane. Zapis zawartoci klasy do pliku ilustruje listing 17.18. Listing 17.18. Zapis zawartoci klasy do pliku
Usunito: do Usunito: a

0: //Listing 17.18. Zapis zawartoci klasy do pliku 1: #include <fstream> 2: #include <iostream.h> 3: using namespace std; 4: 5: class Animal 6: { 7: public: 8: Animal(int weight,long days):itsWeight(weight),DaysAlive(days){} 9: ~Animal(){} 10: 11: int GetWeight()const { return itsWeight; } 12: void SetWeight(int weight) { itsWeight = weight; } 13: 14: long GetDaysAlive()const { return DaysAlive; } 15: void SetDaysAlive(long days) { DaysAlive = days; } 16: 17: private: 18: int itsWeight; 19: long DaysAlive; 20: }; 21: 22: int main() // zwraca 1 w przypadku bdu 23: { 24: char fileName[80]; 25: 26: 27: cout << "Prosze wpisac nazwe pliku: "; 28: cin >> fileName; 29: ofstream fout(fileName,ios::binary); 30: if (!fout) 31: { 32: cout << "Nie mozna otworzyc " << fileName << " do zapisu.\n"; 33: return(1); 34: } 35: 36: Animal Bear(50,100); 37: fout.write((char*) &Bear,sizeof Bear); 38: 39: fout.close(); 40: 41: ifstream fin(fileName,ios::binary); 42: if (!fin) 43: { 44: cout << "Nie mozna otworzyc " << fileName << " do odczytu.\n"; 45: return(1); 46: } 47: 48: Animal BearTwo(1,1); 49: 50: cout << "BearTwo.GetWeight(): " << BearTwo.GetWeight() << endl; 51: cout << "BearTwo.GetDaysAlive(): " << BearTwo.GetDaysAlive() << endl; 52: 53: fin.read((char*) &BearTwo, sizeof BearTwo); 54:

55: endl; 56: << endl; 57: 58: 59: }

cout << "BearTwo.GetWeight(): " << BearTwo.GetWeight() << cout << "BearTwo.GetDaysAlive(): " << BearTwo.GetDaysAlive() fin.close(); return 0;

Wynik
Prosze wpisac nazwe pliku: Animals BearTwo.GetWeight(): 1 BearTwo.GetDaysAlive(): 1 BearTwo.GetWeight(): 50 BearTwo.GetDaysAlive(): 100

Analiza W liniach od 5. do 20. zostaa zadeklarowana okrojona klasa Animals. W liniach od 24. do 34. zostaje stworzony plik otwarty do zapisu w trybie binarnym. W linii 36. zostaje stworzony obiekt, ktrego waga zostaje ustawiona na 50, a wiek na 100 dni. Dane zawarte w tym obiekcie zostaj w linii 37. zapisane do pliku. W linii 39. plik jest zamykany, a w linii 41. ponownie otwierany do odczytu w trybie binarnym. W linii 48. tworzony jest drugi obiekt klasy Animal, o wadze 1 i wieku wynoszcym tylko jeden dzie. W linii 53. do nowego obiektu odczytywane s dane z pliku, zastpujc istniejce dane obiektu danymi odczytanymi z pliku.

Przetwarzanie linii polecenia


Wiele systemw operacyjnych, takich jak DOS czy UNIX, umoliwia uytkownikowi przekazywanie parametrw do programu podczas jego uruchamiania. Parametry te s nazywane opcjami linii polecenia i zwykle s oddzielone od siebie spacjami. Na przykad:
SomeProgram Param1 Param2 Param3

Te parametry nie s przekazywane bezporednio do funkcji main(), zamiast tego funkcja main() kadego programu otrzymuje dwa parametry. Pierwszym jest ilo parametrw w linii polecenia (parametr typu int). Nazwa pliku take jest wliczana do iloci parametrw, wic kady program posiada co najmniej jeden parametr. Przedstawiona powyej przykadowa linia polece zawiera cztery parametry (nazwa programu, SomeProgram, plus trzy parametry daje cztery argumenty wywoania programu). Drugim parametrem przekazywanym do funkcji main() jest tablica wskanikw do acuchw znakw. Poniewa nazwa tablicy jest staym wskanikiem do pierwszego elementu tablicy,

moemy zadeklarowa ten wskanik jako wskanik do wskanika do typu char, jako wskanik do tablicy elementw char lub jako wskanik do tablicy tablic elementw char. Zwykle pierwszy argument ma nazw argc (argument count, ilo argumentw), ale moesz nazwa go tak, jak chcesz. Drugi argument czsto nosi nazw argv (argument vector, wektor argumentw), ale to take tylko konwencja. Zwykle sprawdza si warto parametru argc, aby upewni si, czy program otrzyma oczekiwan ilo argumentw, a nastpnie poprzez argv odwouje si do samych acuchw parametrw. Pamitaj, e argv[0] jest nazw programu, a argv[1] jest jego pierwszym parametrem, reprezentowanym przez acuch. Jeli argumentami programu maj by liczby, naley je zamieni z acuchw na wartoci numeryczne. W rozdziale 21. zobaczymy, jak mona wykorzysta w tym celu standardowe funkcje biblioteczne konwersji. Sposb korzystania z argumentw linii polecenia przedstawia listing 17.19. Listing 17.19. Uywanie argumentw linii polecenia
0: //Listing 17.19. Uywanie argumentw linii polecenia 1: #include <iostream> 2: int main(int argc, char **argv) 3: { 4: std::cout << "Otrzymano " << argc << " argumentow...\n"; 5: for (int i=0; i<argc; i++) 6: std::cout << "argument " << i << ": " << argv[i] << std::endl; 7: return 0; 8: }

Wynik
TestProgram Otrzymano 7 argument 0: argument 1: argument 2: argument 3: argument 4: argument 5: argument 6: Teach Yourself C++ In 21 Days argumentow... TestProgram.exe Teach Yourself C++ In 21 Days

UWAGA Musisz uruchomi ten kod albo z linii polece (tj. z okna DOS-a) lub ustawi parametry linii polecenia w kompilatorze (zajrzyj do dokumentacji swojego kompilatora).

Analiza Funkcja main() deklaruje dwa argumenty: argc jest zmienn cakowit zawierajc ilo argumentw linii polecenia, a argv jest wskanikiem do tablicy acuchw. Kady acuch w tablicy wskazywanej przez argv jest jednym argumentem linii polecenia. Zauwa, e argv mogoby rwnie atwo zosta zadeklarowane jako char *argv[] lub char argv[][]. Jest to

zalene wycznie od stylu programowania. W tym programie, mimo i argv zostao zadeklarowane jako wskanik do wskanika, to jednak do kolejnych acuchw (bdcych argumentami z linii polece) odwoujemy si jak do elementw tablicy. W linii 4. argument argc zostaje wykorzystany przy wypisywaniu iloci argumentw linii polecenia: siedem, wliczajc w to nazw programu. W liniach 5. i 6. zostaje wypisany kady z argumentw linii polecenia; do cout przekazywane s zakoczone zerem acuchy znakw pobierane z tablicy acuchw. Bardziej popularne zastosowanie argumentw linii polecenia zostao przedstawione na listingu 17.20, powstaym w wyniku zmodyfikowania listingu 17.18 tak, by program odczytywa nazw pliku z linii polecenia. Listing 17.20. Uycie argumentw linii polecenia
0: //Listing 17.20. Uycie argumentw linii polecenia 1: #include <fstream> 2: #include <iostream> 3: using namespace std; 4: 5: class Animal 6: { 7: public: 8: Animal(int weight,long days):itsWeight(weight),DaysAlive(days){} 9: ~Animal(){} 10: 11: int GetWeight()const { return itsWeight; } 12: void SetWeight(int weight) { itsWeight = weight; } 13: 14: long GetDaysAlive()const { return DaysAlive; } 15: void SetDaysAlive(long days) { DaysAlive = days; } 16: 17: private: 18: int itsWeight; 19: long DaysAlive; 20: }; 21: 22: int main(int argc, char *argv[]) // zwraca 1 w przypadku bdu 23: { 24: if (argc != 2) 25: { 26: cout << "Uzycie: " << argv[0] << " <nazwa_pliku>" << endl; 27: return(1); 28: } 29: 30: ofstream fout(argv[1],ios::binary); 31: if (!fout) 32: { 33: cout << "Nie mozna otworzyc " << argv[1] << " do zapisu.\n"; 34: return(1); 35: } 36: 37: Animal Bear(50,100); 38: fout.write((char*) &Bear,sizeof Bear); 39: 40: fout.close();

Usunito: nawet mimo i w Usunito: do dostpu Usunito: poszczeglnych Usunito: nadal s wykorzystywane Usunito: y

41: 42: ifstream fin(argv[1],ios::binary); 43: if (!fin) 44: { 45: cout << "Nie mozna otworzyc " << argv[1] << " do odczytu.\n"; 46: return(1); 47: } 48: 49: Animal BearTwo(1,1); 50: 51: cout << "BearTwo.GetWeight(): " << BearTwo.GetWeight() << endl; 52: cout << "BearTwo.GetDaysAlive(): " << BearTwo.GetDaysAlive() << endl; 53: 54: fin.read((char*) &BearTwo, sizeof BearTwo); 55: 56: cout << "BearTwo.GetWeight(): " << BearTwo.GetWeight() << endl; 57: cout << "BearTwo.GetDaysAlive(): " << BearTwo.GetDaysAlive() << endl; 58: fin.close(); 59: return 0; 60: }

Wynik
BearTwo.GetWeight(): 1 BearTwo.GetDaysAlive(): 1 BearTwo.GetWeight(): 50 BearTwo.GetDaysAlive(): 100

Analiza Deklaracja klasy Animal jest taka sama jak na listingu 17.18. Tym razem jednak zamiast prosi uytkownika o nazw pliku, wykorzystujemy argument linii polecenia. W linii 22. funkcja main() zostaa zadeklarowana jako przyjmujca dwa parametry: ilo argumentw linii polecenia oraz wskanik do tablicy acuchw tych argumentw. W liniach od 24. do 28. program sprawdza, czy otrzyma wymagan ilo argumentw (dokadnie dwa). Jeli uytkownik nie poda pojedynczej nazwy pliku, zostanie wypisany komunikat bdu:
Uzycie TestProgram <nazwa_pliku>

Nastpnie program koczy dziaanie. Zwr uwag, e uywajc argv[0] zamiast sztywno okrelonej nazwy programu, moemy skompilowa ten program tak, aby przyjmowa dowoln nazw i by bya ona automatycznie wywietlana w tym komunikacie. W linii 30. program prbuje otworzy wskazany plik do zapisu binarnego. Nie ma powodu, by kopiowa nazw pliku do tymczasowego bufora lokalnego, gdy mona bezporednio wykorzysta argument argv[1].

Ta technika zostaje powtrzona w linii 42., gdzie ten sam plik zostaje otwarty do odczytu; a take w liniach 33. i 45., w ktrych wypisywane s komunikaty bdw otwarcia pliku.

Rozdzia 18. Przestrzenie nazw


Przestrzenie nazw pomagaj programistom unika konfliktw nazw powstajcych w trakcie korzystania z wicej ni jednej biblioteki. Z tego rozdziau dowiesz si jak funkcje i klasy s rozpoznawane poprzez nazwy, jak stworzy przestrze nazw, jak uywa przestrzeni nazw, jak uywa standardowej przestrzeni nazw std.

Zaczynamy
Konflikty nazw s rdem irytacji zarwno dla programistw C, jak i C++. Konflikt nazw powstaje wtedy, gdy w dwch czciach programu, w tym samym zakresie, wystpuje kilka takich samych nazw. Najczstsz przyczyn konfliktu jest wystpienie tej samej nazwy w kilku rnych bibliotekach. Na przykad, biblioteka klasy kontenera prawie na pewno bdzie deklarowa i implementowa klas List (lista). Wicej na temat klas kontenerw dowiesz si z rozdziau 19., w ktrym bdziemy omawia szablony. Nie powinno nas dziwi take wystpowanie klasy List w bibliotece zwizanej z okienkami. Przypumy teraz, e chcemy stworzy list okien wystpujcych w naszej aplikacji. Zakadamy take, e uywamy klasy List z biblioteki klasy kontenera. Deklarujemy egzemplarz klasy List z biblioteki okienkowej (w celu przechowania okien) i okazuje si, e funkcja skadowa, ktr wywoujemy, jest niedostpna. Kompilator dopasowa deklaracj naszej klasy List do kontenera List w bibliotece standardowej, mimo i w rzeczywistoci chcielimy skorzysta z klasy List w bibliotece okienkowej, stworzonej przez kogo innego.

Usunito: wzorce

Przestrzenie nazw su do podzielenia globalnej przestrzeni nazw i wyeliminowania (lub przynajmniej ograniczenia) konfliktw nazw. Przestrzenie nazw s nieco podobne do klas i posiadaj bardzo podobn skadni. Elementy zadeklarowane w przestrzeni nazw nale do tej przestrzeni. Wszystkie elementy w przestrzeni nazw s widoczne publicznie. Przestrzenie nazw mog by zagniedone. Funkcje mog by definiowane w ciele przestrzeni nazw lub poza nim. Jeli funkcja jest zdefiniowana poza ciaem przestrzeni nazw, musi by kwalifikowana nazw tej przestrzeni.

Funkcje i klasy s rozpoznawane poprzez nazwy


Podczas przetwarzania kodu rdowego i budowania listy nazw funkcji i zmiennych, kompilator sprawdza take wystpowanie konfliktw nazw. Te konflikty, ktrych kompilator nie potrafi rozwiza sam, mog by czasem rozwizane przez program czcy (linker). Kompilator nie potrafi wykry konfliktu nazw pomidzy jednostkami kompilacji (na przykad pomidzy plikami object); jest to zadaniem programu czcego (linkera). Dlatego kompilator nie zgosi nawet ostrzeenia. Nie powinien nas dziwi komunikat linkera informujcy o powielonym identyfikatorze, bdcym jakim nazwanym typem. Ten komunikat pojawia si, gdy zdefiniujemy t sam nazw w tym samym zakresie w rnych jednostkach kompilacji. Jeli powielimy nazw w tym samym zakresie w tym samym pojedynczym pliku, pojawi si bd kompilacji. Poniszy kod powoduje wystpienie bdu czenia podczas kompilowania i czenia:
// plik first.cpp int integerValue = 0; int main( ) { int integerValue = 0; // . . . return 0; }; // plik second.cpp int integerValue = 0; // koniec pliku second.cpp Usunito: obiektw Usunito: do tego suy Usunito: y

Mj linker zgasza komunikat: in second.obj: integerValue already defined in first.obj (w second.obj: integerValue ju jest zdefiniowane w first.obj). Gdyby te nazwy wystpoway w innych zakresach, kompilator i linker przestayby zgasza bdy. Istnieje take moliwo, e kompilator zgosi ostrzeenie ukrywaniu identyfikatora. Kompilator powinien ostrzec, w pliku first.cpp, e integerValue w funkcji main() ukrywa zmienn globaln o tej samej nazwie.

Aby uy zmiennej integerValue zadeklarowanej poza funkcj main(), musisz jawnie przypisa t zmienn do zakresu globalnego. Spjrzmy na poniszy przykad, w ktrym warto 10 przypisujemy do zmiennej integerValue poza main(), a nie do zmiennej integerValue zadeklarowanej wewntrz main():
// plik first.cpp int integerValue = 0; int main() { int integerValue = 0; ::integerValue = 10; // przypisanie do globalnej zmiennej // . . . return 0; }; // plik second.cpp int integerValue = 0; // koniec pliku second.cpp Usunito: a

UWAGA Zwr uwag na uycie operatora zakresu (::) wskazujcego, e chodzi o zmienn globaln integerValue, a nie lokaln.

Usunito: est zmienn globaln

Problem z dwiema zmiennymi globalnymi zdefiniowanymi poza funkcjami polega na tym, e posiadaj one takie same nazwy i t sam widoczno, a tym samym powoduj bd linkera. NOWE OKRELENIE Okrelenie widoczno jest uywane do oznaczenia zakresu zdefiniowanego obiektu, bez wzgldu na to, czy jest to zmienna, klasa, czy funkcja. Na przykad, zmienna zadeklarowana i zdefiniowana poza funkcj posiada zakres pliku, czyli globalny. Zmienna ta jest widoczna od punktu jej zadeklarowania a do koca pliku. Zmienna o zakresie bloku, czyli lokalna, wystpuje wewntrz bloku kodu. Najczstszymi przykadami takich zmiennych s zmienne zadeklarowane wewntrz funkcji. Zakres zmiennych przedstawia poniszy przykad:
int globalScopeInt = 5; void f() { int localScopeInt = 10; } int main() { int localScopeInt = 15; { int anotherLocal = 20; int localScopeInt = 30; }

Usunito: globalnymi

return 0; }

Pierwsza definicja int, zmienna globalScopeInt, jest widoczna wewntrz funkcji f() oraz main(). Nastpna definicja znajduje si wewntrz funkcji f() i ma nazw localScopeInt. Ta zmienna ma zakres lokalny, co oznacza, e jest widoczna tylko w bloku, w ktrym zostaa zdefiniowana. Funkcja main() nie moe odwoywa si do zmiennej localScopeInt zdefiniowanej wewntrz funkcji f(). Gdy funkcja f() koczy dziaanie, zmienna ta wychodzi poza zakres. Zmienna ta ma zakres blokowy. Zwr uwag, e zmienna localScopeInt w funkcji main() nie koliduje ze zmienn localScopeInt w funkcji f(). Dwie nastpne definicje, anotherLocal oraz localScopeInt, maj zakres blokowy. Gdy dochodzimy do nawiasu klamrowego zamykajcego, zmienne te staj si niewidoczne. Zauwa, e zmienna localScopeInt ukrywa wewntrz bloku zmienn localScopeInt zdefiniowan tu przed nawiasem klamrowym otwierajcym blok (drug zmienn localScopeInt zdefiniowan w programie). Gdy program przechodzi poza nawias klamrowy zamykajcy blok, druga zdefiniowana zmienna localScopeInt znw staje si widoczna. Wszelkie zmiany dokonane w zmiennej localScopeInt zdefiniowanej wewntrz bloku nie maj wpywu na zawarto innych zmiennych localScopeInt. NOWE OKRELENIE Nazwy mog by czone wewntrznie i zewntrznie. Te dwa terminy stosujemy okrelajc dostpno nazwy w rnych jednostkach kompilacji lub wewntrz pojedynczej jednostki kompilacji. Nazwa czona wewntrznie moe by uywana tylko w tej jednostce kompilacji, w ktrej jest zdefiniowana. Na przykad, zmienna zdefiniowana jako czona wewntrznie moe by wykorzystywana przez funkcje w tej samej jednostce kompilacji. Nazwy czone zewntrznie s dostpne take w innych jednostkach kompilacji. Poniszy przykad demonstruje czenie wewntrzne i zewntrzne:
// plik first.cpp int externalInt = 5; const int j = 10; int main() { return 0; } // plik second.cpp extern int externalInt; int anExternalInt = 10; const int j = 10; Usunito: obie

Zmienna externalInt zdefiniowana w pliku first.cpp jest czona zewntrznie (ang. external). Cho jest zdefiniowana w pliku first.cpp, moe z niej korzysta take plik second.cpp. Dwie zmienne j, wystpujce w obu plikach, s zmiennymi const, wic domylnie s czone wewntrznie. Moemy przesoni domylne czenie dla const, stosujc jawn deklaracj, tak jak ta:
// plik first.cp extern const int j = 10; // plik second.cpp extern const int j; #include <iostream> int main() { std::cout << "j ma wartosc " << j << std::endl; return 0; }

Zwr uwag, e wywoujemy cout z okreleniem przestrzeni nazw std; w ten sposb moemy korzysta ze standardowej biblioteki ANSI. Po zbudowaniu i uruchomieniu, program ten wypisuje:
j ma wartosc 10

Komitet standaryzacji potpi przedstawione poniej zastosowanie:


static int staticInt = 10; int main() { // ... }

Uycie modyfikatora static w celu ograniczenia zakresu zmiennych zewntrznych nie jest ju zalecane, a w przyszoci moe sta si niedozwolone. Zamiast static powiniene uywa przestrzeni nazw. TAK Zamiast statc uywaj przestrzeni nazw. NIE Nie uywaj sowa kluczowego static w zmiennych zdefiniowanych w zakresie pliku.

Tworzenie przestrzeni nazw


Skadnia deklaracji przestrzeni nazw jest podobna do skadni deklaracji struktury lub klasy: stosujemy sowo kluczowe namespace, po nim opcjonaln nazw przestrzeni nazw, a nastpnie nawias klamrowy otwierajcy. Przestrze nazw koczy si nawiasem klamrowym zamykajcym, bez rednika. Na przykad:
namespace Window { void move( int x, int y); }

Nazwa Window identyfikuje przestrze nazw, ktra moe wystpi wielokrotnie. Wielokrotne wystpienia mog pojawi si w tym samym pliku lub w kilku rnych jednostkach kompilacji. Przykadem wielokrotnego wystpienia jest przestrze nazw standardowej biblioteki C++, std. Wystpienie to w tym przypadku ma sens, gdy biblioteka standardowa grupuje funkcjonalno w logiczny sposb. Gwnym celem przestrzeni nazw jest grupowanie powizanych elementw w okrelonym (nazwanym) obszarze. Oto prosty przykad przestrzeni nazw rozcigajcej si na kilka plikw nagwkowych:
// header1.h namespace Window { void move( int x, int y); } // header2.h namespace Window { void resize( int x, int y); }

Deklarowanie i definiowanie typw


W przestrzeni nazw mona deklarowa i definiowa typy i funkcje. Oczywicie, jest to zwizane z projektem i konserwacj. W dobrym projekcie interfejsy powinny by oddzielone od implementacji. Powiniene przestrzega tej zasady nie tylko w przypadku klas, ale take w przypadku przestrzeni nazw. Poniszy przykad demonstruje zamiecon i kiepsko zdefiniowan przestrze nazw:
namespace Window { // . . . inne deklaracje i definicje zmiennych. void move( int x, int y); // deklaracje void resize( int x, int y);

Usunito: sabo

// . . . inne deklaracje i definicje zmiennych. void move( int x, int y ) { if( x < MAX_SCREEN_X && x > 0 ) if( y < MAX_SCREEN_Y && y > 0 ) platform.move( x, y ); // specyficzna funkcja } void resize( int x, int y ) { if( x < MAX_SCREEN_X && x > 0 ) if( y < MAX_SCREEN_Y && y > 0 ) platform.resize( x, y ); // specyficzna funkcja } // . . . dalsze definicje }

Wida tu, jak szybko zamieca si przestrze nazw! Ten przykad skada si z okoo dwudziestu linii; wyobra sobie, jak by wygldaa cztery razy wiksza przestrze nazw.

Definiowanie funkcji poza przestrzeni nazw


Funkcje danej przestrzeni nazw powinny by definiowane poza ciaem tej przestrzeni. W ten sposb jawnie separujemy deklaracje funkcji od ich definicji a take utrzymujemy w porzdku przestrze nazw. Oddzielenie definicji funkcji od przestrzeni nazw umoliwia take umieszczenie przestrzeni i zawartych w niej deklaracji w pliku nagwkowym; definicje mog zosta umieszczone w pliku implementacji. Na przykad:
// plik header.h namespace Window { void move( int x, int y ); // . . . inne deklaracje } // plik impl.cpp void Window::move( int x, int y ) { // kod do przesuwania okna }

Dodawanie nowych skadowych


Nowe skadowe mog zosta dodane do przestrzeni nazw tylko w jej ciele. Nie mona definiowa nowych skadowych, uywajc tylko kwalifikatorw. Jedyne, czego mona oczekiwa od tego rodzaju definicji, to bd kompilacji. Demonstruje to poniszy przykad:

namespace Window { // mnstwo deklaracji } // ... jaki kod int Window::newIntegerNamespace; // przykro mi, nie mona tego zrobi

Powysza linia kodu jest niedozwolona. Twj kompilator wypisze komunikat zgaszajcy ten bd. Aby go poprawi czyli unikn przenie deklaracj do ciaa przestrzeni nazw. Wszystkie skadowe zawarte w przestrzeni nazw s publiczne. Poniszy kod nie skompiluje si:
namespace Window { private: void move( int x, int y ); }

Zagniedanie przestrzeni nazw


Przestrzenie nazw mog by zagniedane w innych przestrzeniach nazw. Jest to moliwe, gdy definicja przestrzeni nazw jest take deklaracj. Tak jak w przypadku wszystkich innych przestrzeni nazw, naley wtedy kwalifikowa nazw zagniedonej przestrzeni za pomoc nazwy przestrzeni zewntrznej. Gdy mamy kilka kolejno zagniedonych przestrzeni, musimy kwalifikowa kolejno kad z nich. Na przykad, poniszy kod przedstawia nazwan przestrze nazw, zagniedon w innej nazwanej przestrzeni nazw:
namespace Window { namespace Pane { void size( int x, int y ); } }

Aby odwoa si do funkcji size() spoza przestrzeni nazw Window, musimy kwalifikowa funkcj nazwami obu przestrzeni nazw. Demonstruje to poniszy kod:
int main() { Window::Pane::size( 10, 20 ); return 0; }

Uywanie przestrzeni nazw


Przyjrzyjmy si przykadowi uycia przestrzeni nazw i wynikajcemu z niego przykadowi uycia operatora zakresu. Najpierw deklaruj wszystkie typy i funkcje uywane w przestrzeni nazw Window. Po zdefiniowaniu wszystkiego, co jest wymagane, definiuj zadeklarowane funkcje skadowe. Te funkcje skadowe s definiowane poza przestrzeni nazw; nazwy s jawnie identyfikowane za pomoc operatora zakresu. Uycie przestrzeni nazw demonstruje listing 18.1. Listing 18.1. Uycie przestrzeni nazw
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: #include <iostream> // Uycie przestrzeni nazw namespace Window { const int MAX_X = 30 ; const int MAX_Y = 40 ; class Pane { public: Pane() ; ~Pane() ; void size( int x, int y ) ; void move( int x, int y ) ; void show( ) ; private: static int cnt int x ; int y ; } ; } int Window::Pane::cnt = 0 ; Window::Pane::Pane() : x(0), y(0) { } Window::Pane::~Pane() { } void Window::Pane::size( int x, int y ) { if( x < Window::MAX_X && x > 0 ) Pane::x = x ; if( y < Window::MAX_Y && y > 0 ) Pane::y = y ; } void Window::Pane::move( int x, int y ) { if( x < Window::MAX_X && x > 0 ) Pane::x = x ; if( y < Window::MAX_Y && y > 0 ) Pane::y = y ; } void Window::Pane::show( ) { std::cout << "x " << Pane::x ; std::cout << " y " << Pane::y << std::endl ; } int main( ) {

48: 49: 50: 51: 52: 53: 54:

Window::Pane pane ; pane.move( 20, 20 ) ; pane.show( ) ; return 0 ; }

Wynik
x 20 y 20

Analiza Zwr uwag, e klasa Pane jest zagniedona wewntrz przestrzeni nazw Window. Dlatego musimy kwalifikowa nazw Pane kwalifikatorem, Window::. Statyczna zmienna cnt, zadeklarowana w przestrzeni nazw Pane w linii 16., jest definiowana jak zwykle. Zauwa, e wewntrz funkcji Pane::size(), w liniach od 26. do 32., stae MAX_X i MAX_Y s w peni kwalifikowane. Jest to konieczne, poniewa jestemy tu w zakresie klasy Pane; w przeciwnym razie kompilator zgosiby bd. To samo odnosi si do funkcji Pane::move(). Interesujce jest take kwalifikowanie Pane::x oraz Pane::y wewntrz obu definicji funkcji. Do czego ono suy? C, gdyby funkcja Pane::move() zostaa zapisana tak, jak poniej, pojawiby si problem:
void Window::Pane::move( int x, int y ) { if( x < Window::MAX_X && x > 0 ) x = x ; if( y < Window::MAX_Y && y > 0 ) y = y ; Platform::move( x, y ); } Usunito: Wewntrz funkcji Pane::size() w liniach od 26 do 32 z Usunito: ak Usunito: a Usunito: jest w zakresie

Czy widzisz bd? Kompilator najprawdopodobniej nie okae si w tej sytuacji pomocny; niektre kompilatory nie zgosz adnego komunikatu. rdem problemu s argumenty funkcji. Argumenty x i y ukrywaj prywatne zmienne x i y egzemplarza, zadeklarowane w klasie Pane. W efekcie instrukcje te przypisuj zmienne x i y samym sobie:
x = x; y = y;

Sowo kluczowe using


Sowo kluczowe using jest uywane zarwno jako dyrektywa using, jak i deklaracja using. O tym, czy jest to dyrektywa, czy deklaracja, decyduje skadnia tego sowa kluczowego.

Dyrektywa using
Dyrektywa using powoduje wyeksponowanie wszystkich nazw zadeklarowanych w przestrzeni nazw tak, aby stay si dostpne w biecym zakresie. Wtedy moemy odwoywa si do nich bez koniecznoci kwalifikowania ich waciw nazw przestrzeni nazw. Poniszy przykad pokazuje uycie dyrektywy using:
namespace Window { int value1 = 20; int value2 = 40; } . . . Window::value1 = 10; using namespace Window; value2 = 30;

Zakres dziaania dyrektywy using zaczyna si w miejscu jej deklaracji i koczy si wraz z biecym zakresem. Zauwa, e zmienna value1 musiaa by kwalifikowana. Z kolei zmienna value2 nie wymagaa kwalifikowania, gdy dyrektywa using spowodowaa wprowadzenie do biecego zakresu wszystkich nazw ze wskazanej przestrzeni nazw. Dyrektywa using moe by uywana na dowolnym poziomie zakresu. Dziki temu moemy uywa jej w zakresie bloku; gdy blok znajdzie si poza zakresem, wszystkie nazwy z przestrzeni nazw stan si niedostpne. To zachowanie przedstawia nastpny przykad:
namespace Window { int value1 = 20; int value2 = 40; } // . . . void f() { { using namespace Window; value2 = 30; } value2 = 20; // bd! }

Ostatnia linia w funkcji f(), value2 = 20; jest bdna, gdy nazwa value2 nie jest zdefiniowana. Jest ona dostpna w poprzednim bloku, gdy zostaa w nim uyta dyrektywa

using, udostpniajca nazw zmiennej value2 w ramach tego bloku. Gdy wychodzimy poza zakres bloku, nazwy w przestrzeni nazw Window staj si niedostpne.

Nazwy zmiennych zadeklarowanych w lokalnym zakresie ukrywaj nazwy wprowadzone do tego zakresu z przestrzeni nazw. To zachowanie przypomina ukrywanie zmiennych globalnych przez zmienne lokalne. Ta zasada obowizuje nawet wtedy, gdy przestrze nazw zostanie wprowadzona ju po zadeklarowaniu zmiennej lokalnej; zmienna lokalna w kadym przypadku ukrywa zmienn z przestrzeni nazw. Pokazuje to poniszy przykad:
namespace Window { int value1 = 20; int value2 = 40; } // . . . void f() { value2 = 10; using namespace Window; std::cout << value2 << std::endl; } Usunito: 4

Wynikiem dziaanie tej funkcji jest 10, a nie 40. To potwierdza, i zmienna value2 w przestrzeni nazw Window zostaje ukryta przez zmienn value2 funkcji f(). Gdybymy musieli uy nazwy z przestrzeni nazw, musielibymy kwalifikowa j nazw tej przestrzeni. W przypadku uycia nazwy zdefiniowanej zarwno globalnie, jak i w przestrzeni nazw, moe wystpi niejednoznaczno. Ta niejednoznaczno objawia si tylko w przypadku uycia nazwy, a nie w przypadku wprowadzenia przestrzeni nazw. Ilustruje to poniszy fragment kodu:
namespace Window { int value1 = 20; } // . . . using namespace Window; int valu1 = 10; void f() { value1 = 10; }

Niejednoznaczno wystpuje wewntrz funkcji f(). Dyrektywa using wprowadza do globalnej przestrzeni nazw nazw Window::value1. Poniewa nazwa value1 jest ju globalnie zdefiniowana, uycie nazwy value1 w funkcji f() jest bdem. Zwr jednak uwag, e gdyby z tej funkcji zostaa usunita linia kodu, nie pojawiby si bd.

Deklaracja using
Deklaracja using jest podobna do dyrektywy o tej samej nazwie, lecz umoliwia bardziej precyzyjn kontrol. Deklaracja using suy do wprowadzania do biecego zakresu okrelonej nazwy z przestrzeni nazw. Do wskazanego obiektu mona si wtedy odwoywa poprzez sam jego nazw. Nastpny przykad demonstruje zastosowanie deklaracji using:
namespace Window { int value1 = 20; int value2 = 40; int value3 = 60; } // . . . using Window::value2; // wprowadza nazw value2 do biecego zakresu Window::value1 = 10; // nazwa value1 musi by kwalifikowana value2 = 30; Window::value3 = 10; // nazwa value3 musi by kwalifikowana

Deklaracja using wprowadza wskazan nazw do biecego zakresu. Deklaracja nie wpywa na widoczno innych nazw w przestrzeni nazw. W powyszym przykadzie do zmiennej value2 moemy odwoywa si bez korzystania z kwalifikacji, lecz zmienne value1 i value3 musz by kwalifikowane. Deklaracja using zapewnia wiksz kontrol nad wprowadzanymi do zakresu nazwami przestrzeni nazw. Tym wanie deklaracja using rni si od dyrektywy using, ktra wprowadza do biecego zakresu wszystkie nazwy z przestrzeni nazw. Gdy nazwa zostanie wprowadzona do zakresu, pozostaje widoczna a do jego koca (czyli tak samo, jak w przypadku wszystkich innych deklaracji). Deklaracja using moe zosta uyta w globalnej przestrzeni nazw lub w dowolnym zakresie lokalnym. Wprowadzenie duplikatu nazwy do lokalnego zakresu, w ktrym zostaa zadeklarowana nazwa z przestrzeni nazw, jest bdem. Bdna jest take sytuacja odwrotna. Pokazuje to poniszy przykad:
namespace Window { int value1 = 20; int value2 = 40; } // . . . void f() { int value2 = 10; using Window::value2; // wielokrotna deklaracja std::cout << value2 << std::endl; }

Usunito: przednim

Usunito: powielonej

Druga linia w funkcji f() powoduje bd kompilacji, gdy nazwa value2 zostaa ju zdefiniowana. Ten sam bd wystpuje, gdy deklaracja using zostanie wstawiona przed definicj lokalnej zmiennej value2.

Kada nazwa, wprowadzona do zakresu lokalnego za pomoc deklaracji using, powoduje ukrycie nazwy poza tym zakresem. Demonstruje to nastpny fragment kodu:
namespace Window { int value1 = 20; int value2 = 40; } int value2 = 10; // . . . void f() { using Window::value2; std::cout << value2 << std::endl; }

Deklaracja using w funkcji f() ukrywa nazw value2, zdefiniowan w globalnej przestrzeni nazw. Jak ju wspomniaem, deklaracja using umoliwia bardziej precyzyjn kontrol nazw wprowadzanych z przestrzeni nazw. Dyrektywa using wprowadza do biecego zakresu wszystkie nazwy z przestrzeni nazw. Zaleca si uywanie deklaracji, a nie dyrektywy, gdy dyrektywa anuluje mechanizm przestrzeni nazw. Deklaracja jest bardziej definitywna: jawnie identyfikujemy nazwy, ktre chcemy wprowadzi do zakresu. Deklaracja using nie zamieca globalnej przestrzeni nazw, tak jak dzieje si w przypadku dyrektywy using (chyba, e zadeklarujemy wszystkie nazwy znajdujce si w przestrzeni nazw). Dziki uyciu deklaracji using likwidowane s takie problemy, jak ukrywanie nazw, zamiecanie globalnej przestrzeni nazw, czy te wystpowanie niejednoznacznoci.

Usunito: oraz

Alias przestrzeni nazw


Alias przestrzeni nazw zosta zaprojektowany w celu nadania innej nazwy nazwanej ju wczeniej przestrzeni nazw. Alias stanowi skrcone, okrelenie uywane przy odwoywaniu si do przestrzeni nazw i przydaje si, gdy nazwa przestrzeni nazw jest bardzo duga. Stworzenie aliasu pomaga unikn cigego wpisywania tych samych, dugich nazw. Spjrzmy na przykad:
namespace the_software_company { int value; // . . . } the_software_company::value = 10; . . . namespace TSC = the_software_company; TSC::value = 20;

Oczywicie, moe si okaza, e alias koliduje z istniejc nazw. W takim przypadku kompilator wykryje konflikt i bdziemy musieli zmieni nazw aliasu.

Nienazwana przestrze nazw


Nienazwana przestrze nazw jest po prostu pozbawiona nazwy. Takie przestrzenie zwykle s uywane w celu ochrony globalnych danych przed potencjalnym konfliktem nazw wystpujcym w rnych jednostkach kompilacji. Kada jednostka kompilacji posiada wasn, unikaln, nienazwan przestrze nazw. Wszystkie nazwy zdefiniowane w przestrzeni nienazwanej (w kadej z jednostek kompilacji) mog by uywane bez jawnego kwalifikowania. Oto przykad dwch nienazwanych przestrzeni nazw, wystpujcych w dwch oddzielnych plikach:
// plik one.cpp namespace { int value; char p( char *p ); // . . . } // plik two.cpp namespace { int value; char p( char *p ); // . . . } int main() { char c = p( ptr ); }

Kada z nazw, zmiennej value oraz funkcji p(), naley do waciwego sobie pliku. Aby odwoa si do nazwy (z nienazwanej przestrzeni nazw) w jednostce kompilacji, uywamy tej nazwy bez kwalifikowania. To uycie zostao zademonstrowane w powyszym przykadzie, w wywoaniu funkcji p(). Takie odwoanie jest rwnowane z uyciem dyrektywy using dla obiektw zawartych w nienazwanej przestrzeni nazw. Z tego powodu nie mona odwoywa si do skadowych nienazwanej przestrzeni nazw w innej jednostce kompilacji. Zachowanie nienazwanej przestrzeni nazw jest takie samo, jak zachowanie obiektu posiadajcego czno zewntrzn, ale zadeklarowanego jako static. Spjrzmy na poniszy przykad:
static int value = 10;

Usunito: przednim Usunito: zakada Usunito: e Usunito: Usunito: zadeklarowanego jako zewntrzny (external).

Pamitaj, e uycie sowa kluczowego static zostao potpione przez komitet standaryzacji. Obecnie istniej ju przestrzenie nazw, ktre powinny zastpi pokazany tu kod. Innym sposobem radzenia sobie z nienazwanymi przestrzeniami nazw jest potraktowanie ich jako czonych wewntrznie zmiennych globalnych.

Standardowa przestrze nazw std


Najlepsze przykady zastosowa przestrzeni nazw znajduj si w bibliotece standardowej C++. Biblioteka ta jest w caoci ujta w przestrzeni nazw o nazwie std. W tej przestrzeni s zadeklarowane wszystkie funkcje, klasy, obiekty i wzorce. Bez wtpienia widziae ju kod podobny do poniszego:
#include <iostream> using namespace std;

Pamitaj, e dyrektywa using umieszcza w biecym zakresie wszystkie nazwy z nazwanej przestrzeni nazw. W przypadku biblioteki standardowej uycie tej dyrektywy jest niewaciwe. Dlaczego? Poniewa uywajc jej negujemy cel, w jakim stworzono przestrze nazw; globalna przestrze nazw zostaje zamiecona wszystkimi nazwami wystpujcymi w pliku nagwkowym. Pamitaj, e wszystkie pliki nagwkowe w bibliotece standardowej korzystaj z mechanizmu przestrzeni nazw, wic gdy doczysz kilka standardowych plikw nagwkowych i uyjesz dyrektywy using, w globalnej przestrzeni nazw znajdzie si wszystko to, co znajduje si w tych plikach. Zauwa prosz, e w wikszoci przykadw w tej ksice naruszamy t regu; nie chciaem w ten sposb zachca do naruszania zasad, a jedynie zachowa przejrzysto przykadw. W przeciwiestwie do mnie, powiniene uywa deklaracji using, tak jak w poniszym przykadzie:
#include <iostream> using std::cin; using std::cout; using std::endl; int main() { int value = 0; cout << "Ile wiec jajek sobie zyczysz?" << endl; cin >> value; cout << value << " jajka sadzone, raz!" << endl; return( 0 ); }

Oto wynik dziaania tego programu:


Ile wiec jajek sobie zyczysz? 4 4 jajka sadzone, raz!

Moesz rwnie w peni kwalifikowa uywane nazwy, tak jak w poniszym fragmencie kodu:
#include <iostream>

int main() { int value = 0; std::cout << "Ile wiec jajek sobie zyczysz?" << std::endl; std::cin >> value; std::cout << value << " jajka sadzone, raz!" << std::endl; return( 0 ); }

Wynik dziaania tego programu jest taki sam, jak poprzednio:


Ile wiec jajek sobie zyczysz? 4 4 jajka sadzone, raz!

Ta metoda nadaje si do krtszych programw, ale w przypadku wikszej iloci kodu moe by kopotliwa. Wyobra sobie poprzedzanie przedrostkiem std:: kadej nazwy pochodzcej z biblioteki standardowej!

Rozdzia 19. Wzorce


Przydatnym dla programistw C++ nowym narzdziem s typy sparametryzowane, czyli wzorce. S one tak uytecznym narzdziem, e do definicji jzyka C++ zostaa wprowadzona standardowa biblioteka wzorcw (STL, Standard Template Library). Z tego rozdziau dowiesz si: czym s wzorce i jak ich uywa, jak tworzy wzorce klas, jak tworzy wzorce funkcji, czym jest standardowa biblioteka wzorcw i jak z niej korzysta.

Komentarz [MP1]: Template w odniesieniu do jzyka C++ to wzorzec, a nie szablon. (Za: Bjorne Stroustrup, Jzyk C++, PWN). Usunito: Wzorce Usunito: Szablony Usunito: wzorce Usunito: Wzorce Usunito: wzorce Usunito: wzorce Usunito: wzorce Usunito: wzorcw

Usunito: wzorce

Czym s wzorce?
Pod koniec czternastego rozdziau stworzylimy obiekt PartsList i wykorzystalimy go do stworzenia katalogu czci, klasy PartsCatalog. Jeli jednak chcielibymy na obiekcie PartsList oprze list kotw, pojawiby si problem: klasa PartsList wie tylko o czciach. Aby rozwiza ten problem, moglibymy stworzy klas bazow List. Wtedy moglibymy wyci i wklei wikszo kodu z klasy PartsList do nowej deklaracji klasy CatsList. Tydzie pniej, gdybymy zechcieli stworzy list obiektw Car, musielibymy stworzy now klas i znw kopiowa i wkleja kod. Nie jest to dobre rozwizanie. Z czasem mogoby si okaza, e klasa List oraz klasy z niej wyprowadzone musz by rozszerzone. Rozprowadzenie wszystkich zmian po wszystkich powizanych z ni klasach byoby koszmarem. Problem ten rozwizuj wzorce, ktre po zaadoptowaniu standardu ANSI stay si integraln czci jzyka. Podobnie jak wszystkie elementy w C++, s one bardzo elastyczne, a przy tym bezpieczne (pod wzgldem typw).

Typy parametryzowane
Wzorce pozwalaj na nauczenie kompilatora, w jaki sposb ma tworzy list rzeczy dowolnego typu (zamiast tworzenia zestawu list specyficznego typu) PartsList jest list czci, CatsList jest list kotw. Listy te rni si tylko rodzajem przechowywanych w nich rzeczy. W trakcie korzystania z wzorcw typ rzeczy zawartej w licie staje si parametrem definicji klasy. Standardowym komponentem w prawie wszystkich bibliotekach C++ jest klasa tablicy. Jak widzielimy w przypadku klasy List, tworzenie osobnych tablic dla liczb cakowitych, liczb zmiennoprzecinkowych czy dla obiektw jest mudne i nieefektywne. Wzorce umoliwiaj zadeklarowanie sparametryzowanej klasy tablicy, a nastpnie okrelenie, jaki typ obiektu bdzie zawiera kady jej egzemplarz. Zauwa, e standardowa biblioteka wzorcw zawiera standardowy zestaw klas kontenerw, obejmujcy tablice, listy i tak dalej. W tym rozdziale sami stworzymy klas kontenera, aby przekona si, jak dziaaj wzorce, ale w komercyjnych aplikacjach prawie z pewnoci uyjesz klas standardowych (zamiast tworzy wasne).

Usunito: ozycyjnych Usunito: Wzorce Usunito: wzorcw Usunito: wzorce

Tworzenie egzemplarza wzorca


Tworzenie egzemplarza wzorca oznacza tworzenie okrelonego typu na podstawie tego wzorca. Poszczeglne, tworzone aktualnie klasy s nazywane egzemplarzami wzorca. Parametryzowane wzorce pozwalaj na tworzenie klasy oglnej i przekazywanie jej typw jako parametrw, w celu stworzenia konkretnego egzemplarza wzorca.
Usunito: wzorca Usunito: wzorce Usunito: wzorca Usunito: wzorca

Definicja wzorca
Parametryzowany obiekt Array (wzorzec dla tablic) deklarujemy, piszc:
1: 2: 3: 4: 5: 6: 7: template <class T> // deklaruje wzorzec i parametr class Array // parametryzowana klasa { public: Array(); // w tym miejscu pena deklaracja klasy }; Usunito: wzorzec Usunito: wzorzec

Usunito: wzorzec Usunito: wzorca

Sowo kluczowe template (wzorzec) jest uywane na pocztku kadej deklaracji i definicji klasy wzorcowej. Parametry wzorca wystpuj po sowie kluczowym template. Parametry s zmieniane (uaktualniane) w kadym egzemplarzu. Na przykad, w pokazanym tu wzorcu tablicy

Usunito: wzorca Usunito: rzeczami Usunito: wzorcu

zmienia si typ przechowywanych w niej obiektw. Jeden egzemplarz moe by tablic liczb cakowitych, a inny moe by tablic obiektw typu Animal. W tym przykadzie zostao uyte sowo kluczowe class, po ktrym nastpuje identyfikator T. Sowo kluczowe class wskazuje e ten parametr (identyfikator) jest typem. Identyfikator T jest uywany w pozostaej czci definicji wzorca i odnosi si do typu parametryzowanego. W jednym z egzemplarzy tej klasy w miejscu wszystkich identyfikatorw T zostanie podstawiony na przykad typ int, a w innym egzemplarzu zostanie podstawiony typ Cat. Aby zadeklarowa egzemplarze wzorca typu int i Cat parametryzowanej klasy Array, moglibymy napisa:
Array<int> anIntArray; Array<Cat> aCatArray;

Usunito: wzorca

Usunito: wzorca

Obiekt anIntArray to obiekt typu tablica liczb cakowitych; obiekt aCatArray to obiekt typu tablica kotw. Od tego momentu moemy uywa typu Array<int> wszdzie tam, gdzie normalnie uywalibymy jakiego typu (np. dla wartoci zwracanej przez funkcj, jako parametru funkcji i tak dalej). Pen deklaracj tej okrojonej klasy Array przedstawia listing 19.1.

Usunito: moglibymy uy typu jako

UWAGA Listing 19.1 nie jest kompletnym programem!

Listing 19.1. Wzorzec klasy tablicy


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: //Listing 19.1 Wzorzec klasy tablicy #include <iostream> using namespace std; const int DefaultSize = 10; template <class T> // deklaruje wzorzec i parametr class Array // parametryzowana klasa { public: // konstruktory Array(int itsSize = DefaultSize); Array(const Array &rhs); ~Array() { delete [] pType; } // operatory Array& operator=(const Array&); T& operator[](int offSet) { return pType[offSet]; } // akcesory int getSize() { return itsSize; } private: T *pType; int itsSize; };

Usunito: Wzorzec

Wynik Brak. Program nie jest kompletny.

Analiza Definicja wzorca rozpoczyna si w linii 5. od sowa kluczowego template, po ktrym wystpuje parametr. W tym przypadku parametr jest identyfikowany przez sowo kluczowe class, za do reprezentowania parametryzowanego typu zosta uyty identyfikator T. Od linii 6. a do koca wzorca w linii 24., pozostaa cz deklaracji wyglda tak samo jak deklaracja kadej innej klasy. Jedyn rnic jest to, e tam, gdzie normalnie wystpowaby typ obiektu, wystpuje identyfikator T. Na przykad, mona oczekiwa, e operator[] bdzie zwraca referencj do obiektu zawartego w tablicy rzeczywicie, jest zadeklarowany jako zwracajcy referencj do T. Gdy deklarowany jest egzemplarz tablicy liczb cakowitych, to zdefiniowany dla tej tablicy operator= zwrci referencj do liczby cakowitej. Gdy jest deklarowany egzemplarz tablicy obiektw klasy Animal, to zdefiniowany dla tej tablicy operator= zwrci referencj do obiektu Animal.
Usunito: wzorca

Usunito: wzorca

Usunito: dostarczony Usunito: dostarczony

Uycie nazwy
Sowo Array moe by uyte bez kwalifikowania wewntrz deklaracji klasy. We wszystkich innych miejscach programu do klasy tej trzeba si odwoywa jako do Array<T>. Na przykad, jeli nie wpiszemy konstruktora wewntrz deklaracji klasy, to musimy napisa:
template <class T> Array<T>::Array(int size): itsSize = size { pType = new T[size]; for (int i = 0; i < size; i++) pType[i] = 0; } Usunito: za

Deklaracja w pierwszej linii tego fragmentu kodu jest wymagana do zidentyfikowania typu (class T). Nazw wzorca jest Array<T>, a nazw funkcji jest Array(int size). Pozostaa cz funkcji jest taka sama, jak w przypadku zwykej funkcji. Powszechn i zalecan praktyk jest przetestowanie dziaania klasy i jej funkcji jako zwykych deklaracji przed zamienieniem ich we wzorzec. To rozwizanie upraszcza tworzenie wzorca, umoliwiajc skoncentrowanie si na celu programowania; rozwizanie, poprzez stworzenie wzorca, uoglniamy dopiero pniej.

Usunito: wzorca Usunito: e Usunito: wzorzec Usunito: wzorca Usunito: a Usunito: wzorca Usunito: enie

Implementowanie wzorca
Pena implementacja klasy wzorca Array wymaga zaimplementowania konstruktora kopiujcego, operatora= i tak dalej. Prosty program sterujcy, przenaczony do testowania tej klasy wzorcowej przedstawia listing 19.2.

Usunito: wzorca Usunito: i

Usunito: wzorca Usunito: wzorcw

UWAGA Niektre starsze kompilatory nie obsuguj wzorcw. Wzorce s jednak czci standardu ANSI C++ i s obsugiwane w obecnych wersjach kompilatorw wiodcych producentw. Jeli masz bardzo stary kompilator, nie bdziesz mg skompilowa i uruchomi przykadowych programw przedstawionych w tym rozdziale. Jednak mimo to, powiniene go w caoci i wrci do niego pniej, gdy zdobdziesz nowszy kompilator.

Usunito: Wzorce

Listing 19.2. Implementacja wzorca klasy tablicy


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: //Listing 19.2 Implementacja wzorca klasy tablicy #include <iostream> const int DefaultSize = 10; // deklaruje prost klas Animal tak, abymy // mogli tworzy tablice obiektw typu Animal class Animal { public: Animal(int); Animal(); ~Animal() {} int GetWeight() const { return itsWeight; } void Display() const { std::cout << itsWeight; } private: int itsWeight; }; Animal::Animal(int weight): itsWeight(weight) {} Animal::Animal(): itsWeight(0) {} template <class T> // deklaruje wzorzec i parametr class Array // parametryzowana klasa { public: // konstruktory Array(int itsSize = DefaultSize); Array(const Array &rhs); ~Array() { delete [] pType; } // operatory Array& operator=(const Array&); T& operator[](int offSet) { return pType[offSet]; }

Usunito: wzorca Usunito: wzorca

Usunito: wzorzec

41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101:

const T& operator[](int offSet) const { return pType[offSet]; } // akcesory int GetSize() const { return itsSize; } private: T *pType; int itsSize; }; // oraz implementacje... // implementacja konstruktora template <class T> Array<T>::Array(int size): itsSize(size) { pType = new T[size]; for (int i = 0; i<size; i++) pType[i] = 0; } // konstruktor kopiujcy template <class T> Array<T>::Array(const Array &rhs) { itsSize = rhs.GetSize(); pType = new T[itsSize]; for (int i = 0; i<itsSize; i++) pType[i] = rhs[i]; } // operator= template <class T> Array<T>& Array<T>::operator=(const Array &rhs) { if (this == &rhs) return *this; delete [] pType; itsSize = rhs.GetSize(); pType = new T[itsSize]; for (int i = 0; i<itsSize; i++) pType[i] = rhs[i]; return *this; } // program sterujcy int main() { Array<int> theArray; Array<Animal> theZoo; Animal *pAnimal; Usunito: i

// tablica liczb cakowitych // tablica obiektw typu Animal

// wypeniamy tablice for (int i = 0; i < theArray.GetSize(); i++) { theArray[i] = i*2; pAnimal = new Animal(i*3); theZoo[i] = *pAnimal; delete pAnimal; }

102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113:

// wypisujemy zawarto tablic for (int j = 0; j < theArray.GetSize(); j++) { std::cout << "theArray[" << j << "]:\t"; std::cout << theArray[j] << "\t\t"; std::cout << "theZoo[" << j << "]:\t"; theZoo[j].Display(); std::cout << std::endl; } return 0; }

Wynik
theArray[0]: theArray[1]: theArray[2]: theArray[3]: theArray[4]: theArray[5]: theArray[6]: theArray[7]: theArray[8]: theArray[9]: 0 2 4 6 8 10 12 14 16 18 theZoo[0]: theZoo[1]: theZoo[2]: theZoo[3]: theZoo[4]: theZoo[5]: theZoo[6]: theZoo[7]: theZoo[8]: theZoo[9]: 0 3 6 9 12 15 18 21 24 27

Analiza Linie od 8. do 26. zawieraj okrojon klas Animal, stworzon tu po to, by w tablicy mg zosta umieszczony take typ definiowany przez uytkownika. Linia 29. deklaruje, e to, co nastpuje po niej, jest wzorcem, i e parametrem tego wzorca jest typ oznaczony jako T. Klasa Array posiada dwa konstruktory; pierwszy z nich otrzymuje rozmiar, ktry domylnie ustawiany jest zgodnie ze sta DefaultSize (rozmiar domylny). Deklarowane s operatory przypisania i indeksu, przy czym ten drugi deklarowany jest zarwno w wariancie const jak i zwykym. Jedynym zdefiniowanym akcesorem jest funkcja GetSize(), zwracajca rozmiar tablicy. Z pewnoci mona sobie wyobrazi lepszy interfejs; zdefiniowany tu interfejs byby nieodpowiedni w kadym powanym programie korzystajcym z klasy Array. Jako minimum wymagane s operatory do usuwania elementw, zwikszania i pakowania tablicy i tak dalej. Wszystko to jest dostarczane przez klasy kontenerw w STL, ktre opiszemy w dalszej czci rozdziau. Prywatne dane to: rozmiar tablicy oraz wskanik do rzeczywistej, zawartej w pamici tablicy obiektw.
Usunito: wzorcem Usunito: wzorca

Usunito: dostarczonym

Usunito: dostarczony

Funkcje wzorcowe
Gdy chcemy przekaza obiekt tablicy do funkcji, musimy przekaza jej okrelony egzemplarz tej tablicy, a nie wzorzec. Tak wic, jeli funkcja SomeFunction()przyjmuje jako parametr tablic liczb cakowitych, moemy napisa:
void SomeFunction(Array<int>&); // ok

Usunito: wzorca

Usunito: wzorzec

nie moemy jednak napisa:


void SomeFunction(Array<T>&); // bd!

gdy kompilator nie wie, jakim typem jest T&. Nie moemy take napisa:
void SomeFunction(Array &); // bd Usunito: jest Usunito: wzorzec Usunito: wzorca

gdy klasa Array nie istnieje istnieje jedynie wzorzec i jego egzemplarze. Aby uzyska bardziej oglny pogld, musimy zadeklarowa funkcj wzorcow.
template <class T> void MyTemplateFunction(Array<T>&);

// ok Usunito: wzorca Usunito: ej Usunito: wzorca Usunito: wzorcw Usunito: wzorca

Funkcja MyTemplateFunction() jest zadeklarowana jako funkcja wzorcowa (poprzez deklaracj w poprzedzajcej j linii). Zauwa, e funkcja wzorcowa moe mie dowoln nazw, podobnie jak kada inna funkcja. Funkcje wzorcowe mog take przyjmowa egzemplarze wzorca, a nie tylko jego sparametryzowane formy. Na przykad:
template <class T> void MyOtherFunction(Array<T>&, Array<int>&);

// ok

Zwr uwag, e ta funkcja otrzymuje dwie tablice: tablic parametryzowan oraz tablic liczb cakowitych. Pierwsza moe by tablic dowolnych obiektw, lecz druga musi by zawsze tablic liczb cakowitych.

Wzorce i przyjaciele
Klasy wzorcowe mog deklarowa trzy rodzaje przyjaci: niewzorcowe zaprzyjanione klasy i funkcje, oglne wzorcowe zaprzyjanione klasy i funkcje, specyficzne dla typu wzorca zaprzyjanione klasy i funkcje.

Usunito: Wzorce Usunito: wzorcw Usunito: wzorcowe Usunito: wzorcowe Usunito: wzorcowe

Usunito: wzorcowe

Niewzorcowe zaprzyjanione klasy i funkcje.


Istnieje moliwo zadeklarowania dowolnej klasy lub funkcji jako zaprzyjanionej z klas wzorcow . Kady egzemplarz klasy bdzie traktowa klas lub funkcj zaprzyjanion tak, jakby deklaracja przyjani zostaa zawarta z tym konkretnym egzemplarzem. Listing 19.3 dodaje definicj funkcji zaprzyjanionej, Intrude(), do definicji wzorca klasy Array. Funkcja ta zostaje wywoana przez program sterujcy. Poniewa jest zaprzyjaniona, funkcja Intrude() moe odwoywa si do prywatnych danych klasy Array. Jednak poniewa nie jest funkcj wzorcow, moe by wywoywana jedynie dla klas Array zawierajcych zmienne typu int. Listing 19.3. Niewzorcowa funkcja zaprzyjaniona
0: // Listing 19.3 - Specyficzne dla typu funkcje zaprzyjanione we wzorcu 1: 2: #include <iostream> 3: using namespace std; 4: 5: const int DefaultSize = 10; 6: 7: // deklaruje prost klas Animal tak abymy 8: // mogli tworzy tablice obiektw typu Animal 9: 10: class Animal 11: { 12: public: 13: Animal(int); 14: Animal(); 15: ~Animal() {} 16: int GetWeight() const { return itsWeight; } 17: void Display() const { cout << itsWeight; } 18: private: 19: int itsWeight; 20: }; 21: 22: Animal::Animal(int weight): 23: itsWeight(weight) 24: {} 25: 26: Animal::Animal(): 27: itsWeight(0) 28: {} 29: Usunito: wzorca Usunito: wzorca

Usunito: wzorca Usunito: wzorcowa Usunito: e Usunito: wzorcu

30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90:

template <class T> // deklaruje wzorzec i parametr class Array // klasa parametryzowana { public: // konstruktory Array(int itsSize = DefaultSize); Array(const Array &rhs); ~Array() { delete [] pType; } // operatory Array& operator=(const Array&); T& operator[](int offSet) { return pType[offSet]; } const T& operator[](int offSet) const { return pType[offSet]; } // akcesory int GetSize() const { return itsSize; } // funkcja zaprzyjaniona friend void Intrude(Array<int>); private: T *pType; int itsSize; }; // funkcja zaprzyjaniona. Nie jest wzorcowa, wic moe by // uyta tylko z tablicami wartoci int! Wnika w prywatne dane. void Intrude(Array<int> theArray) { cout << "\n*** Funkcja Intrude() ***\n"; for (int i = 0; i < theArray.itsSize; i++) cout << "i: " << theArray.pType[i] << endl; cout << "\n"; } // oraz implementacje... // implementacja konstruktora template <class T> Array<T>::Array(int size): itsSize(size) { pType = new T[size]; for (int i = 0; i<size; i++) pType[i] = 0; } // konstruktor kopiujcy template <class T> Array<T>::Array(const Array &rhs) { itsSize = rhs.GetSize(); pType = new T[itsSize]; for (int i = 0; i<itsSize; i++) pType[i] = rhs[i]; } // operator= template <class T> Array<T>& Array<T>::operator=(const Array &rhs) {

Usunito: wzorzec

Usunito: wzorcowa

Usunito: i

91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129:

if (this == &rhs) return *this; delete [] pType; itsSize = rhs.GetSize(); pType = new T[itsSize]; for (int i = 0; i<itsSize; i++) pType[i] = rhs[i]; return *this;

// program sterujcy int main() { Array<int> theArray; Array<Animal> theZoo; Animal *pAnimal;

// tablica liczb cakowitych // tablica obiektw typu Animal

// wypeniamy tablice for (int i = 0; i < theArray.GetSize(); i++) { theArray[i] = i*2; pAnimal = new Animal(i*3); theZoo[i] = *pAnimal; } int j; for (j = 0; j < theArray.GetSize(); j++) { cout << "theZoo[" << j << "]:\t"; theZoo[j].Display(); cout << endl; } cout << "Teraz uzywamy funkcji zaprzyjaznionej do \n"; cout << "wypisania elementow tablicy Array<int>"; Intrude(theArray); cout << "\n\nGotowe.\n"; return 0; }

Wynik
theZoo[2]: 6 theZoo[3]: 9 theZoo[4]: 12 theZoo[5]: 15 theZoo[6]: 18 theZoo[7]: 21 theZoo[8]: 24 theZoo[9]: 27 Teraz uzywamy funkcji zaprzyjaznionej do wypisania elementow tablicy Array<int> *** Funkcja Intrude() *** i: 0 i: 2 i: 4 i: 6 i: 8

i: i: i: i: i:

10 12 14 16 18

Gotowe.

Analiza Deklaracja wzorca Array zostaa rozszerzona o zaprzyjanion funkcj Intrude(). Deklarujemy, e kady egzemplarz tablicy typw int bdzie traktowa funkcj Intrude() jak funkcj zaprzyjanion, wic bdzie ona miaa dostp do prywatnych danych i funkcji skadowych egzemplarza tablicy. W linii 60. funkcja Intrude() bezporednio odwouje si do skadowej itsSize, a w linii 61. do wskanika pType. Standardowe uycie tych skadowych nie byo konieczne, gdy klasa Array zapewnia akcesory publiczne, suy ono jednak zilustrowaniu, w jaki sposb mona deklarowa funkcje zaprzyjanione dla wzorcw.
Usunito: wzorcowe Usunito: wzorca

Oglne wzorcowe zaprzyjanione klasy i funkcje


Dodanie operatora wywietlania do klasy Array byoby pomocne. Operator wywietlania mona zadeklarowa dla kadego moliwego typu wzorca Array, ale w ten sposb cay wysiek woony w zmian klasy Array na wzorzec staby si bezcelowy. My potrzebujemy operatora wstawiania dziaajcego w przypadku zastosowania kadego typu wzorca Array:
ostream& operator<< (ostream&, Array<T>&); Usunito: wzorca Usunito: wzorca

Aby uzyska zamierzony efekt, musimy zadeklarowa operator<< jako funkcj wzorca:
template <class T> ostream& operator<< (ostream&, Array<T>&) Usunito: wzorca Usunito: dostarczy Usunito: wzorzec

Teraz, gdy operator<< jest funkcj wzorcow, musimy jedynie poda jego implementacj. Listing 19.4 przedstawia wzorzec Array rozszerzony o t deklaracj oraz implementacj operatora<<. Listing 19.4. Uycie operatora ostream
0: 1: //Listing 19.4 Uycie operatora ostream #include <iostream>

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

using namespace std; const int DefaultSize = 10; class Animal { public: Animal(int); Animal(); ~Animal() {} int GetWeight() const { return itsWeight; } void Display() const { cout << itsWeight; } private: int itsWeight; }; Animal::Animal(int weight): itsWeight(weight) {} Animal::Animal(): itsWeight(0) {} template <class T> // deklaruje wzorzec i parametr class Array // parametryzowana klasa { public: // konstruktory Array(int itsSize = DefaultSize); Array(const Array &rhs); ~Array() { delete [] pType; } // operatory Array& operator=(const Array&); T& operator[](int offSet) { return pType[offSet]; } const T& operator[](int offSet) const { return pType[offSet]; } // akcesory int GetSize() const { return itsSize; } friend ostream& operator<< (ostream&, Array<T>&); private: T *pType; int itsSize; }; template <class T> ostream& operator<< (ostream& output, Array<T>& theArray) { for (int i = 0; i<theArray.GetSize(); i++) output << "[" << i << "] " << theArray[i] << endl; return output; } // oraz implementacje... // implementacja konstruktora template <class T> Array<T>::Array(int size):

63: itsSize(size) 64: { 65: pType = new T[size]; 66: for (int i = 0; i<size; i++) 67: pType[i] = 0; 68: } 69: 70: // konstruktor kopiujcy 71: template <class T> 72: Array<T>::Array(const Array &rhs) 73: { 74: itsSize = rhs.GetSize(); 75: pType = new T[itsSize]; 76: for (int i = 0; i<itsSize; i++) 77: pType[i] = rhs[i]; 78: } 79: 80: // operator= 81: template <class T> 82: Array<T>& Array<T>::operator=(const Array &rhs) 83: { 84: if (this == &rhs) 85: return *this; 86: delete [] pType; 87: itsSize = rhs.GetSize(); 88: pType = new T[itsSize]; 89: for (int i = 0; i<itsSize; i++) 90: pType[i] = rhs[i]; 91: return *this; 92: } 93: 94: int main() 95: { 96: bool Stop = false; // znacznik dla ptli 97: int offset, value; 98: Array<int> theArray; 99: 100: while (!Stop) 101: { 102: cout << "Podaj indeks (0-9) "; 103: cout << "oraz wartosc. (-1 aby skonczyc): " ; 104: cin >> offset >> value; 105: 106: if (offset < 0) 107: break; 108: 109: if (offset > 9) 110: { 111: cout << "***Prosze uzywac wartosci pomiedzy 0 i 9.***\n"; 112: continue; 113: } 114: 115: theArray[offset] = value; 116: } 117: 118: cout << "\nOto cala tablica:\n"; 119: cout << theArray << endl; 120: return 0; 121: }

Usunito: i

Wynik
Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 ***Prosze uzywac wartosci pomiedzy 0 Podaj indeks (0-9) oraz wartosc. (-1 Oto [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] cala tablica: 0 10 20 30 40 50 60 70 80 90 aby skonczyc): aby skonczyc): aby skonczyc): aby skonczyc): aby skonczyc): aby skonczyc): aby skonczyc): aby skonczyc): aby skonczyc): aby skonczyc): i 9.*** aby skonczyc): 1 10 2 20 3 30 4 40 5 50 6 60 7 70 8 80 9 90 10 10 -1 -1

Analiza W linii 43. funkcja wzorcowa operator<<() zostaa zadeklarowana jako funkcja zaprzyjaniona klasy wzorcowej Array. Poniewa operator<<() jest implementowany jako funkcja wzorcowa, kady egzemplarz tej parametryzowanej klasy tablicy automatycznie bdzie posiada operator<<(). Implementacja tego operatora rozpoczyna si w linii 50. Wywoywany jest kolejno kady element tablicy. Moemy uzyska zamierzony efekt tylko wtedy, gdy dla kadego typu obiektw przechowywanych w tablicy bdzie zdefiniowany operator<<().
Usunito: wzorca Usunito: wzorca Usunito: wzorca

Usunito: wzorca

Uycie elementw wzorca


Elementy wzorca mog by traktowane tak jak kady inny typ. Mona przekazywa je jako parametry, poprzez warto lub poprzez referencj, mona je take zwraca jako wartoci zwrotne funkcji, rwnie poprzez warto lub poprzez referencj. Listing 19.5 przedstawia sposb przekazywania obiektw wzorca. Listing 19.5. Przekazywanie obiektw wzorca do i z funkcji
0: 1: 2: //Listing 19.5 Przekazywanie obiektw wzorca do i z funkcji #include <iostream> using namespace std; Usunito: wzorca Usunito: wzorca Usunito: wzorca

3: 4: const int DefaultSize = 10; 5: 6: // Standardowa klasa do umieszczania w tablicach 7: class Animal 8: { 9: public: 10: // konstruktory 11: Animal(int); 12: Animal(); 13: ~Animal(); 14: 15: // akcesory 16: int GetWeight() const { return itsWeight; } 17: void SetWeight(int theWeight) { itsWeight = theWeight; } 18: 19: // zaprzyjanione operatory 20: friend ostream& operator<< (ostream&, const Animal&); 21: 22: private: 23: int itsWeight; 24: }; 25: 26: // operator ekstrakcji dla wypisywania wartoci obiektu klasy Animals 27: ostream& operator<< 28: (ostream& theStream, const Animal& theAnimal) 29: { 30: theStream << theAnimal.GetWeight(); 31: return theStream; 32: } 33: 34: Animal::Animal(int weight): 35: itsWeight(weight) 36: { 37: // cout << "Animal(int)\n"; 38: } 39: 40: Animal::Animal(): 41: itsWeight(0) 42: { 43: // cout << "Animal()\n"; 44: } 45: 46: Animal::~Animal() 47: { 48: // cout << "Destruktor klasy Animal...\n"; 49: } 50: 51: template <class T> // deklaruje wzorzec i parametr 52: class Array // klasa parametryzowana 53: { 54: public: 55: Array(int itsSize = DefaultSize); 56: Array(const Array &rhs); 57: ~Array() { delete [] pType; } 58: 59: Array& operator=(const Array&); 60: T& operator[](int offSet) { return pType[offSet]; } 61: const T& operator[](int offSet) const 62: { return pType[offSet]; }

Usunito: wzorzec

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

int GetSize() const { return itsSize; } // funkcja zaprzyjaniona friend ostream& operator<< (ostream&, const Array<T>&); private: T *pType; int itsSize; }; template <class T> ostream& operator<< (ostream& output, const Array<T>& theArray) { for (int i = 0; i<theArray.GetSize(); i++) output << "[" << i << "] " << theArray[i] << endl; return output; } // oraz implementacje... // implementacja konstruktora template <class T> Array<T>::Array(int size): itsSize(size) { pType = new T[size]; for (int i = 0; i<size; i++) pType[i] = 0; } // konstruktor kopiujcy template <class T> Array<T>::Array(const Array &rhs) { itsSize = rhs.GetSize(); pType = new T[itsSize]; for (int i = 0; i<itsSize; i++) pType[i] = rhs[i]; } void IntFillFunction(Array<int>& theArray); void AnimalFillFunction(Array<Animal>& theArray); int main() { Array<int> intArray; Array<Animal> animalArray; IntFillFunction(intArray); AnimalFillFunction(animalArray); cout << "intArray...\n" << intArray; cout << "\nanimalArray...\n" << animalArray << endl; return 0; } void IntFillFunction(Array<int>& theArray) { bool Stop = false; int offset, value; while (!Stop) { cout << "Podaj indeks (0-9) "; Usunito: i

124: cout << "oraz wartosc. (-1 aby skonczyc): " ; 125: cin >> offset >> value; 126: if (offset < 0) 127: break; 128: if (offset > 9) 129: { 130: cout << "***Prosze uzywac wartosci pomiedzy 0 i 9.***\n"; 131: continue; 132: } 133: theArray[offset] = value; 134: } 135: } 136: 137: 138: void AnimalFillFunction(Array<Animal>& theArray) 139: { 140: Animal * pAnimal; 141: for (int i = 0; i<theArray.GetSize(); i++) 142: { 143: pAnimal = new Animal; 144: pAnimal->SetWeight(i*100); 145: theArray[i] = *pAnimal; 146: delete pAnimal; // kopia zostaa umieszczona w tablicy 147: } 148: }

Wynik
Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 Podaj indeks (0-9) oraz wartosc. (-1 ***Prosze uzywac wartosci pomiedzy 0 Podaj indeks (0-9) oraz wartosc. (-1 intArray... [0] 0 [1] 10 [2] 20 [3] 30 [4] 40 [5] 50 [6] 60 [7] 70 [8] 80 [9] 90 animalArray... [0] 0 [1] 100 aby skonczyc): aby skonczyc): aby skonczyc): aby skonczyc): aby skonczyc): aby skonczyc): aby skonczyc): aby skonczyc): aby skonczyc): aby skonczyc): i 9.*** aby skonczyc): 1 10 2 20 3 30 4 40 5 50 6 60 7 70 8 80 9 90 10 10 -1 1

[2] [3] [4] [5] [6] [7] [8] [9]

200 300 400 500 600 700 800 900

Analiza W celu zaoszczdzenia miejsca, w programie zrezygnowano z wikszoci implementacji klasy Array. Klasa Animal zostaa zadeklarowana w liniach od 7. do 24. Cho jest ona okrojona i uproszczona, posiada wasny operator wstawiania (<<), umoliwiajcy wypisywanie wartoci obiektw typu Animal. W tym przypadku wypisywanie wartoci polega po prostu na wypisaniu wagi zwierzcia. Zauwa, e klasa Animal posiada konstruktor domylny. Jest to konieczne, poniewa gdy dodajemy do tablicy obiekt, w celu jego stworzenia uywany jest domylny konstruktor jego klasy. Jak wkrtce zobaczymy, wynikaj z tego pewne trudnoci. W linii 103. zostaa zadeklarowana funkcja IntFillFunction(). Jej prototyp wskazuje, e ta funkcja przyjmuje tablic liczb cakowitych. Zwr te uwag, e nie jest to funkcja wzorcowa. IntFillFunction() oczekuje tylko jednego typu tablicy tablicy wartoci cakowitych. W linii 104. funkcja AnimalFillFunction() przyjmuje tablic Array obiektw Animal. Implementacje tych funkcji rni si od siebie, gdy wypenianie tablicy wartoci cakowitych nie musi odbywa si w ten sam sposb, co wypenianie tablicy obiektw Animal.
Usunito: wzorca

Funkcje specjalizowane
Gdy w listingu 19.5 usuniesz znaki komentarza z instrukcji wypisujcych komunikaty w konstruktorach i destruktorach klasy Animal, odkryjesz nowe, dodatkowe wywoania tych konstruktorw i destruktorw. Gdy obiekt jest dodawany do tablicy, wywoywany jest jego konstruktor domylny. Jednak konstruktor klasy Array przypisuje warto 0 kademu elementowi tablicy (co widzielimy w liniach 59. i 60. listingu 19.2). Gdy piszemy someAnimal = (Animal) 0;, wywoujemy dla obiektu Animal domylny operator=. To powoduje utworzenie tymczasowego obiektu Animal z uyciem konstruktora
operatora=, a nastpnie jest niszczony.

przyjmujcego warto cakowit (zero). Ten obiekt tymczasowy jest uywany po prawej stronie

Jest to niepotrzebne marnotrawstwo czasu, gdy obiekt Animal jest ju odpowiednio zainicjalizowany. Nie moemy jednak usun tej linii, gdy zmienne cakowite nie s automatycznie inicjalizowane wartoci zero. W tej sytuacji naley nakza wzorcowi, by nie uywa tego konstruktora dla klasy Animal, a zamiast niego uy specjalnego konstruktora tej klasy.

Usunito: wzorca

Na listingu 19.6 pokazano, jak moemy dostarczy jawnej implementacji dla klasy Animal. Listing 19.6. Specjalne implementacje wzorca
0: #include <iostream> 1: using namespace std; 2: 3: const int DefaultSize = 3; 4: 5: // Standardowa klasa do umieszczania w tablicach 6: class Animal 7: { 8: public: 9: // konstruktory 10: Animal(int); 11: Animal(); 12: ~Animal(); 13: 14: // akcesory 15: int GetWeight() const { return itsWeight; } 16: void SetWeight(int theWeight) { itsWeight = theWeight; } 17: 18: // zaprzyjanione operatory 19: friend ostream& operator<< (ostream&, const Animal&); 20: 21: private: 22: int itsWeight; 23: }; 24: 25: // operator ekstrakcji dla wypisywania wartoci obiektu klasy Animals 26: ostream& operator<< 27: (ostream& theStream, const Animal& theAnimal) 28: { 29: theStream << theAnimal.GetWeight(); 30: return theStream; 31: } 32: 33: Animal::Animal(int weight): 34: itsWeight(weight) 35: { 36: cout << "Animal(int) "; 37: } 38: 39: Animal::Animal(): 40: itsWeight(0) 41: { 42: cout << "Animal() "; 43: } 44: 45: Animal::~Animal() 46: { 47: cout << "Destruktor klasy Animal..."; 48: } 49: 50: template <class T> // deklaruje wzorzec i parametr 51: class Array // klasa parametryzowana 52: { 53: public: 54: Array(int itsSize = DefaultSize);

Usunito: o

Usunito: wzorzec

55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115:

Array(const Array &rhs); ~Array() { delete [] pType; } // operatory Array& operator=(const Array&); T& operator[](int offSet) { return pType[offSet]; } const T& operator[](int offSet) const { return pType[offSet]; } // akcesory int GetSize() const { return itsSize; } // funkcja zaprzyjaniona friend ostream& operator<< (ostream&, const Array<T>&); private: T *pType; int itsSize; }; template <class T> Array<T>::Array(int size = DefaultSize): itsSize(size) { pType = new T[size]; for (int i = 0; i<size; i++) pType[i] = (T)0; } template <class T> Array<T>& Array<T>::operator=(const Array &rhs) { if (this == &rhs) return *this; delete [] pType; itsSize = rhs.GetSize(); pType = new T[itsSize]; for (int i = 0; i<itsSize; i++) pType[i] = rhs[i]; return *this; } template <class T> Array<T>::Array(const Array &rhs) { itsSize = rhs.GetSize(); pType = new T[itsSize]; for (int i = 0; i<itsSize; i++) pType[i] = rhs[i]; } template <class T> ostream& operator<< (ostream& output, const Array<T>& theArray) { for (int i = 0; i<theArray.GetSize(); i++) output << "[" << i << "] " << theArray[i] << endl; return output; }

116: Array<Animal>::Array(int AnimalArraySize): 117: itsSize(AnimalArraySize) 118: { 119: pType = new Animal[AnimalArraySize]; 120: } 121: 122: 123: void IntFillFunction(Array<int>& theArray); 124: void AnimalFillFunction(Array<Animal>& theArray); 125: 126: int main() 127: { 128: Array<int> intArray; 129: Array<Animal> animalArray; 130: IntFillFunction(intArray); 131: AnimalFillFunction(animalArray); 132: cout << "intArray...\n" << intArray; 133: cout << "\nanimalArray...\n" << animalArray << endl; 134: return 0; 135: } 136: 137: void IntFillFunction(Array<int>& theArray) 138: { 139: bool Stop = false; 140: int offset, value; 141: while (!Stop) 142: { 143: cout << "Podaj indeks (0-2) "; 144: cout << "oraz wartosc. (-1 aby skonczyc): " ; 145: cin >> offset >> value; 146: if (offset < 0) 147: break; 148: if (offset > 2) 149: { 150: cout << "***Prosze uzywac wartosci pomiedzy 0 i 2.***\n"; 151: continue; 152: } 153: theArray[offset] = value; 154: } 155: } 156: 157: 158: void AnimalFillFunction(Array<Animal>& theArray) 159: { 160: Animal * pAnimal; 161: for (int i = 0; i<theArray.GetSize(); i++) 162: { 163: pAnimal = new Animal(i*10); 164: theArray[i] = *pAnimal; 165: delete pAnimal; 166: } 167: }

UWAGA W celu uatwienia analizy, do wyniku zostay dodane numery linii. Numery te nie s wypisywane przez program.

Wynik

1: Animal() Animal() Animal() Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): 0 0 2: Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): 3: Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): 4: Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): 1 5: Animal(int) Destruktor klasy Animal...Animal(int) Destruktor klasy Animal...Animal(int) Destruktor klasy Animal...intArray... 6: [0] 0 7: [1] 1 8: [2] 2 9: 10: animalArray... 11: [0] 0 12: [1] 10 13: [2] 20 14: 15: Destruktor klasy Animal...Destruktor klasy Animal...Destruktor klasy Animal... 16: <<< Drugie uruchomienie >>> 17: Animal() Destruktor klasy Animal... 18: Animal() Destruktor klasy Animal... 19: Animal() Destruktor klasy Animal... 20: Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): 21: Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): 22: Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): 23: Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): 1 24: Animal(int) 25: Destruktor klasy Animal... 26: Animal(int) 27: Destruktor klasy Animal... 28: Animal(int) 29: Destruktor klasy Animal... 30: intArray... 31: [0] 0 32: [1] 1 33: [2] 2 34: 35: animalArray... 36: [0] 0 37: [1] 10 38: [2] 20 39: 40: Destruktor klasy Animal... 41: Destruktor klasy Animal... 42: Destruktor klasy Animal...

1 1 2 2 -1 -

0 0 1 1 2 2 -1 -

Analiza

Na listingu 19.6, z wywoa wypisujcych komunikaty, zostay usunite znaki komentarza, dziki czemu wida, kiedy s tworzone tymczasowe obiekty Animal. W celu skrcenia zapisu wynikw, warto DefaultSize zostaa zmniejszona do trzech. Konstruktory i destruktor klasy Animal, zawarte w liniach od 33. do 48., wypisuj komunikaty wskazujce moment, kiedy zostay wywoane. W liniach od 75. do 82. zostaje zadeklarowane dziaanie konstruktora klasy Array. W liniach od 116. do 120. zosta zademonstrowany wyspecjalizowany konstruktor dla tablicy Array obiektw typu Animal. Zwr uwag, e w tym wyspecjalizowanym konstruktorze do ustawiania domylnej wartoci kadego obiektu Animal jest uywany jego konstruktor domylny i nie jest dokonywane adne jawne przypisanie. Przy pierwszym uruchomieniu programu zostaje wypisany pierwszy zestaw wynikw. Pierwsza linia wyniku pokazuje, e w wyniku tworzenia tablicy s wywoywane trzy konstruktory domylne. Uytkownik wpisuje cztery liczby, z ktrych trzy s umieszczane w tablicy wartoci cakowitych. Wykonanie programu przechodzi do funkcji AniamFillFunction(). W linii 163. na stercie jest tworzony nowy obiekt tymczasowy, a jego warto jest w linii 164. uywana do zmodyfikowania obiektu Animal w tablicy. W linii 165. ten obiekt tymczasowy jest niszczony. Czynno t powtarza si dla kadego elementu tablicy, co odzwierciedla pita linia wyniku. Pod koniec programu tablice s niszczone i gdy s wywoywane ich destruktory, zostaj zniszczone take zawarte w nich obiekty. Odzwierciedla to pitnasta linia wyniku. Przed drugim uruchomieniem programu (linie wyniku od 17. do 42.) zostaa wykomentowana specjalna implementacja konstruktora tablicy, zawarta w liniach od 116. do 120. Gdy program zosta uruchomiony ponownie, podczas konstruowania tablicy obiektw Animal nastpio wic wywoanie konstruktora wzorca, zawartego w liniach od 74. do 81. To spowodowao, e w liniach 79. i 80. programu, dla kadego elementu tablicy by tworzony obiekt tymczasowy Animal. Pokazuj to linie od 18. do 20. wyniku. Jak mona byo oczekiwa, poza tym wyniki obu uruchomie programu nie ulegy zmianie.
Usunito: Wzorce

Usunito: wzorca

Wzorce i skadowe statyczne


Wzorzec moe deklarowa statyczne dane skadowe. Kady egzemplarz wzorca ma wtedy wasny zestaw danych statycznych, po jednym dla kadego typu klasy. Tak wic, gdy dodamy skadow statyczn do klasy Array (na przykad licznik okrelajcy, ile tablic zostao utworzonych), bdziemy mieli jedn tak skadow dla kadego typu: jedn dla wszystkich tablic Animal i inn dla wszystkich tablic wartoci cakowitych. Na listingu 19.7 do klasy Array dodano dan statyczn i statyczn funkcj skadow. Listing 19.7. Uycie danych statycznych i funkcji skadowych we wzorcach
0: 1: 2: #include <iostream> using namespace std; Usunito: wzorca

Usunito: e Usunito: wzorcach

3: const int DefaultSize = 3; 4: 5: // Standardowa klasa do umieszczania w tablicach 6: class Animal 7: { 8: public: 9: // konstruktory 10: Animal(int); 11: Animal(); 12: ~Animal(); 13: 14: // akcesory 15: int GetWeight() const { return itsWeight; } 16: void SetWeight(int theWeight) { itsWeight = theWeight; } 17: 18: // zaprzyjanione operatory 19: friend ostream& operator<< (ostream&, const Animal&); 20: 21: private: 22: int itsWeight; 23: }; 24: 25: // operator ekstrakcji dla wypisywania wartoci obiektu klasy Animals 26: ostream& operator<< 27: (ostream& theStream, const Animal& theAnimal) 28: { 29: theStream << theAnimal.GetWeight(); 30: return theStream; 31: } 32: 33: Animal::Animal(int weight): 34: itsWeight(weight) 35: { 36: //cout << "Animal(int) "; 37: } 38: 39: Animal::Animal(): 40: itsWeight(0) 41: { 42: //cout << "Animal() "; 43: } 44: 45: Animal::~Animal() 46: { 47: //cout << "Destruktor klasy Animal..."; 48: } 49: 50: template <class T> // deklaruje wzorzec i parametr 51: class Array // parametryzowana klasa 52: { 53: public: 54: // konstruktory 55: Array(int itsSize = DefaultSize); 56: Array(const Array &rhs); 57: ~Array() { delete [] pType; itsNumberArrays--; } 58: 59: // operatory 60: Array& operator=(const Array&); 61: T& operator[](int offSet) { return pType[offSet]; } 62: const T& operator[](int offSet) const

63: { return pType[offSet]; } 64: // akcesory 65: int GetSize() const { return itsSize; } 66: static int GetNumberArrays() { return itsNumberArrays; } 67: 68: // funkcja zaprzyjaniona 69: friend ostream& operator<< (ostream&, const Array<T>&); 70: 71: private: 72: T *pType; 73: int itsSize; 74: static int itsNumberArrays; 75: }; 76: 77: template <class T> 78: int Array<T>::itsNumberArrays = 0; 79: 80: template <class T> 81: Array<T>::Array(int size = DefaultSize): 82: itsSize(size) 83: { 84: pType = new T[size]; 85: for (int i = 0; i<size; i++) 86: pType[i] = (T)0; 87: itsNumberArrays++; 88: } 89: 90: template <class T> 91: Array<T>& Array<T>::operator=(const Array &rhs) 92: { 93: if (this == &rhs) 94: return *this; 95: delete [] pType; 96: itsSize = rhs.GetSize(); 97: pType = new T[itsSize]; 98: for (int i = 0; i<itsSize; i++) 99: pType[i] = rhs[i]; 100: } 101: 102: template <class T> 103: Array<T>::Array(const Array &rhs) 104: { 105: itsSize = rhs.GetSize(); 106: pType = new T[itsSize]; 107: for (int i = 0; i<itsSize; i++) 108: pType[i] = rhs[i]; 109: itsNumberArrays++; 110: } 111: 112: template <class T> 113: ostream& operator<< (ostream& output, const Array<T>& theArray) 114: { 115: for (int i = 0; i<theArray.GetSize(); i++) 116: output << "[" << i << "] " << theArray[i] << endl; 117: return output; 118: } 119: 120: int main() 121: { 122: cout << Array<int>::GetNumberArrays() << " tablic typu int\n";

123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: int\n"; 135: 136: 137: 138: 139: 140: int\n"; 141: 142: 143: 144: }

cout << Array<Animal>::GetNumberArrays(); cout << " tablic typu Animal\n\n"; Array<int> intArray; Array<Animal> animalArray; cout << intArray.GetNumberArrays() << " tablic typu int\n"; cout << animalArray.GetNumberArrays(); cout << " tablic typu Animal\n\n"; Array<int> *pIntArray = new Array<int>; cout << Array<int>::GetNumberArrays() << " tablic typu cout << Array<Animal>::GetNumberArrays(); cout << " tablic typu Animal\n\n"; delete pIntArray; cout << Array<int>::GetNumberArrays() << " tablic typu cout << Array<Animal>::GetNumberArrays(); cout << " tablic typu Animal\n\n"; return 0;

Wynik
0 tablic typu int 0 tablic typu Animal 1 tablic typu int 1 tablic typu Animal 2 tablic typu int 1 tablic typu Animal 1 tablic typu int 1 tablic typu Animal

Analiza Do klasy Array dodano w linii 74. statyczn zmienn tsNumberArrays, a poniewa ta dana jest prywatna, w linii 66. dodano take publiczny akcesor GetNumberArrays(). Inicjalizacja tej danej statycznej odbywa si z uyciem penej kwalifikacji wzorca, co pokazuj linie 77. i 78. Konstruktory oraz destruktor klasy Array modyfikuj warto tej zmiennej tak aby zawsze zawieraa poprawn ilo istniejcych tablic. Dostp do skadowych statycznych we wzorcu odbywa si tak samo, jak dostp do skadowych statycznych we wszystkich innych klasach: poprzez istniejcy obiekt (jak pokazano w liniach 134. i 135.) lub z uyciem penej specyfikacji klasy (co pokazano w liniach 128. i 129.). Zwr uwag, e odwoujc si do statycznej danej musisz uy tablicy waciwego typu. Dla kadego typu tablicy istnieje jedna zmienna statyczna.
Usunito: wzorca

Usunito: e Usunito: wzorcu

TAK W razie potrzeby moesz uy skadowych statycznych we wzorcach. Specjalizuj dziaanie wzorca, przesaniajc funkcje wzorcowe okrelonym typem. Uywaj parametrw w funkcjach wzorcowych, aby zawzi ich egzemplarze tak, aby byy bezpieczne (ze wzgldu na typy).

NIE
Usunito: e wzorcach Usunito: wzorca Usunito: wzorca Usunito: dla Usunito: ego Usunito: u Usunito: wzorca Usunito: typw

Standardowa biblioteka wzorcw


Nowoci w jzyku C++ jest zaadoptowanie Standardowej Biblioteki Typw (STL, Standard Template Library). Obecnie biblioteka ta jest obsugiwana we wszystkich waniejszych kompilatorach. STL jest bibliotek klas kontenerw opartych na wzorcach, obejmuje ona wektory, listy, kolejki i stosy. STL zawiera take kilka standardowych algorytmw, takich jak sortowanie i wyszukiwanie. STL jest alternatyw dla ponownego wymylania koa, przynajmniej w przypadku wymienionych tu standardowych zastosowa. Biblioteka STL jest dokadnie przetestowana, zapewnia wysok wydajno i jest darmowa. Ponadto, STL nadaje si do ponownego wykorzystania. Gdy zrozumiesz ju, jak dziaa kontener STL, moesz go wykorzystywa we wszystkich swoich programach, nie trudzc si za kadym razem nad tworzeniem go od nowa.
Usunito: Type Usunito: wzorcach

Usunito: ; g Usunito: zastanawiajc Usunito: jak go uy

Kontenery
Kontener jest obiektem przechowujcym inne obiekty. Biblioteka standardowa C++ zawiera seri klas kontenerw stanowicych wydajne narzdzie, pomagajce programistom C++ w obsudze standardowych zada programu. Klasy kontenerw zawarte w STL dziel si na sekwencyjne i asocjacyjne. Kontenery sekwencyjne zostay zaprojektowane w celu zapewnienia sekwencyjnego oraz swobodnego dostpu do swoich elementw. Kontenery asocjacyjne s zoptymalizowane do dostpu do swoich elementw poprzez tak zwane klucze. Tak jak w przypadku wszystkich innych komponentw biblioteki standardowej C++, bibliotek STL mona przenosi pomidzy rnymi systemami operacyjnymi. Wszystkie klasy kontenerw STL s zdefiniowane w przestrzeni nazw std.
Usunito: tyw

Kontenery sekwencyjne
Kontenery sekwencyjne, znajdujce si w standardowej bibliotece wzorcw, zapewniaj efektywny sekwencyjny dostp do listy obiektw. Biblioteka standardowa C++ zawiera trzy kontenery sekwencyjne: vector, list oraz deque.
Usunito: typw

Kontener vector
Do przechowywania elementw czsto wykorzystuje si tablice. Wszystkie elementy w tablicy maj ten sam typ i s dostpne poprzez swoje indeksy. STL zawiera klas kontenera vector (wektor), ktra zachowuje si jak tablica, lecz jest bardziej elastyczna i bezpieczniejsza w uyciu ni standardowa tablica C++.
vector jest kontenerem zoptymalizowanym w celu zapewnienia szybkiego dostpu do elementu na podstawie jego indeksu. Klasa kontenera vector jest zdefiniowana w pliku nagwkowym <vector> w przestrzeni nazw <std> (wicej informacji na temat przestrzeni nazw znajdziesz w rozdziale 18., Przestrzenie nazw). Wektor moe, w miar potrzeby, zwiksza objto. Przypumy, e stworzylimy wektor mogcy zmieci dziesi elementw. Gdy wypenimy go dziesicioma obiektami, bdzie peny. Gdy nastpnie dodamy do niego kolejny obiekt, wektor automatycznie zwikszy swoj objto tak, aby zmieci jedenasty element. Klasa vector jest zdefiniowana nastpujco:
template <class T, class A = allocator<T>> class vector { // skadowe klasy };

Pierwszy argument (class T) jest typem elementw w wektorze. Drugi argument (class A) jest klas alokatora. Alokatory s menederami pamici odpowiedzialnymi za alokacj i dealokacj pamici dla elementw przechowywanych w kontenerze. Koncepcja alokatorw i ich implementacja s bardziej skomplikowanymi zagadnieniami i wykraczaj poza zakres tej ksiki. Domylnie, elementy s tworzone za pomoc operatora new() i s zwalniane za pomoc operatora delete(). Oznacza to, e do stworzenia nowego elementu jest wywoywany domylny konstruktor klasy T. To kolejny argument przemawiajcy za tym, by jawnie definiowa domylne konstruktory dla wasnych klas. Gdy tego nie uczynimy, nie bdziemy mogli przechowywa egzemplarzy obiektw swojej klasy w standardowych kontenerach. Wektory zawierajce wartoci cakowite i wartoci zmiennoprzecinkowe mona zdefiniowa nastpujco:
vector<int> vector<float> vInts; vFloat; // wektor zawierajcy elementy typu int // wektor zawierajcy elementy typu float Usunito: ozycyjne

Zwykle mamy pewne pojcie o tym, ile elementw moe zawiera wektor. Na przykad, przypumy, e w naszej szkole maksymalna ilo uczniw w klasie nie przekracza pidziesiciu. Aby stworzy wektor uczniw w klasie, chcemy, aby by on na tyle duy, by zmieci pidziesit elementw. Klasa standardowego wektora posiada konstruktor pozwalajcy na okrelenie pocztkowej iloci elementw. Tak wic wektor dla pidziesiciu uczniw w klasie moemy zdefiniowa nastpujco:
vector<Student> MathClass(50);

Kompilator zaalokuje pami wystarczajc dla zmieszczenia pidziesiciu uczniw; kady element zostanie stworzony za pomoc domylnego konstruktora Student:: Student(). Aktualna ilo przechowywanych w wektorze elementw moe zosta odczytana funkcj size() (rozmiar). W tym przykadzie vStudent.size() zwrci warto 50. Inna funkcja skadowa, capacity() (pojemno), informuje nas dokadnie, ile elementw moe zosta zmieszczonych w tablicy, zanim konieczne bdzie zwikszenie jej rozmiaru. Zajmiemy si tym pniej. Mwimy, e wektor jest pusty, gdy nie zawiera on adnego elementu, tj. gdy jego rozmiar wynosi zero. Aby uatwi sprawdzenie, czy wektor jest pusty, klasa vector posiada funkcj skadow empty(), ktra zwraca warto true wtedy, gdy wektor jest pusty. Aby przypisa obiekt typu Student o nazwie Harry do elementu wektora MathClass, moemy uy operatora indeksu []:
MathClass[5] = Harry;

Indeksy s liczone od zera. Jak by moe zauwaye, do przypisania obiektu Harry do szstego elementu wektora zosta uyty przeciony operator= klasy Student. Aby sprawdzi wiek Harryego, moemy odwoa si do jego danych, uywajc:
MathClass[5].GetAge();

Jak ju wspominaem, gdy do wektora zostanie dodanych wicej elementw ni moe zmieci, wektor automatycznie zwiksza swoj pojemno. Przypumy na przykad, e jedna z klas staa si tak popularna, e ilo jej uczniw przekroczya pidziesit. C, moe w przypadku klasy o profilu matematycznym jest to mao prawdopodobne, ale kto wie, wszystko moe si zdarzy. Gdy do klasy MathClass zostanie dodana pidziesita pierwsza osoba, Sally, kompilator rozszerzy tablic. Element moe zosta dodany do wektora na kilka sposobw; jednym z nich jest uycie funkcji
push_back():
MathClass.push_back(Sally);

Ta funkcja skadowa docza nowy obiekt Student::Sally do koca wektora MathClass. W tym momencie w wektorze MathClass mamy pidziesit jeden elementw, a Sally znajduje si w elemencie MathClass[50]. Aby ta funkcja moga dziaa, nasza klasa Student musi posiada zdefiniowany konstruktor kopiujcy. W przeciwnym razie funkcja push_back() nie bdzie moga stworzy kopii obiektu Sally. STL nie okrela maksymalnej ilo elementw w wektorze; decyzj t moe podj producent kompilatora. Klasa vector posiada jednak specjaln funkcj, zwracajc warto tej magicznej dla twojego kompilatora liczby; jest ni funkcja max_size(). Listing 19.8 demonstruje omwione dotd skadowe klasy vector. W listingu tym zostaa uyta standardowa klasa string, uatwiajca posugiwanie si acuchami znakw. Wicej informacji na temat tej klasy znajdziesz w dokumentacji swojego kompilatora. Listing 19.8. Tworzenie wektora i dostp do jego elementw
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: #include <iostream> #include <string> #include <vector> using namespace std; class Student { public: Student(); Student(const string& name, const int age); Student(const Student& rhs); ~Student(); void SetName(const string& name); string GetName() const; void SetAge(const int age); int GetAge() const; Student& operator=(const Student& rhs); private: string itsName; int itsAge; }; Student::Student() : itsName("Nowy uczen"), itsAge(16) {} Student::Student(const string& name, const int age) : itsName(name), itsAge(age) {} Student::Student(const Student& rhs) : itsName(rhs.GetName()), itsAge(rhs.GetAge()) {} Student::~Student() Usunito: i

Usunito:

38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98:

{} void Student::SetName(const string& name) { itsName = name; } string Student::GetName() const { return itsName; } void Student::SetAge(const int age) { itsAge = age; } int Student::GetAge() const { return itsAge; } Student& Student::operator=(const Student& rhs) { itsName = rhs.GetName(); itsAge = rhs.GetAge(); return *this; } ostream& operator<<(ostream& os, const Student& rhs) { os << rhs.GetName() << " ma " << rhs.GetAge() << " lat(a)"; return os; } template<class T> // wywietla waciwoci wektora void ShowVector(const vector<T>& v); typedef vector<Student> int main() { Student Student Student Student SchoolClass;

Harry; Sally("Sally", 15); Bill("Bill", 17); Peter("Peter", 16);

SchoolClass EmptyClass; cout << "EmptyClass:\n"; ShowVector(EmptyClass); SchoolClass GrowingClass(3); cout << "GrowingClass(3):\n"; ShowVector(GrowingClass); GrowingClass[0] = Harry; GrowingClass[1] = Sally; GrowingClass[2] = Bill; cout << "GrowingClass(3) po przypisaniu uczniow:\n"; ShowVector(GrowingClass);

99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128:

GrowingClass.push_back(Peter); cout << "GrowingClass() po dodaniu czwartego ucznia:\n"; ShowVector(GrowingClass); GrowingClass[0].SetName("Harry"); GrowingClass[0].SetAge(18); cout << "GrowingClass() po wywolaniu funkcji Set\n:"; ShowVector(GrowingClass); return 0; } // // Wywietla waciwoci wektora // template<class T> void ShowVector(const vector<T>& v) { cout << "max_size() = " << v.max_size(); cout << "\tsize() = " << v.size(); cout << "\tcapacity() = " << v.capacity(); cout << "\t" << (v.empty()? "pusta": "nie pusta"); cout << "\n"; for (int i = 0; i < v.size(); ++i) cout << v[i] << "\n"; cout << endl; }

Wynik
EmptyClass: max_size() = 214748364 GrowingClass(3): max_size() = 214748364 pusta Nowy uczen ma 16 lat Nowy uczen ma 16 lat Nowy uczen ma 16 lat size() = 0 size() = 3 capacity() = 0 capacity() = 3 pusta nie

GrowingClass(3) po przypisaniu uczniow: max_size() = 214748364 size() = 3 capacity() = 3 pusta Nowy uczen ma 16 lat Sally ma 15 lat Bill ma 17 lat GrowingClass() po dodaniu czwartego ucznia: max_size() = 214748364 size() = 4 capacity() = 6 pusta Nowy uczen ma 16 lat Sally ma 15 lat Bill ma 17 lat Peter ma 16 lat

nie

nie

GrowingClass() po wywolaniu funkcji Set max_size() = 214748364 size() = 4 capacity() = 6 pusta Harry ma 18 lat Sally ma 15 lat Bill ma 17 lat Peter ma 16 lat

nie

Analiza Klasa Student jest zdefiniowana w liniach od 5. do 23. Implementacje jej funkcji skadowych s zawarte w liniach od 25. do 65. Klasa ta jest prosta i przyjazna dla kontenerw. Z powodw omawianych wczeniej, zdefiniowalimy w niej domylny konstruktor, konstruktor kopiujcy oraz przeciony operator przypisania. Zwr uwag, e jej zmienna skadowa itsName zostaa zdefiniowana jako egzemplarz klasy string jzyka C++. Jak wida, z acuchami C++ pracuje si duo atwiej ni z acuchami char* w stylu jzyka C. W liniach 73. i 75. jest zadeklarowana funkcja wzorcowa ShowVector(); jej definicja znajduje si w liniach od 115. do 128. Demonstruje ona uycie niektrych funkcji skadowych wektora: max_size(), size(), capacity() oraz empty(). Jak wida w wynikach, maksymalna ilo obiektw typu Student, jak moe pomieci wektor w Visual C++, wynosi 214 748 364. Dla elementw innego typu warto ta moe by inna. Na przykad, wektor wartoci typu int moe zawiera do 1 073 741 823 elementw. Jeli uywasz innych kompilatorw, wartoci te mog by inne. W liniach 124. i 125. przechodzimy przez kady element w wektorze i wywietlamy jego warto, uywajc przecionego operatora wstawiania <<, zdefiniowanego w liniach od 67. do 71. W liniach od 81. do 84. tworzonych jest czterech uczniw. W linii 86. zostaje zdefiniowany pusty wektor o nazwie EmptyClass (pusta klasa). Jest on tworzony za pomoc domylnego konstruktora klasy vector. Gdy wektor jest tworzony w ten sposb, kompilator nie alokuje dla niego adnej pamici. Jak wida w wynikach wypisywanych przez funkcj ShowVector(EmptyClass), jego rozmiar i pojemno wynosz zero. W linii 90. zostaje zdefiniowany wektor mogcy zmieci trzech uczniw. Jak mona byo oczekiwa, zarwno jego rozmiar, jak i pojemno maj warto 3. Elementy tego wektora (GrowingClass) s wypeniane obiektami w liniach od 94. do 96.; wykorzystujemy do tego operator []. W linii 100. do wektora jest dodawany czwarty ucze, Peter. To powoduje zwikszenie rozmiaru wektora do czterech. Co ciekawe, jego objto faktycznie zwiksza si do szeciu. To oznacza, e kompilator zaalokowa miejsce wystarczajce do zmieszczenia szeciu obiektw typu Student. Poniewa wektory musz by alokowane w cigym bloku pamici, rozszerzanie ich wymaga wykonania caego zestawu operacji. Najpierw alokowany jest nowy blok pamici, wystarczajcy do zmieszczenia wszystkich czterech obiektw Student. Nastpnie do nowo zaalokowanej pamici s kopiowane trzy elementy; po trzecim elemencie zostaje doczony czwarty element. Na koniec zostaje zwolniony blok pamici zajmowany pierwotnie. Gdy w wektorze znajduje si wiele elementw, ten proces alokacji i dealokacji moe zajmowa duo czasu. W zwizku z tym

Usunito: i

Usunito: wzorca

Usunito: n

kompilator przyjmuje strategi redukujc czstotliwo przeprowadzania tak kosztownych operacji. W tym przykadzie, gdy dodamy do wektora jeden czy dwa nowe obiekty, nie bdzie potrzeby dealokowania i ponownego alokowania pamici. W liniach 104. i 105. ponownie uywamy operatora indeksu [] w celu zmiany zmiennych skadowych pierwszego elementu w wektorze GrowingClass. TAK Jeli zechcesz przechowywa egzemplarze klasy w wektorze, zdefiniuj dla tej klasy domylny konstruktor. Zdefiniuj te konstruktor kopiujcy dla takiej klasy. Zdefiniuj dla niej take przeciony operator przypisania. Klasa kontenera vector posiada jeszcze inne funkcje skadowe. Funkcja front() zwraca referencj do pierwszego elementu wektora. Funkcja back() zwraca referencj do ostatniego elementu. Funkcja at() dziaa jak operator indeksu []. Jest jednak bardziej bezpieczna, gdy sprawdza, czy przekazany jej indeks pasuje do zakresu dostpnych elementw. Jeli indeks jest poza zakresem, funkcja zgasza wyjtek out_of_range (poza zakresem; wyjtki zostan omwione w nastpnym rozdziale). Funkcja insert() wstawia jeden lub wicej elementw we wskazane miejsce w wektorze. Funkcja pop_back() usuwa z wektora ostatni element. Na koniec, funkcja remove() usuwa z wektora jeden lub wicej elementw. NIE
Usunito: bd Usunito: ne Usunito: e Usunito: i

Kontener list
Lista (list) jest kontenerem przeznaczonym do zoptymalizowania czstych operacji wstawiania i usuwania elementw. Klasa kontenera list w STL jest zdefiniowana w pliku nagwkowym <list> w przestrzeni nazw std. Klasa list jest zaimplementowana jako lista poczona podwjnie, w ktrej kady wze posiada dowizania do poprzedniego i nastpnego wza listy. Klasa list posiada wszystkie funkcje skadowe oferowane przez klas vector. Jak widzielimy w programie podsumowujcym wiadomoci w rozdziale 14., po licie moemy porusza si podajc za czami zawartymi w kadym z wzw. Zwykle takie cza s zaimplementowane za pomoc wskanikw. Standardowy kontener list wykorzystuje w tym celu mechanizm zwany iteratorem.

Iterator jest uoglnieniem wskanika. Aby uzyska wze wskazywany przez iterator, moemy wyuska go z tego iteratora. Listing 19.9 przedstawia uycie iteratorw w celu uzyskania dostpu do wzw listy. Listing 19.9. Przejcie przez list za pomoc iteratora
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: #include <iostream> #include <list> using namespace std; typedef list<int> IntegerList; int main() { IntegerList Usunito: chodzenie Usunito: y

intList;

for (int i = 1; i <= 10; ++i) intList.push_back(i * 2); for (IntegerList::const_iterator ci = intList.begin(); ci != intList.end(); ++ci) cout << *ci << " "; } return 0;

Wynik
2 4 6 8 10 12 14 16 18 20

Analiza W linii 8. zmienna intList zostaje zdefiniowana jako lista elementw typu int. W liniach 10. i 11., za pomoc funkcji push_back(), do listy zostaje dodanych dziesi dodatnich, parzystych wartoci. W liniach od 13. do 15. odwoujemy si do kadego wza listy, uywajc iteratora
const_iterator. Oznacza to, e poprzez ten iterator nie chcemy zmienia wzw. Gdybymy

chcieli zmieni wze wskazywany przez iterator, musielibymy zamiast tego uy operatora nie const:
intList::iterator

Funkcja skadowa begin() zwraca iterator wskazujcy na pierwszy wze listy. Jak wida, do przeniesienia iteratora na nastpny wze moemy uy operatora ++. Funkcja skadowa end() jest do niezwyka zwraca iterator wskazujcy o jeden wze za koniec listy. Musimy wic dopilnowa, by nasz iterator nie osiga wza wskazywanego prze end(). Iterator jest wyuskiwany tak samo jak wskanik i zwraca wskazywany przez siebie wze, co widzimy w linii 15.

Cho iteratory zostay tu przedstawione wraz z klas list, s one jednak dostarczane take przez klas vector. Oprcz funkcji przedstawionych dla klasy vector, klasa list posiada take funkcje push_front() oraz pop_front(), ktre dziaaj podobnie jak funkcje push_back() i pop_back(), z tym e nie dodaj i nie usuwaj elementu na kocu listy, ale na jej pocztku.

Kontener deque
Kontener deque przypomina podwjnie zakoczony wektor podobnie jak klasa vector, zapewnia on efektywno operacji sekwencyjnego zapisu i odczytu. Jednak oprcz tego, klasa kontenera deque optymalizuje operacje na kocach wektora. Te operacje s zaimplementowane podobnie jak w klasie kontenera list, w ktrej alokacje pamici odbywaj si tylko w przypadku nowych elementw. Ta waciwo klasy deque eliminuje potrzeb realokowania caego kontenera do nowego bloku pamici, co jest jedn z wad klasy vector. W zwizku z tym kontenery tego typu doskonale nadaj si do aplikacji, w ktrych wstawianie i usuwanie elementw dotyczy ktrego z kocw wektora i w ktrych wany jest sekwencyjny dostp do elementw. Przykadem takiej aplikacji moe by symulator zestawiania skadw pocigw, w ktrym wagony mog by doczane na pocztku lub na kocu skadu.

Stosy
Stos jest jedn ze struktur najczciej wykorzystywanych przy programowaniu komputerw. Nie zosta on jednak zaimplementowany jako osobna klasa kontenera, ale jako klasa porednia dla klasy kontenera. Klasa wzorcowa stack (stos) jest zdefiniowana w pliku nagwkowym <stack> w przestrzeni nazw std. Stos jest cigym, zaalokowanym blokiem, ktry moe si rozszerza i kurczy na jednym kocu. Elementy znajdujce si na stosie s dostpne tylko poprzez jego koniec. Podobne waciwoci zauwaylimy w kontenerach sekwencyjnych, a mianowicie w kontenerach vector i deque. W rzeczywistoci, do zaimplementowania stosu moe zosta uyty kady kontener sekwencyjny, obsugujcy operacje back(), push_back() oraz pop_back(). W przypadku stosu wikszo innych metod kontenerw nie jest potrzebna i w zwizku z tym klasa stack ich nie udostpnia. Klasa wzorca stack w STL jest zaprojektowana tak, by moga przechowywa dowolny typ obiektw, jednak by wszystkie elementy na stosie musz by tego samego typu. Stos jest struktur LIFO (last in first out, pierwszy wchodzi ostatni wychodzi). Przypomina nieco zatoczon wind: pierwsza osoba wchodzca do windy przesuwa si w stron ciany, za ostatnia osoba stoi tu przy drzwiach. Gdy winda dotrze na dane pitro, wtedy ostatni wychodzc osob jest ta, ktra wesza do windy pierwsza. Jeli ktra z osb chce wysi na ktrym z wczeniejszych piter, musz wyj wszystkie te osoby, ktre stoj pomidzy ni a drzwiami windy i dopiero potem mog wej z powrotem.
Usunito: wzorca

Zgodnie z konwencj, otwarty koniec nazywa si szczytem stosu, za operacje wykonywane na stosie nazywane s odkadaniem (push) i zdejmowaniem (pop). Te konwencjonalne okrelenia przeja klasa stack.

UWAGA Klasa stack w STL nie przypomina mechanizmu stosu uywanego przez kompilatory i systemy operacyjne, w ktrych stosy mog zawiera obiekty rnych rodzajw. Jednak sama zasada dziaania stosu jest bardzo podobna.

Kolejki
Kolejka jest kolejn, powszechnie wykorzystywan w programowaniu struktur. Elementy s dodawane do jednego koca kolejki, a pobierane s z drugiego. Moemy zastosowa prost analogi: stos przypomina stos talerzy w restauracji. Talerze dodaje si do stosu, kadc je zawsze na grze; zabiera si je take z gry (tzn. pierwszy zostaje zabrany talerz odoony na stos ostatnio). Kolejka przypomina kolejk w sklepie. Stajesz na kocu kolejki i wychodzisz na jej pocztku. Nazywa si to struktur FIFO (first in first out, pierwszy wchodzi pierwszy wychodzi); stos jest struktur LIFO. Podobnie jak stos, kolejka jest zaimplementowana jako klasa porednia dla kontenera. Kontener musi obsugiwa operacje front(), back(), push_back() oraz pop_front().

Kontenery asocjacyjne
O ile kontenery sekwencyjne s zaprojektowane do sekwencyjnego i swobodnego dostpu do elementw z uyciem indeksu lub operatora, o tyle kontenery asocjacyjne s zaprojektowane dla szybkiego swobodnego dostpu z wykorzystaniem kluczy. Biblioteka standardowa C++ zawiera cztery kontenery asocjacyjne: map, multimap, set oraz multiset.

Kontener map
Widzielimy e wektor jest jakby rozszerzon wersj tablicy. Posiada wszystkie charakterystyki tablicy oraz kilka dodatkowych waciwoci. Niestety, wektory przejy take jedn z powaniejszych wad tablic, a mianowicie brak mechanizmu swobodnego dostpu z uyciem kluczy innych ni indeksy lub iteratory. Z drugiej strony, kontenery asocjacyjne zapewniaj wanie szybki swobodny dostp z wykorzystaniem kluczy.

Biblioteka standardowa C++ zawiera cztery kontenery asocjacyjne: map (mapa), multimap (multimapa), set (zestaw) oraz multiset (multizestaw). Nastpny listing pokazuje sposb zaimplementowania przy pomocy mapy naszego szkolnego przykadu z listingu 19.8. Listing 19.8. Klasa kontenera mapy
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: #include <iostream> #include <string> #include <map> using namespace std; class Student { public: Student(); Student(const string& name, const int age); Student(const Student& rhs); ~Student(); void SetName(const string& name); string GetName() const; void SetAge(const int age); int GetAge() const; Student& operator=(const Student& rhs); private: string itsName; int itsAge; }; Student::Student() : itsName("Nowy uczen"), itsAge(16) {} Student::Student(const string& name, const int age) : itsName(name), itsAge(age) {} Student::Student(const Student& rhs) : itsName(rhs.GetName()), itsAge(rhs.GetAge()) {} Student::~Student() {} void Student::SetName(const string& name) { itsName = name; } string Student::GetName() const { return itsName; } void Student::SetAge(const int age) { itsAge = age; }

54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: mapy 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111:

int Student::GetAge() const { return itsAge; } Student& Student::operator=(const Student& rhs) { itsName = rhs.GetName(); itsAge = rhs.GetAge(); return *this; } ostream& operator<<(ostream& os, const Student& rhs) { os << rhs.GetName() << " ma " << rhs.GetAge() << " lat"; return os; } template<class T, class A> void ShowMap(const map<T, A>& v); typedef map<string, Student> int main() { Student Student Student Student // wywietla waciwoci

SchoolClass;

Harry("Harry", 18); Sally("Sally", 15); Bill("Bill", 17); Peter("Peter", 16);

SchoolClass MathClass; MathClass[Harry.GetName()] = Harry; MathClass[Sally.GetName()] = Sally; MathClass[Bill.GetName()] = Bill; MathClass[Peter.GetName()] = Peter; cout << "MathClass:\n"; ShowMap(MathClass); cout << "Wiemy ze " << MathClass["Bill"].GetName() << " ma " << MathClass["Bill"].GetAge() << " lat\n"; return 0; } // // wywietla waciwoci mapy // template<class T, class A> void ShowMap(const map<T, A>& v) { for (map<T, A>::const_iterator ci = v.begin(); ci != v.end(); ++ci) cout << ci->first << ": " << ci->second << "\n"; cout << endl; }

Wynik
MathClass: Bill: Bill ma 17 lat Harry: Harry ma 18 lat Peter: Peter ma 16 lat Sally: Sally ma 15 lat Wiemy ze Bill ma 17 lat

Analiza W linii 2. doczylimy plik nagwkowy <map>, gdy bdziemy uywa standardowej klasy kontenera map. W zwizku z tym definiujemy funkcj wzorcow ShowMap, wywietlajc elementy w mapie. W linii 76. typ SchoolClass jest definiowany jako mapa elementw; kady z nich stanowi par skadajc si z klucza i wartoci. Pierwsza warto w parze jest kluczem. W naszej klasie SchoolClass jako nazw kluczy uylimy imion uczniw, ktre s acuchami znakw. Klucze elementw w kontenerze mapy musz by unikalne, tzn. dwa elementy nie mog mie tego samego klucza. Drug wartoci w parze jest sam obiekt; w tym przykadzie jest to obiekt klasy Student. Typ pary danych jest w STL zaimplementowany jako struktura zawierajca dwie skadowe, mianowicie first (pierwsza) oraz second (druga). Moemy uywa tych skadowych w celu uzyskania dostpu do klucza i wartoci zawartych w wle. Pominiemy funkcj main() i najpierw przyjrzymy si funkcji ShowMap(). Ta funkcja, odwoujc si do obiektu mapy, uywa iteratora const. W linii 108. ci->first wskazuje klucz (nazw ucznia), a ci->second wskazuje warto (obiekt klasy Student). Wrmy do linii od 80. do 83. S w nich tworzone cztery obiekty klasy Student. W linii 85. zostaje zdefiniowany obiekt MathClass, bdcy egzemplarzem naszego kontenera SchoolClass. W liniach od 86. do 89. dodajemy do kontenera MathClass czterech uczniw, korzystajc przy tym z nastpujcej skadni:
obiekt_mapy[warto_klucza] = warto_obiektu; Usunito: wzorca

Usunito: o

W celu dodania pary (klucz, warto) do mapy moglibymy take uy funkcji push_back() lub insert(); wicej szczegw na ten temat znajdziesz w dokumentacji swojego kompilatora. Po dodaniu do mapy wszystkich uczniw, moemy odwoywa si do nich poprzez ich wartoci kluczy. W liniach 94. i 95. uywamy MathClass["Bill"], aby odwoa si do danych Billa.

Inne kontenery asocjacyjne


Klasa kontenera multimap jest klas map, ktra nie wymaga, by klucze byy unikalne. T sam warto klucza moe mie wicej ni jeden element. Klasa kontenera set take przypomina klas mapy; jednak jej elementami nie s pary (klucz, warto), lecz same klucze.

Llasa kontenera multiset jest klas set pozwalajc na wystpowanie powielonych kluczy.

Klasy algorytmw
Kontener to dobre miejsce do przechowywania sekwencji elementw. Wszystkie kontenery standardowe definiuj operacje manipulowania kontenerami i ich elementami. Implementowanie wszystkich tych operacji we wasnych sekwencjach moe by pracochonne i podatne na bdy. Poniewa w przypadku wikszoci sekwencji wykonywane operacje s takie same, zastosowanie zestawu oglnych algorytmw moe zredukowa potrzeb tworzenia wasnych operacji dla kadego nowego kontenera. Biblioteka standardowa zawiera okoo szedziesiciu standardowych algorytmw wykonujcych wikszo podstawowych i powszechnie uywanych operacji na kontenerach. Te standardowe algorytmy s zdefiniowane w pliku nagwkowym <algorithm> w przestrzeni nazw std. Aby zrozumie, jak dziaaj standardowe algorytmy, musimy pozna pojcie obiektw funkcyjnych. Obiekt funkcyjny jest egzemplarzem klasy definiujcej przeciony operator (). W zwizku z tym moe on by wywoany jako funkcja. Listing 19.11 demonstruje obiekt funkcyjny. Listing 19.11. Obiekt funkcji
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: #include <iostream> using namespace std; template<class T> class Print { public: void operator()(const T& t) { cout << t << " "; } }; int main() { Print<int> DoPrint; for (int i = 0; i < 5; ++i) DoPrint(i); return 0; } Usunito: koncept Usunito: ji Usunito: ji Usunito: ji

Wynik
0 1 2 3 4

Analiza

W liniach od 3. do 10. zostaa zdefiniowana klasa wzorcowa Print. Przeciony operator () zdefiniowany w liniach od 6. do 9. przyjmuje obiekt i wypisuje go na standardowym wyjciu. W linii 14. obiekt DoPrint jest definiowany jako egzemplarz klasy Print. Moemy wic uy obiektu DoPrint w celu wypisania dowolnej wartoci cakowitej tak, jak uylibymy funkcji, co pokazano w linii 16.

Usunito: wzorca

Bezmutacyjne operacje sekwencyjne


Algorytmy nie zmieniajce sekwencji to operacje, ktre nie zmieniaj elementw w sekwencji. Obejmuj one operatory takie, jak for_each() (dla kadego) oraz find() (znajd), search() (szukaj), count() (policz) itd. Listing 19.12 przedstawia sposb uycia obiektu funkcyjnego oraz algorytmu for_each() w celu wypisania elementw wektora. Listing 19.12. Uycie algorytmu for_each()
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: #include <iostream> #include <vector> #include <algorithm> using namespace std; template<class T> class Print { public: void operator()(const T& t) { cout << t << " "; } }; int main() { Print<int> vector<int>

Usunito: Algorytmy nie zmieniajce Usunito: ji

Usunito: ji

DoPrint; vInt(5);

for (int i = 0; i < 5; ++i) vInt[i] = i * 3; cout << "for_each()\n"; for_each(vInt.begin(), vInt.end(), DoPrint); cout << "\n"; return 0; }

Wynik
for_each() 0 3 6 9 12

Analiza

Zwr uwag, e algorytmy standardowe w C++ s zdefiniowane w pliku nagwkowym <algorithm>, wic musimy doczy go do kodu programu. Wikszo programu powinna by atwo zrozumiaa. W linii 24. zostaje wywoana funkcja for_each() przechodzca przez wszystkie elementy w wektorze vInt. Dla kadego elementu wywouje ona obiekt funkcji DoPrint i przekazuje element do funkcji DoPrint.operator(). To powoduje, e warto elementu zostaje wypisana na ekranie.
Usunito: A

Mutacyjne algorytmy sekwencyjne


Mutacyjne algorytmy sekwencyjne wykonuj operacje zmieniajce elementy w sekwencji. Nale do nich operacje wypeniajce sekwencje lub zmieniajce kolejno zawartych w nich elementw. Listing 19.13 przedstawia algorytm fill() (wypenij). Listing 19.13. Algorytm zmieniajcy sekwencj
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: #include <iostream> #include <vector> #include <algorithm> using namespace std; template<class T> class Print { public: void operator()(const T& t) { cout << t << " "; } }; int main() { Print<int> vector<int>

Usunito: zmieniajce Usunito: A Usunito: zmieniajce

DoPrint; vInt(10);

fill(vInt.begin(), vInt.begin() + 5, 1); fill(vInt.begin() + 5, vInt.end(), 2); for_each(vInt.begin(), vInt.end(), DoPrint); cout << "\n\n"; return 0; }

Wynik
1 1 1 1 1 2 2 2 2 2

Analiza

Jedyn now zawartoci tego listingu s linie 20. i 21., w ktrych jest uywany algrytm fill(). Algorytm ten wypenia elementy sekwencji dan wartoci. W linii 20. przypisuje warto cakowit 1 do piciu pierwszych elementw wektora vInt. W linii 21. piciu ostatnim elementom wektora jest przypisywana warto 2.

Rozdzia 20. Wyjtki i obsuga bdw


Kod zawartych w tej ksice przykadw zosta stworzony w celach ilustracji. Aby nie odwraca twojej uwagi od prezentowanych tutaj zagadnie, nie zastosowano w nich adnych mechanizmw obsugi bdw. Jednak w prawdziwych programach obsuga bdw jest wana. Z tego rozdziau dowiesz si: czym s wyjtki, jak uywa wyjtkw i jakie s rezultaty ich dziaania, jak budowa hierarchie wyjtkw, Jak traktowa wyjtki w stosunku do obsugi bdw, czym jest debugger.

Pluskwy, bdy, pomyki i psujcy si kod


Wszystkie programy zawieraj pluskwy (czyli bdy). Im wikszy program, tym wicej pluskiew i wiele z nich przedostaje si do ostatecznej jego wersji. Tworzenie stabilnych, wolnych od pluskiew programw powinno by priorytetem kadego, kto powanie myli o programowaniu. Najwaniejszym problemem przy tworzeniu oprogramowania jest bdny, niestabilny kod. W wielu powanych przedsiwziciach informatycznych najwikszym wydatkiem jest jego testowanie i poprawianie. Kto, kto wpadnie na pomys, jak tworzy niskim kosztem i na czas dobre, solidne i odporne programy, zrewolucjonizuje przemys oprogramowania. Kopoty z programem mog by powodowane przez kilka rnych rodzajw bdw. Pierwszym z nich jest bdna logika: program robi to, co ma robi, ale algorytm nie zosta waciwie przemylany. Drugim rodzajem bdw jest syntaktyka: niewaciwa konstrukcja, funkcja, czy struktura. Te dwa rodzaje bdw wystpuj najczciej i szuka ich wikszo programistw.

Badania i dowiadczenie programistw wykazay, e im pniej zostanie wykryty bd, tym bardziej kosztowne staje si jego usunicie. Najmniej kosztowne bdy i pomyki to te, ktrych uda si unikn. Kolejne mao kosztowne bdy to bdy wykrywane przez kompilator. Standardy jzyka C++ wymuszaj na kompilatorze wychwytywanie coraz wikszej iloci bdw ju podczas kompilacji. Bdy, ktre zostay wkompilowane i zostay wychwycone przy pierwszym tecie tj. te, ktre niezmiennie powoduj zaamanie programu s mniej kosztowne w wyszukaniu i poprawieniu ni bdy, ktre powoduj zaamanie programu dopiero po pewnym czasie. Czstszym problemem ni bdy logiczne lub syntaktyczne jest wraliwo programu: dziaa on poprawnie, gdy uytkownik wpisuje liczb tam, gdzie powinien j wpisa, lecz zaamuje si, gdy uytkownik wpisze litery (zamiast liczby). Inne programy zaamuj si po wyczerpaniu si pamici, gdy dyskietka zostanie wyjta ze stacji lub gdy modem zerwie poczenie. Aby walczy z tego rodzaju bdami, programici staraj si uodporni swoje programy. Odporny program potrafi obsuy wszystko, co moe si wydarzy podczas jego dziaania, od dziwnych danych wprowadzanych przez uytkownika po nagy brak pamici. Naley dokona rozrnienia pomidzy pluskwami, ktre powstay, poniewa pomyli si programista; bdami logicznymi, ktre powstay, poniewa programista nie zrozumia zagadnienia lub nie wie, jak sobie z nim poradzi, oraz wyjtkami, ktre powstaj, gdy wystpi niezwyky, cho przewidywalny problem, taki jak wyczerpanie si zasobw (pamici czy miejsca na dysku).

Usunito: a

Wyjtki
Sytuacji wyjtkowych nie da si wyeliminowa; mona si jedynie na nie przygotowa. Uytkownikom programw od czasu do czasu koczy si pami i jedynym zagadnieniem pozostaje to, co twj program zrobi w takim przypadku. Masz wtedy do wyboru: zaamanie programu, poinformowanie uytkownika i zamknicie programu, poinformowanie uytkownika i pozwolenie mu na zwolnienie dodatkowej pamici i podjcie ponownej prby, podjcie odpowiednich dziaa i kontynuowanie pracy bez niepokojenia uytkownika.

Cho nie zawsze jest konieczne (a czasem nawet niewskazane) automatyczne i niewidoczne obsugiwanie wszystkich wyjtkowych sytuacji, jednak trzeba co zrobi, aby nie pozwoli na zaamanie si programu. Obsuga wyjtkw w C++ dostarcza bezpiecznej (ze wzgldu na typ), zintegrowanej metody reagowania na niezwyke, cho przewidywalne sytuacje, ktre pojawiaj si podczas dziaania programu.

Wyjtki
W C++ wyjtek jest obiektem, ktry jest przekazywany z obszaru kodu, w ktrym wystpi problem, do tej czci kodu, ktra odpowiada za jego obsuenie. Typ wyjtku okrela obszar kodu, ktry ma obsuy problem, za zawarto zgoszonego obiektu, o ile istnieje, moe posuy do dokadniejszego poinformowania uytkownika. Reguy rzdzce wyjtkami s bardzo proste: rzeczywista alokacja zasobw (na przykad alokacja pamici czy blokowanie pliku) zwykle odbywa si na bardzo niskim poziomie programu, logika okrelajca, co naley zrobi, gdy operacja si nie powiedzie, pami nie moe zosta zaalokowana czy plik nie moe zosta zablokowany, zwykle znajduje si na duo wyszym poziomie programu, wraz z kodem wsppracujcym z uytkownikiem, wyjtki stanowi ekspresow ciek od kodu alokujcego zasoby do kodu mogcego obsuy sytuacj wyjtkow. Gdy pomidzy nimi wystpuj warstwy funkcji interwencyjnych, daje im si moliwo uporzdkowania zaalokowanej pamici. Nie wymaga si od nich jednak, aby zawieray wycznie kod, ktrego przeznaczeniem jest przekazywanie informacji o bdzie dalej.
Usunito: inne Usunito: maj Usunito: musz jednak Usunito: Usunito: u

Jak uywane s wyjtki


Obszary kodu, ktre mog powodowa problem, ujmowane s w bloki try (sprbuj). Na przykad:
try { SomeDangerousFunction(); // potencjalnie niebezpieczna funkcja }

Usunito: jedynym Usunito: wyej

Z kolei bloki catch (wychwy) obsuguj wyjtki zgoszone w bloku try. Na przykad:
try { SomeDangerousFunction(); } catch(OutOfMemory) { // podejmij jakie dziaania przeciwdziaajce brakowi pamici } catch(FileNotFound)

{ // podejmij czynnoci przeciwdziaajce brakowi pliku na dysku }

Podstawowe etapy obsugiwania wyjtkw to: 1. Zidentyfikowanie tych obszarw programu, w ktrych zaczynaj si operacje mogce powodowa wyjtek i umieszczenie ich w blokach try. 2. Stworzenie blokw catch wychwytujcych zgaszane wyjtki, porzdkujce zaalokowan pami i ewentualnie informujce uytkownika. Listing 20.1 przedstawia uycie zarwno blokw try, jak i catch. Wyjtki s obiektami uywanymi do przekazywania informacji o problemie. Blok try jest blokiem ujtym w nawiasy klamrowe, wewntrz ktrego mog by zgaszane wyjtki. Blok catch jest blokiem wystpujcym bezporednio po bloku try; s w nim obsugiwane zgoszone wyjtki. Gdy zostanie zgoszony wyjtek, sterowanie przechodzi do waciwego bloku catch nastpujcego po biecym bloku try.

UWAGA Niektre bardzo stare kompilatory nie obsuguj wyjtkw. Wyjtki s jednak czci standardu ANSI C++ i wszystkie najnowsze wersje kompilatorw w peni je obsuguj. Jeli posiadasz starszy kompilator, nie bdziesz mg skompilowa i uruchomi przykadw zawartych w tym rozdziale. Jednak mimo to powiniene przeczyta ca jego zawarto i wrci do tego materiau pniej, gdy zdobdziesz nowsz wersj kompilatora.

Listing 20.1. Zgaszanie wyjtku


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: #include <iostream> using namespace std; const int DefaultSize = 10; class Array { public: // konstruktory Array(int itsSize = DefaultSize); Array(const Array &rhs); ~Array() { delete [] pType;} // operatory Array& operator=(const Array&); int& operator[](int offSet); const int& operator[](int offSet) const; // akcesory

19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79:

int GetitsSize() const { return itsSize; } // funkcja zaprzyjaniona friend ostream& operator<< (ostream&, const Array&); class xBoundary {}; private: int *pType; int itsSize; }; Array::Array(int size): itsSize(size) { pType = new int[size]; for (int i = 0; i<size; i++) pType[i] = 0; } Array& Array::operator=(const Array &rhs) { if (this == &rhs) return *this; delete [] pType; itsSize = rhs.GetitsSize(); pType = new int[itsSize]; for (int i = 0; i<itsSize; i++) pType[i] = rhs[i]; return *this; } Array::Array(const Array &rhs) { itsSize = rhs.GetitsSize(); pType = new int[itsSize]; for (int i = 0; i<itsSize; i++) pType[i] = rhs[i]; } int& Array::operator[](int offSet) { int size = GetitsSize(); if (offSet >= 0 && offSet < GetitsSize()) return pType[offSet]; throw xBoundary(); return pType[0]; // ucisza MSC } const int& Array::operator[](int offSet) const { int mysize = GetitsSize(); if (offSet >= 0 && offSet < GetitsSize()) return pType[offSet]; throw xBoundary(); return pType[0]; // ucisza MSC } // definiuje klas wyjtku

80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105:

ostream& operator<< (ostream& output, const Array& theArray) { for (int i = 0; i<theArray.GetitsSize(); i++) output << "[" << i << "] " << theArray[i] << endl; return output; } int main() { Array intArray(20); try { for (int j = 0; j< 100; j++) { intArray[j] = j; cout << "intArray[" << j << "] w porzadku..." << endl; } } catch (Array::xBoundary) { cout << "Nie moglem przetworzyc tych danych!\n"; } cout << "Gotowe.\n"; return 0; }

Wynik
intArray[0] w porzadku... intArray[1] w porzadku... intArray[2] w porzadku... intArray[3] w porzadku... intArray[4] w porzadku... intArray[5] w porzadku... intArray[6] w porzadku... intArray[7] w porzadku... intArray[8] w porzadku... intArray[9] w porzadku... intArray[10] w porzadku... intArray[11] w porzadku... intArray[12] w porzadku... intArray[13] w porzadku... intArray[14] w porzadku... intArray[15] w porzadku... intArray[16] w porzadku... intArray[17] w porzadku... intArray[18] w porzadku... intArray[19] w porzadku... Nie moglem przetworzyc tych danych! Gotowe.

Analiza Listing 20.1 przedstawia nieco okrojon klas Array, opart na szablonie opracowanym w rozdziale 19., Szablony.
Usunito: wzorcu Usunito: Wzorce

W linii 24., wewntrz deklaracji zewntrznej klasy Array zostaje zadeklarowana nowa klasa, xBoundary. Ta nowa klasa w aden sposb nie jest wyrniana jako klasa wyjtku. Jest po prostu tak sam klas, jak kada inna. Jest ona bardzo prosta; nie zawiera adnych danych ani funkcji skadowych. Mimo to ,jest jednak najzupeniej poprawn klas. W rzeczywistoci, stwierdzenie, e nie posiada ona adnych metod, jest bdne, gdy kompilator automatycznie przypisuje jej konstruktor domylny, destruktor, konstruktor kopiujcy oraz operator przypisania. Naprawd klasa ta posiada cztery funkcje, przy braku jakichkolwiek wasnych danych. Zwr uwag, e zadeklarowanie jej wewntrz klasy Array suy jedynie do wzajemnego powizania tych klas. Jak mwilimy w rozdziale 16., Dziedziczenie zaawansowane, ani klasa Array nie ma specjalnych uprawnie dostpu do klasy xBoundary, ani klasa xBoundary nie ma preferencyjnego dostpu do skadowych klasy Array. W liniach od 62. do 69. oraz od 72. do 79. operatory indeksu zostay zmodyfikowane tak, by sprawdzay dany indeks i, gdy znajduje si on poza zakresem, zgaszay klas xBoundary jako wyjtek. Nawiasy s wymagane dla odrnienia tego wywoania konstruktora klasy xBoundary od uycia staej wyliczeniowej. Zauwa, e niektre kompilatory Microsoftu wymagaj dostarczenia instrukcji return, adekwatnej do deklaracji funkcji (w tym przypadku referencji do typu int), mimo, i gdy w linii 67. zostanie zgoszony wyjtek, to wykonanie kodu nigdy nie dotrze do linii 68. Jest to bd kompilatora, wskazujcy e nawet Microsoft mia problemy z tym zagadnieniem! W linii 91. sowo kluczowe try rozpoczyna blok try, ktry koczy si na linii 98. Wewntrz tego bloku, do tablicy zadeklarowanej w linii 90. zostaje wpisanych 101 elementw. W linii 99. rozpoczyna si blok catch, wychwytujcy wyjtki typu xBoundary. W programie sterujcym, w liniach od 88. do 105, wystpuje blok try, w ktrym jest inicjalizowany kady element tablicy. Gdy zmienna j (linia 93.) dojdzie do wartoci 20, nastpuje odwoanie do elementu tablicy o indeksie 20. To powoduje, e test przeprowadzany w linii 65. nie udaje si i w linii 67. operator[] zgasza wyjtek xBoundary. Sterowanie programem przechodzi do bloku catch, zaczynajcego si w linii 99., w ktrym wyjtek zostaje obsuony (w tym przypadku przez wypisanie komunikatu bdu). Wykonanie programu przechodzi przez koniec bloku catch w linii 102.
Usunito: za Usunito: i Usunito: , wic w

Usunito: ten

Usunito: dodanych dwadziecia

Usunito: 6

Bloki try

Blok try jest seri instrukcji, zaczynajc si od sowa kluczowego try, po ktrym nastpuje nawias klamrowy otwierajcy. Blok koczy si nawiasem klamrowym zamykajcym.

Przykad
try

{ Funkcja(); } Usunito: ;

Bloki catch

Blok catch jest seri instrukcji, z ktrych kada zaczyna si od sowa kluczowego catch, po ktrym nastpuje ujty w nawiasy okrge typ wyjtku oraz nawias klamrowy otwierajcy i zamykajcy.

Przykad
try { Funkcja(); } catch (OutOfMemory) { // obsuga braku pamici }

Usunito: ;

Uycie blokw try oraz blokw catch


Okrelenie, w ktrym miejscu naley umieci bloki try, nie jest atwe: nie zawsze oczywiste jest, ktre dziaania mog powodowa wyjtki. Nastpn zagadk jest to, gdzie wyjtek ma zosta wychwycony. By moe zechcemy zgasza wszystkie wyjtki pamici podczas operacji alokowania pamici, ale moe te bdziemy chcie wyapywa je na wyszym poziomie programu (tym wsppracujcym z interfejsem uytkownika). Prbujc wyznaczy lokalizacje dla blokw try, poszukaj tych miejsc, w ktrych alokujesz pami lub uywasz zasobw. Inne bdy, ktre moesz wyapywa, to bdy zakresu, niewaciwych danych wejciowych, itd.

Wychwytywanie wyjtkw
Gdy zostaje zgoszony wyjtek, sprawdzany jest stos wywoa. Stos wywoa jest list wywoa funkcji tworzon w momencie, gdy ktra z czci programu wywouje t funkcj.

Stos wywoa ledzi ciek wykonania. Jeli funkcja main() wywouje funkcj Animal::GetFavoriteFood(), a funkcja GetFavoriteFood() wywouje funkcj Animal::LookupPreferences(), ktra z kolei wywouje fstream::operator>>(), wszystkie te wywoania znajd si na stosie. Funkcja wywoywana rekurencyjnie moe wystpi na stosie wielokrotnie. Wyjtek jest przekazywany w gr stosu, do kadego obejmujcego bloku. Nazywa si to rozwijaniem stosu. Gdy stos jest rozwijany, wywoywane s destruktory lokalnych obiektw na stosie i obiekty te s niszczone. Po kadym bloku try wystpuje jedna lub wicej instrukcji catch. Gdy wyjtek pasuje do jednej z instrukcji catch, zakada si, e zostaje on obsuony przez wykonanie tej instrukcji. Jeli nie pasuje do adnej instrukcji catch, rozwijanie stosu przebiega dalej. Gdy wyjtek przebdzie ca drog a do pocztku programu (funkcji main()) i wci nie jest wychwycony, jest wywoywana wbudowana procedura obsugi, ktra koczy dziaanie programu. Naley zdawa sobie spraw, e rozwijanie stosu jest dziaaniem jednokierunkowym. W miar rozwijania stosu, zawarte na nim obiekty s niszczone. Nie ma wic powrotu: gdy wyjtek zostanie obsuony, program kontynuuje dziaanie po bloku try tej instrukcji catch, ktra obsuya wyjtek. Na listingu 20.1 wykonanie programu zostanie wznowione od linii 101., czyli pierwszej linii po bloku try instrukcji catch, ktra obsuya wyjtek xBoundary. Pamitaj, e po zgoszeniu wyjtku dziaanie programu jest kontynuowane za blokiem catch, a nie w punkcie, w ktrym zosta zgoszony wyjtek.

Wychwytywanie wicej ni jednego rodzaju wyjtkw


Istnieje moliwo, e wyjtek moe zosta spowodowany przez wicej ni jedn sytuacj wyjtkow. W takim przypadku instrukcje catch mog by ukadane jedna za drug, podobnie jak bloki case w instrukcji switch. Odpowiednikiem bloku default jest instrukcja wychwy wszystko, zapisywana jako catch(...). Listing 20.2 przedstawia wychwytywanie wicej ni jednego rodzaju wyjtkw. Listing 20.2. Wyjtki wielokrotne
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: #include <iostream> using namespace std; const int DefaultSize = 10; class Array { public: // konstruktory Array(int itsSize = DefaultSize); Array(const Array &rhs); ~Array() { delete [] pType;}

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

// operatory Array& operator=(const Array&); int& operator[](int offSet); const int& operator[](int offSet) const; // akcesory int GetitsSize() const { return itsSize; } // funkcja zaprzyjaniona friend ostream& operator<< (ostream&, const Array&); // definiujemy klasy wyjtkw class xBoundary {}; class xTooBig {}; class xTooSmall{}; class xZero {}; class xNegative {}; private: int *pType; int itsSize; }; int& Array::operator[](int offSet) { int size = GetitsSize(); if (offSet >= 0 && offSet < GetitsSize()) return pType[offSet]; throw xBoundary(); return pType[0]; // ucisza MSC } const int& Array::operator[](int offSet) const { int mysize = GetitsSize(); if (offSet >= 0 && offSet < GetitsSize()) return pType[offSet]; throw xBoundary(); return pType[0]; } Array::Array(int size): itsSize(size) { if (size == 0) throw xZero(); if (size < 10) throw xTooSmall(); if (size > 30000) throw xTooBig(); if (size < 1) throw xNegative(); pType = new int[size]; for (int i = 0; i<size; i++) pType[i] = 0; } // ucisza MSC

73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107:

int main() { try { Array intArray(0); for (int j = 0; j< 100; j++) { intArray[j] = j; cout << "intArray[" << j << "] w porzadku...\n"; } } catch (Array::xBoundary) { cout << "Nie moglem przetworzyc tych danych!\n"; } catch (Array::xTooBig) { cout << "Ta tablica jest zbyt duza...\n"; } catch (Array::xTooSmall) { cout << "Ta tablica jest zbyt mala...\n"; } catch (Array::xZero) { cout << "Poprosiles o tablice"; cout << " zawierajaca zero obiektow!\n"; } catch (...) { cout << "Cos poszlo nie tak!\n"; } cout << "Gotowe.\n"; return 0; }

Wynik
Poprosiles o tablice zawierajaca zero obiektow! Gotowe.

Analiza W liniach od 25. do 29. zostay utworzone cztery nowe klasy: xTooBig, xTooSmall, xZero oraz xNegative. W konstruktorze, zawartym w liniach do 56. do 71., sprawdzany jest rozmiar przekazywany jako parametr tego konstruktora. Gdy jest zbyt duy, zbyt may, ujemny lub zerowy, zostaje zgoszony wyjtek. Blok try zosta zmodyfikowany i zawiera teraz instrukcje catch dla kadego warunku innego ni rozmiar ujemny. Warunek ten zostaje wychwycony (rozpoznany) przez wychwytujc wszystko instrukcj catch(...), zawart w linii 101. Wyprbuj dziaanie tego programu, stosujc rne wartoci rozmiaru tablicy. Nastpnie sprbuj zastosowa rozmiar wynoszcy 5. Moge oczekiwa, e zostanie zgoszony wyjtek xNegative, ale uniemoliwia to kolejno testw przeprowadzanych w konstruktorze: size <
Usunito: se

Usunito:

10 jest obliczane wczeniej ni size < 1. Aby to poprawi, zamie miejscami linie 61. i 62. z liniami 65. i 66., po czym ponownie skompiluj program.

Hierarchie wyjtkw
Wyjtki s klasami, wic mona z nich wyprowadza klasy pochodne. By moe przydatne byoby stworzenie klasy xSize i wyprowadzenie z niej klas xZero, xTooSmall, xTooBig oraz xNegative. Niektre funkcje mogyby wtedy wyapywa po prostu wyjtki xSize, a inne mogyby wyapywa bardziej specyficzne typy bdw. Listing 20.3 ilustruje ten pomys. Listing 20.3. Hierarchie klas i wyjtki
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: #include <iostream> using namespace std; const int DefaultSize = 10; class Array { public: // konstruktory Array(int itsSize = DefaultSize); Array(const Array &rhs); ~Array() { delete [] pType;} // operatory Array& operator=(const Array&); int& operator[](int offSet); const int& operator[](int offSet) const; // akcesory int GetitsSize() const { return itsSize; } // funkcja zaprzyjaniona friend ostream& operator<< (ostream&, const Array&); // definiujemy klasy wyjtkw class xBoundary {}; class xSize {}; class xTooBig : public xSize {}; class xTooSmall : public xSize {}; class xZero : public xTooSmall {}; class xNegative : public xSize {}; private: int *pType; int itsSize; }; Array::Array(int size): itsSize(size) { if (size == 0) throw xZero(); if (size > 30000)

43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103:

throw if (size throw if (size throw

xTooBig(); <1) xNegative(); < 10) xTooSmall();

pType = new int[size]; for (int i = 0; i<size; i++) pType[i] = 0; } int& Array::operator[](int offSet) { int size = GetitsSize(); if (offSet >= 0 && offSet < GetitsSize()) return pType[offSet]; throw xBoundary(); return pType[0]; // ucisza MSC } const int& Array::operator[](int offSet) const { int mysize = GetitsSize(); if (offSet >= 0 && offSet < GetitsSize()) return pType[offSet]; throw xBoundary(); return pType[0]; } int main() { try { Array intArray(0); for (int j = 0; j< 100; j++) { intArray[j] = j; cout << "intArray[" << j << "] w porzadku...\n"; } } catch (Array::xBoundary) { cout << "Nie moglem przetworzyc tych danych!\n"; } catch (Array::xTooBig) { cout << "Ta tablica jest zbyt duza...\n"; } catch (Array::xTooSmall) { cout << "Ta tablica jest zbyt mala...\n"; } catch (Array::xZero) { cout << "Poprosiles o tablice"; cout << " zawierajaca zero obiektow!\n"; } catch (...) // ucisza MSC

104: 105: 106: 107: 108: 109:

{ cout << "Cos poszlo nie tak!\n"; } cout << "Gotowe.\n"; return 0; }

Wynik
Ta tablica jest zbyt mala... Gotowe.

Analiza Jedyne znaczce zmiany pojawiaj si w liniach od 27. do 30., gdzie ustanawiana jest hierarchia klas. Klasy xTooBig, xTooSmall oraz xNegative s wyprowadzone z klasy xSize, za klasa xZero jest wyprowadzona z klasy xTooSmall. Stworzona zostaa tablica Array o zerowym rozmiarze, ale co si stao? Wyglda na to, e zosta wychwycony niewaciwy wyjtek! Sprawd dokadnie blok catch okae si, e wyjtek typu xTooSmall jest wychwytywany przed wyjtkiem typu xZero. Poniewa zgaszany jest wyjtek typu xZero, a obiekt klasy xZero jest obiektem klasy xTooSmall, zostaje on wychwycony przez blok catch wyjtku typu xTooSmall. Po obsueniu, wyjtek nie jest przekazywany dalej, do kolejnych blokw wychwytywania, dlatego nigdy nie zostaje wywoany blok catch dla wyjtku typu xZero. Rozwizaniem tego problemu jest uwany dobr kolejnoci blokw catch (tak, aby najbardziej specyficzne wyjtki byy wychwytywane w pierwszej kolejnoci, a mniej specyficzne w dalszej). W tym konkretnym przykadzie problem ten mona rozwiza, zamieniajc miejscami bloki catch dla wyjtkw xZero i xTooSmall.

Dane w wyjtkach oraz nazwane obiekty wyjtkw


Aby mc odpowiednio zareagowa na bd, czsto chcemy wiedzie wicej; nie tylko zna typ zgoszonego wyjtku. Klasy wyjtkw s takie same jak inne klasy, wic mona w nich umieszcza dane, inicjalizowa je w konstruktorze i w dowolnym momencie odczytywa. Ilustruje to listing 20.4. Listing 20.4. Odczytywanie danych z obiektu wyjtku
0: 1: 2: 3: 4: #include <iostream> using namespace std; const int DefaultSize = 10;

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

class Array { public: // konstruktory Array(int itsSize = DefaultSize); Array(const Array &rhs); ~Array() { delete [] pType;} // operatory Array& operator=(const Array&); int& operator[](int offSet); const int& operator[](int offSet) const; // akcesory int GetitsSize() const { return itsSize; } // funkcja zaprzyjaniona friend ostream& operator<< (ostream&, const Array&); // definiujemy klasy wyjtkw class xBoundary {}; class xSize { public: xSize(int size):itsSize(size) {} ~xSize(){} int GetSize() { return itsSize; } private: int itsSize; }; class xTooBig : public xSize { public: xTooBig(int size):xSize(size){} }; class xTooSmall : public xSize { public: xTooSmall(int size):xSize(size){} }; class xZero : public xTooSmall { public: xZero(int size):xTooSmall(size){} }; class xNegative : public xSize { public: xNegative(int size):xSize(size){} }; private: int *pType; int itsSize; };

66: Array::Array(int size): 67: itsSize(size) 68: { 69: if (size == 0) 70: throw xZero(size); 71: if (size > 30000) 72: throw xTooBig(size); 73: if (size <1) 74: throw xNegative(size); 75: if (size < 10) 76: throw xTooSmall(size); 77: 78: pType = new int[size]; 79: for (int i = 0; i<size; i++) 80: pType[i] = 0; 81: } 82: 83: 84: int& Array::operator[] (int offSet) 85: { 86: int size = GetitsSize(); 87: if (offSet >= 0 && offSet < GetitsSize()) 88: return pType[offSet]; 89: throw xBoundary(); 90: return pType[0]; 91: } 92: 93: const int& Array::operator[] (int offSet) const 94: { 95: int size = GetitsSize(); 96: if (offSet >= 0 && offSet < GetitsSize()) 97: return pType[offSet]; 98: throw xBoundary(); 99: return pType[0]; 100: } 101: 102: int main() 103: { 104: try 105: { 106: Array intArray(9); 107: for (int j = 0; j< 100; j++) 108: { 109: intArray[j] = j; 110: cout << "intArray[" << j << "] w porzadku..." << endl; 111: } 112: } 113: catch (Array::xBoundary) 114: { 115: cout << "Nie moglem przetworzyc tych danych!\n"; 116: } 117: catch (Array::xZero theException) 118: { 119: cout << "Poprosiles o tablice Array zawierajaca zero elementow" << endl; 120: cout << "Otrzymano " << theException.GetSize() << endl; 121: } 122: catch (Array::xTooBig theException) 123: { 124: cout << "Ta tablica jest zbyt duza...\n"; 125: cout << "Otrzymano " << theException.GetSize() << endl;

126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138:

} catch (Array::xTooSmall theException) { cout << "Ta tablica jest zbyt mala...\n"; cout << "Otrzymano " << theException.GetSize() << endl; } catch (...) { cout << "Cos poszlo nie tak, ale nie mam pojecia co!\n"; } cout << "Gotowe.\n"; return 0; }

Wynik
Ta tablica jest zbyt mala... Otrzymano 9 Gotowe.

Analiza Deklaracja klasy xSize zawiera teraz w linii 33. zmienn skadow itsSize, a w linii 31. funkcj skadow GetSize(). Oprcz tego, zosta dla niej stworzony konstruktor przyjmujcy warto cakowit i inicjalizujcy t zmienn skadow, co zostao pokazane w linii 29. Klasy pochodne deklaruj konstruktory, ktre tylko inicjalizuj klas bazow. Nie zostay zadeklarowane adne inne funkcje (midzy innymi po to, by skrci objto listingu). Instrukcje catch w liniach od 113. do 135. posiadaj teraz nazwany obiekt xException wychwytywanego wyjtku i uywaj go w celu uzyskania dostpu do danej zawartej w jego zmiennej skadowej itsSize.

UWAGA Pamitaj o tym, e obiekt wyjtku jest tworzony w wyniku wystpienia wyjtkowej sytuacji, wic powiniene uwaa, eby nie spowodowa takiej samej sytuacji w konstruktorze. Gdy tworzysz wyjtek braku pamici (OutOfMemory), nie powiniene alokowa pamici w konstruktorze.

Indywidualne wypisywanie odpowiednich komunikatw przez poszczeglne instrukcje catch jest kopotliwe i podatne na bdy. To zadanie naley do obiektu, ktry zna swj typ i zawarte w nim wartoci. Listing 20.5 przedstawia bardziej obiektowo zorientowane podejcie do tego problemu, w ktrym kady wyjtek sam wykonuje odpowiedni prac. Listing 20.5. Przekazywanie przez referencj i uycie funkcji wirtualnych w wyjtkach
0: 1: 2: 3: #include <iostream> using namespace std; const int DefaultSize = 10;

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

class Array { public: // konstruktory Array(int itsSize = DefaultSize); Array(const Array &rhs); ~Array() { delete [] pType;} // operatory Array& operator=(const Array&); int& operator[](int offSet); const int& operator[](int offSet) const; // akcesory int GetitsSize() const { return itsSize; } // funkcja zaprzyjaniona friend ostream& operator<< (ostream&, const Array&); // definiujemy klasy wyjtkw class xBoundary {}; class xSize { public: xSize(int size):itsSize(size) {} ~xSize(){} virtual int GetSize() { return itsSize; } virtual void PrintError() { cout << "Blad rozmiaru. Otrzymano: "; cout << itsSize << endl; } protected: int itsSize; }; class xTooBig : public xSize { public: xTooBig(int size):xSize(size){} virtual void PrintError() { cout << "Zbyt duza! Otrzymano: "; cout << xSize::itsSize << endl; } }; class xTooSmall : public xSize { public: xTooSmall(int size):xSize(size){} virtual void PrintError() { cout << "Zbyt mala! Otrzymano: "; cout << xSize::itsSize << endl; } }; class xZero : public xTooSmall

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

{ public: xZero(int size):xTooSmall(size){} virtual void PrintError() { cout << "Zerowa!!. Otrzymano: " ; cout << xSize::itsSize << endl; } }; class xNegative : public xSize { public: xNegative(int size):xSize(size){} virtual void PrintError() { cout << "Ujemna! Otrzymano: "; cout << xSize::itsSize << endl; } }; private: int *pType; int itsSize; }; Array::Array(int size): itsSize(size) { if (size == 0) throw xZero(size); if (size > 30000) throw xTooBig(size); if (size <1) throw xNegative(size); if (size < 10) throw xTooSmall(size); pType = new int[size]; for (int i = 0; i<size; i++) pType[i] = 0; } int& Array::operator[] (int offSet) { int size = GetitsSize(); if (offSet >= 0 && offSet < GetitsSize()) return pType[offSet]; throw xBoundary(); return pType[0]; } const int& Array::operator[] (int offSet) const { int size = GetitsSize(); if (offSet >= 0 && offSet < GetitsSize()) return pType[offSet]; throw xBoundary(); return pType[0]; }

126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151:

int main() { try { Array intArray(9); for (int j = 0; j< 100; j++) { intArray[j] = j; cout << "intArray[" << j << "] w porzadku...\n"; } } catch (Array::xBoundary) { cout << "Nie moglem przetworzyc tych danych!\n"; } catch (Array::xSize& theException) { theException.PrintError(); } catch (...) { cout << "Cos poszlo nie tak!\n"; } cout << "Gotowe.\n"; return 0; }

Wynik
Zbyt mala! Otrzymano: 9 Gotowe.

Analiza Listing 20.5 deklaruje w klasie xSize wirtualn metod o nazwie PrintError(). Ta metoda wypisuje komunikat bdu oraz aktualny rozmiar klasy. Jest przesonita w kadej z klas pochodnych. W linii 141. obiekt wyjtku jest deklarowany jako referencja. Gdy zostaje wywoana funkcja PrintError() dla referencji do obiektu, polimorfizm powoduje, e zostaje wywoana metoda waciwej klasy. Dziki temu kod jest bardziej przejrzysty, atwiejszy do zrozumienia i duo atwiejszy w konserwacji.

Wyjtki i wzorce
Tworzc wspdziaajce z wzorcami wyjtki, masz do wyboru: tworzenie wyjtku dla kadego egzemplarza wzorca lub uycie klas wyjtkw zadeklarowanych poza deklaracj szablonu. Listing 20.6 ilustruje obie moliwoci.
Usunito: wzorcami Usunito: wzorca Usunito: wzorca

Listing 20.6. Uycie wyjtkw z wzorcami


0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: #include <iostream> using namespace std; const int DefaultSize = 10; class xBoundary {}; template <class T> class Array { public: // konstruktory Array(int itsSize = DefaultSize); Array(const Array &rhs); ~Array() { delete [] pType;} // operatory Array& operator=(const Array<T>&); T& operator[](int offSet); const T& operator[](int offSet) const; // akcesory int GetitsSize() const { return itsSize; } // funkcja zaprzyjaniona friend ostream& operator<< (ostream&, const Array<T>&); // definiujemy klasy wyjtkw class xSize {}; private: int *pType; int itsSize; }; template <class T> Array<T>::Array(int size): itsSize(size) { if (size <10 || size > 30000) throw xSize(); pType = new T[size]; for (int i = 0; i<size; i++) pType[i] = 0; } template <class T> Array<T>& Array<T>::operator=(const Array<T> &rhs) { if (this == &rhs) return *this; delete [] pType; itsSize = rhs.GetitsSize(); pType = new T[itsSize]; for (int i = 0; i<itsSize; i++) pType[i] = rhs[i]; } template <class T> Array<T>::Array(const Array<T> &rhs) {

60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116:

itsSize = rhs.GetitsSize(); pType = new T[itsSize]; for (int i = 0; i<itsSize; i++) pType[i] = rhs[i]; } template <class T> T& Array<T>::operator[](int offSet) { int size = GetitsSize(); if (offSet >= 0 && offSet < GetitsSize()) return pType[offSet]; throw xBoundary(); return pType[0]; } template <class T> const T& Array<T>::operator[](int offSet) const { int mysize = GetitsSize(); if (offSet >= 0 && offSet < GetitsSize()) return pType[offSet]; throw xBoundary(); } template <class T> ostream& operator<< (ostream& output, const Array<T>& theArray) { for (int i = 0; i<theArray.GetitsSize(); i++) output << "[" << i << "] " << theArray[i] << endl; return output; } int main() { try { Array<int> intArray(9); for (int j = 0; j< 100; j++) { intArray[j] = j; cout << "intArray[" << j << "] w porzadku..." << endl; } } catch (xBoundary) { cout << "Nie moglem przetworzyc tych danych!\n"; } catch (Array<int>::xSize) { cout << "Zly rozmiar!\n"; } cout << "Gotowe.\n"; return 0;

Wynik

Zly rozmiar! Gotowe.

Analiza Pierwszy wyjtek, xBoundary, jest zadeklarowany w linii 4., poza definicj szablonu. Drugi wyjtek, xSize, jest zadeklarowany w linii 28., wewntrz definicji szablonu. Wyjtek xBoundary nie jest powizany z klas szablonu, ale moe by uywany tak samo, jak inne klasy. Wyjtek xSize jest zwizany z szablonem i musi by wywoywany w oparciu o egzemplarz klasy Array. Rnic wida w skadni dwch instrukcji catch. Linia 105. zawiera instrukcj catch(xBoundary), a linia 109. zawiera instrukcj catch(Array<int>::xSize). Ta druga instrukcja jest powizana z klas Array przechowujc wartoci typu int.
Usunito: wzorca Usunito: wzorca Usunito: wzorca Usunito: wzorcem

Wyjtki bez bdw


Gdy programici C++ spotykaj si po pracy w cyberprzestrzennym barze przy wirtualnym piwie, czsto rozmawiaj o tym, czy w rutynowych sytuacjach powinny by uywane wyjtki. Niektrzy utrzymuj, e z racji swojej natury, wyjtki powinny by zarezerwowane dla tych wyjtkowych (std nazwa!), lecz przewidywalnych sytuacji, ktre programista musi wzi pod uwag i nie powinny by czci rutynowego dziaania kodu. Inni wskazuj, e wyjtki oferuj wygodny i przejrzysty sposb powrotu poprzez wiele poziomw wywoa funkcji bez ryzyka powstawania wyciekw pamici. Czsto przytaczany jest nastpujcy przykad: uytkownik da dziaania w rodowisku GUI. Przechwytujca danie cz kodu musi wywoa funkcj skadow w menederze dialogw, ktry z kolei wywouje kod przetwarzajcy danie, ktry wywouje kod decydujcy o tym, ktre okno dialogowe ma by uyte; ten kod z kolei wywouje kod tworzcy okno dialogowe, ktre nastpnie wywouje kod obsugujcy dziaania uytkownika w oknie. Gdy uytkownik kliknie przycisk Anuluj, kod musi powrci do pierwszej metody wywoujcej, w ktrej byo obsugiwane pierwotne danie. Jednym z rozwiza tego problemu jest umieszczenie bloku try wok wywoania pierwotnego i wychwycenie CancelDialog (anuluj okno dialogowe) jako wyjtku, ktry moe zosta zgoszony przez procedur obsugi przycisku Anuluj. To rozwizanie jest bezpieczne i efektywne. Klikanie przycisku Anuluj nie jest bdem, lecz standardow operacj w interfejsie uytkownika. Taka dyskusja czsto przeradza si w zacity spr, ale najbardziej sensownym sposobem podjcia decyzji jest zadanie sobie nastpujcych pyta: czy uycie wyjtkw uatwi, czy utrudni zrozumienie kodu? Czy ryzyko powstania bdw i wyciekw pamici zwikszy si, czy zmniejszy? Czy modernizacja kodu bdzie atwiejsza, czy trudniejsza? Te decyzje, podobnie jak wiele innych, wymagaj analizy wad i zalet; nie ma jednej, waciwej odpowiedzi.

Usunito: utrzymanie Usunito: e Usunito: e

Kilka sw na temat psujcego si kodu


Psucie si kodu jest powszechnie znanym zjawiskiem, w ktrym, z powodu zaniedbania pogarsza si jako oprogramowania. Doskonale napisany, w peni przetestowany program zmienia si w bdny ju po kilku tygodniach od dostarczenia go do klienta. Po kilku miesicach klient zauway, e programowi brakuje logiki i e wiele obiektw zaczyna si rozsypywa. Oprcz dostarczania kodu rdowego w opakowaniach prniowych, twoj jedyn ochron jest pisanie programw w taki sposb, by mc szybko i atwo zidentyfikowa problem, gdy wrcisz do nich w celu poprawienia jakiego elementu.

UWAGA Psucie si kodu to art programistw, z ktrego pynie wana lekcja. Programy s bardzo zoone: pluskwy, bdy i pomyki mog si ukrywa przez duszy czas. Chro sam siebie, piszc atwy w utrzymaniu kod.

To oznacza, e kod musi by napisany zrozumiale i uzupeniony komentarzami tam, gdzie korzystasz z jakich sztuczek. Sze miesicy po dostarczeniu kodu bdziesz go czyta jak kto zupenie obcy, zastanawiajc si ze zgroz, jak kto mg stworzy tak udziwnion skadni.

Pluskwy i odpluskwianie
Prawie wszystkie nowoczesne rodowiska programowania zawieraj jeden lub wicej wysokowydajnych debuggerw. Podstawowa zasada korzystania z debuggera (nazwa pochodzi od sowa debug, odpluskwia) jest nastpujca: uruchamiasz debugger, ktry aduje kod rdowy programu, po czym uruchamiasz swj program w debuggerze. Dziki temu moesz ledzi wykonanie kadej instrukcji w programie oraz sprawdza wartoci zmiennych, ktre zmieniaj si podczas jego dziaania. Wszystkie kompilatory umoliwiaj kompilacj z symbolami lub bez. Kompilowanie z symbolami informuje kompilator, by tworzy konieczne odwzorowanie pomidzy kodem rdowym programu a generowanym kodem wykonywalnym; debugger uywa tego odwzorowania do wskazania linii kodu rdowego, ktra odpowiada nastpnemu dziaaniu w programie. Penoekranowe symboliczne debuggery bardzo uatwiaj odpluskwianie kodu. Gdy adujesz debugger, odczytuje on cay kod rdowy i wywietla go w oknie. Moesz przechodzi przez wywoania funkcji lub nakaza wejcie do poszczeglnych funkcji, wykonujc kod linia po linii. W wikszoci debuggerw mona przecza si pomidzy kodem rdowym a wynikami programu tak, aby mc zobaczy wynik wykonania poszczeglnych instrukcji. Mona sprawdza biecy stan kadej zmiennej, przeglda zoone struktury danych oraz przeglda pami wskazywan przez wskaniki lub zajmowan przez obiekty. W debuggerze mona wykonywa take rne operacje, takie jak ustawianie punktw wstrzymania, ledzenie wartoci zmiennych, sprawdzanie pamici i przegldanie kodu maszynowego.

Punkty wstrzymania
Punkty wstrzymania (ang. breakpoint) s instrukcj dla debuggera nakazujc mu, by wstrzyma wykonanie programu w momencie dotarcia do okrelonej linii kodu. Dziki temu moesz uruchomi program tak, aby dziaa normalnie a do chwili dotarcia do obszaru kodu, ktry ci interesuje. Punkty wstrzymania pomagaj w analizowaniu biecych wartoci zmiennych tu przed wykonaniem krytycznych linii kodu.

Usunito: z

ledzenie wartoci zmiennych


Istnieje moliwo poinstruowania debuggera, by wywietli warto okrelonej zmiennej lub by wstrzyma dziaanie kodu w momencie, gdy nastpuje zapis lub odczyt zmiennej. Punkty ledzenia (ang. watch points) pozwalaj na okrelenie tych warunkw, a czasem nawet na modyfikowanie wartoci zmiennej podczas dziaania programu.
Usunito: y

Sprawdzanie pamici
Czasem uytkownik musi przejrze rzeczywiste wartoci przechowywane w pamici. Nowoczesne debuggery mog wywietla wartoci w postaci odpowiadajcej typowi zmiennej, tj. acuchy mog by wywietlane jako napisy zoone ze znakw, zmienne typu long jako liczby, a nie cztery bajty, i tak dalej. Wymylne debuggery C++ mog nawet pokazywa pene klasy i wywietla biece wartoci wszystkich ich zmiennych skadowych, cznie ze wskanikiem this.
Usunito: rzeczywistej Usunito: s

Asembler
Cho czytanie kodu rdowego moe by wystarczajce do znalezienia bdu, jednak gdy zawiod inne sposoby, istnieje moliwo poinstruowania debuggera, by wywietli kod maszynowy wygenerowany dla poszczeglnych linii programu. Mona sprawdza wartoci rejestrw i znacznikw procesora i analizowa dziaanie programu dosownie do goego metalu. Naucz si korzysta ze swojego debuggera. Moe by on najlepsz broni w witej wojnie przeciwko pluskwom. Bdy czasu dziaania s najtrudniejsze do znalezienia i usunicia, a wydajny debugger moe umoliwi (nie tylko uatwi) wyszukanie prawie wszystkich z nich.
Usunito:

Rozdzia 21. Co dalej


Gratulacje! Przebrne ju prawie przez cae wprowadzenie do C++. W tym momencie powiniene ju dobrze go rozumie, ale w nowoczesnym programowaniu zawsze jest co, czego jeszcze mona si nauczy. W tym rozdziale uzupenimy brakujce szczegy i wskaemy ci dalsze kierunki rozwoju. Wikszo kodu, ktry zapisuje si w plikach kodu rdowego, to C++. Ten kod jest interpretowany przez kompilator i zamieniany w program. Jednak przed uruchomieniem kompilatora zostaje uruchomiony preprocesor, ktry umoliwia kompilacj warunkow. Z tego rozdziau dowiesz si: czym jest kompilacja warunkowa i jak ni zarzdza, jak pisa makra preprocesora, jak uywa preprocesora do wyszukiwania bdw, jak manipulowa poszczeglnymi bitami i uywa ich jako znacznikw, jakie s nastpne kroki w efektywnej nauce C++.

Preprocesor i kompilator
Za kadym razem, gdy uruchamiasz kompilator, jako pierwszy rusza preprocesor. Preprocesor szuka swoich dyrektyw, z ktrych kada zaczyna si od znaku hash (#). Efektem dziaania kadej z takich instrukcji jest zmiana tekstu kodu rdowego. Rezultatem tej zmiany jest nowy plik kodu rdowego tymczasowy plik, ktrego zwykle nie widzisz, cho moesz poinstruowa kompilator, aby zapisa go tak, aby mg go przeanalizowa. Kompilator nie odczytuje oryginalnego pliku kodu rdowego; zamiast tego odczytuje i kompiluje plik bdcy wynikiem pracy preprocesora. Wykorzystywalimy ten mechanizm ju wczeniej, doczajc pliki nagwkowe za pomoc dyrektywy #include. Ta dyrektywa powoduje odszukanie pliku o wskazanej w instrukcji nazwie i doczenie go w biecym miejscu

do pliku poredniego. Odpowiada to wpisaniu caego pliku nagwkowego do kodu rdowego; w momencie, gdy plik trafia do kompilatora, plik nagwkowy ju znajduje si w kodzie.

Przegldanie formy poredniej


Prawie kady kompilator posiada przecznik powodujcy zapisanie pliku poredniego na dysku; przecznik ten mona ustawia albo w zintegrowanym rodowisku programistycznym (IDE) albo w linii polece kompilatora. Jeli chcesz przejrze plik poredni, poszukaj odpowiedniego przecznika w podrczniku dla swojego kompilatora.

Uycie dyrektywy #define


Dyrektywa #define definiuje podstawienie symbolu. Jeli napiszemy:
#define BIG 512

to poinstruujemy preprocesor, by podstawi acuch 512 w kade miejsce, w ktrym napotka symbol BIG. Nie jest to jednak acuch w rozumieniu C++. Znaki 512 s wstawiane do kodu rdowego w kadym miejscu, w ktrym zostanie napotkany symbol BIG. Symbol jest acuchem znakw, ktry moe by uyty tam, gdzie moe by uyty acuch, staa lub inny spjny zestaw znakw. Tak wic, jeli napiszemy:
#define BIG 512 int myArray[BIG];

wtedy stworzony przez preprocesor plik poredni bdzie wyglda nastpujco:


int myArray[512];

Zwr uwag na brak instrukcji #define. Instrukcje preprocesora s usuwane z pliku poredniego i w ogle nie wystpuj w ostatecznym kodzie rdowym.

Uycie #define dla staych


Jednym z zada dyrektywy #define jest podstawianie staych. Jednak nie naley jej w tym celu wykorzystywa, gdy dyrektyw ta jedynie podstawia acuch i nie dokonuje sprawdzenia typu. Jak wyjaniono w podrozdziale dotyczcym staych, uycie sowa kluczowego const ma o wiele wicej zalet ni uycie dyrektywy #define.

Uycie #define do definiowania symboli


Drugim zastosowaniem #define jest po prostu definiowanie okrelonych symboli. W zwizku z tym moemy napisa:
#define BIG

Pniej moemy sprawdzi, czy symbol BIG zosta zdefiniowany i jeli tak, podj odpowiednie dziaania. Dyrektywami preprocesora, ktre sprawdzaj, czy symbol zosta zdefiniowany, s dyrektywy #ifdef (if defined, jeli zdefiniowany) oraz #ifndef (if not defined, jeli nie zdefiniowany). Po obu z nich musi wystpi dyrektywa #endif, koczca blok kompilowany warunkowo. Dyrektywa #ifdef jest prawdziwa, jeli sprawdzany w niej symbol jest ju zdefiniowany. Moemy wic napisa:
#ifdef DEBUG cout << "Debug defined"; #endif

Gdy kompilator odczyta dyrektyw #ifdef, sprawdzi we wbudowanej wewntrz siebie tablicy, czy zdefiniowany zosta symbol DEBUG. Jeli tak, to warunek dyrektywy #ifdef jest speniony i w pliku porednim znajdzie si wszystko, a do nastpnej dyrektywy #else lub #endif. Jeli warunek dyrektywy #ifdef nie zostanie speniony, to w pliku rdowym nie znajdzie si adna linia zawarta pomidzy tymi dyrektywami; efektem bdzie zupene pominicie kodu znajdujcego si w tym miejscu. Zwr uwag, e #ifndef stanowi logiczn odwrotno dyrektywy #ifdef. Warunek dyrektywy #ifndef jest speniony, gdy w danym miejscu pliku nie zosta jeszcze zdefiniowany symbol.

Usunito: , Usunito: a Usunito: zostanie Usunito: a Usunito: a Usunito: a Usunito: D Usunito: a

Dyrektywa #else preprocesora


Jak mona si domyla, dyrektywa #else moe by wstawiona pomidzy dyrektyw #ifdef (lub #ifndef) a dyrektyw #endif. Sposb uycia tych dyrektyw ilustruje listing 21.1.

Listing 21.1. Uycie #define


0: #define DemoVersion 1: #define NT_VERSION 5 2: #include <iostream> 3: 4: 5: int main() 6: { 7: std::cout << "Sprawdzanie definicji DemoVersion,"; 8: std::cout << "NT_VERSION oraz WINDOWS_VERSION...\n"; 9: 10: #ifdef DemoVersion 11: std::cout << "Symbol DemoVersion zdefiniowany.\n"; 12: #else 13: std::cout << "Symbol DemoVersion nie zdefiniowany.\n"; 14: #endif 15: 16: #ifndef NT_VERSION 17: std::cout << "Symbol NT_VERSION nie zdefiniowany!\n"; 18: #else 19: std::cout<<"Symbol NT_VERSION zdefiniowany jako: "<<NT_VERSION<<std::endl; 20: #endif 21: 22: #ifdef WINDOWS_VERSION 23: std::cout << "Symbol WINDOWS_VERSION zdefiniowany!\n"; 24: #else 25: std::cout << "Symbol WINDOWS_VERSION nie zostal zdefiniowany.\n"; 26: #endif 27: 28: std::cout << "Gotowe.\n"; 29: return 0; 30: }

Wynik
Sprawdzanie definicji DemoVersion,NT_VERSION oraz WINDOWS_VERSION... Symbol DemoVersion zdefiniowany. Symbol NT_VERSION zdefiniowany jako: 5 Symbol WINDOWS_VERSION nie zostal zdefiniowany. Gotowe.

Analiza W liniach 0. i 1. zostay zdefiniowane symbole DemoVersion oraz NT_VERSION, przy czym symbol NT_VERSION zosta zdefiniowany jako acuch 5. W linii 10. sprawdzana jest definicja DemoVersion, a poniewa zostaa zdefiniowana (mimo, i nie ma wartoci), warunek zosta speniony, dlatego wypisany zostaje acuch z linii 11. W linii 16. dyrektywa #ifndef sprawdza, czy symbol NT_VERSION nie zosta zdefiniowany. Poniewa zosta zdefiniowany, warunek nie jest speniony i wykonanie programu przeskakuje do

linii 19. W tej linii, w miejscu symbolu NT_VERSION, jest podstawiany acuch 5, wic dla kompilatora caa linia ma posta:
std::cout<<"Symbol NT_VERSION zdefiniowany jako: "<<5<<std::endl;

Zwr uwag, e w miejscu pierwszego sowa NT_VERSION nic nie zostao podstawione, gdy znajduje si ono w acuchu ujtym w cudzysowy. Drugie NT_VERSION zostao jednak podstawione, wic kompilator widzi warto 5, tak jakbymy j sami wpisali. Na koniec, w linii 22., program sprawdza symbol WINDOWS_VERSION. Poniewa nie zdefiniowalimy tego symbolu, warunek nie jest speniony i wypisany zostaje komunikat z linii 25.

Doczanie i wartowniki doczania


W przyszoci bdziesz tworzy projekty zawierajce wiele rnych plikw. Prawdopodobnie zorganizujesz swoje kartoteki tak, aby kada klasa posiadaa swj wasny plik nagwkowy (na przykad .hpp), zawierajcy deklaracj klasy oraz wasny plik implementacji (na przykad .cpp), zawierajcy kod rdowy dla metod tej klasy. Funkcja main() znajdzie si we wasnym pliku .cpp, a wszystkie pliki .cpp bd kompilowane do plikw .obj, ktre z kolei zostan poczone przez linker w pojedynczy program. Poniewa twoje programy bd uywa metod z wielu klas, wic do kadego pliku bdzie doczanych wiele plikw nagwkowych. Poza tym, pliki nagwkowe czsto musz docza nastpne pliki. Na przykad, plik nagwkowy dla deklaracji klasy pochodnej musi doczy plik nagwkowy dla jej klasy bazowej. Wyobramy sobie, e klasa Animal jest zadeklarowana w pliku ANIMAL.hpp. Klasa Dog (pochodzca od klasy Animal) musi w pliku DOG.hpp docza plik ANIMAL.hpp, gdy w przeciwnym razie klasa Dog nie bdzie moga zosta wyprowadzona z klasy Animal. Plik nagwkowy klasy Cat z tego samego powodu take docza plik ANIMAL.hpp. Gdy stworzysz metod uywajc zarwno klas Cat, jak i Dog, oznacza to niebezpieczestwo dwukrotnego doczenia pliku ANIMAL.hpp. To spowoduje bd kompilacji, gdy dwukrotne zadeklarowanie klasy (Animal) nie jest dozwolone, nawet jeli obie deklaracje s identyczne. Moesz rozwiza ten problem, stosujc wartowniki doczania. Na pocztku pliku nagwkowego ANIMAL.hpp dopisz ponisze linie:
#ifndef ANIMAL_HPP #define ANIMAL_HPP ... #endif // ANIMAL_HPP

// w tym miejscu caa zawarto pliku

W ten sposb informujemy preprocesor, e jeli symbol ANIMAL_HPP nie jest zdefiniowany, ma go zdefiniowa i doczy ca zawarto pliku wystpujc pomidzy dyrektywami #define a #endif. Za pierwszym razem, gdy program docza ten plik, odczytuje pierwsz lini i sprawdza, czy symbol ANIMAL_HPP nie jest zdefiniowany. Poniewa nie jest on jeszcze zdefiniowany, zostaje zdefiniowany w nastpnej linii, a do pliku poredniego zostaje doczona caa zawarto pliku nagwkowego. Za drugim razem, gdy preprocesor docza plik ANIMAL.hpp, take testuje symbol ANIMAL_HPP, lecz tym razem jest on ju zdefiniowany, wic preprocesor pomija cay blok kodu, a do dyrektywy #else (ktra w tym przypadku nie wystpuje) lub #endif (na kocu naszego pliku). Tak wic pomija ca zawarto pliku nagwkowego, a klasa nie zostaje zadeklarowana ponownie. Sama nazwa definiowanego symbolu (ANIMAL_HPP) nie ma znaczenia, ale przyjo si stosowanie nazwy pliku zapisanej duymi literami, w ktrej znak kropki (.) zostaje zamieniony na znak podkrelenia (_). Jest to jednak jedynie konwencja.
Usunito: one

UWAGA Stosowanie wartownikw doczania zawsze jest przydatne. Mog one oszczdzi ci wielu godzin debuggowania.

Funkcje makro
Dyrektywa #define moe by uywana take w celu tworzenia funkcji makro (tzw. makr). Funkcja makro jest symbolem stworzonym za pomoc dyrektywy #define; przyjmuje ona argument, podobnie jak zwyke funkcje. Preprocesor podstawi przekazany acuch w kadym miejscu definicji, w ktrym wystpuje argument makra. Na przykad, makro TWICE (dwakro) moemy zdefiniowa jako:
#define TWICE(x) ( (x) * 2 )

a nastpnie napisa w kodzie:


TWICE(4)

Cay acuch TWICE(4) zostanie usunity, a w jego miejscu zostanie podstawiona warto 8! Gdy preprocesor natrafi na to makro z argumentem 4, w jego miejscu podstawi ( (4) * 2), co z kolei zostanie obliczone jako 4*2, czyli 8.

Makro moe mie wicej ni jeden parametr; kady parametr moe wystpowa w tekcie definicji wielokrotnie. Dwa powszechnie uywane makra to MAX oraz MIN:
#define MAX(x,y) ( (x) > (y) ? (x) : #define MIN(x,y) ( (x) < (y) ? (x) : (y) ) (y) )

Zwr uwag, e w definicji makra nawiasy otwierajce list parametrw musz wystpowa bezporednio po nazwie makra bez wystpujcej pomidzy nimi spacji. Preprocesor nie pozwala na tak liberalne uywanie spacji, jak robi to kompilator. Jeli napiszemy:
#define MAX (x,y) ( (x) > (y) ? (x) : (y) )

a nastpnie sprbujemy uy tak zdefiniowanego makra MAX:


int x = 5, y = 7, z; z = MAX(x,y);

wtedy kod poredni przyjmie posta:


int x = 5, y = 7, z; z = (x,y) ( (x) > (y) ? (x) :

(y) )(x,y);

Zostaby jedynie podstawiony prosty tekst; nie nastpioby wywoanie funkcji makro. Symbol MAX zostaby zastpiony przez (x,y) ( (x) > (y) ? (x) : (y) ), po ktrym wystpuje acuch (x,y), ktry mia peni rol parametrw makra. Po usuniciu spacji pomidzy MAX a (x,y) kod poredni przyjby posta:
int x =5, y = 7, z; z =7;

Po co te wszystkie nawiasy?
By moe zastanawiasz si, po co w przedstawianych dotd makrach stosowalimy tak wiele nawiasw. Preprocesor nie wymaga, by wok argumentw w acuchu podstawiania byy umieszczane nawiasy, ale nawiasy te pomagaj unika niepodanych efektw ubocznych w przypadkach, gdy do makra przekazujemy skomplikowane wyraenia. Na przykad, jeli zdefiniujemy makro MAX jako:
#define MAX(x,y) x > y ? x : y

i przekaemy mu wartoci 5 oraz 7, wtedy to makro bdzie dziaao tak, jak oczekujemy. Jeli jednak przekaemy mu bardziej skomplikowane wyraenia, otrzymamy niepodane rezultaty, tak jak ilustruje listing 21.2. Listing 21.2. Uycie nawiasw w makrach
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: // Listing 21.2 Rozwijanie makr #include <iostream> using namespace std; #define CUBE(a) ( (a) * (a) * (a) ) #define THREE(a) a * a * a int main() { long x = 5; long y = CUBE(x); long z = THREE(x); cout << "y: " << y << endl; cout << "z: " << z << endl; long a = 5, b = 7; y = CUBE(a+b); z = THREE(a+b); cout << "y: " << y << endl; cout << "z: " << z << endl; return 0; }

Wynik
y: z: y: z: 125 125 1728 82

Analiza W linii 4. zostao zdefiniowane makro CUBE, w ktrym przy kadym uyciu x argumenty zostay umieszczone w nawiasach. W linii 5. zostao zdefiniowane makro THREE, w ktrym parametry s uywane bez nawiasw. W pierwszym zastosowaniu obu makr jako parametr przekazujemy warto 5; oba makra dziaaj tak, jak oczekujemy. CUBE(5) zostaje rozwinite do ( (5) * (5) * (5) ), co daje warto 125, za THREE(5) zostaje rozwinite do 5 * 5 * 5, co take daje warto 125. Jednak przy drugim uyciu, w liniach od 16. do 18., parametrem jest 5 + 7. W tym przypadku CUBE(5+7) jest rozwijane jako:
( (5+7) * (5+7) * (5+7) )

co jest obliczane jako:


( (12) * (12) * (12) )

co z kolei daje wynik 1728. Jednak makro THREE(5+7) zostaje rozwinite jako:
5 + 7 * 5 + 7 * 5 + 7

co jest obliczane jako:


5 + (35) + (35) + 7

co ostatecznie daje wynik 82.


Usunito: wzorce

Makra a funkcje i wzorce


Makra w C++ oznaczaj cztery problemy. Pierwszym jest to, e rozrastajce si makro staje si bardzo skomplikowane, gdy musi by zdefiniowane w pojedynczej linii. Co prawda, mona t lini przeduy, stosujc lewy ukonik (\), ale due makra szybko staj si zbyt trudne w zarzdzaniu. Drugi problem polega na tym, i makro jest rozwijane w kod w kadym miejscu, w ktrym zostaje uyte. To oznacza, e jeli makro zostaje uyte w tuzinie miejsc, zostanie podstawione w dwunastu miejscach kodu, nie wystpi tylko raz, tak jak w przypadku funkcji wywoywanej. Z drugiej strony, makra s zwykle szybsze ni funkcje, gdy unikaj narzutu zwizanego z wywoaniem. Fakt, i makra s rozwijane w kodzie, prowadzi do trzeciego problemu: kod makra nie pojawia si w porednim kodzie rdowym, wic wikszo kompilatorw (analizujcych kod poredni) go nie widzi. To powoduje, e debuggowanie makr jest bardzo trudne. Jednak ostatni problem jest najpowaniejszy: makra nie s bezpieczne ze wzgldu na typy. W makrze moe zosta uyty absolutnie dowolny argument, co jest sprzeczne z ide silnej kontroli typw w jzyku C++ i jest kar dla programistw tego jzyka. Oczywicie, jak mwilimy w rozdziale 19., poprawnym sposobem rozwizania tego problemu jest uycie wzorcw.
Usunito: dopiero Usunito: oryginalny

Usunito: wzorcw

Funkcje inline
Czsto zamiast uycia makra istnieje moliwo zadeklarowania funkcji inline. Na przykad, listing 21.3 zawiera funkcj Cube(), ktra wykonuje to samo, co makro CUBE z listingu 21.2, lecz jednoczenie jest bezpieczna ze wzgldu na typ. Listing 21.3. Uycie funkcji inline zamiast makra
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: #include <iostream> using namespace std; inline unsigned long Square(unsigned long a) { return a * a; } inline unsigned long Cube(unsigned long a) { return a * a * a; } int main() { unsigned long x=1 ; for (;;) { cout << "Wpisz liczbe (0 by zakonczyc): "; cin >> x; if (x == 0) break; cout << "Wpisales: " << x; cout << ". Square(" << x << "): "; cout << Square(x); cout<< ". Cube(" << x << "): "; cout << Cube(x) << "." << endl; } return 0; }

Wynik
Wpisz liczbe Wpisales: 1. Wpisz liczbe Wpisales: 2. Wpisz liczbe Wpisales: 3. Wpisz liczbe Wpisales: 4. Wpisz liczbe Wpisales: 5. Wpisz liczbe Wpisales: 6. Wpisz liczbe (0 by zakonczyc): 1 Square(1): 1. Cube(1): 1. (0 by zakonczyc): 2 Square(2): 4. Cube(2): 8. (0 by zakonczyc): 3 Square(3): 9. Cube(3): 27. (0 by zakonczyc): 4 Square(4): 16. Cube(4): 64. (0 by zakonczyc): 5 Square(5): 25. Cube(5): 125. (0 by zakonczyc): 6 Square(6): 36. Cube(6): 216. (0 by zakonczyc): 0

Analiza W liniach 3. i 4. zostay zdefiniowane dwie funkcje inline: Square() oraz Cube(). Kada z nich jest zadeklarowana jako inline, wic podobnie jak makra, zostan rozwinite w miejscu uycia, a zwizany z ich wywoaniem narzut nie pojawi si.

Przypomnijmy, e rozwijanie funkcji inline oznacza, e zawarto funkcji zostaje umieszczana w miejscu, w ktrym funkcja ma by wywoana (w tym przykadzie w liniach 17. i 19.). Poniewa nie nastpuje wywoanie funkcji, nie wystpuje take narzut spowodowany umieszczaniem na stosie parametrw funkcji i jej powrotnego adresu. W linii 17. zostaje wywoana funkcja Square(), a w linii 19. funkcja Cube(). Poniewa s to funkcje inline, zostaje wygenerowany program skompilowany tak, jak poniszy kod:
16: 17: 18: 19: cout << ". Square(" << x << "): "; cout << x * x; cout<< ". Cube(" << x << "): "; cout << x * x * x << "." << endl;

Manipulacje acuchami
Preprocesor oferuje dwa specjalne operatory przeznaczone do manipulowania acuchami w makrach. Operator zamiany na acuch (#) podstawia ujty w nawiasy acuch zamiast argumentu wystpujcego po tym operatorze. Natomiast operator konkatenacji czy ze sob dwa acuchy.
Usunito: O

Zamiana w acuch
Operator zamiany w acuch umieszcza cudzysowy dookoa wszelkich znakw tworzcych operator, a do miejsca wystpienia nastpnej biaej spacji. Zatem, jeli napiszemy:
#define WRITESTRING(x) cout << #x

a nastpnie wywoamy:
WRITESTRING(To jest lancuch);

to preprocesor zmieni to w:
cout << "To jest lancuch";

Zwr uwag, e acuch To jest lancuch zosta ujty w cudzysowy, tak jak wymaga tego cout.

Konkatenacja
Operator konkatenacji pozwala na czenie kilku fraz w pojedyncze sowo. Nowe sowo jest w rzeczywistoci symbolem, ktry moe by uyty jako nazwa klasy, nazwa zmiennej, indeks tablicy, czy inna forma, jak moe przyj cig znakw. Zamy przez moment, e mamy pi funkcji o nazwach fOnePrint, fTwoPrint, fThreePrint, fFourPrint oraz fFivePrint. Moemy wtedy zadeklarowa:
#define fPRINT(x) f ## x ## Print

a nastpnie uy makra fPRINT(Two), aby wygenerowa fTwoPrint oraz fPRINT(Three) aby wygenerowa fThreePrint. W programie podsumowujcym, zawartym w rozdziale 14., zostaa opracowana klasa PartsList. Ta klasa listy moga obsugiwa jedynie obiekty typu List. Przypumy, e ta lista dziaaa bardzo dobrze i chcielibymy za jej pomoc tworzy listy zwierzt, samochodw, komputerw i tak dalej. Jedn z moliwoci jest tworzenie klas AnimalList, CarList, ComputerList i tak dalej, poprzez wycinanie i wklejanie kod. Ta zabawa szybko staoby si koszmarem, gdy kada zmiana w licie wymagaaby uwzgldnienia jej we wszystkich innych klasach. Alternatyw moe by uycie makr i operatora konkatenacji. Na przykad, moglibymy napisa:
#define Listof(Type) class Type##List \ { \ public: \ Type##List(){} \ private: \ int itsLength; \ };

Ten przykad jest bardzo oglnikowy, ale pokazuje umieszczenie w takiej definicji wszystkich niezbdnych metod i danych. Gdy bdziemy gotowi do stworzenia klasy AnimalList, napiszemy:
Listof(Animal)

co zostanie zamienione na deklaracj klasy AnimalList. Z takim postpowaniem wi si pewne problemy, ktre zostay wyczerpujco opisane w rozdziale 19., Wzorce.

Usunito: Wzorce

Makra predefiniowane
Wiele kompilatorw predefiniuje wiele uytecznych makr, do ktrych nale __DATE__, __TIME__, __LINE__ oraz __FILE__. Kada z tych nazw jest otoczona dwoma znakami podkrelenia (w celu zmniejszenia prawdopodobiestwa wystpienia konfliktu z nazwami ktrych mgby uy w swoim programie). Gdy preprocesor natrafia na ktre z takich makr, dokonuje odpowiedniego podstawienia. Dla makra __DATE__ zostaje podstawiona bieca data. Dla makra __TIME__ zostaje podstawiony biecy czas. Makra __LINE__ i __FILE__ s zastpowane biecym numerem linii w kodzie rdowym oraz biec nazw pliku rdowego. Naley pamita, e to podstawianie odbywa si w momencie prekompilacji kodu, a nie podczas dziaania programu. Gdy poprosisz program o wypisanie makra __DATE__, nie otrzymasz biecej daty, lecz dat kompilacji programu. Makra predefiniowane bardzo przydaj si podczas debuggowania.

Makro assert()
Wiele kompilatorw oferuje makro assert(). To makro zwraca warto TRUE, jeli jego parametr ma warto TRUE i podejmuje pewn akcj w przypadku, gdy jego parametr ma warto FALSE. Wiele kompilatorw przerywa dziaanie programu w przypadku, gdy argument tego makra nie jest speniony; inne zgaszaj wtedy wyjtek (patrz rozdzia 20., Wyjtki i obsuga bdw). Jedn z przydatnych moliwoci makra assert() jest to, e w przypadku niezdefiniowania symbolu DEBUG nie jest pod nie podstawiany aden kod. Moe ono bardzo pomc podczas tworzenia programu, a w ostatecznym programie nie wpywa na wydajno i rozmiar kodu wynikowego. Zamiast polega na dostarczanym przez kompilator makrze assert(), moemy napisa wasne makro. Listing 21.4 przedstawia proste makro ASSERT() wraz z jego zastosowaniem. Listing 21.4. Proste makro ASSERT()
0: // Listing 21.4 makro ASSERT 1: #define DEBUG 2: #include <iostream> 3: using namespace std; 4: 5: #ifndef DEBUG 6: #define ASSERT(x) 7: #else 8: #define ASSERT(x) \ 9: if (! (x)) \ 10: { \ 11: cout << "BLAD!! Asercja " << #x << " nie jest spelniona.\n"; \ 12: cout << " W linii " << __LINE__ << "\n"; \ 13: cout << " pliku " << __FILE__ << "\n"; \ 14: } Usunito: 0

15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:

#endif int main() { int x = 5; cout << "Pierwsza asercja: \n"; ASSERT(x==5); cout << "\nDruga asercja: \n"; ASSERT(x != 5); cout << "\nGotowe.\n"; return 0; }

Wynik
Pierwsza asercja: Druga asercja: BLAD!! Asercja x != 5 nie jest spelniona. W linii 24 pliku C:\WC\test1\test1.cpp Gotowe.

Analiza W linii 1. zostaje zdefiniowany symbol DEBUG. Zwykle definicji tej dokonuje si w linii polecenia kompilatora lub definiuje si j jako opcj rodowiska IDE, dziki czemu mona t definicj dowolnie wcza i wycza. W liniach od. 8 do 14. zostao zdefiniowane makro ASSERT(). Zwykle taka definicja znalazaby si w pliku nagwkowym (na przykad ASSERT.hpp), ktry zostaby doczony w pliku implementacji. W linii 5. sprawdzany jest symbol DEBUG. Jeli nie jest zdefiniowany, makro ASSERT() jest definiowane jako nie generujce adnego kodu. Jeli symbol DEBUG jest zdefiniowany, zostaj wygenerowane linie od 8. do 14. Samo makro ASSERT() jest z punktu widzenia preprocesora dug instrukcj, rozbit na kilka linii kodu. W linii 9. zostaje sprawdzona warto przekazywana jako parametr; jeli da warto FALSE, zostaj wywoane instrukcje zawarte w liniach od 11. do 13., powodujce wypisanie komunikatu bdu. Jeli przekazana warto zostanie obliczona jako TRUE, nie s podejmowane adne dziaania.
Usunito: uje

Debuggowanie za pomoc makra assert()


Podczas pisania programu czsto bdziesz mia cakowit pewno, e co jest prawdziwe: funkcja ma pewn warto, wskanik jest poprawny i tak dalej. Istot bdw jest jednak to, e co, co uwaasz za prawdziwe, w pewnych warunkach takie nie jest. Na przykad, moesz mie pewno, e wskanik jest poprawny, a mimo to program si zaamuje. Makro assert() moe pomc w znajdowaniu tego rodzaju bdw, ale tylko wtedy, gdy bdziesz regularnie stosowa to makro w swoim kodzie. Za kadym razem, gdy przypisujesz warto do wskanika lub przekazujesz

Usunito: m

wskanik jako argument lub warto zwrotn funkcji, pamitaj o sprawdzeniu makrem assert(), czy ten wskanik jest poprawny. Za kadym razem, gdy twj kod jest zaleny od tego, czy w zmiennej znajduje si odpowiednia warto, upewnij si za pomoc makra assert(), e faktycznie jest w niej ta warto. Z korzystaniem z makra assert() nie wie si aden narzut, gdy w ostatecznej wersji programu jest ono usuwane. Przypomina ono czytelnikowi kodu o tym, co uwaae za prawdziwe w danym momencie dziaania kodu.

Makro assert() a wyjtki


W poprzednim rozdziale przekonae si, jak mona obsugiwa bdy, korzystajc z wyjtkw. Naley zdawa sobie spraw, e makro assert() nie jest przeznaczone do obsugi bdw czasu dziaania programu, takich jak bdne dane, brak pamici, niemono otwarcia pliku i tak dalej. Makro to jest tworzone wycznie w celu wychwycenia bdw programisty. Tak wic, jeli makro assert() zadziaa, wiesz, e w twoim kodzie tkwi bd. Jest to wane, poniewa gdy dostarczasz kod swoim klientom, nie zawiera on rozwinitych makr programu, gdy po prostu tych makr nie bdzie.
assert(). Nie moesz wic oczekiwa, e obsu problemy pojawiajce si podczas dziaania

Czstym bdem jest uycie makra assert() w celu sprawdzania wartoci zwracanej z przypisania pamici:
Animal *pCat = new Cat; Assert(pCat); // ze uycie makra assert() pCat->SomeFunction();

Jest to klasyczny bd programisty; za kadym razem, gdy uruchamia on program, dostpna jest wystarczajca ilo pamici i makro assert() nigdy si nie uaktualnia. W kocu programista ma nowoczesny komputer z mnstwem dodatkowej pamici RAM, zoptymalizowany tak, by przyspieszy dziaanie kompilatora, debuggera i tak dalej. Nastpnie programista dostarcza klientom plik wykonywalny, a biedny uytkownik, posiadajcy duo mniej pamici RAM, dochodzi do miejsca, w ktrym wywoanie new si nie udaje i zostaje zwrcona warto NULL. Jednak makra assert() nie ma ju w kodzie i nic nie wskazuje na to, e wskanik ma warto NULL. Gdy tylko zostanie podjta prba wykonania instrukcji pCat->SomeFunction(), program si zaamie. Otrzymanie wartoci NULL z operatora przypisania pamici nie jest bdem programistycznym, cho jest sytuacj wyjtkow. Twj program musi by w stanie poradzi sobie z tak sytuacj, choby tylko poprzez zgoszenie wyjtku. Pamitaj: gdy symbol DEBUG nie jest zdefiniowany, caa instrukcja makra assert() znika. Wyjtki zostay szczegowo opisane w rozdziale 20.

Efekty uboczne
Do czsto zdarza si, e bd wystpuje tylko wtedy, gdy egzemplarze makra assert() zostan usunite. Dzieje si tak prawie zawsze, gdy dziaanie programu zaley od efektw ubocznych wynikajcych ze stosowania makra assert() i innego kodu zwizanego wycznie z debuggowaniem. Na przykad, jeli napiszesz:
ASSERT(x = 5)

(gdy miae na myli sprawdzenie czy x == 5), stworzysz szczeglnie nieprzyjemny bd. Przypumy, e tu przed makrem assert() wywoujesz funkcj, ktra ustawia x na zero. Przy takiej asercji moesz uwaa, e sprawdzasz, czy x jest rwne 5, jednak w rzeczywistoci przypisujesz warto do tej zmiennej 5. Test zwraca warto TRUE, gdy x = 5 nie tylko ustawia zmienn x, ale take zwraca warto 5, a poniewa ta warto jest rna od zera, wic cae wyraenie ma warto odpowiadajc wartoci TRUE. Gdy wykonujesz makro assert(), x rzeczywicie ma warto 5 (w kocu to ty dokonae tego przypisania!). Program dziaa poprawnie. Jeste gotw do rozpowszechnienia go, wic wyczasz opcj debuggowania. W tym momencie makro assert() znika i zmiennej x nie jest ju przypisywana warto 5. Poniewa tu przed tym zmienna x bya ustawiana na zero, ta warto w niej pozostaje i program si zaamuje. Wtedy ponownie wczasz debuggowanie i staje si cud! Bd znikn. To bardzo zabawna sytuacja, ale tylko dla kogo, kto nie musi sam sobie z ni radzi, wic bardzo uwaaj na efekty uboczne w kodzie dla debuggowania. Jeli napotkasz bd wystpujcy tylko przy wyczonym debuggowaniu, dokadnie przejrzyj swj kod debuggowania czy nie ma w nim niepodanych efektw ubocznych.
Usunito: j Usunito: e Usunito: z

Niezmienniki klas
Wikszo klas posiada pewne warunki, ktre, gdy ju zostanie zakoczona jaka metoda klasy, powinny by zawsze prawdziwe. Te niezmienniki klasy s warunkami sine qua non klasy. Na przykad, moemy zaoy, e obiekt CIRCLE nigdy nie ma promienia o wartoci ujemnej lub e obiekt klasy ANIMAL zawsze powinien mie wiek wikszy od zera i mniejszy od stu. Bardzo pomocne moe okaza si zadeklarowanie metody Invariants() (niezmienniki) zwracajcej warto TRUE tylko wtedy, gdy kady z niezmiennikw klasy jest speniony. Wtedy na pocztku i kocu kadej metody moesz uy konstrukcji ASSERT(Invariants()). Nie dotyczy to sytuacji, w ktrej metoda Invariants() moe nie zwrci wartoci TRUE, np. przed wykonaniem konstruktora lub po wykonaniu destruktora. Listing 21.5 demonstruje uycie metody Invariants() w standardowej klasie. Listing 21.5. Uycie metody Invariants()
0: #define DEBUG Usunito: Usunito: do Usunito: e Usunito: y Usunito: zawsze Usunito: Wyjtkiem bdzie

1: #define SHOW_INVARIANTS 2: #include <iostream> 3: #include <string.h> 4: using namespace std; 5: 6: #ifndef DEBUG 7: #define ASSERT(x) 8: #else 9: #define ASSERT(x) \ 10: if (! (x)) \ 11: { \ 12: cout << "BLAD!! Asercja " << #x << " nie jest spelniona.\n"; \ 13: cout << " W linii " << __LINE__ << "\n"; \ 14: cout << " w pliku " << __FILE__ << "\n"; \ 15: } 16: #endif 17: 18: 19: const int FALSE = 0; 20: const int TRUE = 1; 21: typedef int BOOL; 22: 23: 24: class String 25: { 26: public: 27: // konstruktory 28: String(); 29: String(const char *const); 30: String(const String &); 31: ~String(); 32: 33: char & operator[](int offset); 34: char operator[](int offset) const; 35: 36: String & operator= (const String &); 37: int GetLen()const { return itsLen; } 38: const char * GetString() const { return itsString; } 39: BOOL Invariants() const; 40: 41: private: 42: String (int); // prywatny konstruktor 43: char * itsString; 44: // unsigned short itsLen; 45: int itsLen; 46: }; 47: 48: // domylny konstruktor tworzy acuch o dugoci zera bajtw 49: String::String() 50: { 51: itsString = new char[1]; 52: itsString[0] = '\0'; 53: itsLen=0; 54: ASSERT(Invariants()); 55: } 56: 57: // prywatny (pomocniczy) konstruktor, uywany tylko przez 58: // metody klasy do tworzenia nowego acucha 59: // o danej dugoci, wypenionego znakami null. 60: String::String(int len)

61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121:

{ itsString = new char[len+1]; for (int i = 0; i<=len; i++) itsString[i] = '\0'; itsLen=len; ASSERT(Invariants()); } // Konwertuje tablic znakw w obiekt String String::String(const char * const cString) { itsLen = strlen(cString); itsString = new char[itsLen+1]; for (int i = 0; i<itsLen; i++) itsString[i] = cString[i]; itsString[itsLen]='\0'; ASSERT(Invariants()); } // konstruktor kopiujcy String::String (const String & rhs) { itsLen=rhs.GetLen(); itsString = new char[itsLen+1]; for (int i = 0; i<itsLen;i++) itsString[i] = rhs[i]; itsString[itsLen] = '\0'; ASSERT(Invariants()); } // destruktor, zwalnia zaalokowan pami String::~String () { ASSERT(Invariants()); delete [] itsString; itsLen = 0; } // operator przypisania, zwalnia istniejc pami, // po czym kopiuje acuch i rozmiar String& String::operator=(const String & rhs) { ASSERT(Invariants()); if (this == &rhs) return *this; delete [] itsString; itsLen=rhs.GetLen(); itsString = new char[itsLen+1]; for (int i = 0; i<itsLen;i++) itsString[i] = rhs[i]; itsString[itsLen] = '\0'; ASSERT(Invariants()); return *this; } // nie const operator indeksu char & String::operator[](int offset) { ASSERT(Invariants()); if (offset > itsLen) { Usunito: i

122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182:

ASSERT(Invariants()); return itsString[itsLen-1]; } else { ASSERT(Invariants()); return itsString[offset]; }

// const operator indeksu char String::operator[](int offset) const { ASSERT(Invariants()); char retVal; if (offset > itsLen) retVal = itsString[itsLen-1]; else retVal = itsString[offset]; ASSERT(Invariants()); return retVal; } BOOL String::Invariants() const { #ifdef SHOW_INVARIANTS cout << "String OK "; #endif return ( (itsLen && itsString) || (!itsLen && !itsString) ); } class Animal { public: Animal():itsAge(1),itsName("John Q. Animal") {ASSERT(Invariants());} Animal(int, const String&); ~Animal(){} int GetAge() { ASSERT(Invariants()); return itsAge;} void SetAge(int Age) { ASSERT(Invariants()); itsAge = Age; ASSERT(Invariants()); } String& GetName() { ASSERT(Invariants()); return itsName; } void SetName(const String& name) { ASSERT(Invariants()); itsName = name; ASSERT(Invariants()); } BOOL Invariants(); private: int itsAge; String itsName; };

183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208:

Animal::Animal(int age, const String& name): itsAge(age), itsName(name) { ASSERT(Invariants()); } BOOL Animal::Invariants() { #ifdef SHOW_INVARIANTS cout << "Animal OK "; #endif return (itsAge > 0 && itsName.GetLen()); } int main() { Animal sparky(5,"Sparky"); cout << "\n" << sparky.GetName().GetString() << " ma "; cout << sparky.GetAge() << " lat."; sparky.SetAge(8); cout << "\n" << sparky.GetName().GetString() << " ma "; cout << sparky.GetAge() << " lat."; return 0; }

Wynik
String String String Animal Animal Sparky Sparky OK OK OK OK OK ma ma String String String String OK String OK String OK String OK String OK OK OK String OK String OK String OK String OK OK

Animal OK 5 lat.Animal OK Animal OK Animal OK Animal OK 8 lat.String OK

Analiza W liniach od 9. do 15. zostao zdefiniowane makro ASSERT(). Gdy jest zdefiniowany symbol DEBUG, i gdy argument makra assert() przyjmie warto FALSE, wypisany zostanie komunikat bdu. W linii 30. zostaa zadeklarowana metoda Invariants() klasy String; jej definicja znajduje si w liniach od 145. do 151. Konstruktor jest zdefiniowany w liniach od 49. do 55.; w linii 54., po penym skonstruowaniu obiektu (w celu potwierdzenia poprawnego konstruowania) zostaje wywoana metoda Invariants(). Ten wzr powtarza si take dla innych konstruktorw, lecz destruktor wywouje metod przed podjciem jakich dziaa i ponownie przed powrotem z funkcji. To zapewnia nam zgodno z fundamentaln zasad jzyka C++: funkcje skadowe inne ni konstruktory i destruktory powinny operowa na poprawnie skonstruowanych obiektach i powinny pozostawia je w poprawnym stanie.
Invariants() tylko przed zniszczeniem obiektu. Pozostae funkcje klasy wywouj t metod
Usunito: to w przypadku nie spenienia warunku, Usunito: uje Usunito: 3 Usunito: 0

W linii 178. klasa Animal deklaruje wasn metod Invariants(), zaimplementowan w liniach od 191. do 197. Zwr uwag, e w liniach 157., 160., 163. oraz 165. funkcje inline take mog wywoywa metod Invariants().

Usunito: 6 Usunito: 89 Usunito: 5 Usunito: 5

Wypisywanie wartoci tymczasowych


Po sprawdzeniu za pomoc makra assert(), czy wszystkie warunki zostay spenione, moemy zechcie take wypisa biece wartoci znacznikw, zmiennych i acuchw. To moe by bardzo przydatne przy sprawdzaniu naszych zaoe co do postpowania programu i lokalizowaniu bdw przekroczenia zakresw w ptlach. Ide t ilustruje listing 21.6. Listing 21.6. Wypisywanie wartoci w trybie debuggowania
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: // Listing 21.6 - Wypisywanie wartoci w trybie debuggowania #include <iostream> using namespace std; #define DEBUG #ifndef DEBUG #define PRINT(x) #else #define PRINT(x) \ cout << #x << ":\t" << x << endl; #endif enum BOOL { FALSE, TRUE } ; int main() { int x = 5; long y = 73898l; PRINT(x); for (int i = 0; i < x; i++) { PRINT(i); } PRINT (y); PRINT("Hi."); int *px = &x; PRINT(px); PRINT (*px); return 0; }

Usunito: 58 Usunito: 1 Usunito: 3 Usunito: e

Wynik
x: i: i: i: i: i: 5 0 1 2 3 4

y: "Hi.": px: *px:

73898 Hi. 0x2100 5

Analiza Makro w liniach od 5. do 10. zapewnia wypisanie biecej wartoci dostarczonego mu parametru. Zwr uwag, e najpierw dostarczana jest zamieniona na acuch wersja parametru, tj. jeli dostarczymy x, otrzymujemy "x". Nastpnie cout otrzymuje ujty w cudzysowy acuch, ":\t", ktry powoduje wypisanie dwukropka oraz odsunicie odpowiadajce wielkoci znakowi tabulacji. Po trzecie, cout otrzymuje warto parametru (x) oraz ostatecznie endl, ktre powoduje przejcie do nowej linii i oprnienie bufora. W swoich wynikach moesz mie otrzyma inn ni 0x2100.
Usunito: u

Poziomy debuggowania
W duych, zoonych projektach moemy zada wikszej kontroli, nie tylko wczania lub wyczania trybu debuggowania. Moemy definiowa poziomy debuggowania i sprawdza je decydujc, ktrego makra uy, a ktre pomin. Aby zdefiniowa poziom, po prostu wpisz liczb po instrukcji #define DEBUG. Cho mona dysponowa dowoln iloci poziomw, jednak najczciej stosuje si cztery: HIGH (wysoki), MEDIUM (poredni), LOW (niski) oraz NONE (bez debuggowania). Sposb zastosowania tych poziomw ilustruje listing 21.7, wykorzystane zostay w tym celu klasy String i Animal z listingu 21.5. Listing 21.7. Poziomy debuggowania
0: enum LEVEL { NONE, LOW, MEDIUM, HIGH }; 1: const int FALSE = 0; 2: const int TRUE = 1; 3: typedef int BOOL; 4: 5: #define DEBUGLEVEL HIGH 6: 7: #include <iostream.h> 8: #include <string.h> 9: 10: #if DEBUGLEVEL < LOW // musi by MEDIUM lub HIGH 11: #define ASSERT(x) 12: #else 13: #define ASSERT(x) \ 14: if (! (x)) \ 15: { \ 16: cout << "BLAD!! Asercja " << #x << " nie jest spelniona.\n"; \ 17: cout << " W linii " << __LINE__ << "\n"; \

18: cout << " w pliku " << __FILE__ << "\n"; \ 19: } 20: #endif 21: 22: #if DEBUGLEVEL < MEDIUM 23: #define EVAL(x) 24: #else 25: #define EVAL(x) \ 26: cout << #x << ":\t" << x << endl; 27: #endif 28: 29: #if DEBUGLEVEL < HIGH 30: #define PRINT(x) 31: #else 32: #define PRINT(x) \ 33: cout << x << endl; 34: #endif 35: 36: 37: class String 38: { 39: public: 40: // konstruktory 41: String(); 42: String(const char *const); 43: String(const String &); 44: ~String(); 45: 46: char & operator[](int offset); 47: char operator[](int offset) const; 48: 49: String & operator= (const String &); 50: int GetLen()const { return itsLen; } 51: const char * GetString() const 52: { return itsString; } 53: BOOL Invariants() const; 54: 55: private: 56: String (int); // prywatny konstruktor 57: char * itsString; 58: unsigned short itsLen; 59: }; 60: 61: // domylny konstruktor tworzy acuch pusty (o dugoci 0 bajtw) 62: String::String() 63: { 64: itsString = new char[1]; 65: itsString[0] = '\0'; 66: itsLen=0; 67: ASSERT(Invariants()); 68: } 69: 70: // prywatny (pomocniczy) konstruktor, uywany tylko przez 71: // metody klasy do tworzenia nowego acucha 72: // o danej dugoci, wypenionego znakami null. 73: String::String(int len) 74: { 75: itsString = new char[len+1]; 76: for (int i = 0; i<=len; i++) 77: itsString[i] = '\0';

Usunito: zera

78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138:

itsLen=len; ASSERT(Invariants()); } // Konwertuje tablic znakw w obiekt String String::String(const char * const cString) { itsLen = strlen(cString); itsString = new char[itsLen+1]; for (int i = 0; i<itsLen; i++) itsString[i] = cString[i]; itsString[itsLen]='\0'; ASSERT(Invariants()); } // konstruktor kopiujcy String::String (const String & rhs) { itsLen=rhs.GetLen(); itsString = new char[itsLen+1]; for (int i = 0; i<itsLen;i++) itsString[i] = rhs[i]; itsString[itsLen] = '\0'; ASSERT(Invariants()); } // destruktor, zwalnia zaalokowan pami String::~String () { ASSERT(Invariants()); delete [] itsString; itsLen = 0; } // operator przypisania, zwalnia istniejc pami, // po czym kopiuje acuch i rozmiar String& String::operator=(const String & rhs) { ASSERT(Invariants()); if (this == &rhs) return *this; delete [] itsString; itsLen=rhs.GetLen(); itsString = new char[itsLen+1]; for (int i = 0; i<itsLen;i++) itsString[i] = rhs[i]; itsString[itsLen] = '\0'; ASSERT(Invariants()); return *this; } // nie const operator indeksu char & String::operator[](int offset) { ASSERT(Invariants()); if (offset > itsLen) { ASSERT(Invariants()); return itsString[itsLen-1]; } else Usunito: i

139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199:

{ ASSERT(Invariants()); return itsString[offset]; } } // const operator indeksu char String::operator[](int offset) const { ASSERT(Invariants()); char retVal; if (offset > itsLen) retVal = itsString[itsLen-1]; else retVal = itsString[offset]; ASSERT(Invariants()); return retVal; } BOOL String::Invariants() const { PRINT("(Niezmienniki klasy String sprawdzone)"); return ((BOOL)(itsLen&&itsString)||(!itsLen&&!itsString)); } class Animal { public: Animal():itsAge(1),itsName("John Q. Animal") {ASSERT(Invariants());} Animal(int, const String&); ~Animal(){} int GetAge() { ASSERT(Invariants()); return itsAge; } void SetAge(int Age) { ASSERT(Invariants()); itsAge = Age; ASSERT(Invariants()); } String& GetName() { ASSERT(Invariants()); return itsName; } void SetName(const String& name) { ASSERT(Invariants()); itsName = name; ASSERT(Invariants()); } BOOL Invariants(); private:

200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230:

int itsAge; String itsName; }; Animal::Animal(int age, const String& name): itsAge(age), itsName(name) { ASSERT(Invariants()); } BOOL Animal::Invariants() { PRINT("(Niezmienniki klasy Animal sprawdzone)"); return (itsAge > 0 && itsName.GetLen()); } int main() { const int AGE = 5; EVAL(AGE); Animal sparky(AGE,"Sparky"); cout << "\n" << sparky.GetName().GetString(); cout << " ma "; cout << sparky.GetAge() << " lat."; sparky.SetAge(8); cout << "\n" << sparky.GetName().GetString(); cout << " ma "; cout << sparky.GetAge() << " lat."; return 0; }

Wynik
AGE: 5 (Niezmienniki (Niezmienniki (Niezmienniki (Niezmienniki (Niezmienniki (Niezmienniki (Niezmienniki (Niezmienniki (Niezmienniki (Niezmienniki (Niezmienniki (Niezmienniki (Niezmienniki (Niezmienniki (Niezmienniki (Niezmienniki (Niezmienniki klasy klasy klasy klasy klasy klasy klasy klasy klasy klasy klasy klasy klasy klasy klasy klasy klasy String String String String String String String String String String String String String String Animal String Animal sprawdzone) sprawdzone) sprawdzone) sprawdzone) sprawdzone) sprawdzone) sprawdzone) sprawdzone) sprawdzone) sprawdzone) sprawdzone) sprawdzone) sprawdzone) sprawdzone) sprawdzone) sprawdzone) sprawdzone)

Sparky ma (Niezmienniki klasy Animal sprawdzone) 5 lat.(Niezmienniki klasy Animal sprawdzone) (Niezmienniki klasy Animal sprawdzone) (Niezmienniki klasy Animal sprawdzone)

Sparky ma (Niezmienniki klasy Animal sprawdzone) 8 lat.(Niezmienniki klasy String sprawdzone) // Uruchomiony ponownie z DEBUG = MEDIUM AGE: 5 Sparky ma 5 lat. Sparky ma 8 lat.

Analiza W liniach od 10. do 20. makro ASSERT() zostao zdefiniowane tak, by w sytuacji, w ktrej symbol DEBUGLEVEL jest mniejszy od LOW (tj. gdy jest zdefiniowany jako NONE), nie generowao adnego kodu. Gdy jest wczony jakikolwiek wyszy poziom debuggowania, makro ASSERT() dziaa. W linii 22. makro EVAL() jest zdefiniowane jako nie generujce adnego kodu, gdy symbol DEBUGLEVEL jest mniejszy od MEDIUM; gdy DEBUGLEVEL ma warto NONE lub LOW, makro EVAL() jest pomijane. Na koniec, w liniach od 29. do 34. makro PRINT() jest deklarowane jako pomijane, gdy DEBUGLEVEL ma warto mniejsz ni HIGH. Makro PRINT() jest uywane tylko wtedy, gdy symbol DEBUGLEVEL jest zdefiniowany jako HIGH; moemy je wyeliminowa i jednoczenie korzysta z makr EVAL() i ASSERT(), definiujc symbol DEBUGLEVEL jako MEDIUM. Makro PRINT() jest uywane w metodach Invariants() do wypisywania komunikatu informacyjnego. Makro EVAL() zostao uyte w linii 220. do obliczenia biecej wartoci staej cakowitej AGE. TAK NIE
Usunito: 1 Usunito: 1

Usunito: 30 Usunito: 5

W nazwach makr uywaj WIELKICH LITER. Nie pozwalaj, by twoje makra debuggowania To powszechnie stosowana konwencja i jeli nie powodoway efekty uboczne. Nie inkrementuj w bdziesz jej przestrzega, wprowadzisz w bd nich zmiennych, ani nie przypisuj im wartoci. innych programistw. W funkcjach makro umieszczaj wszystkie argumenty w okrgych nawiasach.

Operacje na bitach
Czsto zdarza si, e musimy ustawi znaczniki w obiekcie, aby na bieco zna stan tego obiektu. (Czy jest w stanie alarmu? Czy zosta ju zainicjalizowany?) Mona to osign, korzystajc z wasnych zmiennych logicznych, ale przy duej iloci znacznikw i w przypadkach, gdy ilo zajmowanej pamici ma due znaczenie, wygodne jest uywanie jako znacznikw poszczeglnych bitw.

Kady bajt zawiera osiem bitw, wic czterobajtowy typ long moe zmieci trzydzieci dwa osobne znaczniki. Mwimy, e bit jest ustawiony, gdy ma warto 1, a wyzerowany gdy ma jak mona si domyli warto 0. Bity moemy ustawia i zerowa zmieniajc warto typu long, ale moe to by mudna i podatna na bdy operacja.

Usunito: w

UWAGA Dodatkowe informacje na temat systemu dwjkowego i szesnastkowego znajdziesz w dodatku A.

C++ dostarcza operatorw bitowych, ktre dziaaj na poszczeglnych bitach. Wygldaj one podobnie (cho s czym innym) , jak operatory logiczne, wic wielu pocztkujcych programistw myli je ze sob. Operatory bitowe zostay przedstawione w tabeli 21.1. Tabela 21.1. Operatory bitowe Symbol
& | ^ ~

Operator AND OR XOR Negacja bitowa

Operator AND
Operator bitowy AND (I) to pojedynczy znak ampersand (&) w odrnieniu od logicznego AND, ktrego symbol skada si z dwch takich znakw. Gdy wykonujemy operacj AND na dwch bitach, wynikiem jest 1, gdy oba bity s ustawione, a w przeciwnym przypadku zero. Gdy jeden lub oba bity s wyzerowane, wynikiem operatora AND jest zero.

Operator OR
Drugim operatorem bitowym jest operator OR (LUB). Take ten operator skada si z pojedynczego znaku (|) w odrnieniu od operatora logicznego, ktry skada si z dwch znakw (||). Gdy wykonujemy operacj OR na dwch bitach, wynikiem jest 1, gdy jeden lub oba bity s ustawione. Wynik zero otrzymujemy tylko wtedy, gdy oba bity s wyzerowane.

Operator XOR
Trzeci operator bitowy to XOR (WYCZNIE-LUB), ktrego symbolem jest tzw. daszek (^). Gdy XOR-ujemy dwa bity, otrzymujemy wynik 1wtedy, gdy oba bity maj rne od siebie stany. Gdy stan obu bitw jest jednakowy, otrzymujemy wynik zero.

Operator negacji
Operator negacji, oznaczony znakiem tyldy (~), zmienia stan kadego z bitw w swoim argumencie na przeciwny. Jeli biec wartoci argumentu jest 1010 0011, to po negacji wartoci t bdzie 0101 1100.

Ustawianie bitw
Gdy chcemy ustawi lub wyzerowa okrelony bit, uywamy maski bitowej. Gdy mamy czterobajtowy znacznik i chcemy ustawi bit 8, musimy wykona operacj OR z wartoci maski wynoszc 128. Dlaczego? 128 to binarnie 1000 0000, wic warto smego bitu wynosi 128. Bez wzgldu na biec warto tego bitu, gdy wykonamy operacj OR z wartoci 128, ustawimy ten bit, nie zmieniajc stanu adnego innego bitu. Zamy, e biec wartoci bitow naszego argumentu jest 1010 0110 0010 0110. Wykonanie operacji OR z wartoci 128 bdzie wtedy wyglda nastpujco:
8765 4321 1010 0110 0010 0110 // bit 8 jest wyzerowany | 0000 0000 1000 0000 // warto 128 ====================== 1010 0110 1010 0110 // bit 8 jest ustawiony

Zwr uwag na kilka rzeczy. Po pierwsze, jak zwykle, bity s liczone od strony prawej do lewej. Po drugie, warto 128 ma ustawiony tylko jeden bit, smy, czyli ten, ktry chcielimy ustawi w argumencie. Po trzecie, warto bitw w argumencie 1010 0110 0010 0110, z wyjtkiem ustawienia smego bitu, nie zmienia si w wyniku przeprowadzenia operacji OR. Gdyby bit smy by ju wczeniej ustawiony, wtedy pozostaby ustawiony tak, jakbymy sobie tego yczyli.

Zerowanie bitw
Gdy zechcemy wyzerowa bit smy, moemy uy operacji AND z zanegowan wartoci 128. Negacj wartoci 128 jest warto, ktr otrzymamy, zmieniajc stan wszystkich bitw tej wartoci (1000 0000) na przeciwny, tj. na warto 0111 1111. Gdy uyjemy tej maski w operacji AND z argumentem, pozostawimy bez zmian wszystkie bity, poza bitem smym, ktry zostanie wyzerowany.

8765 4321 1010 0110 1010 0110 // bit 8 jest ustawiony & 1111 1111 0111 1111 // warto ~128 ====================== 1010 0110 0010 0110 // bit 8 jest wyzerowany

Aby w peni zrozumie ten proces, wykonaj obliczenia sam. Za kadym razem, gdy oba bity s jedynkami, zapisz w wyniku jedynk. Porwnaj wynik z pierwotnym argumentem. Powinien by taki sam, lecz z wyzerowanym smym bitem. Zwr take uwag na rozmiary argumentw. Aby uzyska ten wynik, musielimy uy szesnastobitowej maski.

Zmiana stanu bitw na przeciwny


Na koniec, gdy chcemy jedynie zmieni stan bitu smego, musimy wykona operacj XOR z argumentem 128. Tak wic:
8765 1010 0110 1010 ^ 0000 0000 1000 ================= 1010 0110 0010 ^ 0000 0000 1000 ================= 1010 0110 1010 4321 0110 0000 0110 0000 0110

// argument // warto 128 // bit 8 zmieniony // warto 128 // bit 8 ponownie zmieniony

TAK Do ustawiania bitw uywaj maski i operatora OR. Do zerowania bitw uywaj maski i operatora AND. Do negowania stanu bitw uywaj maski i operatora XOR.

NIE

Usunito: ustawiania Usunito: zmiany

Pola bitowe
W pewnych warunkach moe liczy si kady bit i zaoszczdzenie szeciu czy omiu bitw w klasie moe stanowi dla uytkownika zasadnicz rnic. Jeli klasa lub struktura zawiera seri zmiennych logicznych lub zmiennych przechowujcych tylko kilka moliwych wartoci, moemy zaoszczdzi nieco miejsca, uywajc pl bitowych. Kiedy uywamy standardowych typw danych w C++, najmniejszym typem, jaki moemy umieci w klasie, jest typ char, skadajcy si z pojedynczego bajtu. Zwykle jednak uywamy

typu int, ktry moe skada si z dwch lub czciej czterech bajtw. Uywajc pl bitowych moemy zmieci w typie char osiem wartoci bitowych, a w typie long trzydzieci dwie wartoci. Oto sposb dziaania pola bitowego: pole bitowe zostaje nazwane i jest dostpne tak samo, jak inne skadowe klasy. Ich typem jest zawsze unsigned int. Po nazwie pola bitowego zapisujemy dwukropek oraz liczb. Ta liczba informuje kompilator, ile bitw ma przydzieli zmiennej. Jeli napiszemy 1, pole bdzie reprezentowao warto zero lub jeden. Jeli napiszemy 2, bdzie mogo zawiera cznie cztery wartoci: 0, 1, 2 lub 3. Trzybitowe pole moe zawiera osiem wartoci i tak dalej. Wartoci binarne zostan przedstawione w dodatku A. Uycie pl bitowych ilustruje listing 21.8. Listing 21.8. Uycie pl bitowych
0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: #include <iostream> using namespace std; #include <string.h> enum enum enum enum STATUS { FullTime, PartTime } ; GRADLEVEL { UnderGrad, Grad } ; HOUSING { Dorm, OffCampus }; FOODPLAN { OneMeal, AllMeals, WeekEnds, NoMeals };

class student { public: student(): myStatus(FullTime), myGradLevel(UnderGrad), myHousing(Dorm), myFoodPlan(NoMeals) {} ~student(){} STATUS GetStatus(); void SetStatus(STATUS); unsigned GetPlan() { return myFoodPlan; } private: unsigned unsigned unsigned unsigned }; myStatus : 1; myGradLevel: 1; myHousing : 1; myFoodPlan : 2;

STATUS student::GetStatus() { if (myStatus) return FullTime; else return PartTime; } void student::SetStatus(STATUS theStatus) { myStatus = theStatus; } int main()

44: { 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: break; 66: break; 67: 68: 69: 70: 71: 72: }

student Jim; if (Jim.GetStatus()== PartTime) cout << "Jim pracuje na pol etatu" << endl; else cout << "Jim pracuje na caly etat" << endl; Jim.SetStatus(PartTime); if (Jim.GetStatus()) cout << "Jim pracuje na pol etatu" << endl; else cout << "Jim pracuje na caly etat" << endl; cout << "Jim " ; char Plan[80]; switch (Jim.GetPlan()) { case OneMeal: strcpy(Plan,"oplacil jeden posilek."); break; case AllMeals: strcpy(Plan,"oplacil wszystkie posilki."); case WeekEnds: strcpy(Plan,"oplacil posilki w weekend."); case NoMeals: strcpy(Plan,"nie oplacil posilkow.");break; default : cout << "Cos poszlo zle!\n"; break; } cout << Plan << endl; return 0;

Wynik
Jim pracuje na pol etatu Jim pracuje na caly etat Jim nie oplacil posilkow.

Analiza W liniach od 4. do 7. zostao zdefiniowanych kilka typw wyliczeniowych. Su one do definiowania wartoci dla pl bitowych w klasie student. Klasa student jest zadeklarowana w liniach od 9. do 28. Cho sama klasa jest do banalna, jednak interesujcy jest fakt, i wszystkie dane zostay upakowane w piciu bitach. Pierwszy bit reprezentuje prac studenta, na p etatu lub na cay etat. Drugi bit okrela, czy s to studia podyplomowe. Trzeci bit okrela, czy student mieszka w akademiku. Dwa ostatnie bity reprezentuj cztery moliwe sposoby korzystania z posikw. Metody klasy s zapisane tak, jak w kadej innej klasie i nie zmienia tego fakt, e dane klasy s polami bitowymi, a nie zmiennymi cakowitymi czy typami wyliczeniowymi. Funkcja skadowa GetStatus() odczytuje stan bitu i zwraca typ wyliczeniowy, ale nie jest to konieczne. Rwnie dobrze mogaby zwraca warto tego bitu bezporednio. Kompilator dokonaby odpowiedniej translacji automatycznie. Aby to sprawdzi, zastp implementacj funkcji GetStatus() nastpujcym kodem:

STATUS student::GetStatus() { return myStatus; }

W dziaaniu programu nie powinna nastpi adna zmiana. Jest to zagadnienie przejrzystoci programu i nie sprawia kompilatorowi adnej rnicy. Zwr uwag, e kod w linii 47. musi sprawdzi stan, a nastpnie wypisa sensowny komunikat. Chciaoby si teraz napisa:
cout << "Jim pracuje na " << Jim.GetStatus() << endl;

ale w ten sposb wypiszemy po prostu:


Jim pracuje na 0

Kompilator nie ma sposobu na translacj staej wyliczeniowej PartTime na odpowiedni tekst komunikatu. W linii 62. program przecza si (w zalenoci od sposobu opacania posikw) i dla kadej moliwej wartoci umieszcza w buforze odpowiedni komunikat, wypisywany dalej w linii 70. Zwr uwag, e instrukcja switch mogaby zosta zapisana nastpujco:
case case case case 0: 1: 2: 3: strcpy(Plan,"oplacil jeden posilek."); break; strcpy(Plan,"oplacil wszystkie posilki."); break; strcpy(Plan,"oplacil posilki w weekend."); break; strcpy(Plan,"nie oplacil posilkow.");break;

Najwaniejsz zalet uywania pl bitowych w klasie jest to, e klient klasy nie musi interesowa si szczegami implementacji przechowywania danych. Poniewa pola bitowe mog by prywatne, moemy je pniej dowolnie modyfikowa, bez potrzeby zmiany samego interfejsu.

Styl
Jak ju wczeniej wspominaem, naley przyj spjny styl kodowania. W wikszoci przypadkw nie ma znaczenia jaki to bdzie styl. Jednolity styl uatwia odgadnicie, do czego ma suy okrelony fragment kodu i uniknicie sprawdzania za kadym razem, czy nazwa funkcja rozpoczyna si od duej, czy od maej litery.

Przedstawione tu zalecenia nie s obowizkowe; zostay one oparte na wytycznych uywanych w projektach, nad ktrymi pracowaem w przeszoci i ktre si sprawdziy. Moesz stworzy wasny styl zapisu, ale styl opisany tutaj moe stanowi pewien punkt wyjcia. Jak wiadomo, uporczywe trzymanie si regu jest cech maych umysw, ale utrzymywanie spjnoci kodu jest rzecz godn pochway. Stwrz wasny styl, ale potem traktuj go, jakby zosta zesany przez boga programistw.

Wcicia
Rozmiar tabulatorw powinien by ustawiony na cztery spacje. Upewnij si, czy twj edytor stosuje dla tabulatorw cztery spacje.

Nawiasy klamrowe
Sposb uoenia nawiasw klamrowych moe powodowa najwicej kontrowersji pomidzy programistami C i C++. Oto kilka moich rad: odpowiadajce sobie nawiasy klamrowe powinny by wyrwnane w pionie; najbardziej zewntrzna para nawiasw klamrowych w definicji lub deklaracji powinna wystpowa przy lewym marginesie. Zawarte wewntrz niej instrukcje powinny by wcite. Wszystkie inne pary nawiasw klamrowych powinny by zrwnane z ich wiodcymi instrukcjami; aden kod nie powinien wystpowa w tej samej linii, co nawias klamrowy. Na przykad:
if (warunek == true) { j = k; SomeFunction(); } m++; Usunito: Pasujce Usunito: do sie Usunito: oz Usunito: m

Dugo linii
Staraj si utrzyma dugo linii tak, aby mieciy si w szerokoci ekranu. Kod znajdujcy si poza praw krawdzi ekranu zwykle zostaje przeoczony, a przesuwanie w poziomie jest irytujce. Gdy linia zostanie podzielona, dla nastpnych czci zastosuj wcicia. Sprbuj dzieli lini w sensownym miejscu i postaraj si pozostawia operator na kocu poprzedniej linii (a nie na pocztku nastpnej), tak, aby zaznaczy, e nie jest to jej koniec i e dalej pojawi si co jeszcze.
Usunito: jest

W C++ funkcje zwykle s duo krtsze ni w C, ale stare zalecenia wci s aktualne. Staraj si by funkcje byy krtkie, a take by pojedyncza funkcja nie zajmowaa wicej ni jedn stron wydruku.

Instrukcje switch
W celu zaoszczdzenia miejsca w poziomie, instrukcje switch wcinaj w nastpujcy sposb:
switch(zmienna) { case ValueOne: ActionOne(); break; case ValueTwo: ActionTwo(); break; default: assert("zla Akcja"); break; }

Tekst programu
Moesz skorzysta z kilku rad, ktre uatwi ci tworzenie kodu atwego w czytaniu. Kod atwy w czytaniu jest rwnoczenie atwy w utrzymaniu. W celu poprawienia czytelnoci kodu uywaj biaych spacji. Obiekty i tablice odnosz si do tego samego. Nie uywaj spacji w odwoaniach do obiektu (., ->, []). Operatory unarne (jednoargumentowe) s powizane ze swoimi operandami, wic nie umieszczaj pomidzy nimi spacji. Umieszczaj spacj po drugiej stronie operandu. Do operatorw unarnych nale !, ~, ++, , , * (dla wskanikw), & (adresu), sizeof. Operatory binarne powinny mie po obu stronach spacje: +, =, *, /, %, >>, <<, <, >, ==, !=, &, |, &&, ||, ?:, =, += i tak dalej. Nie uywaj spacji do wskazania kolejnoci dziaa (4+ 3*2). Umieszczaj spacje po przecinkach i rednikach, a nie przed nimi. Po obu stronach nawiasw nie powinno by spacji. Sowa kluczowe, takie jak if, powinny by oddzielone spacjami: if (a == b).
Usunito: rzeczy

Ciao komentarza powinno by oddzielone od znakw // spacj. Umieszczaj symbol wskanika lub referencji przy nazwie typu, a nie przy nazwie zmiennej:
char* foo; int& theInt;

zamiast
char *foo; int &theInt; Usunito: e

Nie deklaruj w tej samej linii wicej ni jednej zmiennej.

Nazwy identyfikatorw
Oto zalecenia dotyczce pracy z identyfikatorami. Nazwy identyfikatorw powinny by opisowe. Unikaj zaszyfrowanych skrtw. Powi czas i energi na nadawanie rzeczom nazw. Nie uywaj notacji wgierskiej. C++ jest jzykiem o silnej kontroli typw i nie ma powodu, by umieszcza typ w nazwie zmiennej. Przy typach zdefiniowanych przez uytkownika (klasach), notacja wgierska przestaje si sprawdza. Wyjtkiem moe by uycie przedrostkw dla wskanikw (p), referencji (r) oraz dla zmiennych skadowych klasy (its). Krtkie nazwy (i, p, x itd.) powinny by uywane tylko tam, gdzie ich skrtowo zwiksza czytelno kodu i gdzie ich przeznaczenie jest tak oczywiste, e nazwa opisowa jest zbdna. Dugo nazwy zmiennej powinna by proporcjonalna do jej zakresu. Spraw, by identyfikatory znacznie rniy si od siebie, tak, aby nie mona byo ich pomyli. Nazwy funkcji (lub metod) stanowi zwykle czasowniki lub poczenia czasownikw z rzeczownikiem: Search(), Reset(), FindParagraph(), ShowCursor(). Nazwy zmiennych s zwykle rzeczownikami abstrakcyjnymi, by moe z dodatkowym rzeczownikiem: count, state, windSpeed, windowHeight. Zmienne logiczne powinny by odpowiednio nazywane: windowIconized, fileIsOpen.

Pisownia nazw i zastosowanie w nich wielkich liter


W trakcie tworzenia wasnego stylu nie mona zapomnie o pisowni i stosowaniu wielkich liter. Oto kilka rad. Uywaj wielkich liter i znakw podkrelenia do oddzielenia sw w symbolach definiowanych za pomoc dyrektywy #define, takich jak SOURCE_FILE_TEMPLATE. Zauwa jednak, e w C++ stosuje si je rzadko. W wikszoci przypadkw powiniene stosowa stae i wzorce. Wszystkie inne identyfikatory powinny zawiera litery rnej wielkoci i nie powinny zawiera podkrele. Nazwy funkcji, metod, klas, typedef i nazwy struktur powinny zaczyna si od wielkiej litery. Elementy takie, jak dane skadowe lub zmienne lokalne powinny zaczyna si od maej litery. Stae wyliczeniowe powinny zaczyna si od kilku maych liter, bdcych skrtem nazwy wyliczenia. Na przykad:
enum TextStyle { tsPlain, tsBold, tsItalic, tsUnderscore };

Usunito: wzorce

Komentarze
Komentarze mog bardzo pomc w zrozumieniu programu. Czasami praca nad programem zostaje wstrzymana na kilka dni czy miesicy. W tym czasie moesz zapomnie, do czego suy dany kod lub dlaczego zosta zamieszczony. Problemy ze zrozumieniem kodu mog wystpi take wtedy, gdy czyta go kto inny. Komentarze w spjnym, dobrze przemylanym stylu mog by bardzo cenne dla uytkownika. Oto kilka porad.

O ile to moliwe, uywaj komentarzy w stylu C++ //, a nie w stylu /* */. Komentarze w stylu C (/* */) zarezerwuj dla wykomentowywania tych blokw kodu, ktre mog zawiera komentarze w stylu C++. Komentarze bardziej oglne s duo waniejsze ni komentarze opisujce szczegy procesu. Dodawaj wartociowe informacje, a nie podsumowuj kodu.
n++; // n jest zwikszane o jeden Usunito: wyszego poziomu Usunito:

Ten komentarz nie jest wart nawet czasu, jaki zajmuje jego wpisanie. Skoncentruj si na semantyce funkcji i blokw kodu. Powiedz, co robi funkcja. Wska efekty uboczne, typ parametrw oraz zwracan warto. Opisz wszystkie dokonane zaoenia (lub nie dokonane),

Usunito: y

takie jak zakada si, e n nie jest ujemne lub zwraca 1, gdy x nie jest poprawne. Przy zoonej logice, uywaj komentarzy do wskazania warunkw, ktre wystpuj w danym miejscu kodu. Uywaj penych zda ze znakami przestankowymi i wielkimi literami. Dodatkowy wysiek si opaca. Nie stosuj szyfru i nie rozpisuj si. To, co wydaje si oczywiste w momencie pisania kodu, po kilku miesicach moe by zadziwiajco niezrozumiae. Uywaj wielu pustych linii, aby pomc czytelnikowi w zrozumieniu, co si dzieje. Dziel instrukcje na logiczne grupy.

Dostp
Sposb, w jaki odwoujesz si do czci programu, take powinien by spjny. Oto moje rady. Zawsze uywaj etykiet public:, private: oraz protected:; nie polegaj na dostpie domylnym. Na pocztku umieszczaj skadowe publiczne, potem chronione, a nastpnie prywatne. Dane skadowe umieszczaj w grupach, po metodach. Konstruktory umieszczaj jako pierwsze, w odpowiedniej sekcji, a po nich umie destruktor. Przecione metody o tych samych nazwach umieszczaj obok siebie. O ile to moliwe, grupuj take funkcje akcesorw. We pod uwag alfabetyczny ukad metod w kadej z grup, postaraj si te uoy zmienne skadowe alfabetycznie. Pamitaj o alfabetycznym uoeniu nazw plikw w instrukcjach #include. Cho przy przesanianiu metody sowo kluczowe virtual jest opcjonalne, uywaj go zawsze; pomoe ci to w zapamitaniu, e metoda jest wirtualna oraz w utrzymaniu spjnoci deklaracji.

Definicje klas
Postaraj si utrzyma definicje metod w tej samej kolejnoci, w jakiej wystpuj w klasie. Dziki temu bdziesz mg je atwiej znale. Przy definiowaniu funkcji umie zwracany typ i wszystkie modyfikatory we wczeniejszej linii, tak, aby nazwa klasy i nazwa funkcji rozpoczynay si od lewego marginesu. To znacznie uatwia odszukiwanie funkcji.

Doczanie plikw
O ile to moliwe, staraj si unika doczania plikw w plikach nagwkowych. Idealnym minimum jest plik nagwkowy klasy, z ktrej zostaa wyprowadzona klasa bieca. Innymi obowizkowymi plikami nagwkowymi s pliki zawierajce deklaracje klas stanowicych obiekty skadowe klasy biecej. Klasy, ktrych obiekty s jedynie wskazywane lub uywane poprzez referencje, wymagaj jedynie deklaracji nazwy. Nie rezygnuj z doczenia pliku w pliku nagwkowym tylko dlatego, e zakadasz, e kady plik .cpp doczajcy ten nagwek bdzie docza take wszystkie potrzebne pliki.
Usunito: ego Usunito:

RADA

Wszystkie pliki nagwkowe powinny stosowa wartowniki doczania.

assert()
Uywaj czsto makra assert(). Pomaga ono w znajdowaniu bdw, ale take uatwia czytelnikowi zorientowanie si w dokonywanych zaoeniach. Oprcz tego pomaga skoncentrowa si na tym, co jest prawdziwe, a co nie.
Usunito: dowoli

const
Uywaj modyfikatora const wszdzie tam, gdzie powinien si znale: przy parametrach, zmiennych i metodach. Czsto trzeba uy metody wystpujcej w dwch wersjach: z modyfikatorem const oraz bez niego; nie traktuj tego jako wymwki, by zrezygnowa z jednej z nich. Bd bardzo uwany przy jawnym rzutowaniu z const do nie const i odwrotnie (czasem jest to jedyna metoda, aby co zrobi) i miej pewno, e to ma sens. Opisz to take komentarzem.

Nastpne kroki
Przeczytae ju ponad dwadziecia dugich rozdziaw, pracujc z C++ i jeste ju kompetentnym programist tego jzyka. Nie myl jednak, e na tym zakoczye edukacj. Jest jeszcze wiele miejsc, w ktrych moesz zyska cenne informacje, mogce pomc ci w przebyciu drogi od nowicjusza do eksperta w programowaniu. Nastpne podrozdziay przedstawi ci kilka specyficznych rde informacji; zalecam tu jedynie te rda, z ktrych sam korzystaem. Na kady z tych tematw napisano jednak tuziny ksiek, wic zanim je kupisz, postaraj si zasign opinii.

Gdzie uzyska pomoc i porad


Pierwsz rzecz, jak powiniene zrobi jako programista C++, jest doczenie do jednej z grup dyskusyjnych w Internecie. Te grupy umoliwiaj natychmiastowy kontakt z setkami lub tysicami programistw C++, ktrzy mog odpowiedzie na twoje pytanie, zaoferowa rad lub oceni twoje pomysy. Uczestnicz w internetowych grupach dyskusyjnych C++ (comp.lang.c++ oraz comp.lang.c++.moderated) i polecam je jako wymienite rdo informacji i porad. Oprcz tego, moesz poszuka lokalnych grup uytkownikw. W wielu miastach istniej grupy osb zainteresowanych C++, moesz spotka tam innych programistw i wymienia z nimi pogldy.
Usunito: ji

Przej do C#?
Nowa platforma .Net Microsoftu radykalnie zmienia sposb, w jaki wielu z nas tworzy programy dla Internetu. Kluczowym komponentem .Net jest nowy jzyk C#. C# jest naturalnym rozszerzeniem C++, wic dla programistw C++ przejcie do tego jzyka nie stanowi wikszego problemu. Istnieje kilka dobrych ksiek o C#, lecz mam nadziej, e signiesz do mojej najnowszej ksiki: Programming C# (OReilly press).

Bd w kontakcie
Jeli masz komentarze, sugestie lub pomysy dotyczce tej lub innych ksiek, bardzo chciabym si o nich dowiedzie. Skontaktuj si ze mn poprzez moj witryn WWW www.libertyassociates.com. Czekam na twoje wiadomoci. TAK Zagldaj do innych ksiek. Jedna ksika nie nauczy ci wszystkiego, co powiniene wiedzie. Przycz si do dobrej grupy dyskusyjnej zwizanej z C++. NIE Samo czytanie kodu nie wystarcza! Najlepsz metod nauczenia si C++ jest pisanie programw w tym jzyku.
Usunito: Nie czytaj w

Program podsumowujcy wiadomoci


{uwaga korekta: to jest zawarto rozdziau Week 3 In Review } W tym programie podsumowujcym zostao zebranych w cao wiele zagadnie omawianych w poprzednich rozdziaach. Program zawiera opart na wzorcach list poczon, w ktrej zastosowano take obsug wyjtkw. Przejrzyj go dokadnie; jeli w peni go zrozumiesz, oznacza to, e jeste programist C++.

Usunito: wzorcach

Usunito: wzorcw

OSTRZEENIE Jeli twj kompilator nie obsuguje wzorcw lub nie obsuguje blokw try i catch, nie bdziesz mg skompilowa i uruchomi tego programu.

Listing 21.9. Program podsumowujcy wiadomoci


0: // ************************************************** 1: // 2: // Tytu: Program podsumowujcy numer 3 3: // 4: // Plik: 4eList2109 5: // 6: // Opis: Program demonstruje tworzenie listy poczonej 7: // oraz obsug wyjtkw 8: // 9: // Klasy: PART - zawiera numery czci oraz ewentualnie inne 10: // informacje o czciach. Jest to przykadowa 11: // klasa przechowywana w licie. 12: // Zwr uwag na uycie operatora<< do wypisywania 13: // informacji o czci w oparciu o jej typ, 14: // sprawdzany podczas dziaania programu. 15: // 16: // Node - peni rol wza listy 17: // 18: // List - oparta na wzorcu lista, ktra zapewnia 19: // mechanizm dla listy poczonej 20: // 21: // 22: // Autor: Jesse Liberty (jl) 23: // 24: // Opracowane na: Pentium 200 Pro. 128MB RAM MVC 5.0 25: // 26: // Cel: Niezalene od platformy 27: // 28: // Historia wersji: 9/94 - pierwsze wydanie (jl) 29: // 4/97 - aktualizacja (jl) 30: // ************************************************** 31: 32: #include <iostream>

Usunito: wzorcu

33: using namespace std; 34: 35: // klasy wyjtkw 36: class Exception {}; 37: class OutOfMemory : public Exception{}; 38: class NullNode : public Exception{}; 39: class EmptyList : public Exception {}; 40: class BoundsError : public Exception {}; 41: 42: 43: // **************** Part ************ 44: // Abstrakcyjna klasa bazowa czci 45: class Part 46: { 47: public: 48: Part():itsObjectNumber(1) {} 49: Part(int ObjectNumber):itsObjectNumber(ObjectNumber){} 50: virtual ~Part(){}; 51: int GetObjectNumber() const { return itsObjectNumber; } 52: virtual void Display() const =0; // musi by przesonite 53: 54: private: 55: int itsObjectNumber; 56: }; 57: 58: // implementacja czystej funkcji wirtualnej, dziki czemu 59: // mog z niej korzysta klasy pochodne 60: void Part::Display() const 61: { 62: cout << "\nNumer czesci: " << itsObjectNumber << endl; 63: } 64: 65: // Ten operator<< bdzie wywoywany dla wszystkich obiektw czci. 66: // Nie musi by funkcj zaprzyjanion, gdy nie odwouje si do 67: // prywatnych danych. Wywouje Display(), ktra uywa 68: // polimorfizmu. Chcielibymy mc przesoni go w oparciu o rzeczywisty 69: // typ zmiennej thePart, ale C++ nie obsuguje kontrawariancji. 70: ostream& operator<<( ostream& theStream,Part& thePart) 71: { 72: thePart.Display(); // wirtualna kontrawariancja! 73: return theStream; 74: } 75: 76: // **************** Cz samochodu ************ 77: class CarPart : public Part 78: { 79: public: 80: CarPart():itsModelYear(94){} 81: CarPart(int year, int partNumber); 82: int GetModelYear() const { return itsModelYear; } 83: virtual void Display() const; 84: private: 85: int itsModelYear; 86: }; 87: 88: CarPart::CarPart(int year, int partNumber): 89: itsModelYear(year), 90: Part(partNumber) 91: {}

Usunito: c

92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152:

void CarPart::Display() const { Part::Display(); cout << "Rok modelu: " << itsModelYear << endl; } // **************** Cz samolotu ************ class AirPlanePart : public Part { public: AirPlanePart():itsEngineNumber(1){}; AirPlanePart(int EngineNumber, int PartNumber); virtual void Display() const; int GetEngineNumber()const { return itsEngineNumber; } private: int itsEngineNumber; }; AirPlanePart::AirPlanePart(int EngineNumber, int PartNumber): itsEngineNumber(EngineNumber), Part(PartNumber) {} void AirPlanePart::Display() const { Part::Display(); cout << "Nr silnika: " << itsEngineNumber << endl; } // wstpna deklaracja klasy List template <class T> class List; // **************** Node ************ // Wze oglny, moe by dodawany do listy // ************************************ template <class T> class Node { public: friend class List<T>; Node (T*); ~Node(); void SetNext(Node * node) { itsNext = node; } Node * GetNext() const; T * GetObject() const; private: T* itsObject; Node * itsNext; }; // Implamentacje klasy Node... template <class T> Node<T>::Node(T* pOjbect): itsObject(pOjbect), itsNext(0) {}

153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213:

template <class T> Node<T>::~Node() { delete itsObject; itsObject = 0; delete itsNext; itsNext = 0; } // Gdy nie ma nastpnego wza, zwraca NULL template <class T> Node<T> * Node<T>::GetNext() const { return itsNext; } template <class T> T * Node<T>::GetObject() const { if (itsObject) return itsObject; else throw NullNode(); } // **************** List ************ // Oglny wzorzec listy // Dziaa z kadym obiektem numerowanym // *********************************** template <class T> class List { public: List(); ~List(); T* Find(int & position, int ObjectNumber) T* GetFirst() const; void Insert(T *); T* operator[](int) const; int GetCount() const { return itsCount; } private: Node<T> * pHead; int itsCount; }; // Implementacje dla list... template <class T> List<T>::List(): pHead(0), itsCount(0) {} template <class T> List<T>::~List() { delete pHead; } template <class T> T* List<T>::GetFirst() const const;

Usunito: wzorzec

214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: numer 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273:

{ if (pHead) return pHead->itsObject; else throw EmptyList(); } template <class T> T * List<T>::operator[](int offSet) const { Node<T>* pNode = pHead; if (!pHead) throw EmptyList(); if (offSet > itsCount) throw BoundsError(); for (int i=0;i<offSet; i++) pNode = pNode->itsNext; } return pNode->itsObject;

// wyszukuje w licie dany obiekt w oparciu o jego unikalny (id) template <class T> T* List<T>::Find(int & position, int ObjectNumber) const { Node<T> * pNode = 0; for (pNode = pHead, position = 0; pNode!=NULL; pNode = pNode->itsNext, position++) { if (pNode->itsObject->GetObjectNumber() == ObjectNumber) break; } if (pNode == NULL) return NULL; else return pNode->itsObject; } // wstawia, gdy numer obiektu jest unikalny template <class T> void List<T>::Insert(T* pObject) { Node<T> * pNode = new Node<T>(pObject); Node<T> * pCurrent = pHead; Node<T> * pNext = 0; int New = pObject->GetObjectNumber(); int Next = 0; itsCount++; if (!pHead) { pHead = pNode; return; }

274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334:

// jeli ten wze jest mniejszy ni gowa, // staje si now gow if (pHead->itsObject->GetObjectNumber() > New) { pNode->itsNext = pHead; pHead = pNode; return; } for (;;) { // jeli nie ma nastpnego, doczamy nowy if (!pCurrent->itsNext) { pCurrent->itsNext = pNode; return; } // jeli trafia pomidzy biecy a nastpny, // wstawiamy go tu; w przeciwnym razie bierzemy nastpny pNext = pCurrent->itsNext; Next = pNext->itsObject->GetObjectNumber(); if (Next > New) { pCurrent->itsNext = pNode; pNode->itsNext = pNext; return; } pCurrent = pNext; } }

Usunito: o

int main() { List<Part> theList; int choice; int ObjectNumber; int value; Part * pPart; while (1) { cout << "(0)Wyjscie (1)Samochod (2)Samolot: "; cin >> choice; if (!choice) break; cout << "Nowy numer czesci?: "; cin >> ObjectNumber; if (choice == 1) { cout << "Model?: "; cin >> value; try { pPart = new CarPart(value,ObjectNumber); } catch (OutOfMemory) {

335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: endl; 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: endl; 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: }

cout << "Brak pamieci; Wyjscie..." << endl; return 1; } } else { cout << "Numer silnika?: "; cin >> value; try { pPart = new AirPlanePart(value,ObjectNumber); } catch (OutOfMemory) { cout << "Brak pamieci; Wyjscie..." << endl; return 1; } } try { theList.Insert(pPart); } catch (NullNode) { cout << "Lista jest uszkodzona i wezel jest pusty!" << return 1; } catch (EmptyList) { cout << "Lista jest pusta!" << endl; return 1; } } try { for (int i = 0; i < theList.GetCount(); i++ ) cout << *(theList[i]); } catch (NullNode) { cout << "Lista jest uszkodzona i wezel jest pusty!" <<

return 1; } catch (EmptyList) { cout << "Lista jest pusta!" << endl; return 1; } catch (BoundsError) { cout << "Proba odczytu poza koncem listy!" << endl; return 1; } return 0;

Wynik

(0)Wyjscie (1)Samochod (2)Samolot: Nowy numer czesci?: 2837 Model?: 90 (0)Wyjscie (1)Samochod (2)Samolot: Nowy numer czesci?: 378 Numer silnika?: 4938 (0)Wyjscie (1)Samochod (2)Samolot: Nowy numer czesci?: 4499 Model?: 94 (0)Wyjscie (1)Samochod (2)Samolot: Nowy numer czesci?: 3000 Model?: 93 (0)Wyjscie (1)Samochod (2)Samolot: Numer czesci: 378 Nr silnika: 4938 Numer czesci: 2837 Rok modelu: 90 Numer czesci: 3000 Rok modelu: 93 Numer czesci: 4499 Rok modelu: 94

1 2 1 1 0

Analiza Listing tego programu stanowi modyfikacj programu podsumowujcego wiadomoci w rozdziale 14. i uzupenia go o wzorce, przetwarzanie za pomoc obiektu ostream oraz obsug wyjtkw. Wynik dziaania jest identyczny. W liniach od 36. do 40. zostao zadeklarowanych kilka klas wyjtkw. W zastosowanej w tym programie nieco prymitywnej obsudze wyjtkw, dla wyjtkw nie s wymagane adne dane ani metody; obiekty wyjtkw su jako znaczniki w instrukcjach catch, ktre wypisuj bardzo prosty komunikat i wychodz. Bardziej stabilny program mgby przekazywa te wyjtki poprzez referencje, a nastpnie wydziela z nich kontekst lub inne dane obiektu wyjtku w celu podjcia prby rozwizania problemu. W linii 45. abstrakcyjna klasa bazowa Part zostaa zadeklarowana dokadnie tak samo, jak w poprzednim programie podsumowujcym wiadomoci. Jedyn interesujc zmian jest wystpienie nie nalecego do klasy operatora<<(), zadeklarowanego w liniach od 70. do 74. Zwr uwag, e nie jest to ani funkcja skadowa klasy Part, ani funkcja zaprzyjaniona tej klasy. Otrzymuje on jedynie w jednym ze swoich argumentw referencj do obiektu Part. Moesz kiedy zechcie, by operator<< przyjmowa klasy CarPart i AirPlanePart (w nadziei, e zostanie wywoany waciwy operator<<, w zalenoci od tego, czy przekazana zostaa cz samochodu czy samolotu). Poniewa program przekazuje wskanik do czci, a nie wskanik do czci samochodu i czci samolotu, wic C++ musiaoby wywoywa waciw metod w oparciu o rzeczywisty typ jednego z argumentw funkcji. Nazywa si to kontrawariancj i nie jest obsugiwane w C++.
Usunito: wzorce

Usunito: i klas Part Usunito: jedynie jako referencj Usunito: Usunito: j Usunito: y Usunito: jaki obiekt zostaby Usunito: y Usunito: na Usunito: m Usunito: ie

W C++ istniej tylko dwa sposoby uzyskania polimorfizmu: polimorfizm funkcji oraz funkcje wirtualne. Polimorfizm funkcji tutaj nie zadziaa, gdy w kadym przypadku dopasowujemy t sam sygnatur: sygnatur przyjmujc referencj do klasy Part. Funkcje wirtualne take nie zadziaaj, gdy operator<< nie jest funkcj skadow klasy Part. Nie moemy uczyni z tej funkcji funkcji skadowej, gdy chcemy wywoywa:
cout << thePart

a to oznacza, e rzeczywistym wywoaniem bdzie cout.operator<<(Part&), a cout nie posiada wersji operatora<< przyjmujcego referencj do klasy Part! Aby omin to ograniczenie, w tym programie uywamy tylko jednego operatora<<, przyjmujcego referencj do klasy Part. Funkcja ta wywouje nastpnie metod Display(), ktra jest wirtualn funkcj skadow. W ten sposb zostaje wywoana waciwa metoda. W liniach od 130. do 143. klasa Node jest zadeklarowana jako wzorzec. Suy do tego samego celu, co klasa Node w poprzednim programie podsumowujcym wiadomoci, ale ta wersja klasy nie jest zwizana z obiektem Part. W rzeczywistoci moe by wzem o dowolnym typie obiektu. Zauwa, e jeli chcesz uzyska obiekt z klasy Node, w ktrej nie ma obiektu, bdzie to uznane za wyjtek, ktry jest zgaszany w linii 175. W liniach 182. i 183. zosta zdefiniowany wzorzec oglnej klasy List. Klas ta moe przechowywa wzy bdce dowolnymi obiektami, posiadajcymi unikalne numery identyfikacyjne, utrzymujc je posortowane w kolejnoci od najmniejszego do najwikszego. Kada z funkcji listy sprawdza, czy nie wystpia sytuacja wyjtkowa i w razie potrzeby zgasza odpowiedni wyjtek. W linii 309. program sterujcy tworzy list dwch typw obiektw Part, po czym, korzystajc ze standardowego mechanizmu strumieni, wypisuje wartoci obiektw z listy.
Usunito: wzorzec

Usunito: jest Usunito: wzorzec

Usunito: ach 307 i Usunito: 8 Usunito: w licie

Czsto zadawane pytanie

W komentarzu powyej linii 70. wspominasz, e C++ nie obsuguje kontrawariancji. Czym jest kontrawariancja?

Odpowied: Kontrawariancja jest zdolnoci przypisania wskanika do klasy bazowej wskanikowi do klasy pochodnej.

Gdyby C++ obsugiwao kontrawariancj, moglibymy w czasie dziaania programu przecia funkcj w oparciu o faktyczny typ obiektu. Listing 21.10 nie skompiluje si w jzyku C++, ale skompilowaby si, gdyby jzyk ten obsugiwa kontrawariancj.

OSTRZEENIE Ten listing si nie skompiluje!

Listing 21.10.
#include <iostream.h> class Animal { public: virtual void Speak() { cout "Animal Speaks\n"; } }; class Dog : public Animal { public: void Speak() { cout "Dog Speaks\n"; } }; class Cat : public Animal { public: void Speak() { cout "Cat Speaks\n"; } }; void DoIt(Cat*); void DoIt(Dog*); int main() { Animal * pA = new Dog; DoIt(pA); return 0; } void DoIt(Cat * c) { cout << "Otrzymalem obiekt Cat!\n" << endl; c->Speak(); } void DoIt(Dog * d) { cout << "Otrzymalem obiekt Dog!\n" << endl; d->Speak(); }

Oczywicie, moglibymy uy funkcji wirtualnej, co czciowo rozwizaoby problem.


#include <iostream.h> class Animal { public: virtual void Speak() { cout "Animal Speaks\n"; } };

class Dog : public Animal { public: void Speak() { cout "Dog Speaks\n"; } }; class Cat : public Animal { public: void Speak() { cout "Cat Speaks\n"; } }; void DoIt(Animal*); int main() { Animal * pA = new Dog; DoIt(pA); return 0; } void DoIt(Animal * c) { cout << "Otrzymalem jakis rodzaj obiektu Animal\n" << endl; c->Speak(); }

Dodatek A. Dwjkowo i szesnastkowo


Podstawy arytmetyki poznalimy w tak zamierzchej przeszoci, e trudno sobie wyobrazi co by byo, gdybymy nie posiadali tej wiedzy. Gdy patrzymy na znaki 145, to natychmiast wiemy, e chodzi o liczb sto czterdzieci pi. Zrozumienie sposobu funkcjonowania systemu dwjkowego i szesnastkowego wymaga innego spojrzenia na liczb 145, a mianowicie postrzegania jej nie jako liczby, ale jako jej kodu. Na pocztku wyobra sobie powizanie pomidzy liczb trzy a 3. Cyfra 3 jest znaczkiem na papierze; liczba trzy jest ide. Cyfra suy do reprezentowania liczby. To rozrnienie moe by atwiejsze do zrozumienia, jeli uwiadomimy sobie, e zarwno trzy jak i 3, |||, III oraz *** reprezentuj t sam ide liczby trzy. W systemie dziesitnym (czyli, jak mwi matematycy, o podstawie 10) do reprezentowania wszystkich liczb uywamy cyfr 0, 1, 2, 3, 4, 5, 6, 7, 8 oraz 9. Jak jest reprezentowana liczba 10? Mona sobie wyobrazi, e do reprezentowania liczby dziesi uywamy litery A, lub e uywamy zapisu IIIIIIIIII. Rzymianie uywali znaku X. W systemie arabskim, z ktrego obecnie korzystamy, do reprezentowania wartoci wykorzystujemy cyfry i ich pozycje. Pierwsza (pooona najbardziej na prawo) kolumna jest uywana dla jedynek, a druga (w lew stron) jest uywana dla dziesitek. Tak wic liczba pitnacie jest reprezentowana jako 15 (czytaj: jeden, pi), czyli jedna dziesitka i pi jedynek. Pojawia si regularno, dziki ktrej mona dokona pewnej generalizacji: 1. 2. 3. System o podstawie 10 uywa cyfr od 0 do 9. Kolumny s potgami dziesiciu: 1, 10, 100, itd. Jeli trzecia kolumna reprezentuje setki, to najwiksz liczb, jak mona zapisa w dwch kolumnach, jest 99. Innymi sowy, w n kolumnach moemy reprezentowa liczby od 0 do (10n1). Tak wic, w trzech kolumnach moemy reprezentowa liczby od 0 do (1031), czyli od 0 do 999.

Inne podstawy
To, e korzystamy z podstawy 10, nie jest przypadkiem: w kocu mamy po dziesi palcw. Mona sobie jednak wyobrazi inn podstaw. Uywajc regu okrelonych dla podstawy 10, moemy opisa podstaw 8: 1. 2. 3. System o podstawie 8 uywa cyfr od 0 do 7. Kolumny s potgami omiu: 1, 8, 64, itd. W n kolumnach moemy zapisywa liczby od 0 do 8n1.

W celu rozrniania liczb o rnych podstawach, podstawy zapisujemy jako indeks dolny tu za ostatni cyfr liczby. Liczba pitnacie przy podstawie 10 jest zapisywana jako 1510 i odczytywana jako jeden, pi, dziesitnie. Tak wic, reprezentujc liczb 1510 w systemie o podstawie 8, napisalibymy 178. Naley j odczytywa jako jeden, siedem, semkowo. Zwr uwag, e mona to odczytywa jako pitnacie, gdy t warto reprezentuje. Dlaczego 17? Jedynka oznacza jedn semk, a sidemka oznacza siedem jedynek. Jedna semka plus siedem jedynek daje pitnacie. Wemy pitnacie gwiazdek:
***** ***** *****

Naturalnym dziaaniem bdzie utworzenie dwch grup: grupy dziesiciu gwiazdek i grupy piciu gwiazdek. Dziesitnie byyby one reprezentowane jako 15 (jedna dziesitka i pi jedynek). Mona take pogrupowa gwiazdki nastpujco:
**** **** *******

to jest, jako osiem gwiazdek i siedem. W systemie semkowym zapisalibymy to jako 178, czyli jako jedn semk i siedem jedynek.

Wok podstaw
Liczb pitnacie moemy w systemie dziesitnym zapisywa jako 15, w systemie dziewitkowym jako 169, w systemie semkowym jako 178, a czy w systemie sidemkowym jako 217. Dlaczego 217? W systemie sidemkowym nie ma cyfry 8. Aby wyrazi liczb pitnacie, potrzebujemy dwch sidemek i jednej jedynki.

Jak mona to uoglni? Aby zamieni liczb o podstawie 10 na liczb o podstawie 7, pomyl o kolumnach: w systemie sidemkowym wystpuj kolumny dla jedynek, sidemek, czterdziestek dziewitek, trzysta czterdziestek trjek i tak dalej. Dlaczego takie kolumny? Poniewa reprezentuj 70, 71, 72, 74 i tak dalej. Pamitajmy, e dowolna liczba podniesiona do zerowej potgi (na przykad 70) rwna si 1, kada liczba podniesiona do pierwszej potgi (na przykad 71) rwna si samej sobie, kada liczba podniesiona do drugiej potgi rwna si wynikowi przemnoenia jej przez siebie (72 = 7*7 = 49), a kada liczba podniesiona do trzeciej potgi odpowiada trzykrotnemu przemnoeniu jej przez siebie (73 = 7*7*7 = 343). Wykonaj tabel: Kolumna Potga Warto 4 7
3

Usunito: dwu

3 7
2

2 7 7
1

1 70 1

343

49

Pierwszy wiersz reprezentuje numer kolumny. Drugi wiersz reprezentuje potg sidemki. Trzeci wiersz reprezentuje warto dziesitn kadej liczby w drugim wierszu. Aby zamieni wartoci dziesitne na liczby sidemkowe, postpuj zgodnie z ponisz procedur: sprawd liczb i zdecyduj, ktrej kolumny uy jako pierwszej. Jeli liczb jest na przykad 200, wiemy, e kolumna 4 (343) bdzie zawieraa 0 i nie musimy si ni martwi. Aby dowiedzie si, ile 49-ek jest w liczbie 200, podzielimy 200 przez 49. Otrzymujemy 4, wic w kolumnie trzeciej umieszczamy cyfr 4 i sprawdzamy reszt z dzielenia: 4. W liczbie 4 nie ma adnej sidemki, wic w kolumnie sidemek umieszczamy cyfr 0. W liczbie cztery s cztery jedynki, wic w kolumnie jedynek umieszczamy cyfr 4. Odpowiedzi jest 4047. Kolumna Potga Warto 200 sidemkowo Warto dziesitna 4 7
3

3 7
2

2 7 7 0 0
1

1 70 1 4 4*1 = 4

343 0 0

49 4 4*49 = 196

W tym przykadzie cyfra 4 w trzeciej kolumnie reprezentuje warto dziesitn 196, a cyfra 4 w pierwszej kolumnie reprezentuje warto 4. 196+4 = 200. Tak wic 4047 = 20010. Przejdmy nastpnego przykadu. Aby zamieni liczb 968 na liczb szstkow: Kolumna 5 4 3 2 1

Potga Warto

64 1296

63 216

62 36

61 6

60 1

Sprawd, czy wiesz, dlaczego kolumny reprezentuj takie wartoci. Pamitaj, e 63 = 6*6*6 = 216. Aby wyznaczy reprezentacj liczby 986 w systemie szstkowym, zaczniemy od kolumny 5. Ile 1296-tek mieci si w 986? adna, wic w kolumnie 5. zapisujemy 0. Jeli podzielimy 968 przez 216, to otrzymamy 4 z reszt 104. W kolumnie 4. znajdzie si cyfra 4. To jest, ta kolumna bdzie reprezentowa 4*216 (864). Musimy teraz wyrazi pozosta warto (968-864 = 104). Podzielenie 104 przez 36 daje 2 z reszt 32. Kolumna trzy bdzie zawiera cyfr 2. Podzielenie 32 przez 6 daje 5 z reszt 2. Tak wic otrzymujemy liczb 42526. Kolumna Potga Warto 986 szstkowo Warto dziesitna 5 6 0 0
4

Usunito: P Usunito: enie Usunito: daje

4 6 4 4*216=864
3

3 6 2 2*36=72
2

2 6 6 5 5*6=30
1

1 60 1 2 2*1=2

1296

216

36

864+72+30+2 = 968

Dwjkowo
Ostatecznym etapem tego systemu jest system o podstawie 2. S w nim tylko dwie cyfry: 0 i 1. Kolumny to Kolumna Potga Warto 8 2
7

7 2
6

6 2
5

5 2
4

4 2 8
3

3 2 4
2

2 2 2
1

1 20 1

128

64

32

16

Aby zamieni liczb 88 na liczb dwjkow, postpujemy zgodnie z t sam procedur: w 88 nie ma 128-ek, wic w kolumnie smej wpisujemy cyfr 0. W 88 jest jedna szedziesitka czwrka, wic do kolumny 7 wpisujemy cyfr 1. Zostaje nam reszta wynoszca 24. W 24 nie ma trzydziestek dwjek, wic kolumna 6 zawiera cyfr 0.

W 24 mieci si jedna szesnastka, wic kolumna pi zawiera cyfr 1. Pozostaje nam reszta 8. W 8 jest jedna semka, wic kolumna 4. bdzie zawiera cyfr 1. Nie ma ju adnej reszty, wic pozostae kolumny bd zawiera zera. Kolumna Potga Warto 88 dwjkowo Warto 8 2 0 0
7

7 2
6

6 2
5

5 2
4

4 2 8 1 8
3

3 2 4 0 0
2

2 2 2 0 0
1

1 20 1 0 0

128

64 1 64

32 0 0

16 1 16

Aby sprawdzi wynik, zamiemy to z powrotem na liczb o podstawie dziesi:


1 0 1 1 0 0 0 * * * * * * * 64 = 64 32 = 0 16 = 16 8 = 8 4 = 0 2 = 0 1 = 0 88

Dlaczego podstawa 2?
Podstawa 2 peni wan rol w programowaniu, gdy dokadnie odpowiada temu, co moe by w komputerze reprezentowane. Komputery w rzeczywistoci nie wiedz nic o literach, cyfrach, instrukcjach czy programach. W swoim rdzeniu s jedynie ukadami elektronicznymi, w ktrych w danym punkcie moe wystpowa wiksze albo bardzo mae napicie. Aby zachowa prostot konstrukcji, inynierowie nie traktuj napicia jako skali relatywnej (niskie napicie, wysze napicie, bardzo wysokie napicie czy ogromne napicie), ale raczej jako skal o dwch stanach (napicie wystarczajce lub napicie niewystarczajce). Zamiast jednak mwi wystarczajce lub niewystarczajce, mwi po prostu tak lub nie. Tak lub nie, czyli prawda lub fasz, moe by reprezentowane jako 1 lub 0. Zgodnie z konwencj, 1 oznacza prawd lub Tak, ale to tylko konwencja; rwnie dobrze mogoby oznacza fasz lub Nie. Gdy zauwaysz t regu, potga systemu dwjkowego objawi si w caej okazaoci: za pomoc zer i jedynek mona odda stan kadego ukadu (jest napicie lub go nie ma). Wszystkie komputery znaj tylko dwa stany: wczony = 1 oraz wyczony = 0.

Bity, bajty, nible


Gdy podjto decyzjm by reprezentowa prawd i fasz jedynkami i zerami, bardzo wane stao si pojcie bitu (od binary digit, cyfra binarna 1). Poniewa pierwsze komputery mogy przesa jednoczenie osiem bitw, wic naturalnie pierwszy kod zapisywano, uywajc liczb 8-bitowych nazywanych bajtami (ang. byte).

UWAGA W gwarze programistycznej powka bajtu (4 bity) jest nazywana niblem (ang. nybble).

Za pomoc omiu bitw mona reprezentowa do 256 rnych wartoci. Dlaczego? Sprawd kolumny: gdy wszystkie osiem bitw jest ustawionych (1), wartoci jest 255 (128+64+32+16+8+4+2+1). Jeli nie jest ustawiony aden (wszystkie bity s wyzerowane, czyli maj warto 0), wtedy wartoci jest 0. Od 0 do 255 to 256 moliwych stanw.

Co to jest KB?
Okazuje si, e 210 (1024) to w przyblieniu 103 (1 000). Ten zwizek by zbyt dobry, aby go nie zauway, wic komputerowi specjalici zaczli nazywa 210 bajtw kilobajtem, czyli KB, zapoyczajc przedrostek kilo (k) oznaczajcy tysic. Dla wskazania, e chodzi o warto 1024, a nie 1000, komputerowe kilo oznacza si du liter K. Rwnie 1024*1024 (1 048 576) jest na tyle bliskie miliona, e otrzymao oznaczenie 1 MB, czyli megabajt, za 1 024 megabajtw jest nazywanych gigabajtem (giga to przedrostek oznaczajcy tysic milionw, czyli miliard).

Liczby dwjkowe
Komputery koduj kad warto za pomoc zer i jedynek. Instrukcje maszynowe s zakodowane jako serie jedynek i zer, nastpnie s interpretowane przez ukady procesora. Zestawy zer i jedynek mog by zamienione na liczby, ale traktowanie tych liczb jako posiadajcych jakie specyficzne znaczenie byoby bdem. Na przykad, procesor Intel 8086 interpretuje wzorzec bitw 1001 0101 jako instrukcj. Oczywicie, moemy zamieni te bity na liczb dziesitn 149, ale ta warto sama w sobie nie ma dla nas adnego znaczenia. Czasem liczby s instrukcjami, czasem wartociami, a czasem kodami. Jednym z wanych, standardowych zestaww kodw jest zestaw ASCII. W zestawie tym kada litera, cyfra i znak przestankowy ma przydzielon siedmiobitow reprezentacj. Na przykad, maa litera a jest
1

Usunito: wewntrzne

A take od kawaka informacji, gdy bit to po angielsku take kawaek. przyp. tum.

reprezentowana jako 0110 0001. Nie jest to liczba, cho mona j zamieni na liczb 97 w systemie dziesitnym (64+32+1). Wanie w tym sensie mwi si, e litera a to 97 w ASCII, cho w rzeczywistoci kodem litery a jest dwjkowa reprezentacja wartoci 97 (0110 0001), a warto dziesitna 97 stanowi tylko uatwienie dla ludzi.

Szesnastkowo
Poniewa liczby dwjkowe s trudne do odczytania, stworzono prostszy sposb ich reprezentowania. Przejcie z systemu dwjkowego na dziesitny wymaga sporo przeprowadzenia skomplikowanych operacji na liczbach, ale okazuje si, e przejcie z podstawy 2 do podstawy 16 jest proste, gdy istnieje bardzo dobry skrt. Aby zrozumie ten proces, musisz najpierw zrozumie system o podstawie 16, zwany systemem szesnastkowym lub heksadecymalnym. Przy podstawie 16 mamy do dyspozycji szesnacie cyfr: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E oraz F. Ostatnie sze znakw, litery od A do F, zostay wybrane, poniewa atwo mona je wpisa z klawiatury (i wywietli na wywietlaczu siedmiosegmentowym). Kolumny w systemie szesnastkowym to: Kolumna Potga Warto 4 16
3

3 16
2

2 16 16
1

1 160 1

4096

256

Aby zamieni liczb z systemu szesnastkowego na dziesitny, moemy mnoy. Tak wic liczba F8C reprezentuje:
F * 256 = 15 * 256 = 3840 8 * 16 = 128 C * 1 = 12 * 1 = 12 3980

(Pamitaj, e F16 to 1510) Najlepiej przeprowadzi zamian liczby FC na dwjkow, zamieniajc j najpierw na liczb dziesitn, a nastpnie na dwjkow:
F * 16 = 15 * 16 = 240 C * 1 = 12 * 1 = 12 252

Zamiana 252 na liczb dwjkow wymaga uycia tablicy: Kolumna Potga Warto 9 2
8

8 2
7

7 2
6

6 2
5

5 2
4

4 2 8
3

3 2 4
2

2 2 2
1

1 20 1

256

128

64

32

16

Nie ma 256-ek. 1*128 = 128. 252128 = 124 1*64 = 64. 12464 = 60 1*32 = 32. 6032 = 28 1*16 = 16. 2816=12 1*8 = 8. 128 = 4 1*4 = 4. 44 = 0 0*2 = 0 0*1 = 0 124+60+28+12+4 = 252. Tak wic FC16 w systemie dwjkowym to 1111 1100. Okazuje si, e gdy potraktujemy t liczb dwjkow jako dwa zestawy czterech cyfr (1111 1100), moemy dokona magicznego przeksztacenia. Prawy zestaw to 1100. Dziesitnie to 12, a szesnastkowo C. (1*8 + 1*4 + 0*2 + 0*1). Lewy zestaw to 1111, czyli dziesitnie 15, a szesnastkowo F. Tak wic mamy:
1111 1100 F C

Umiemy dwie cyfry szesnastkowe razem i otrzymamy FC, ktre jest wartoci szesnastkow binarnej liczby 1111 1100. Ten skrt dziaa! Moemy wzi liczb binarn o dowolnej dugoci, podzieli j na zestawy po cztery bity, zamieni kady z zestaww na cyfr szesnastkow i poczy otrzymane cyfry tak, aby otrzyma wynik w systemie szesnastkowym. Oto duo wiksza liczba:
1011 0001 1101 0111

Aby sprawdzi, czy nasze zaoenia s waciwe, najpierw zamiemy t liczb na dziesitn.

Wartoci kolumn moemy obliczy poprzez ich podwajanie. Kolumna pooona najbardziej na prawo ma warto 1, nastpna 2, nastpne 4, 8, 16 i tak dalej. Zaczniemy od kolumny pooonej najbardziej na prawo, ktra, liczc dziesitnie, ma wag 1. Mamy jedynk, wic ta kolumna jest warta 1. Nastpna kolumna ma wag 2. W tej kolumnie take mamy jedynk, wic dodajemy 2 i otrzymujemy sum wynoszc 3. Nastpna kolumna ma wag 4 (podwajamy wag poprzedniej kolumny). W zwizku z tym otrzymujemy 4+2+1=7. Kontynuujemy t procedur dla kolejnych kolumn:
1x1 1x2 1x4 0x8 1x16 0x32 1x64 1x128 1x256 0x512 0x1024 0x2048 1x4096 1x8192 0x16384 1x32768 Razem 1 2 4 0 16 0 64 128 256 0 0 0 096 192 0 768 527

4 8 32 45

Zamiana na liczb szesnastkow wymaga zastosowania tablicy z wartociami szesnastkowymi. Kolumna Potga Warto 4 16
3

3 16
2

2 16 16
1

1 160 1

4096

256

Mamy jedenacie 4096-ek (45 056) z reszt 471. W 471 jest jedna 256-ka z reszt 215. W 215 jest trzynacie 16-ek (208) z reszt 7. Tak wic szukana liczba szesnastkowa to B1D7. Sprawdzamy obliczenia:
B (11) * 4096 = 45 1 * 256 = D (13) * 16 = 7 * 1 = Razem 45 056 256 208 7 527

Skrcone rozwizanie polega na podzieleniu pierwotnej liczby dwjkowej 1011000111010111 na grupy po cztery bity: 1011 0001 1101 0111. Kad z grup mona wtedy przedstawi jako cyfr szesnastkow:
1011 = 1 x 1 = 1 1 x 2 = 2 0 x 4 = 0 1 x 8 = 8 Razem 11 Hex: B 0001 = 1 x 1 = 0 x 2 = 0 x 4 = 0 x 8 = Razem 1 Hex: 1

1 0 0 0

1101 = 1 x 1 = 1 1 x 2 = 0 1 x 4 = 4 1 x 8 = 8 Razem 13 Hex: D 0111 = 1 x 1 = 1 x 2 = 1 x 4 = 0 x 8 = Razem 7 Hex: 7

1 2 4 0

Razem Hex: B1D7

Voila! Skrcona procedura zamiany, liczby dwjkowej na szesnastkow, daa nam ten sam wynik, co wersja dusza. Przekonasz si, e programici bardzo czsto korzystaj z liczb szesnastkowych; ale okae si take, e bardzo dugo mona si bez nich obej!
Usunito: przez dugi Usunito: obej

Dodatek B. Sowa kluczowe C++


Sowa kluczowe s zarezerwowane jako symbole jzyka. Nie mona ich uywa jako nazw klas, zmiennych czy funkcji.
asm auto bool break case catch char class const const_cast continue default delete do double dynamic_cast else enum explicit extern false float for friend goto if inline int long mutable namespace new operator private protected public register

reinterpret_cast return short signed sizeof static static_cast struct switch template this throw true try typedef typeid typename union unsigned using virtual void volatile while

Dodatek C. Kolejno operatorw


Naley pamita, e operatory posiadaj swoje priorytety, cho nie ma potrzeby zapamitywania ich kolejnoci. Priorytet okrela kolejno, w jakiej program wykonuje dziaania zawarte w danym wyraeniu. Jeli jeden operator ma priorytet nad innym operatorem, wtedy jest obliczany jako pierwszy. Operatory o wyszym priorytecie wi mocniej ni operatory o niszych priorytetach; to znaczy, e operatory o wyszym priorytecie s obliczane wczeniej. Im niszy numer pozycji w tabeli C.1, tym wyszy priorytet operatora. Tabela C.1. Priorytety operatorw Pozycja 1 2 Nazwa operator zakresu wybr skadowych, indeksowanie, wywoania funkcji, inkrementacja i dekrementacja postfiksowa 3 sizeof, inkrementacja i dekrementacja prefiksowa, negacja, and, not, jednoargumentowy minus i plus, adres i wyuskanie, new, new[], delete, delete[], rzutowanie, sizeof() Operator
:: . -> () ++ -++ -^ ! - + & * ()
Usunito: postfiksowa Usunito: Usunito: prefiksowa Usunito: Wane jest by Usunito: ich Usunito: wyszy Usunito: ni Usunito: a Usunito: a

4 5 6 7

wybr skadowej dla wskanika mnoenie, dzielenie, modulo dodawanie, odejmowanie przesunicie (w lewo, w prawo)

.* ->* * / % + << >>

8 9 10 11 12 13 14 15 16

relacje wikszoci i mniejszoci rwne, nie rwne bitowe AND bitowe XOR bitowe OR logiczne AND logiczne OR operator warunkowy operatory przypisania

< <= > >= == != & ^ | && || ?: = *= /= %= += -+ <<= >>= &= |= ^=

17 18

operator zgaszania wyjtku przecinek

throw ,

You might also like