Está en la página 1de 146

Vilniaus Universitetas Matematikos ir Informatikos fakultetas Kompiuterijos katedra Saulius Narkeviius

Objektikai Orientuotas Programavimas su C++


paskait konspektai

pavasaris 2005

Turinys

Pratarm........................................................................................................................... 5 Literatra......................................................................................................................... 7 1. Apvalga.......................................................................................................................... 9 1.1. 1.2. 1.3. 1.4. 1.5. 1.6. 1.7. 1.8. 1.9. 1.10. 1.11. 1.12. 1.13. 2. Pirmoji C++ programa..................................................................................................9 Antratini fail (h-fail) traukimas.................................................................... 11 Keletos moduli programos.......................................................................................12 Selektyvus kompiliavimas (make)..........................................................................15 Bendros taisyks (pattern rules) make-failuose................................................17 Keletas btiniausi Unix komand.......................................................................19 Nuo C prie C++ per maisto preki parduotuv............................................ 23 Klas = duomenys + metodai.................................................................................. 24 Konteineriai ir iteratoriai............................................................................................ 26 Palyginimo operatorius, konteineri riavimas ir failai.............................. 29 Dinaminis objekt sukrimas ir naikinimas...................................................... 33 Objekt tarpusavio sryiai....................................................................................... 36 Paveldjimas ir polimorfizmas..................................................................................42

Inkapsuliacija................................................................................................................. 51 2.1. 2.2. 2.3. 2.4. 2.5. 2.6. 2.7. 2.8. 2.9. 2.10. 2.11. 2.12. 2.13. Objektais paremtas programavimas (object based programming).......... 51 Klas, objektas, klass nariai...................................................................................... 52 Klass nari matomumas............................................................................................ 55 Konstruktoriai ir destruktoriai.................................................................................56 Konstruktorius pagal nutyljim.............................................................................58 Kopijavimo konstruktorius....................................................................................... 59 Konstruktoriai ir tip konversija............................................................................ 61 Objekt masyvai............................................................................................................ 63 Objektas, kaip kito objekto laukas (agregacija)............................................... 65 Objekt gyvavimo trukm........................................................................................67 Metodai, apibrti klass aprao viduje...............................................................70 Statiniai nariai...................................................................................................................71 Klass draugai...................................................................................................................73

2.14. 2.15. 2.16. 2.17. 3.

Tip apraai klass viduje (dtiniai tipai)..........................................................75 Vard erdvs isprendimo operatorius ::............................................................ 76 Konstantiniai laukai, laukai-nuorodos...................................................................77 Konstantiniai metodai ir mutable-laukai.............................................................78

Paveldjimas ir polimorfizmas................................................................................... 81 3.1. 3.2. 3.3. 3.4. 3.5. 3.6. 3.7. 3.8. 3.9. 3.10. 3.11. Trys OOP banginiai.......................................................................................................81 Paveldjimas..................................................................................................................... 82 Konstruktoriai ir destruktoriai................................................................................. 85 Bazins klass nari matomumas........................................................................... 86 Metod perkrovimas (overloading) ir pseudo polimorfizmas.................. 87 Virtuals metodai ir polimorfizmas......................................................................88 Virtuali metod lentels (VMT)........................................................................90 Statiniai, paprasti ir virtuals metodai..................................................................91 Polimorfizmas konstruktoriuose ir destruktoriuose.......................................92 variai virtuals metodai ir abstrakios klass..................................................93 varus interfeisas............................................................................................................ 94

4.

Klaid mtymas ir gaudymas (exception handling)............................................ 99 4.1. 4.2. 4.3. 4.4. 4.5. 4.6. 4.7. 4.8. Raktiniai odiai throw, try ir catch..................................................................... 99 Skirting klaid gaudymas.......................................................................................101 dtiniai try-blokai.......................................................................................................102 Automatini objekt naikinimas steko vyniojimo metu..........................103 Klaid mtymas konstruktoriuose ir destruktoriuose................................ 104 Nepagautos klaidos ir funkcija terminate()..................................................... 105 Klaid specifikacija ir netiktos klaidos............................................................106 Standartins klaid klass........................................................................................ 107

5.

Vard erdvs (namespace)...................................................................................... 109 5.1. 5.2. 5.3. 5.4. 5.5. Motyvacija...................................................................................................................... 109 Raktinis odis "using"..................................................................................................111 Vard erdvi apjungimas......................................................................................... 112 Vard erdvi sinonimai.............................................................................................112 Vard erdvs be pavadinimo..................................................................................113

6.

Operatori perkrovimas............................................................................................ 115 6.1. 6.2. 6.3. 6.4. Motyvacija....................................................................................................................... 115 Perkraunami operatoriai............................................................................................117 Unariniai ir binariniai operatoriai......................................................................... 118 Tip konversijos operatoriai...................................................................................120 3

7.

Apibendrintas programavimas (generic programming)...................................... 121 7.1. 7.2. 7.3. 7.4. Programavimo stiliai....................................................................................................121 Funkcij ablonai..........................................................................................................122 Klasi ablonai............................................................................................................... 125 Trumpai apie sudtingesnes ablon savybes..................................................127

8.

Daugialypis paveldjimas.......................................................................................... 131 8.1. 8.2. 8.3. 8.4. Daugialypio paveldjimo pavyzdys......................................................................131 Pasikartojanios bazins klass ir virtualus paveldjimas...........................133 Virtualios bazins klass konstravimo problema.......................................... 134 Objekt tip identifikacija programos vykdymo metu............................. 136

9.

OOP receptai.............................................................................................................. 139 9.1. 9.2. 9.3. 9.4. Objektikai orientuotos visuomens folkloras............................................... 139 Projektavimo ablonai (design patterns)............................................................141 Program pertvarkymas (refactoring).................................................................143 Ekstremalus programavimas (eXtreame programming)............................ 145

Pratarm

Objektinio Programavimo C++ kurse mokinsims viso labo dviej dalyk:


objektikai orientuoto programavimo (OOP) kaipo tokio OOP naudojimo raant programas su C++ programavimo kalba

I skaitytojo tikimasi praktins darbo patirties su programavimo kalba C. Pastarosios ipltimas C++ bus pristatytas nesistengiant pateikti vis programavimo kalbos detali. Pasitikslinti detales galima emiau pateiktame literatros srae. Nebtina visko suprasti ikarto: pradiai pasibandykite ir pripraskite. Kurso tikslas supaindinti klausytojus su OOP ir C++ pagrindais, pakankamais program krimui ir tolimesniam savarankikam tobulinimuisi. iuose konspektuose mes danai vartosime termin objektinis programavimas turdami omenyje objektikai orientuot programavim. Niekam ne paslaptis, jog programavimo kalba C++ yra kildinama i programavimo kalbos C. Skaitytojas dar turt prisiminti, jog C buvo kuriama lygiagreiai su operacine sistema Unix madaug 1969-1973 metais PDP-11 kompiuteriams kompanijoje Bell Labs. Jos tvu laikomas Dennis Ritchie. 90% UNIX kodo buvo parayta C kalboje. C kalbos pirmtakais laikomos dvi programavimo kalbos: Algol68 ir B. Toje paioje kompanijoje Bell Labs, tik gerais deimia met vliau, Bjarne Stroustrup sukr patobulint programavimo kalb: C su klasmis. Neilgai trukus buvo nutarta, jog C kalbos ipltimas Objektikai Orientuoto Programavimo (OOP) priemonmis nusipelno atskiro pavadinimo. Taip 1983 metais pirmt kart pamintas C++ vardas. Jos atsiradim stipriai takojo pirmoji objektikai orientuota programavimo kalba Simula67 (1962-1967). Prireik penkiolikos met kol 1998-j rugpjt buvo vienbalsiai patvirtintas C++ standartas ISO/IEC 14882 (Standard for the C++ Programming Language). Standarto rengimo eigoje buvo neta ioki toki pakeitim pai programavimo kalb. Gerokai isiplt standartin biblioteka: pervelgti vedimo/ivedimo srautai, atsirado klas string, konteineri ablonai, lokalizacija ir t.t.. Kompiliatori krjams prireik dar dviej-keturi met kol j produktai pradjo daugiau ar maiau realizuoti C++ 5

standart. Todl nereikalaukite per daug i C++ kompiliatori, pagamint prie 2002-uosius metus. Svarbiausias dalykas, kur reikia inoti apie C++, yra tai, jog C++ yra objektikai orientuota kalba: ji pateikia klass svok (C kalbos struktr ipltimas), kurios pagalba realizuojami trys Objektikai Orientuoto Programavimo banginiai:

inkapsuliacija paveldjimas polimorfizmas

Aplink mus krvos objekt: mainos, kiemsargiai, mediai. Kiekvienas j turi savo bsen ir elgsenos ypatumus. OOP nutiesia labai patog tilt tarp objekt gyvenime ir objekt programoje. Objektikai orientuotas yra ne tik programavimas: mes analizuojame aplink, projektuojame jai talkinanias kompiuterines sistemas ir pagaliau programuojame tomis paiomis svokomis objektais, j bsenomis, elgsena bei tarpusavio ryiais. Syk perpratus OOP, paprastai jis tampa natraliu program krimo stiliumi. Sunku bna sivaizduoti, kad kakada gyventa be jo. Tai bene stipriausias instrumentas kovoje su program sudtingumu.

Literatra

Paskaitas lydini mediag galima rasti internete: www.mif.vu.lt/~saulukas/oop2 1. Bjarne Stroustrup: The C++ Programming Language; (Third Edition and Special Edition), Addison-Wesley, ISBN 0-201-88954-4 ir 0-201-70073-5, 1997. http://www.research.att.com/~bs Bruce Eckel: Thinking in C++; I ir II tomai; http://www.bruceeckel.com C++ Library reference: http://www.cplusplus.com/ref C/C++ Reference: http://www.cppreference.com C++ Annotations: http://www.icce.rug.nl/documents/cplusplus C++ FAQ LITE Frequently Asked Questions: http://www.parashift.com/c++-faq-lite

2. 3. 4. 5. 6.

1.

Apvalga

1.1.

Pirmoji C++ programa

Pati trumpiausia, nieko nedaranti, C++ programa:


// smallest.cpp int main() {}

Visos C++ programos prasideda nuo funcijos main(), grinanios int-tipo reikm. Pagal nutyljim, grinama reikm nulis, kuri reikia skming programos baigt. Antroji ms programa ekrane atspausdins pasveikinim:
// hello.cpp #include <iostream.h> int main () { cout << "Sveikas, pasauli!" << endl; }

Sukompiliuokime:
g++ hello.cpp

Bus pagaminta programl standartiniu pavadinimu a.out (arba a.exe), kuri paleidiame tokiu bdu:
./a.out

Programa atspausdins ekrane:


Sveikas, pasauli!

Vieno failo kompiliavim schematikai pavaizduokime taip:

redaktorius

hello.cpp

kompiliatorius

hello.o hello1.o

linkeris

a.out

c++ libs

Program krimas prasideda nuo to, jog js pasirinkto redaktoriaus pagalba (pvz. integruoti fail komanderi mc ar far redaktoriai) sukuriamas tekstinis failas (hello.cpp) su programos ieities tekstais. Komanda g++ hello.cpp i karto atlieka du veiksmus: sukompiliuoja kod atskir modul hello.o ir prijungia (prilinkuoja) standartines bibliotekas, kad gauti savistov paleidiam program (a.out Unix, a.exe Windows terpse). iuo paprasiausiu atveju, tarpinis failas hello.o nra isaugomas diske.

10

1.2.

Antratini fail (h-fail) traukimas

Grietai irint, ankstesniame skyrelyje pateikta programa hello.cpp yra parayta "senuoju stiliumi". Madaug 2000-j met ir vlesni kompiliatoriai kompiliuoja kod labai nenoriai, su krva perspjim. Reikalas tas, jog po 1998 rugpjio mnes patvirtinto C++ programavimo kalbos standarto sigaliojo naujas antratini fail traukimo stilius. Anksiau C++ programuotojas traukdavo kvadratin akn i dviej madaug taip:
// headers_old.cpp #include <math.h> #include <iostream.h> int main () { cout << "sqrt(2) = " << sqrt(2.0) << endl; }

Pagal naujj C++ standart nebereikia rayti failo ipltimo ".h" prie standartini antratini fail. Be to, visi C-kalbos h-failai (pvz. stdlib.h, stdio.h, math.h ir t.t.) gauna priekyje raid "c". Prisiminkime jog toki standartini antratini fail kompiliatorius iekos parametrais ar aplinkos kintamaisiais nurodytuose kataloguose:
// headers_new.cpp #include <cmath> #include <iostream> using namespace std; int main () { cout << "sqrt(2) = " << sqrt(2.0) << endl; }

Apie raktinius odius using namespace mes plaiau pakalbsime kitame skyriuje. Dabar tik siminkite, jog magik fraz using namespace std reikia rayti po vis antratini fail traukimo. Tai, be kita ko, reikia, jog visos standartins C++ bibliotekos funkcijos ir klass yra vard erdvje std. Naujasis stilius galioja tik standartiniams C++ h-failams. Js pai rayti h-failai, kaip ir anksiau, traukiami raant failo vard tarp kabui:
#include "mano.h"

Prisiminkime, jog toki h-fail kompiliatorius pradioje iekos einamajame kataloge, o vliau parametrais ar aplinkos kintamaisiais nurodytuose kataloguose.

11

1.3.

Keletos moduli programos

Ms menkuiai pavyzdliai apsiribojo vienu moduliu (cpp-failu). Kiek rimtesn programa neivengs keletos moduli taip patogiau kod priirti ir kompiliuoti. Pasitreniruokime raydami program apie proting vaikin CleverBoy. i programa susideda i trij fail: CleverBoy.h su CleverBoy.cpp ir main.cpp. Faile CleverBoy.h turime klass CleverBoy apra, t.y. modulio interfeis. Aprao utenka, kad inotume, k duotoji klas gali daryti, nesigilinant tai, kaip ji tai daro:
// CleverBoy.h #ifndef __CleverBoy_h #define __CleverBoy_h #include <string> class CleverBoy { std::string cleverWords; public: CleverBoy(std::string cleverWords); std::string getCleverWords(); }; #endif // __CleverBoy_h

Faile CleverBoy.cpp turime klass CleverBoy realizacij. Tokia h- ir cpp-fail pora yra natralus bdas nusakyti modul: interfeis ir realizacij. Atkreipkime dmes tai, jog po raktini odi using namespace std galime naudoti klass string trumpaj vard. ie raktiniai odiai skirti naudoti tik cpp-failuose. Tuo tarpu h-faile mums teko nurodyti piln vard std::string.
// CleverBoy.cpp #include "CleverBoy.h" using namespace std; CleverBoy::CleverBoy(string cw) { cleverWords = cw; } string CleverBoy::getCleverWords() { return cleverWords; }

12

Treiasis failas main.cpp naudoja modul CleverBoy:


// main.cpp #include <iostream> #include "CleverBoy.h" using namespace std; int main() { CleverBoy cleverBoy("Do not worry,C ++ is easy:)"); cout << "O dabar paklausykime protingu zodziu:" << endl; cout << cleverBoy.getCleverWords() << endl; }

Priklausomyb tarp i trij fail galime pavaizduoti taip:


CleverBoy.h
naudoja realizuoja

main.cpp

CleverBoy.cpp

Pastaba: nepamirkite h-failus apskliausti #ifndef/#define/#endif konstrukcijomis. Taip ivengsite daugkartinio to paties failo traukimo sudtingesniuose projektuose. Tiesa sakant, nuo i odi privalo prasidti vis h-fail raymas. Tai gero tono enklas. Turime du modulius: CleverBoy.cpp ir main.cpp. Vienas j realizuoj CleverBoy.h faile apraytj interfeis, antrasis j naudoja. Patys h-failai atskirai nra kompiliuojami. Abu modulius sukompiliuoti ir sujungti (sulinkuoti) vien program galime keliais bdais, kaip antai:
g++ CleverBoy.cpp main.cpp

Arba, jei einamajame kataloge nra paalini cpp-fail, tai tiesiog:


g++ *.cpp

Arba, jei norime vietoje standartinio vardo a.out (a.exe) parinkti prasmingesn gautos programos vard, pvz. CleverBoy.exe:
g++ -o CleverBoy.exe CleverBoy.cpp main.cpp

13

Kiekvienu atveju buvo atlikti trys veiksmai. Pavyzdiui, vietoje paskutins komandos galjome parayti tris atskiras komandas, daranias tiksliai t pat:
g++ c CleverBoy.cpp g++ c main.cpp g++ -o CleverBoy.exe CleverBoy.o main.o - kompiliavimas - kompiliavimas - linkavimas

Kompiliatoriaus raktas -c nurodo, jog reikia tik kompiliuoti ir nenaudoti linkavimo, t.y. i tekstinio cpp-failo pagaminti dvejetain o-fail. Patys savaime o-failai negali bti vykdomi. Kad jie tapt savistove vykdoma programa, juos dar reikia apjungti (sulinkuoti) tarpusavyje, kartu prijungiant (savaime, be papildom nurodym) standartines C++ bibliotekas. Toks ir yra klasikinis C++ program krimo ciklas:

naudojame kelet moduli (cpp-fail), kuri kiekvienas kompiliuojamas atskirai. Visi cpp-failai, iskyrus fail su funcija main(), turi atitinkamus h-failus gauti o-failai susiejami vien exe-fail, kartu prijungiant C++ bibliotekas

CleverBoy.cpp

kompiliatorius

CleverBoy.o hello1.o

linkeris

CleverBoy.exe

main.cpp

kompiliatorius

main.o hello1.o

c++ libs

Dvejojanius skaitytojus galime utikrinti, jog programa CleverBoy.exe tikrai atspausdins:


O dabar paklausykime protingu zodziu: Do not worry, C++ is easy:)

14

1.4.

Selektyvus kompiliavimas (make)

Ms projektlyje tra du moduliai. Praktikoje j bna deimtys ir imtai. Program skaidymas modulis nuostabus dalykas, siekiant suvaldyti dideli sudging program kod. Daug lengviau skyriumi tvarkyti nedidelius failus negu kapstytis po vien monstr. Pirm kart kompiliuojant, kompiliuojami visi moduliai tai neivengiama. Taiau vliau pakanka perkompiliuoti tik naujai pakeistus cpp-failus ir sulinkuoti su likusiais anksiau sukompiliuotais o-failais. Tenka rayti nemaai komand. Be to, kiekvien syk reikia nepamirti perkompiliuoti visus pakeistus modulius, antraip neivengsime bd. Toks pastovus sekimas, kas buvo pakeista, o kas ne, yra labai nuobodus usimimas. Jau nekalbant apie tai, jog usiiopsoti ir pamirti yra labai mogika. Gyvenim i esms palengvina plaiai paplitusi programl make. Ji seka, kuriuos cpp-failus reikia perkompiliuoti, o kuri ne. Tuo tikslu atskirame faile (make-faile) mes suraome, k norime pagaminti, ir kokie ingsniai turi bti atlikti tikslui pasiekti. Programl make dirba selektyviai: atlieka tik tuos ingsnius, kuriuos reikia, o ne visus drauge. Klasikinis make-failo pavadinimas yra makefile:
# makefile CleverBoy.exe : CleverBoy.o main.o g++ -o CleverBoy.exe CleverBoy.o main.o CleverBoy.o : CleverBoy.cpp CleverBoy.h g++ -c CleverBoy.cpp main.o : main.cpp CleverBoy.h g++ -c main.cpp

Tuomet, kokius failus beredaguotume, komandinje eilutje uteks parayti:


make

Ekrane pamatysime, kaip po vien ingsn gimsta reikiami o-failai, ir vliau pagaminamas exe-failas. make-failas susideda i taisykli:
tikslas : prielaidos-atskirtos-tarpais komanda-tikslui-pasiekti

Pagal nutyljim, programa make stengiasi gauti pirmj faile pamint tiksl (ms atveju CleverBoy.exe). Jei bent viena prielaida tikslui pasiekti (failai, nuo kuri priklauso tikslas) yra naujesn, nei tikslo failas (patikrinama pagal fail datas), tai vykdoma nurodyta komanda-tikslui-pasiekti. Priklausomyb tikrinama rekurentikai: 15

prielaidos failai taip pat yra atnaujinami, jei jie priklauso nuo kit pasikeitusi fail. Pavyzdiui, jei paredaguosite ir isaugosite fail main.cpp, tai komanda make rekurentikai atseks, jog tikslui CleverBoy.exe pasiekti, reikia pirma atnaujinti main.o (naudojant komand g++ -c main.cpp), o vliau CleverBoy.exe (g++ -o CleverBoy.exe CleverBoy.o main.o). Analogikai, jei paredaguosime ir isaugosime fail CleverBoy.h, tai bus atnaujinti visi trys nuo jo priklausantys failai: main.o, CeleverBoy.o ir CleverBoy.exe. Pastaba: dauguma make-program grietai reikalauja, kad prie komand tikslui pasiekti bt padtas lygiai vienas tarpas arba tabuliacijos enklas. make-failai nra grietai skirti C++ program krimui. Juos galima naudoti ir kituose projektuose, kur galutinis rezultatas priklauso nuo keletos tarpini moduli. Labai patogu papildyti make-fail dar viena taisykle clean. Ji ivalo visk, k sugeneravo kompiliatorius, ir palieka ms pai raytus ieities failus. Taisykl geriausia pridti failo pabaig, kad ji nebt pagrindin:
clean: rm -f *.o rm -f *.exe

iuo atveju mums nereikalingus darbinius failus ivalys komanda make su nurodytu tikslu:
make clean

Unix operacins sistemos komanda rm skirta failams trinti. Parameras *.o (atitinkamai *.exe) nurodo trinti visus failus, kuri vardai baigiasi simboliais .o (atitinkamai *.exe). Tiek Unix, tiek ir Windows sistem komandose vaigdut * reikia bet koki simboli sek (taip pat ir tui). Taip kad neraykite rm *, nes itrinsite visus einamojo katalogo failus! Raktas -f nurodo, kad nespausdinti klaidos praneimo, jei nra k trinti. Skyrelio pabaigai palyginkime svokas:

compile link make build

- kompiliuoti vien modul: i cpp-failo gauname o-fail - susieti modulius: i o- ir lib-fail gauname exe-fail - perkompiliuoti tik pasikeitusius modulius ir susieti vien exe-fail - perkompiliuoti visk, nekreipiant dmesio keitimo datas

16

1.5.

Bendros taisyks (pattern rules) make-failuose

Yra dar daug patogi dalyk, kurie palengvina programuotojo gyvenim raant makefailus. Mes aptarsime du i j: bendras taisykles ir automatin o-failo prielaid radim. Kai turime deimtis cpp-fail, tai visai neapsimoka prie kiekvieno i j rayti g++ -c ir t.t.. Tuo tikslu galime aprayti bendr taisykl, kaip i cpp-fail gauti o-failus (analogikai: i o-fail gauti exe-fail). Pavelkime pagerint make-failo variant:
# nicemake compile : CleverBoy.exe

CleverBoy.exe : CleverBoy.o main.o CleverBoy.o : CleverBoy.cpp CleverBoy.h main.o : main.cpp CleverBoy.h #------------------------------------------------------------clean: rm -f *.o rm -f *.exe

build: clean compile %.exe: %.o g++ -o $@ $^ %.o: %.cpp g++ -c $<

Kadangi failo vardas nra makefile, tai turime naudoti parametr -f:
make -f nicemake

Eiluts, esanios vir brknio, nusako priklausomybes tarp fail. Eiluts, esanios emiau brknio, nusako bendras taisykles. Bendros taisykls taikomos failams, pagal fail vard pabaigas (.cpp .o .exe), vietoje specialij simboli statant atitinkam fail vardus: $@ - tikslo failo vardas $< - pirmojo prielaidos failo vardas $^ - vis prielaidos fail vardai, atskirti tarpais Atkreipkime dmes, jog pirmasis make-failo tikslas yra compile. Toks tikslo uvardinimas leidia emiau brknio aprayti patog tiksl build: clean compile, 17

kuris visk pradioje ivalo, o paskui sukompiliuoja. Tokiu bd daugumos ms program make-failai emiau brknio bus identiki. Skirsis tik aukiau brknio ivardinti moduliai su savo prielaidomis. Vis ms make-fail vardai toliau bus makefile, kad nereikt rainti rakto -f. Pastaba: jei make-failo viduje raote labai ilgas eilutes, suskaldykite jas kelias, kiekvienos pabaigoje raydami simbol "\", po kurio i karto seka enter-klavias. Labai pravartu inoti dar vien kompiliatoriaus g++ savyb galimyb automatikai sugeneruoti o-fail prielaid tekstin eilut:
g++ -MM main.cpp

Ekrane bus atspausdinta viena eilut:


main.o: main.cpp CleverBoy.h

Unix ir Windows terpse vis komandins eiluts komand ivedimas ekran gali bti nukreiptas fail simbolio > pagalba:
g++ -MM main.cpp > tarpinis.txt

Vlgi galima pasinaudoti simbolio "*" privalumais:


g++ -MM *.cpp

Ekrane pamatysime:
CleverBoy.o: CleverBoy.cpp CleverBoy.h main.o: main.cpp CleverBoy.h

18

1.6.

Keletas btiniausi Unix komand

emiau pateikiama keletas btiniausi Unix komand, skirt darbui su failine sistema. Komandas reikia vedinti specialiame komandiniame lange, ms atveju vadinamame shell. Naujai paleistas komandinis langas paprastai vartotoj nukelia jo asmenin "nam katalog" (home directory). Pasidomkime, kokie failai yra saugomi nam kataloge:
ls

Komanda atspausdins einamajame kataloge esani fail ir kit katalog vardus. Jei norime matyti fail sukrimo datas, dydius bei poym, ar failas yra failas, ar katalogas, raome:
ls -l

Kai kurie failai laikomi tarnybiniais-paslptais. J vardai prasideda tako simboliu. Juos daniausiai kuria ne pats vartotojas, o jo naudojamos aplikacijos. Komanda ls pagal nutyljim j nerodo. Pamatyti visus einamojo katalogo failus galima rakto -a pagalba:
ls -al

Mayt gudryb, kaip susikurti nauj tekstin fail, kad po to j galtume redaguoti:
ls > naujas_failas.txt

Jei nenorime kurti naujo failo, o tik prirasyti prie jau egzistuojanio failo pabaigos, vietoje simbolio ">" naudokime simbolius ">>". Jei norime suinoti einamojo katalogo piln keli, renkame:
pwd

Jei ms kataloge yra oop2 kurso pakatalogis, tai j patekti galime taip:
cd oop2

Atgal grtame su komanda (dar sakoma, grti tvin katalog arba grti katalogu auksiau turint omenyje katalog medio metafor):
cd ..

19

Jei norime atsidurti paioje katalog aknyje renkame:


cd /

Komandos cd pagalba mes galime nukeliauti bet kur katalog. Namo, homekatalog, i bet kurios vietos grtame taip:
cd ~

Keliaudami po katalogus galime pastoviai naudoti komand pwd, kuri mums pasufleruos buvimo viet. Pagrindiniai cd komandos variantai trumpai:
cd cd cd cd cd cd cd cd Pakatalogis - nukelia gylyn Pakatalog Pakat/Mano - nukelia gylyn per du pakatalogius .. - grina atgal (auktyn) per vien katalog ../.. - grina auktyn per du katalogus / - nukelia katalog medio akn /usr/bin - nukelia absoliut keli (skaiiuojant nuo aknies) ~ - nukelia nam katalog . - nieko nekeiia, nes "." reikia einamj katalog

Dabar turt bti aiku, kodl failas a.out i einamojo katalogo yra paleidiamas taip:
./a.out

Pakeisti failo vard arba perkelti j kit katalog galima su komanda:


mv mv mv dabartinis_vardas.txt dabartinis_vardas.txt dabartinis_vardas.txt naujas_vardas.txt naujas_kelias/ naujas_kelias/naujas_vardas.txt

Visikai analogikai veikia ir fail kopijavimo komanda, tik ji neitrina originalo:


cp cp cp dabartinis_vardas.txt dabartinis_vardas.txt dabartinis_vardas.txt naujas_vardas.txt naujas_kelias/ naujas_kelias/naujas_vardas.txt

Failas trinamas taip:


rm failas.txt

Jei norime itrinti visus failus su galne .o:


rm *.o

Jei norime itrinti rekursyviai i vis pakatalogi:


rm -r *.o

20

spjimas !!! Du, dar geriau, septynis kartus pagalvokite, prie rinkdami komandas:
rm * rm -r *

Pirmoji itrins visus einamojo katalogo failus, iskyrus pakatalogius. Antroji rekursyviai itrins ne tik visus failus, bet ir visus pakatalogius. Katalogai kuriami ir naikinami su komandomis:
mkdir NaujasKatalogas rmdir NereikalingasKatalogas

Komanda less leidia perirti tekstin fail. I jos ieiname paspaud "q". Ji rodo fail po vien puslap ekrane. Jei norime vis failo turin ivesti ekran, naudojame komand cat:
less mano_failas.txt cat mano_failas.txt

Jei kataloge yra daug fail, ir visas j sraas netelpa ekrane, nukreipkime komandos ls ijim komandos less jim:
ls -al | less

Jei mes surenkame shell'ui neinom komand, tai jis bandys surasti sistemoje nurodytuose kataloguose atitinkam program, pvz.:
g++ hello1.cpp

Kartais pravartu inoti, i kokio katalogo shell'as ikas i program. Tuo tikslu renkame:
which g++

Verta inoti: Labai patogu naudotis tabuliacijos klaviu renkant komandas. Renkant failo ar pakatalogio vard utenka surinkti tik jo pradi ir spausti tabuliacijos klavi - failo pabaiga bus parinkta ir atspausdinta vienareikmikai. Jei pamirote, kaip naudotis viena ar kita komanda, pvz. cp ar g++ surinkite:
man cp man g++

Tursite gerokai maiau vargo, jei pirmj savo komanda pasirinksite mc - Midnight 21

Comander. Tuomet fail sraas visuomet bus ekrane, kopijavimas - tikras malonumas, o sitas tekst redaktorius dar ir parykins C++ sintaks. Beje, redaguojant su vidiniu mc programos redaktoriumi tekstinius failus, sukurtus DOS/Windows terpje, galite aptikti simbolius ^M kiekvienos eiluts pabaigoje. Taip yra todl, jog Unix sistemoje eiluts pabaiga koduojama vienu specialiu simboliu, o DOS/Windows dviem, kuri pirmasis ir yra vaizduojamas kaip ^M. Konvertuoti tarp dviej tekstini fail format pirmyn-atgal galima komandomis:
dos2unix dosfailas.txt unix2dos unixfailas.txt

Antra komanda vartojama reiau, nes dauguma Windows tekst redaktori (nebent iskyrus NotePad) supranta ir Unix eilui pabaigas.

22

1.7.

Nuo C prie C++ per maisto preki parduotuv

Pailiustruokime C++ klasi svoka maisto preki parduotuvs pavyzdiu. ia turime maisto produktus su savo pavadinimais ir kainomis. Taip pat turime pardavim sra: kok produkt, kada pardavme, u kiek ir kok kiek. Be abejo, tai bus tik labai supaprastintas realios maisto preki parduotuvs modelis. O pradsime mes dar i toliau: apsiraykime C stiliaus maisto produkto struktr (ne klas) Food su dviem laukais (name ir price):
// food1.cpp #include <cstring> #include <iostream> using namespace std; struct Food { char name [80]; double price; }; void printPrice (Food& food, double amount) { cout << " " << amount << " units of " << food.name << " costs " << (food.price * amount) << endl; } int main () { Food bread = {"bread", 2.50}; Food milk = {"milk", 1.82}; printPrice(bread, printPrice(milk, strcpy(milk.name, printPrice(milk, 0.5); 1.5); "beer"); 1.5);

Funkcijos main() viduje sukuriami du kintamieji tipo Food:


bread name: price: bread 2.50 milk name: price: milk 1.82

i programa ekrane atspausdins:


0.5 units of bread costs 1.25 1.5 units of milk costs 2.73 1.5 units of beer costs 2.73

23

1.8.

Klas = duomenys + metodai

irint i gero programavimo tono puss, programoje food1 yra maiausiai du prasto skonio dalykai: 1. funkcija printPrice() dirba su struktros Food tipo kintamasiais, taiau pati funkcija nra struktros dalis, 2. funkcijoje main() mes patys kiame nagus prie pieno vardo. Idealiu atveju pats pienas turi sprsti, ar mes galime keisti jo vard ir ne. Isprsime abi problemas apraydami klas Food. C++ klas yra C struktros svokos ipltimas. Klasje saugomi ne tik duomenys, bet ir funkcijos manipuliuojanios jais. Tokios funkcijos vadinamos metodais. Tiek klass duomenys (atributai), tiek ir klass metodai yra vadinami klass nariais. Klass tipo kintamieji vadinami objektais. Klasse mes galime vienus narius padaryti privaiais, kad i iors nebt galima j naudoti, o tik paios klass metod viduje, kaip pailiustruota programoje food2:
// food2.cpp class Food { private: string name; double price; public: Food (string name, double price); string getName double getPrice void setPrice }; void () {return name;} () {return price;} (double p) {price = p;}

printPrice (double amount);

Matome, jog klas Food turi tuos paius duomen laukus, kaip ir ankstesns programos struktra Food, tik dar prisidjo keletas vie (public) metod, pasiekiam klass iorje. Atkreipkime dmes, jog mes leidome keisti maisto kain metodo setPrice() pagalba, bet neleidome keisti maisto vardo. Tiesiog pasirinkome nerayti analogiko metodo setName(). Vienos eiluts metodai aprayti ir realizuoti paiame klass aprae, o didesnieji apibriami atskirai:

24

Food::Food (string n, double p) { name = n; price = p; } void Food::printPrice (double amount) { cout << " " << amount << " units of " << name << " costs " << (price * amount) << endl; }

Dabar atkreipkime dmes pasikeitusi funkcij printPrice(), kuri virto metodu. Vis pirma, metod, realizuot klass iorje, vardai gauna priedl KlassVardas::. Be to, nebra pirmojo argumento food. Mat metod galima ikviesti tik konkreiam objektui (klass tipo kintamajam), kuris ir atstos argument. Ir btent jo laukais name ir price yra manipuliuojama metodo viduje be papildom priera. Klass naudojimas primena struktr naudojim: metodai pasiekiami taip pat, kaip ir duomenys:
int main () { Food bread ("bread", 2.50); Food milk ("milk", 1.82); bread.printPrice milk.printPrice milk.setPrice milk.printPrice (0.5); (1.5); (2.10); (1.5);

Programa food2 atspausdins:


0.5 units of bread costs 1.25 1.5 units of milk costs 2.73 1.5 units of milk costs 3.15

Klass kintamieji (objektai) apraomi taip pat, kaip ir visi kiti C/C++ kintamieji. Vienintelis skirtumas yra tas, jog visi objektai privalo bti inicializuoti. Tai daroma specialaus metodo, konstruktoriaus, pagalba. Konstruktoriaus vardas sutampa su klass vardu, be to, jis negrina jokios reikms, netgi void. Jis kvieiamas lygiai vien kart, objekto sukrimo metu, kartu perduodant reikalingus argumentus. Skirtingai nuo programos food1, mes nebeturime teiss rayti strcpy(milk.name, "beer") programoje food2, nes laukas name (kaip ir price) yra privatus. ioje vietoje buvo nusprsta, kad po maisto produkto sukrimo nebegalima keisti jo pavadinimo, taiau leidiama keisti jo kain vieojo metodo setPrice() pagalba.

25

1.9.

Konteineriai ir iteratoriai

Programose retai pavyksta apsiriboti pavieni objekt naudojimu. Danai tenka dirbti su objekt masyvais ir sraais. Standartin C++ biblioteka pateikia itis rinkin dinamikai augani konteineri bei algoritm darbui su jais. Mes pasinaudosime dviem i j: vektoriumi (masyvu) ir srau. Konteineriuose saugosime tuos paius programos food2 objektus Food:
// food3.cpp ... #include <vector> #include <list> ... int main () { vector<Food> foodVector; list<Food> foodList; for (;;) { string name; double price; cout << "Enter food name ('q' - quit): "; cin >> name; if (name == "q") break; cout << "Price of one unit of " << name << ": "; cin >> price; Food food(name, price); foodVector.push_back(food); foodList .push_back(food);

} ...

Standartini konteineri objektai yra apibriami nurodant juose saugom element tip tarp < ir >. i konstrukcija, vadinama ablonais (template), detaliau bus aptarta daug vliau. Kaip matome, abiej tip konteineriai prigrdami duomenimis metodo push_back() pagalba. Vektorius yra labai panaus paprast C kalbos masyv. Jo elementus galime indeksuoti. Vektori papildymas elementais nra greitas veiksmas, nes kuomet element kiekis (size) virija vektoriaus vidinio buferio talp (capacity), tenka iskirti nauj bufer ir j perkopijuoti visus elementus bei itrinant senj bufer. Tuo tarpu sraai auga greitai, bet j elementus galime perbgti tik i eils.

26

foodVector name: price: bread 2.50 name: price: size=3 capacity=4 foodList name: price: bread 2.50 name: price: milk 1.82 name: price: beer 2.40 milk 1.82 name: price: beer 2.40

emiau esantis kodo fragmentas demonstruoja, jog vektoriai yra indeksuojami taip pat, kaip ir C kalbos masyvai.
cout << "Vector size=" << foodVector.size() << " capacity=" << foodVector.capacity() << endl; for (int i = 0; i < foodVector.size(); i++) foodVector[i].printPrice(2.00);

ved tris produktus, gausime:


Vector size=3 capacity=4 2 units of bread costs 5 2 units of milk costs 3.64 2 units of beer costs 4.8

Tuo tarpu srao elementai perbgami iteratori pagalba. Tai specialios paskirties objektai, kurie elgiasi labai panaiai, kaip rodykl vien konteinerio element: operatorius ++ perveda iteratori prie sekanio elemento, o operatorius * - grina objekt, kur iteratorius rodo iuo metu. Konteineriai turi tokius metodus begin() ir end(), kuri pirmasis grina iteratori, rodant pirmj elemnt, o antrasis - element, esant u paskutiniojo. Itaratori mechanizmas veikia ne tik sraams, ir ne tik vektoriams, bet ir visiems kitiems standartiniams konteineriams:
cout << "List size=" << foodList.size() << endl; list<Food>::iterator li = foodList.begin(); for (; li != foodList.end(); li++) (*li).printPrice(3.00); // not so nice style cout << "Iterating through vector:" << endl; vector<Food>::iterator vi = foodVector.begin(); for (; vi != foodVector.end(); vi++) cout << vi->getName () << " " // better this way << vi->getPrice() << endl;

27

is kodo fragmentas atspausdins:


List size 3 units 3 units 3 units Iterating bread 2.5 milk 1.82 beer 2.4 = 3 of bread costs 7.5 of milk costs 5.46 of beer costs 7.2 through vector:

Kaip ir su C kalbos rodyklmis struktras, taip ir su iteratoriais, struktr ir klasi laukus galime pasiekti dviem bdais:
(*iterator).printPrice(3.00); iterator->printPrice(3.00); // not so nice style // better this way

Antrasis bdas yra stilikai priimtinesnis.

28

1.10.

Palyginimo operatorius, konteineri riavimas ir failai

Praeitame skyrelyje duomenis vedinjome i klaviatros. iame skyrelyje mes juos pakrausime i failo, suriuosime ir isaugosime naujame faile. Tam, kad standartiniai konteineriai galt palyginti du maisto objektus Food, mes turime aprayti special metod: perkraut operatori maiau. Apie operatori perkrovim plaiau kalbsime vlesniuose skyriuose. Dabar tiesiog susitaikykime su kiek keistoka operator< sintakse. Pateikta operator< versija lygina tik maisto pavadinimus:
// food4.cpp class Food { private: string name; double price; public: Food (const string& name, double price); string double void void }; getName getPrice setPrice printPrice () const {return name;} () const {return price;} (double p) {price = p;} (double amount) const;

bool operator < (const Food& f) const;

Food::Food (const string& n, double p) { name = n; price = p; } void Food::printPrice (double amount) const { cout << " " << amount << " units of " << name << " costs " << (price * amount) << endl; } bool Food::operator < (const Food& f) const { return name < f.getName(); }

29

Kiek modernizuotame klass Food aprae yra naudojami trys nauji tampriai susij dalykai: 1. Objekt perdavimas metodams per nuorod (ampersendo enklas &). Be io enklo metodams ir funkcijoms perduodama objekto kopija (kaip ankstesniuose pavyzdiuose). Kuomet objektai yra didesni, perduodant kopij bereikalingai uimama vieta steke ir ltja programos veikimas. Perduodant metodui nuorod objekt, pats objektas nra kopijuojamas - metodas dirba su originalu. Darbas su nuoroda niekuom nesiskiria nuo darbo su paiu originalu. 2. Kadangi perduodant objektus pagal nuorod metodas dirba su originalais, jis potencialiai gali pakeisti original objekt, t.y. priskirti naujas reikmes objekto laukams arba ikviesti objekto metodus, kurie pakeis jo vidin bsen. Toki ketinim neturintis metodas pasiada savo viduje nekeisti originalaus objekto priraydamas odel const prie parametr (pvz., const string& name), o kompiliatorius pasirpina, kad paado bt laikomasi. 3. Taigi, metodas gavo konstantin nuorod objekt ir nebegali jo keisti. Taip pat jis negali kviesti objekto metod, kurie keist jo bsen. Gali kviesti tik konstantinius metodus, kuri antrats gale priraytis odelis const. (pvz getName, printPrice). Konstantini metod viduje negalima keisti objekto lauk. Ms pavyzdyje metodas keistu pavadinimu operator < gauna konstantin nuorod f klass Food objekt. Objektui f kvieiamas metodas getName, kuris savo ruotu apraytas kaip konstantinis: jis nekeiia objekto f, tik grina jo pavadinimo kopij. Tuo tarpu metodas setPrice nra konstantinis, nes keiia objekto lauk price. Kuomet turime perkraut palyginimo operatori, mes galime j naudoti tuo paiu bdu, kaip ir baziniams C++ tipams:
int main () { Food bread ("bread", 2.50); Food milk ("milk", 1.82); cout << "Compare food names:" << endl << bread.getName() << " < " << milk.getName() << " = " << (bread < milk) << endl; ... }

is kodo fragmentas atspausdins:


Compare food names: bread < milk = 1

Toliau mes vietoje vedimo i klaviatros naudosime skaitym i failo input.txt. io failo viduje yra penkios tekstins eiluts: 30

razinos 5.99 pienas 1.80 alus 1.78 duona 1.20 grybai 16.99

Skaitymas i failo labai primena skaitym i klaviatros. Abiem atvejais C++ terminologija sakoma, kad duomenys skaitomi i vedimo srauto. Tik pirmu atveju srautas sujungas su failu (klass ifstream objektu), antruoju su klaviatra (globalus objektas cin). Mes naudosime failo objekto metod good(), kuris pasako, ar pavyko prie tai buvusi skaitymo operacija:
vector<Food> foodVector; list <Food> foodList; ifstream inFile ("input.txt"); string name; inFile >> name; while (inFile.good()) { double price; inFile >> price; foodVector.push_back(Food(name, price)); foodList .push_back(Food(name, price)); inFile >> name; } inFile.close();

Vektoriaus (masyvo) elementai riuojami globalios funkcijos sort() pagalba. Pastaroji reikalauja dviej parametr: iteratoriaus, rodanio pirmj element, ir iteratoriaus, rodanio element, esant u paskutiniojo:
sort(foodVector.begin(), foodVector.end()); cout << "Vector sorted by name:" << endl; for (int i = 0; i < foodVector.size(); i++) cout << foodVector[i].getName() << " " << foodVector[i].getPrice() << endl;

is kodo fragmentas atspausdins:


Vector sorted by name: alus 1.78 duona 1.2 grybai 16.99 pienas 1.8 razinos 5.99

Kaip minjome praeitame skyrelyje, standartini konteineri iteratoriai elgiasi labai panaiai, kaip ir prastos C/C++ kalbos rodykls struktras ar klases. Todl algoritmai, dirbantys su dinaminiais vektoriais, yra pritaikyti darbui ir su prastiniais C/C++ masyvais. Suriuokime sveikj skaii masyv:

31

int intArray[] = {1, 2, 15, 7, -2, 14, -20, 6}; sort(&intArray[0], &intArray[8]); cout << "Sorted integers:" << endl; for (int i = 0; i < 8; i++) cout << intArray[i] << " "; cout << endl;

Atkreipkime dmes, jog masyve yra atuoni elementai, ir paskutiniojo indeksas yra 7. Taiau standartiniu pabaigos poymiu laikoma rodykl neegzistuojant element, esant ikart u paskutiniojo. Ekrane pamatysime:
Sorted integers: -20 -2 1 2 6 7 14 15

Tuo tarpu sraas suriuojamas su jo paties metodu sort(). Suriuotus duomenis isaugosime faile output.txt. Raymas fail labai primena spausdinim ekrane: abiem atvejais raome ivedimo sraut. Tik pirmuoju atveju srautas sujungiamas su fail atstovaujaniu objektu (tipo ofstream ouput file stream), o antruoju su ekran atitinkaniu globaliu objektu cout:
foodList.sort(); ofstream outFile("output.txt"); list<Food>::iterator li = foodList.begin(); for (; li != foodList.end(); li++) outFile << li->getName () << " " << li->getPrice() << endl; outFile.close();

Atkreipkite dmes, jog baigus darb su failu (tiek skaitym, tiek ir raym), kvieiamas metodas close(), kuris atlieka reikiamus baigiamusius darbus ir atlaisvina darbui su failu naudotus resursus.

32

1.11.

Dinaminis objekt sukrimas ir naikinimas

Iki iol mes naudojomi tik vadinamuosius lokalius, dar vadinamus, automatinius objektus. T.y. kintamojo vardas nusako pat objekt, kuris yra automatikai sukuriamas jo apraymo vietoje ir automatikai sunaikinamas, kuomet programos vykdymas ieina i jo apraymo bloko (metodo, ciklo ir pan.), apriboto figriniais sklaustais {}. Taip pat mes naudojome nuorodas (konstantines) objektus, kaip parametrus metodui. Abiem atvejais, kintamasis, viso savo gyvavimo metu yra susities su vienu ir tuo paiu objektu. Tuo tarpu objektikai orientuotas programavimas yra sunkiai sivaizduojamas, be rodykli dinaminius objektus. iuo atveju kintamasis (rodykl) nusako ne pat objekt, o rodo dinaminje atmintyje ireiktai (operatoriaus new pagalba) sukurt objekt. Programos vykdymo metu ta pati rodykl gali bti nukreipta ir kitus objektus. Taip pat, jai galima priskirti ypating reikm nul (svokos null analog), kuri reikia, kad rodykl iuo metu niek nerodo. Dinaminiai objektai nra automatikai sunaikinami t padaro pats programuotojas operatoriaus delete pagalba. io operatoriaus ikvietimo umirimas vadinamas atminties nutekjimu tai viena daniausi programuotoj klaid. Dinamini objekt laukai (duomenys ir metodai) pasiekiami operatoriaus -> pagalba:
// food5.cpp int main () { Food* bread = new Food ("bread", 2.50); Food* milk = new Food ("milk", 1.82); cout << "Two dynamically created objects:" << endl << bread->getName() << " and " << milk ->getName() << endl; delete milk; milk = 0; milk = bread; cout << "Name of the milk: " << milk->getName() << endl; delete bread; bread = 0; milk = 0;

is kodo fragmentas atspausdins:


Two dynamically created objects: bread and milk Name of the milk: bread

Atmintyje patys objektai saugomi atsietai, nuo juos rodani rodykli. Turime keturis objektus atmintyje: dvi rodykles ir du klass Food objektus.

33

name: price:

bread 2.50

name: price:

milk 1.82

bread

milk

Punktyru paymta, jog programos vykdymo eigoje rodykl milk buvo nukreipta kit objekt. Prisiminkime, jog paios rodykls uima vienodai vietos atmintyje, nepriklausomai nuo to, kokio dydio objekt jos rodo. Ankstesniame skyrelyje mes turjome sra, kuriame saugojome klass Food objektus. Dabar apsibrkime sra, kuriame saugosime rodykles klass Food objektus. Paius duomenis imsime i jau inomo failo input.txt:
list<Food*> foods; ifstream inFile ("input.txt"); string name; inFile >> name; while (inFile.good()) { double price; inFile >> price; foods.push_back(new Food(name, price)); inFile >> name; } inFile.close();

Tokiu bd srao foods elementai yra rodykls dinamikai sukurtus objektus:

name: razinos price: 5.99

name: pienas price: 1.80

name: price:

alus 1.78

name: duona price: 1.20

name: price:

grybai 16.99

foods

Norint suriuoti tok sra nebeieis pasinaudoti ankstesniame skyrelyje apraytu palyginimo operatoriumi (operator <), nes jis buvo skirtas lyginti Food objektams, o ne Food*. Tuo tikslu apsiraysime dar vien standartins bibliotekos stiliaus keistenyb: klas-funkcij, kuri nusako t pat palyginimo operatori kitu bdu. Tai klas, turinti tik vien metod, labai keistu pavadinimu operator(), kuris palygina maisto pavadinimus:

34

class FoodPtrNameCompare { public: bool operator() (const Food* a, const Food* b) const { return (a->getName() < b->getName()); } };

Srao metodui sort() perduodamas papildomas parametras: laikinas objektas-funkcija, atsakingas u srao elemtent (rodykli) palyginim:
foods.sort(FoodPtrNameCompare()); list<Food*>::iterator li = foods.begin(); cout << "Food sorted by name:" << endl; for (; li != foods.end(); li++) cout << (*li)->getName () << " " << (*li)->getPrice() << endl;

Prisiminkime, jog iraika (*li) grina konteinerio element, kur rodo iteratorius. Ms atveju elementas yra rodykl objekt, todl jo metodai kvieiami taip:
(*li)->getName()

is kodo fragmentas atspausdins:


Food sorted by name: alus 1.78 duona 1.2 grybai 16.99 pienas 1.8 razinos 5.99

Be savo keistos sintakss, klas-funkcija mums leidia t pat sraa suriuoti ir kitais bdais, pvz. pagal kain. Tuo tikslu apsiraome klas-funkcij FoodPtrPriceCompare:
class FoodPtrPriceCompare { public: bool operator() (const Food* a, const Food* b) const { return (a->getPrice() < b->getPrice()); } };

Ji naudojama lygiai taip pat, kaip ir klas-funkcija FoodPtrNameCompare. Analogikas kodo fragmentas atspausdins:
Food sorted by price: 1.2 duona 1.78 alus 1.8 pienas 5.99 razinos 16.99 grybai

35

1.12.

Objekt tarpusavio sryiai

Ankstesniuose pavyzdliuose mes turjome vienintel klas Food. Tam, kad maisto parduotuv galt pradti pardavinti produktus, apraykime pardavimo klas Selling. ia mes saugome informacij apie vien pardavim: kur maisto produkt kada pardavme, kiek vienet pardavme ir kokia buvo vieno produkto vieneto kaina.
// selling.h class Food; // forward declaration class Selling { private: const Food* food; std::string date; int itemCount; double itemPrice; public: Selling (const Food* food, const std::string& date, int itemCount, double itemPrice); const Food* getFood () const {return std::string getDate () const {return int getItemCount () const {return double getItemPrice () const {return double getProfit () const; void print () const; };

food;} date;} itemCount;} itemPrice;}

Klasje Food udraudme keisti kainas ir palikome tik nauj pirkim registravim:
// food.h class Food { private: std::string name; double price; std::vector<Selling*> sellings; public: Food (const std::string& name, double price); std::string getName () const {return name;} double getPrice () const {return price;} double getProfit () const; void print () const; void addSelling (Selling* selling); int getSellingCount () const {return sellings.size();} Selling* getSelling (int i) {return sellings[i];} };

36

Kadangi klass Food ir Selling abi turi rodykli viena kit, tai mums tenka viename i fail (pasirinkome selling.h) naudoti iankstin klass apibrim be jos kno (class Food;). Vieno pardavimo metu udirbtas pelnas nra atskirai saugomas. Jis apskaiiuojamas pagal kitus duomenis:
// selling.cpp #include <iostream> #include "selling.h" #include "food.h" using namespace std; Selling::Selling (const Food* const std::string& int double { this->food = food; this->date = date; this->itemCount = itemCount; this->itemPrice = itemPrice; } food, date, itemCount, itemPrice)

double Selling::getProfit () const { return itemCount * (itemPrice - food->getPrice()); } void Selling::print () const { cout << food->getName() << " " << date << " " << itemCount << " " << itemPrice << " pelnas=" << getProfit() << endl; }

Visi metodai ir konstruktoriai gauna vien papildom nematom parametr this, rodant objekt, kuriam yra ikviestas metodas. Mes parametr this naudojame ireiktai tik ten, kur klass nari vardai sutampa su metodo parametr vardais. Analogikai, vieno produkto vis pardavim pelnas irgi yra skaiiuojamas metodo pagalba. Atkreipkime dmes, jog metodas getProfit yra konstantinis, todl jo viduje iteruoti per lauko sellings elementus galime tik konstantinio iteratoriaus pagalba:

37

// food.cpp #include <iostream> #include "food.h" using namespace std; Food::Food (const string& name, double price) { this->name = name; this->price = price; } double Food::getProfit () const { double profit = 0; vector<Selling*>::const_iterator iter = sellings.begin(); for (; iter != sellings.end(); iter++) profit += (*iter)->getProfit(); return profit; } void Food::print () const { cout << name << " " << price << " pelnas=" << getProfit() << endl; } void Food::addSelling(Selling* selling) { sellings.push_back(selling); }

Abi klases ir j tarpusavio sry galime pavaizduoti grafikai:


Food
name price :string :double 1 N

Selling
date :string itemCount :int itemPrice :double getProfit() print()

getProfit() print() addSelling() getSellingCount() getSelling()

N
foodList sellingList

Klass vaizduojamos kaip trij dali staiakampis: virutinje dalyje klass vardas, vidurinje duomen laukai (j tipai po dvitakio), apatinje dalyje metodai. Jei klas turi nuorod kit klas, tai ry vaizduojame atitinkamos krypties rodykle su skaiiais: klas Selling turi rodykl vien (1) Food objekt, o klas Food turi itis rodykli masyv daug (N) klass Selling objekt. 38

Duts foodList ir sellingList nusako kintamuosius: sraus i rodykli atitinkamai Food ir Selling objektus. Rombas viename rodykls gale sako, jog foodList susideda i N klass Food objekt. Toks ryys vadinamas agregacija. Sraai foodList ir sellingList yra lokals funkcijos main kintamieji. Geras programavimo tonas pareigoja vengti globali kintamj. Programa susideda i pagrindinio aminojo ciklo: rodyti meniu, laukti vartotojo vedimo ir vykdyti pasirinkt komand:
// main.cpp ... typedef std::list<Food*> FoodList; typedef std::list<Selling*> SellingList; ... int main () { FoodList foodList; SellingList sellingList; for (;;) { cout << endl << "Maisto produktai:" << endl << " 1 - sarasas" << endl << " 2 - sukurti" << endl << " 3 - parduoti" << endl << " 4 - produkto detales" << endl << " 5 - laikotarpio pelnas" << endl << endl << " 0 - baigti darba" << endl; string key; cin >> key; if (key == "0") return 0; else if (key == "1") printFoodList(foodList); else if (key == "2") addNewFood(foodList); else if (key == "3") sellFood(foodList, sellingList); else if (key == "4") showFoodDetails(foodList); else if (key == "5") showProfit(sellingList); else cout << endl << "Neteisinga komanda..." << endl; } }

Maisto parduotuvs ieities tekstai susideda i penketos fail, kuri include-ryiai pavaizduoti punktyrinmis rodyklmis.
selling.h food.h

selling.cpp

food.cpp

main.cpp

39

emiau pateikiamos likusios modulio main.cpp funkcijos:


// main.cpp Food* selectFood (FoodList& foodList) { for (;;) { string name; printFoodList(foodList); cout << "Pasirinkite produkta ('#'-baigti): "; cin >> name; if (name == "#") return 0; FoodList::iterator iter = foodList.begin(); for (; iter != foodList.end(); iter++) if (name == (*iter)->getName()) return *iter; } return 0; // unreacheable } void printFoodList (FoodList& foodList) { cout << "-- produktu sarasas " << foodList.size() << endl; FoodList::iterator iter = foodList.begin(); for (; iter != foodList.end(); iter++) (*iter)->print(); } void addNewFood (FoodList& foodList) { string name; double price; cout << "-- naujas maisto produktas --" << endl; cout << "pavadinimas: "; cin >> name; cout << "kaina: "; cin >> price; foodList.push_back(new Food(name, price)); } void sellFood (FoodList& foodList, SellingList& sellingList) { cout << "-- produkto pardavimas --" << endl; Food* food = selectFood(foodList); if (food == 0) return; string date; int itemCount; double itemPrice; food->print(); cout << "pardavimo data (yyyy.mm.dd): "; cin >> date; cout << "vienetu skaicius: "; cin >> itemCount; cout << "vieneto kaina: "; cin >> itemPrice; Selling* selling = new Selling(food, date, itemCount, itemPrice); food->addSelling(selling); sellingList.push_back(selling); }

40

void showFoodDetails(FoodList& foodList) { cout << "-- produkto pardavimo detales --" << endl; Food* food = selectFood(foodList); if (food == 0) return; cout << "Maisto pardavimai: "; food->print(); for (int i = 0; i < food->getSellingCount(); i++) food->getSelling(i)->print(); } void showProfit(SellingList& sellingList) { string dateFrom; string dateTo; cout << "-- laikotarpio pardavimu pelnas --" << endl; cout << " nuo kada (yyyy.mm.dd): "; cin >> dateFrom; cout << " iki kada (yyyy.mm.dd): "; cin >> dateTo; double totalProfit = 0; SellingList::iterator iter = sellingList.begin(); for (; iter != sellingList.end(); iter++) if (dateFrom <= (*iter)->getDate() && (*iter)->getDate() <= dateTo) { (*iter)->print(); totalProfit += (*iter)->getProfit(); } cout << "laikotarpio pelnas: " << totalProfit << endl; }

41

1.13.

Paveldjimas ir polimorfizmas

Paskutin vadins dalies skyrel paskirkime likusiems dviems (i trij) objektikai orientuoto programavimo (OOP) banginiams: paveldjimui ir polimorfizmui, kurie visuomet eina drauge. Pirmasis OOP banginis kur mes jau matme inkapsuliacija. Kartais sakoma, kad inkapsuliacija, tai kai klasje kartu su duomenimis yra metodai, operuojantys tais duomenimis. Taiau mes mstykime, jog inkapsuliacija, tai klass realizacijos detali paslpimas po gerai apgalvotu interfeisu. Ieities tekstuose inkapsuliacija pasireik dviem bdais:

Klass interfeisas (apraas) buvo ikeltas atskir antratin h-fail, kur gali matyti ir naudoti (traukti) kiti moduliai (cpp-failai). Tuo tarpu klass realizacija (apibrimas) nuosavame modulyje (cpp-faile) paprastai nra matomas kitiems moduliams. Tokia inkapsuliacija yra atkeliavusi i modulinio programavimo, kuris dera ne tik su OOP, bet ir su kitais programavimo stiliais (procedriniu, struktriniu ir t.t.). Skirtingai nuo C/C++ struktr duomen-lauk, kurie yra viei (public), klass duomenys-laukai daniausiai bna paslpti (private) nuo tiesioginio skaitymo ir keitimo. Jais manipuliuoja viei (public) klass metodai. Tai OOP inkapsuliacija.

Paveldjim ir polimorfizm pailiustruosime toliau vystydam maisto preki parduotuvs pavyzd. Modeliuosime nedalomus ir sudtinius maisto produktus. Pavyzdiui, laikysime, kad druska, miltai, sviestas, dera ir pan. yra nedalomi maisto produktai (PrimitiveFood), o sumutinis, pyragas ir t.t. - dalomi (CompositeFood).

42

Food
name :string Clone() getName() getPrice() print() getProfit() addSelling() getSellingCount() getSelling()

Selling
date :string itemCount :int itemPrice :double getProfit() print()

CompositeFood
addFoodCopy() getFoodCount() getFood() clone() getPrice() print()

PrimitiveFood
price :double clone() getPrice()

sellingList

foodList

Ms klasi hierarchijoje bendriausia maisto produkto svok nusako klas Food. Joje yra duomenys ir metodai bdingi visiems maisto produktams:
// food.h class Food { private: std::string name; std::vector<Selling*> sellings; public: Food (const std::string& name); Food (const Food& food); virtual Food* std::string virtual double clone getName getPrice () const = 0; () const {return name;} () const = 0;

virtual void print (const std::string& margin = "") const; double getProfit () const;

};

void addSelling (Selling* selling); int getSellingCount () const {return sellings.size();} Selling* getSelling (int i) {return sellings[i];}

Rodykl su dideliu baltu trikampiu viename gale ymi paveldjimo sry. Klass 43

PrimitiveFood ir CompositeFood paveldi (turi savyje) visus bazins klass Food laukus (duomenis ir metodus). Pavyzdiui, tiek paprasti, tiek ir sudtiniai maisto produktai turi pavadinim (name). Todl pavadinimas saugomas bazinje klasje Food, o ivestins klass PrimitiveFood ir CompositeFood j paveldi i klass Food. odelis virtual ymi polimorfinius metodus. Virtuals metodai gali bti perrayti paveldtose klasse. Pavyzdiui, paprasti produktai savyje turi kain, o sudtiniai produktai jos nesaugo savyje jie apskaiiuoja kain virtualaus metodo getPrice viduje sumuodami sudtini dali kainas. Kadangi bazinje klasje Food mes i viso negalime pateikti prasmingos metodo getPrice realizacijos, tai jis paymtas kaip variai virtualus pabaigoje priraant "= 0". variai virtuals metodai neturi jokio kno (realizacijos), netgi tuio. Klas, kuri turi bent vien variai virtual metod vadinama abstrakia klase. Mes negalime kurti abstrakios klass objekt. Abstrakios klass visuomet turi ivestines klases, kurios pateikia trkstamas variai virtuali metod realizacijas. Metodas print turi vienintel parametr margin (parat) su reikme pagal nutyljim. Tok metod galime kviesti su vienu parametru arba be parametr, tuomet bus perduota reikm pagal nutyljim. Atsirado papildomas konstruktorius, priimantis vienintel argument - konstantin nuorod tos paios klass objekt: Food (const Food& food). Toks konstruktorius vadinamas kopijavimo konstruktoriumi. Ms pavyzdyje jis naudojamas metoduose clone, kad pagaminti tiksli objekto kopij. Klas PrimitiveFood paveldi visus laukus i bazins klass Food, ir pateikia savas (perraydama) variai virtuali metod clone ir getPrice realizacijas. ia taip pat rasime nuosav kopijavimo konstruktori. Pats paveldjimas yra nusakomas urau : public Food:
// food.h class PrimitiveFood : public Food { private: double price; public: PrimitiveFood (const std::string& name, double price); PrimitiveFood (const PrimitiveFood& food); virtual Food* virtual double clone getPrice () const; () const {return price;}

};

Sudtinio maisto produkto klas CompositeFood turi tik pavadinim, kur paveldi i klass Food. Vis kit savyje saugo sudtins produkto dalys kiti klass Food 44

objektai:
// food.h class CompositeFood : public Food { private: std::vector<Food*> foods; public: CompositeFood (const std::string& name); CompositeFood (const CompositeFood& food); void int addFoodCopy getFoodCount Food* getFood const Food* getFood (const Food* food); () const {return foods.size();} (int i) {return foods[i];} (int i) const {return foods[i];}

};

virtual Food* clone () const; virtual double getPrice () const; virtual void print (const std::string& margin = "") const;

Atkreipkime dmes, jog sudtinio maisto klas net tik perrao variai virtulius metodus clone ir getPrice, bet ir pateikia sav virtualaus metodo print realizacij: jame atspausdina ir visas sudtines savo dalis. Gal kas nors jau pastebjo, jog CompositeFood susideda i rodykli Food. O mes juk sakme, kad klas Food turi variai virtuali metod, todl ji yra abstrakti klas, ir bdama abstrakia klase negali turti joki sukurt objekt. Reikalas tame, jog paveldjimas susieja klases yra-ryiu (is-a). T.y. PrimitiveFood yra Food, ir CompositeFood irgi yra Food. Arba galime sivaizduoti, kad PrimitiveFood ir CompositeFood turi savyje klas Food. Programose tai reikia: ten, kur reikia rodykls (nuorodos) bazins klass Food objektus galima perduoti rodykles (nuorodas) paveldt klasi PrimitiveFood ir CompositeFood objektus. iame pavyzdyje mes turime failus su tais paiais vardais, kaip ir praeitame: food.h/cpp, selling.h/cpp ir main.cpp. Failai selling.h ir selling.cpp yra identiki praeito pavyzdio bendravardiams. Faile food.h mes jau matme klasi Food, PrimitiveFood ir CompositeFood apraus, o atitinkamame realizacijos faile food.cpp turime metod apibrimus (knus). Klass Food metoduose mes nepamatysime didesni naujovi:

45

// food.cpp Food::Food (const string& name) : name (name) { } Food::Food (const Food& food) : name(food.name) { } void Food::print (const string& margin) const { cout << margin << name << " " << getPrice() << endl; } double Food::getProfit () const { double profit = 0; vector<Selling*>::const_iterator iter = sellings.begin(); for (; iter != sellings.end(); iter++) profit += (*iter)->getProfit(); return profit; } void Food::addSelling(Selling* selling) { sellings.push_back(selling); }

Konstruktori viduje panaudota speciali duomen lauk inicializavimo sintaks. J galime naudoti tik konstruktoriuose. Taip siekiama suvienondinti bazins klass konstruktoriaus kvietim ir lauk inicializavim kaip kad klasje PrimitiveFood:
// food.cpp PrimitiveFood::PrimitiveFood (const string& name, double price) : Food (name), price (price) { } PrimitiveFood::PrimitiveFood (const PrimitiveFood& food) : Food (food), price (food.price) { } Food* PrimitiveFood::clone() const { return new PrimitiveFood(*this); }

Klas PrimitiveFood yra Food, arba dar kartais laisvu argonu sakoma, jog klas PrimitiveFood turi savyje klas Food. Todl ji privalo inicializuoti ne tik savo

46

duomen laukus (price), bet ir paveldtus (name). Geras programavimo tonas pareigoja klass Food lauk inicializacij palikti paiai klasei Food, ikvieiant atitinkam jos konstruktori. Metode clone mes vl naudojame paslpt parametr this, kuris rodo klass PrimitiveFood objekt, kuriam ikviestas is metodas. Kartu pagalb pasitelk kopijavimo konstruktori, mes griname rodykl dinamikai sukurt kopij. Panaiai realizuota ir sudtinio maisto klas CompositeFood. Ji savyje saugo maisto objekt kopijas, kurias pasigamina metodo clone pagalba.
// food.cpp CompositeFood::CompositeFood (const string& name) : Food (name) { } CompositeFood::CompositeFood (const CompositeFood& food) : Food (food) { for (int i = 0; i < food.getFoodCount(); i++) addFoodCopy(food.getFood(i)); } Food* CompositeFood::clone() const { return new CompositeFood(*this); } void CompositeFood::addFoodCopy (const Food* food) { foods.push_back(food->clone()); } double CompositeFood::getPrice () const { double price = 0; for (int i = 0; i < foods.size(); i++) price += foods[i]->getPrice(); return price; } void CompositeFood::print (const string& margin) const { Food::print(margin); for (int i = 0; i < foods.size(); i++) foods[i]->print(margin + " "); }

Metode print matome bazins klass metodo kvietimo pavyzd Food::print(margin). Pastarasis atspausdina (kaip jau matme klasje Food), CompositeFood klass vard ir sumin kain. O toliau yra spausdinamos sudtins dalys, patraukiant jas keliais tarpais dein. Vieno programos veikimo metu buvo atspausdintas toks tekstas: 47

pienas 1.85 duona 0.85 sviestas 1.99 desra 2.15 sumustinis 4.99 duona 0.85 sviestas 1.99 desra 2.15 alus 1.75 uzkanda 6.74 sumustinis 4.99 duona 0.85 sviestas 1.99 desra 2.15 alus 1.75

ia matome, jog paprast maisto produkt buvo penki: pienas, duona, sviestas, dera ir alus. Sudtinis produktas sumutinis susidjo i duonos, sviesto ir desros kopiju. O sudtinis produkatas ukanda savyje turjo lygiai du produktus: sudtin sumutin ir paprast al. is programos rezultatas demonstruoja, jog paveldjimas drauge su polimorfizmu leidia daugelyje programos viet tiek sudtinius, tiek ir paprastus objektus traktuoti vienodai. Mums beliko panagrinti fail main.cpp. Kad ir kaip bebt keista, jis praktikai nepasikeit, tik vietoje meniu punkto sukurti atsirado du punktai: sukurti paprast produkt ir sukurti sudtin produkt. Nauja funkcija addPrimitiveFood yra tokia pati, kaip ir ankstesnio pavyzdio addNewFood, todl i esms failas main.cpp tepasipild nauja funkcija addCompositeFood:
// main.cpp void addCompositeFood (FoodList& foodList) { string name; cout << "-- naujas sudetinis produktas --" << endl; cout << "pavadinimas: "; cin >> name; CompositeFood* composite = 0; for (;;) { cout << "-- pasirinkite produkto " << name << " dali --" << endl; Food* food = selectFood(foodList); if (food == 0) break; if (composite == 0) composite = new CompositeFood(name); composite->addFoodCopy(food); } if (composite != 0) foodList.push_back(composite); }

I funkcijos addCompositeFood matome, jog naujus sudtinius produktus galime konstruoti tik i srae foodList jau esani produkt kopij. 48

Viena esmin detal, kurios mes nepalietme vade (neskaitant tkstanio nepaliest maiau esmini detali) yra objekt naikinimas. Mes vis laik tiktai krme objektus ir n sykio nenaikinome nebereikaling objekt. io neivengiamo meno pasimokinsime vlesniuose skyriuose. vado pabaigai pasiirkime naujausi make-failo redakcij. is failas mums leidia ne tik selektyviai kompiliuoti (compile) ir trinti sugeneruotus failus (clean), bet ir visk beslygikai perkompiliuoti (build = clean + compile), ir net paleisti sukompiliuot program (run). Kad nereikt daugelyje viet rainti ta pat failo vard main.exe, sivedme kintamj TARGET:
# makefile TARGET=main.exe compile: $(TARGET) run: compile $(TARGET) main.exe main.o food.o selling.o : : : : main.o food.o selling.o main.cpp food.h selling.h food.cpp food.h selling.h selling.cpp selling.h

#------------------------------------------------------------clean: rm -f *.o rm -f *.exe

build: clean compile %.exe: %.o g++ -o $@ $^ %.o: %.cpp g++ -c $<

49

50

2.

Inkapsuliacija

2.1.

Objektais paremtas programavimas (object based programming)

Objektais paremto programavimo (dar ne OOP) esm yra inkapsuliacija: duomenys ir funkcijos (metodai) dirbanios su jais laikomi kartu. Pavyzdiui, grafin figra Apskritimas ne tik saugo savyje centro koordinates ir spindul, bet dar ir moka pati save nupieti ekrane ar printeryje, isaugoti save faile ir t.t.. Niekas neturi teiss tiesiogiai keisti centro koordinai ar spindulio dydio, jei Apskritimas nepateikia atitinkam metod tam atlinkti. Apibrimas: inkapsuliacija tai realizacijos paslpimas po gerai apgalvotu interfeisu. Objektais paremtas programavimas neturi paveldjimo ir polimorfizmo svok - tai objektikai orientuoto programavimo privilegija.

51

2.2.

Klas, objektas, klass nariai

C++ kalboje klas yra struktros svokos ipltimas, t.y. struktros papildymas funkcijomis. Neformalus apibrimas: klas = duomenys + metodai emiau aprayta fiksuoto maksimalaus dydio sveikj skaii steko klas:
// stack.h #ifndef __stack_h #define __stack_h class Stack { private: static const int MAX_SIZE = 10; int elements [MAX_SIZE]; int size; public: Stack (); void int int bool push pop peek isEmpty (int element); (); (); ();

};

#endif // __stack_h

Nepamirkite kabliatakio klass aprao pabaigoje. Antraip daugelis kompiliatori pateikia labai miglot klaidos praneim, ir ne h-faile, o kakur cpp-failo viduryje. Klas pieiama kaip dut i trij sekcij: Stack size: int elements: int[] push (element: int) pop (): int peek (): int isEmpty (): bool - klass vardas - duomenys - metodai

Klausimas ne tem: kas skaniau spageti ar ravioli (James Rumbaugh, OMT, tekstas dutse). 52

Klas duomen tipas. Klass tipo kintamieji (egzemplioriai) vadinami objektais. Objektams galioja tos paios taisykls, kaip ir visiems C/C++ kintamiesiems: jie gali bti statiniai, automatiniai arba dinaminiai. Klass nariai bna dviej ri: nariai-duomenys (laukai) ir nariai-funkcijos (metodai). Metodai visuomet kvieiami kakokiam objektui. emiau pateiktas steko klass panaudojimo pavyzdyje tris stekus atitinkamai dedami sveikieji skaiiai, j kvadratai ir kubai:
// demostack.cpp Stack values; int main () { Stack squares; Stack* cubes = new Stack; for (int i = 1; i <= 10; { values.push(i); squares.push(i*i); cubes->push(i*i*i); } i++) // global variable

// automatic variable // dynamic variable

while (!values.isEmpty()) cout << values.pop() << " " << squares.pop() << " " << cubes->pop() << endl; } delete cubes;

Programa demostack.exe atspausdins ekrane:


10 100 1000 9 81 729 8 64 512 7 49 343 6 36 216 5 25 125 4 16 64 3 9 27 2 4 8 1 1 1

Klausimas ne tem: kokias funkcijas turi atlikti programa? irint kam j naudosime (Ivar Jocobson, Use Cases, mogeliukai prie oval). Klass metodai apraomi nurodant j piln vard. Tam naudojamas vard erdvs isprendimo operatorius (::): 53

// stack.cpp #include "stack.h" //-----------------------------------------------------------Stack :: Stack () { size = 0; } //-----------------------------------------------------------void Stack :: push (int element) { if (size < MAX_SIZE) { elements [size] = element; size += 1; } } //-----------------------------------------------------------int Stack :: pop () { if (size > 0) { size -= 1; return elements [size]; } return 0; } //-----------------------------------------------------------int Stack :: peek () { if (size > 0) return elements [size - 1]; return 0; } //-----------------------------------------------------------bool Stack :: isEmpty () { return (size <= 0); }

Kiekvienas objektas turi nuosav duomen egzemplior. Tuo tarpu egzistuoja tik vienas kiekvieno metodo egzempliorius, bendras visiems objektams. Metodo kno viduje visuomet yra apibrta rodykl this, rodanti objekt, kuriam is metodas yra ikviestas. Steko metod peek() mes galjome urayti ir kitaip:
int Stack :: peek () { if (this->size > 0) return this->elements [this->size - 1]; return 0; }

Klausimas ne tema: kuri profesija senesn fiziko, ininieriaus ar programuotojo (Grady Booch, Booch method, debesliai).

54

2.3.

Klass nari matomumas

Klass metod viduje yra matomi visi tos paios klass nariai: tiek duomenys, tiek ir metodai. Metodai laisvai manipuliuoja savo objekto bsena ir yra atsakingi u jos konsistentikum (teisingum). Daniausiai klass metodai vieninteliai tiksliai ino, kaip teisingai elgtis su objekto bsena. Tuo tarpu kodui, naudojaniam klas, neleidiama kiti nag prie objekto bsenos, o tik naudoti vieuosius metodus. Tiek duomenys, tiek ir metodai gali bti privats (private) arba viei (public). Tolesniuose skyriuose, kalbdami apie paveldjim, susipainsime ir su apsaugotais nariais (protected). ie raktiniai odiai gali eiti bet kokia tvarka ir kartotis kiek norima kart. Privats nariai yra pasiekiami tik klass metod viduje, o vieieji nariai pasiekiami visiems. emiau pateiktas kodas nekompetetingai kia nagus prie steko vidins realizacijos. Kompiliatorius ives klaidos praneim:
int main () { Stack stack; stack.size = -1; // error: Stack::size is not accessible // in function main() }

C++ kalboje struktra (struct) yra ta pati klas, kurios nari matomumas pagal nutyljim yra public. Klass nari matomumas pagal nutyljim yra private. Geras programavimo stilius reikalauja visuomet ireiktininiu bdu nurodyti klass nari matomum.

55

2.4.

Konstruktoriai ir destruktoriai

Apsiraykime stek, kuris dinamikai didin elementams skirt masyv. Isiskiria dvi svokos: steko dydis kiek steke yra element, ir steko talpa kokio dydio yra iuo metu dinamikai sukurtas element masyvas (nors nebtinai upildytas).
class Stack { private: int* elements; int size; int capacity; public: Stack (int initialCapacity = 4); ~Stack (); void push (int element); int pop (); int peek (); bool isEmpty (); };

Sukrus steko objekt, reikia pasirpinti, kad bt teisingai inicializuoti steko laukai elements, capacity ir size. Tuo tikslu C++ turi special metod - konstruktori. Jo tikslas - sukonstruoti objekt, t.y. inicializuoti jo duomenis. Sukrus klass egzempliori (objekt), visuomet yra ikvieiamas konstruktorius - to nemanoma ivengti. Konstruktoriaus vardas yra toks pats kaip ir klass. Kontstruktori gali bti keli, tuomet jie skirsis argument sraais (signatromis). Jie negrina jokios reikms, netgi void:
Stack::Stack (int initialCapacity) { if (initialCapacity < 1) initialCapacity = 1; capacity = initialCapacity; size = 0; elements = new int [capacity]; }

Naikinant objekt, jis privalo atlaisvinti visus naudojamus bendrus resursus: dinamin atmint, atidarytus failus ir t.t.. Tuo tikslu C++ turi special metod - destruktori. Sunaikinus klass egzempliori (objekt), visuomet yra ikvieiamas destruktorius to nemanoma ivengti. Destruktoriaus vardas yra toks pats kaip ir klass, tik su bangele priekyje (~). Klas gali turti tik vien destruktori, kuris neturi joki argument ir negrina jokios reikms, netgi void:
Stack::~Stack () { delete[] elements; }

56

Yra plaiai paplit naudoti konstruktori ir destrukotri poroje: konstruktorius pasiima resursus (pvz., sukuria dinaminius kintamuosius, atidaro failus), o destruktorius atlaisvina juos (pvz., atlaisvina dinamin atmint, udaro failus). irint i vartotojo puss, dinaminis stekas elgiasi taip pat, kaip ir fiksuoto dydio stekas. Tik syk galime neriboti savs ir prigrsti ne 10, o pvz. 20 skaii. Parametrai konstruktoriams perduodami objekto konstravimo metu. emiau esaniame kodo fragmente pradin steko values talpa bus 40 element, squares - 4 elementai (konstruktorius su parametru pagal nutyljim), cubes - 20 element:
// dynastack.cpp #include <iostream> #include "stack.h" using namespace std; Stack values(40); int main () { Stack squares; Stack* cubes = new Stack(20); for (int i = 1; { values.push squares.push cubes->push } i <= 20; (i); (i*i); (i*i*i); i++) // global variable

// automatic variable // dynamic variable

while (!values.isEmpty()) cout << values.pop () << " " << squares.pop () << " " << cubes->pop () << endl; } delete cubes;

57

2.5.

Konstruktorius pagal nutyljim

Konstruktorius pagal nutyljim yra ikvieiamas be argument, t.y. jis neturi argument, arba visi jo argumentai turi reikmes pagal nutyljim. Ms dinaminis stekas turi konstruktori pagal nutyljim, kuris turi vien argument, pradin tuio steko talp, su jam priskirta reikme pagal nutyljim:
class Stack { ... public: Stack (int initialCapacity = 4); ~Stack (); ... };

Pastaba: tik tuo atveju, kai klas neturi jokio konstruktoriaus, kompiliatorius sugeneruos konstruktori pagal nutyljim. Ivadl: galima apsirayti klas, kuri neturs konstruktoriaus pagal nutyljim, o tik konstruktorius, reikalaujanius parametr.

58

2.6.

Kopijavimo konstruktorius

C++ vienas i konstruktori turi special paskirt: konstruktorius, kurio vienintelis paramentras yra nuoroda tos paios klass objekt. Jis vadinamas kopijavimo konstruktoriumi. Apsiraykime standartine datos klas su konstruktoriumi pagal nutyljim ir su kopijavimo konstruktoriumi:
// copyconstr.cpp class Date { private: int year; int month; int day; public: Date (int year = 2003, int month = 2, int day = 28); Date (const Date& date); ~Date (); int getYear () const {return year;} int getMonth () const {return month;} int getDay () const {return day;} void print (const string& prefix) const;

};

Kartu su interfeisu tame paiame cpp-faile ir klass realizacija:


Date::Date (int year, int month, int day) { this->year = year; this->month = month; this->day = day; print("Date"); } //-----------------------------------------------------------Date::Date (const Date& date) { year = date.getYear(); month = date.getMonth(); day = date.getDay(); print("Date(Date&)"); } //-----------------------------------------------------------Date::~Date () { print("~Date"); } //-----------------------------------------------------------void Date::print (const string& prefix) const { cout << prefix << "(" << year << " " << month << " " << day << ")" << endl; }

59

Kopijavimo konstruktori galima ikviesti dvejopai: kaip ir visus kitus konstruktorius objekto krimo metu, arba naudojant priskyrimo simbol. Priskyrimo enklas objekto sukrimo metu kvieia kopijavimo konstruktori, o ne priskyrimo operatori:
int main () { Date yesturday (2003, 2, 27); Date date1 = yesturday; // copy constructor Date date2 (yesturday); // preffered syntax }

Programa atspausdins:
Date(2003 2 27) Date(Date&)(2003 2 27) Date(Date&)(2003 2 27) ~Date(2003 2 27) ~Date(2003 2 27) ~Date(2003 2 27)

Jei klas neturi kopijavimo konstruktoriaus, tuomet j sugeneruos kompiliatorius. iuo atveju klass duomenys bus inicializuoti panariui. Ms klass Date atveju kompiliatoriaus sugeneruotas kopijavimo konstruktorius veikt lygiai taip pat, kaip ir ms paraytasis. Jis netikt tuomet, kai klas savyje turi rodykles kitus objektus. Tuomet but kopijuojamos tik paios rodykls (adresai), o ne objektai, kuriuos jos rodo.

60

2.7.

Konstruktoriai ir tip konversija

Panagrinkime kompleksinius skaiius, kurie turi realij ir menamj dalis:


// typeconstr.cpp class Complex { private: double r; double i; public: Complex (double re=0, double im=0) : r(re), i(im) {} Complex add (const Complex& c); }; void print();

Trumput klass realizacija:


Complex Complex::add (const Complex& c) { return Complex(r+c.r, i+c.i); } void Complex::print () { cout << "(" << r << ", " << i << ")" << endl; }

Atlikime kelet skaiiavim su kompleksiniais skaiiais:


int main () { Complex a (10, 2); a = a.add( Complex(2.0)); a.print(); a = a.add( Complex(2, -1) ); a.print(); a = a.add( 0.5 ); a.print(); }

Programa atspausdins:
(12, 2) (14, 1) (14.5, 1)

Kiek keistai atrodo funkcijos main kodo eilut:


a = a.add( 0.5 );

61

Juk klas Complex neturi metodo


Complex Complex::add (double d);

C++ u "programuotojo nugaros" atlieka automatin tip konversij naudojant konstruktori. Aukiau urayti eilut yra ekvivalentika emiau esaniai:
a = a.add( Complex(0.5) );

Toks ireiktinis ar neireiktinis konstruktoriaus kvietimas sukuria laikin automatin objekt, egzistuojant tik iraikos skaiiavimo metu. O k, jei mes nenorime, kad kompiliatorius u programuotojo nugaros kviest konstruktori tip konversijai atlikti ir laikinam objektui sukurti? T.y. mes patys norime valdyti, kas ir kada yra kvieiama. Tuo tikslu, apraant konstruktori, naudojamas raktinis odis explicit:
class Complex { ... explicit Complex (double re=0, double im=0); }; void f () { Complex a (10, 2); ... a = a.add(0.5); a = a.add(Complex(0.5)); }

// syntax error // OK

62

2.8.

Objekt masyvai

Jei klass turi konstruktori pagal nutyljim, tuomet mes galime apsirayti jos objekt masyv. Kita alternatyva - inicializatori sraas. Pasinaudokime klase Date i ankstesnio skyrelio:
// array.cpp int main () { Date defaultDates [2]; Date customDates[] = {Date(1500, 1, 3), Date(1999, 4, 20)}; cout << customDates[1].getYear() << endl; }

Programa atspausdins:
Date(2003 2 28) Date(2003 2 28) Date(1500 1 3) Date(1999 4 20) 1999 ~Date(1999 4 20) ~Date(1500 1 3) ~Date(2003 2 28) ~Date(2003 2 28)

Nepamirkime, kad C++ kalboje nra skirtumo tarp rodykls atskir objekt ir rodykls pirmj masyvo element. Programuotojas pats atsakingas u tai, kad objektai, sukurti su new bt sunaikinti su delete, o objektai sukurti su new[] bt sunaikinti su delete[]:
// newarray.cpp int main () { Date* date = new Date(1000, Date* two = new Date [2]; cout << date->getYear() << cout << two[0].getYear() << cout << two->getYear() << delete date; delete[] two; }

1, 1); endl; endl; // same as bellow endl;

63

i programa atspausdins:
Date(1000 1 1) Date(2003 2 28) Date(2003 2 28) 1000 2003 2003 ~Date(1000 1 1) ~Date(2003 2 28) ~Date(2003 2 28)

Paprastai masyvuose saugomi nedideli objektai. Esant didesniems objektams, dalyvaujantiems klasi hierarchijose, masyvuose saugomos rodykls juos.

64

2.9.

Objektas, kaip kito objekto laukas (agregacija)

Panagrinkime atvej, kai objekto duomenimis yra kiti objektai, pvz. asmuo turi vard, gimimo dat ir g:
// person.cpp class Person { private: string name; Date birth; float height; public: Person (); Person (const string& name, const Date& birth, float height); ~Person (); };

Konstruojant objekt pradioje bus sukonstruoti objekto laukai, o tik po to bus ikviestas paties objekto konstruktorius:
Person::Person () { height = 0; cout << "Person()" << endl; } int main () { Person person; }

Person konstruktorius pradioje ikvies objekto name konstruktori pagal nutyljim, tuomet objekto date konstruktori, o tik po to vykdys savo paties kn ir inicializuos lauk height. Aukiau pateiktas kodo fragmentas atspausdins:
Date(2003 2 28) Person() ~Person() 0 ~Date(2003 2 28)

Galima ireiktiniu bdu nurodyti, kur duomen nario konstruktori kviesti. Tuomet dar prie objekto konstruktoriaus kn dedamas dvitakis, po kurio vardijami kableliais atskirti objekto lauk konstruktoriai:
Person::Person (const string& n, const Date& b, float h) : name(n), birth(b), height(h) { cout << "Person() " << name << " " << height << endl; }

65

int main () { Person zigmas("zigmas", Date(1984, 4, 17), 178.5); }

Atkreipkime dmes, kaip funkcijoje main klass Person konstruktoriui perduodamas laikinas klass Date objektas, kuris egzistuoja tik konstruktoriaus kvietimo metu ir yra i karto sunaikinamas. emiau pateiktame programos ivedime pirmasis Date konstruktorius ir pirmasis destrukotrius priklauso iam laikinajam objektui:
Date(1984 4 17) Date(Date&)(1984 4 17) Person() zigmas 178.5 ~Date(1984 4 17) ~Person() zigmas 178.5 ~Date(1984 4 17) - sukuriamas laikinas objektas - sunaikinamas laikinas objektas

C++ kalboje toki objekto lauk inicializavimo uraymo bd galima naudoti ne tik laukams objektams, bet ir baziniems C++ tipams (int, char, double, ). Lauk konstruktoriai ikvieiami ta tvarka, kuria laukai yra aprayti klass aprae, o ne ta tvarka, kaip jie ivardinti konstruktoriuje inicializatori srae. Konstruojant objekt, pradioje sukonstruojami jo objektai-laukai ir tik po to kvieiamas objekto konstruktoriaus knas. Destruktori knai kvieiami atvirkia tvarka.

66

2.10.

Objekt gyvavimo trukm

Panagrinkime, kaip ir kada yra kuriami ir naikinami objektai. Tuo tikslu apsiraykime klasyt, su besipasakojaniais konstruktoriumi ir destruktoriumi:
// lifecycle.cpp class A { private: string text; public: A(const char* t) : text(t) {cout << " " << text << endl;} ~A() {cout << "~" << text << endl;} };

Dinaminiai objektai konstruojami operatoriaus new pagalba, o naikinami su delete:


int main () { A* d = new A ("dynamic"); delete d; }

is kodo fragmentas atspausdins:


dynamic ~dynamic

Automatiniai objektai konstruojami tuomet, kai programos vykdymas pasiekia j apraymo viet (funcijos kn, kodo blok), o naikinami vykdymui paliekant kodo blok. C++ kalboje kiekvienos ciklo iteracijos metu yra einama ir ieinama i ciklo kno, todl ciklo kn aprayti automatiniai objektai bus konstruojami ir naikinami kiekvienos iteracijos metu. Ciklo inicializavimo dalyje aprayti objektai konstruojami tik vien kart - prie visas iteracijas, ir naikinami joms pasibaigus, t.y. tokie objektai gyvuoja tik vieno ciklo sakinio viduje ir neegzistuoja jo iorje. Ankstyvieji C++ kompiliatoriai elgdavosi truput kitaip, ne pagal standart: ciklo inicializavimo dalyje aprayti kintamieji ir toliau gyvuodavo u ciklo rib. Jei funkcija grina lokal objekt kaip savo rezultat, tuomet aidim gali sijungti ir kopijavimo konstruktorius (jei optimizuojantis kompiliatorius neivengs jo kvietimo).

67

void hasCycle() { A l("local"); cout << "enter hasCycle()" << endl; int i = 0; for (A ic("init cycle"); i < 2; i++) { A cb("cycle body"); } cout << "leave hasCycle()" << endl; } int main { hasCycle(); }

is kodo fragmentas atspausdins:


local enter hasCycle() init cycle cycle body ~cycle body cycle body ~cycle body ~init cycle leave hasCycle() ~local

Globals statiniai objektai konstruojami prie vykdant funkcij main, o naikinami po to, kai ieinama i main, arba kai programa baigiama standartins funkcijos exit pagalba. Jie nebus naikinama, jei programa bus ubaigta funkcijos abort pagalba.
A gs("global static"); int main () { cout << "enter main()" << endl; cout << "leave main()" << endl; }

is kodo fragmentas atspausdins:


global static enter main() leave main() ~global static

Lokals statiniai objektai yra vartojami labai retai. Jie konstruojami, kai programos vykdymas pirm kart pasiekia j apraymo viet, o naikinami programos pabaigoje:

68

void hasLocalStatic() { cout << "enter hasLocalStatic()" << endl; static A ls("local static"); cout << "leave hasLocalStatic()" << endl; } int main () { cout << "enter main()" << endl; hasLocalStatic(); hasLocalStatic(); cout << "leave main()" << endl; }

is kodo fragmentas atspausdins:


enter main() enter hasLocalStatic() local static leave hasLocalStatic() enter hasLocalStatic() leave hasLocalStatic() leave main() ~local static

Jei programos vykdymas nepasiekia funkcijos/metodo su lokaliu statiniu objektu, tai jis niekada nebus sukonstruotas ir sunaikintas.

69

2.11.

Metodai, apibrti klass aprao viduje

Metodai, apibrti klass aprao viduje (h-faile) yra inline-metodai, t.y. kompiliatorius j ikvietimo vietoje stengsis tiesiogiai terpti metodo kn, o ne kviesti klass realizacijos modulyje esant kn. Tuo bdu sutaupomas metodo kvietimo laikas:
class String { ... const char* getChars() const {return buffer;} }; int main() { String text ("hello"); const char* chars = text.getChars(); // chars = text.buffer ... }

Praktikoje klass viduje apibriami tik kai kurie trumpuiai vienos eiluts metodai, tuo paiu neant daugiau aikumo klass apra. inline-metodai gali bti apibrti ir klass iorje, naudojant raktin od inline:
inline const char* String::getChars() const { return buffer; }

Bet kokiu bdu apraytas inline-metodas tai tik rekomendacija kompiliatoriui, kad jis vietoje metodo kvietimo, terpt metodo kn. Kompiliatoriai gali to ir neatlikti. Kai kurie grietesni kompiliatoriai spausdina spjimus apie inline-metodus, kuri knai niekados nebus terpti kod, o visuomet bus formuojamas metodo kvietimas.

70

2.12.

Statiniai nariai

Statiniai klass metodai yra panas paprastas C kalbos funkcijas. Jie nesusieti su jokiu konkreiu objektu, todl j viduje nra rodykls this. Statiniai klass duomenys yra panas globalius kintamuosius. Skirtumas yra tas, jok statiniai nariai (duomenys ir metodai) yra apraomi klass viduje ir, analogikai kaip ir prasti metodai, gali prieiti prie klass nari. Be to, statiniams nariams galioja matomumo taisykls (private, protected, public). Statiniai duomenys yra bendri visiems klass egzemplioriams, skirtingai nuo nestatini duomen, kuriuos kiekvienas objektas turi nuosavus. emiau esantis pavyzdys skaiiuoja, kiek i viso buvo sukurta duotos klass objekt ir kiek i j dar gyvi:
// counted.h class Counted { private: static int createdCount; static int livingCount; public: Counted (); ~Counted (); static int getCreatedCount () {return createdCount;} static int getLivingCount () {return livingCount;}

};

Statinius narius duomenis reikia ne tik aprayti klass viduje h-faile, bet ir apibrti kartu su klass realizacija cpp-faile.
// counted.cpp #include "counted.h" int Counted::createdCount = 0; int Counted::livingCount = 0; Counted::Counted () { createdCount += 1; livingCount += 1; } Counted::~Counted () { livingCount -= 1; }

Pasinaudokime tuom, jog ciklo viduje aprayti objektai sukuriami kiekvienos iteracijos pradioje, o jos pabaigoje vl sunaikinami. Kadangi statiniai nariai nesusieti 71

su jokiu konkreiu tos klass objektu, jie kvieiami nurodant j piln vard: klassvardas::metodo-vardas:
// main.cpp #include <iostream> #include "counted.h" using namespace std; int main () { Counted c1; Counted c2; for (int i = 0; i < 10; i++) { Counted c3; } cout << "Created: " << Counted::getCreatedCount() << endl; cout << "Living: " << Counted::getLivingCount() << endl; }

is kodo fragmentas atspausdins:


Created: 12 Living: 2

Ir dar vienas praktikas pavyzdys: klas, galinti turti ne daugiau kaip vien objekt. Projektuotojai iai abloniniai situacijai sugalvojo pavadinim Singleton. Triukas: paslpkime konstruktori (padarykime j private) ir pateikime statin vie metod, grinant rodykl vienintel klass egzempliori. emiau randame itrauk:
class Singleton { private: static Singleton* instance; Singleton (); public: static Singleton* getInstance (); ... }; //-----------------------------------------------------------Singleton* Singleton::instance = 0; Singleton* Singleton::getInstance() { if (instance == 0) instance = new Singleton(); return instance; } //-----------------------------------------------------------int main () { for (int i = 0; i < 10; i++) Singleton::getInstance()->doSomething(i*i); Singleton::getInstance()->print(); }

72

2.13.

Klass draugai

Paprastam klass metodui garantuojami trys dalykai: 1. metodas turi prijimo teises prie apsaugotos klass aprao dalies 2. metodo matomumas nusakomas raktiniais odiais public, protected ir private 3. turi bti kvieiamas klass objektui (turi rodykl this) Statiniams klass metodams (static) galioja tik pirmi du punktai. Funkcijos-draugai (friend) pasiymi tik pirmja savybe. Pavyzdys: sudkime du stekus vien:
// friend.cpp class Stack { private: int* elements; int size; int capacity; ... friend Stack* addStacks (const Stack* s1, const Stack* s2); }; Stack* addStacks (const Stack* s1, const Stack* s2) { Stack* result = new Stack(); result->size = s1->size + s2->size; result->capacity = result->size + 1; result->elements = new int [result->capacity]; for (int i = 0; i < s1->size; i++) result->elements[i] = s1->elements[i]; for (int i = 0; i < s2->size; i++) result->elements[s1->size + i] = s2->elements[i]; return result; } //-----------------------------------------------------------int main () { Stack values; Stack squares; for (int i = 1; i <= 7; i++) { values.push (i); squares.push (i*i); } Stack* sum = addStacks(&values, &squares); while (!sum->isEmpty()) cout << sum->pop() << " "; delete sum; }

73

Programa atspausdins:
49 36 25 16 9 4 1 7 6 5 4 3 2 1

Nesvarbu kurioje klass dalyje apraysime (private, protected ar public) klass draug. J vis vien bus galima naudoti klass iorje (tarsi public nar), o jis pats turs prijim prie vis klass duomen. Klass draugais gali bti ne tik paprastos funkcijos, bet ir kitos klass metodai:
class ListIterator { ... int* next(); }; class List { ... friend int* ListIterator::next(); };

Klass draugu gali bti ir kita klass, t.y. visi kitos klass metodai:
class List { ... friend class ListIterator; };

74

2.14.

Tip apraai klass viduje (dtiniai tipai)

Klass viduje mes galime aprayti bet kokius kitus tipus, pavyzdiui, ivardijamus tipus (enum), typedef- konstrukcijas, kitas klases:
// innertype.cpp class List { public: typedef string Element; enum Position {FIRST, LAST}; private: class Node { private: Element element; Node* next; public: void removeTail (); }; };

Tai yra prastiniai tipai, tik j vardai turi bti pilnai kvalifikuoti:
void List::Node :: removeTail() { if (next != 0) { next->removeTail(); delete next; next = 0; } } int main () { List::Element element = "stabdis"; List::Position position = List::LAST; cout << element << " " << position << endl; }

dtiniams tipams galioja klass nari matomumo taisykls:


int main () { List::Node node; // klaida - List::Node yra private }

dtins klass neturi joki privilegij. Pvz. klass List::Node metodai negali prieiti prie apsaugot klass List nari.

75

2.15.

Vard erdvs isprendimo operatorius ::

Mes jau vartojome operatori (::):


nordami apibrti metodo realizacij klass iorje naudodami vieuosius statinius klass narius klass iorje naudodami klass viduje apibrtus tipus.

is operatorius taipogi leidia pasiekti globaliuosius vardus, kuriuos konkreioje situacijoje paslepia lokals kintamieji ar parametrai:
class MyClass { ... int value; }; int value; // global variable void MyClass::myMethod { this->value = 1; value = 2; ::value = 3; } (int value) // class member // method parameter // global variable

76

2.16.

Konstantiniai laukai, laukai-nuorodos

Konstantas ir nuorodas btina inicializuoti j apibrimo vietoje. Vliau mes nebegalime keisti konstantos reikms, o nuoroda taip ir liks susieta su tuo paiu objektu:
const int MAX_SIZE = 15; Stack& reference = anotherStack;

Trumpai prisiminke rodykles ir konstantas:


const char * char const * char * const const char * const const char const * pointerToConstString pointerToConstString constPointerToString constPointerToConstString constPointerToConstString = = = = = "s1"; "s2"; "s3"; "s4"; "s5";

Jei klas turi konstantini lauk arba lauk nuorod, jie privalo bti inicializuoti konstruktoriaus inicializatori srae:
class List { public: const int MAX_SIZE; Node& defaultNode; List (int maxSize, Node& defNode); }; List::List (int maxSize, Node& defNode) : MAX_SIZE (maxSize), defaultNode (defNode) { }

Statiniai konstantiniai laukai yra inicializuojami apraymo vietoje (senesni kompiliatoriai reikalaudavo apibrti ir inicializuoti toki konstant cpp-faile, kartu su kitais statiniais laukais):
class List { public: static const int DEFAULT_MAX_SIZE = 18; }; int main () { cout << List::DEFAULT_MAX_SIZE << endl; }

77

2.17.

Konstantiniai metodai ir mutable-laukai

O kas tuomet, kai konstanta yra pats objektas? Tuomet, po jo apibrimo, mes nebeturime teiss keisti nei vieno jo lauko. Taip pat tokiam objektui negalime kviesti metod, jei jie nra konstantiniai. Konstantiniai metodai turi raktin odel const savo signatros pabaigoje ir savo viduje negali keisti objekto lauk:
// constmet.cpp class Date { public: int getYear () const {return year;} int getMonth () const {return month;} int getDay () const {return day;} }; const Date defaultDate (2003, 3, 14); int main () { cout << defaultDate.getYear () << " " << defaultDate.getMonth () << " " << defaultDate.getDay () << endl; }

odelis const priklauso metodo signatrai, todl klas gali turti du metodus su tais paiais argument sraais, bet besiskirianiais const odelio buvimu. emiau pateikti metodai grina nuorod i objekto laukus, t.y. jie palieka galimyb keisti laukus i iors, todl mes negalime j aprayti konstantiniais:
// constmet.cpp class Date { public: int& getYear () {return year;} int& getMonth () {return month;} int& getDay () {return day;} }; int main () { Date date2; date2.getYear() = 2004; // non-const method called cout << date2.getYear () << endl; }

Kartais objekto login bsena nepasikeis, nors ir bus pakeisti kai kurie laukai. Daniausiai tai bna apskaiiuojami laukai, atliekantys cache-stiliaus funkcijas. Jie apraomi su odeliu mutable. Yra leidiama keisti konstantinio objekto mutable78

laukus. emiau mes iliustruojame situocija, kuomet metodas getString grina tekstin eilut. Laikome, jog pastaroji gali bti apskaiiuojama i kit objekto lauk, tik jos skaiiavimas labai ilgai utrunka. Todl mes saugome poym cacheValid, kuris parodo, ar nuo paskutinio eiluts skaiiavimo pasikeit objekto laukai, ar ne. Jei laukai pasikeit, tai esame priversti i naujo suskaiiuoti cacheString pagal naujas lauk reikmes. Pati cacheString nelaikome objekto bsena, nes yra apskaiiuojama i kit objekto lauk:
class Date { private: mutable bool cacheValid; mutable string cacheString; void computeCacheString() const; ... public: ... string getString() const; }; string Date::getString() const { if (!cacheValid) { computeCacheString(); cacheValid = true; } return cacheString; }

79

80

3.

Paveldjimas ir polimorfizmas

3.1.

Trys OOP banginiai

Mes jau artimai susipainome su vienu i Objektikai Orientuoto Programavimo bangini - inkapsuliacija. iame skyrelyje panagrinsime likusius du - paveldjim ir polimorfizm. Paveldjimas ir polimorfizmas gerokai palengvina daugkartin kodo panaudojim:

galime naudotis jau anksiau paraytomis klasmis kaip bazinmis tam, kad sukurti savas, specializuotas, kartu paveldint visus bazins klass duomenis ir kod. Belieka tik prirayti paveldtai klasei specifinius laukus ir metodus; kodas, manipuliuojantis objetais, danai gali su visa klasi hierarchija dirbti vienodai, t.y. jis neturi bti dubliuojamas kiekvienai klasei atskirai, o raomas tik bazinei klasei.

ie du teoriniai pamastymai plaiau atsiskleis, kuomet panagrinsime kelet pavyzdi. Vienas natraliausi objektikai orientuoto programavimo pritaikym kompiuterin grafika. Tarkime, turime abstraki figros svok Shape, ir konreias figras Circle, Square ir Rectangle. Tuomet:

paveldjimas leidia urayti natral fakt: Circle, Square ir Rectangle yra figros. OOP svokomis sakoma, kad jos paveldi i klass Shape. Tokiu bdu, jei bazin figra turs centro koordinates, tai ir paveldjusios figros jas turs. polimorfizmas leidia realizuoti apskritim, kvadrat ir staiakampi elgsenos skirtumus, pvz. paiym, ploto skaiiavim ir pan..

Grafiniuose pavyzdiuose naudosime bibliotek Qt, kuri galima rasti internete adresu www.trolltech.com Naudojimosi ia biblioteka dokumentacija su pamokomis yra adresu doc.trolltech.com

81

3.2.

Paveldjimas

Turime geometrin figr su centro koordinatmis x ir y, kuri pieia save kaip vien tak:
// shapes.h class Shape { protected: int x; int y; public: Shape (int cx, int cy); int getCenterX () const {return x;} int getCenterY () const {return y;} void setCenter (int cx, int cy) {x=cx; y=cy;} }; void draw (QPainter* p);

Apskritimas, kvadratas ir staiakampis taip pat yra figros turinios centr. Objektikai orientuoto programavimo terminologijoje odelis "yra" keiiamas odeliu "paveldi". Taigi, apskritimas, kvadratas ir staiakampis paveldi i figros visas jos savybes bei prideda papildom duomen ir metod. is faktas C++ kalboje uraomas tokiu bdu.
class Circle : public Shape { protected: int radius; public: Circle (int cx, int getRadius void setRadius void draw }; int cy, int radius); () const {return radius;} (int r) {radius = r;} (QPainter* p);

class Square : public Shape { protected: int width; public: Square(int cx, int cy, int width); int getWidth () const {return width;} void setWidth (int w) {width=w;} void draw (QPainter* p); };

82

class Rectangle : public Shape { protected: int width; int height; public: Rectangle(int cx, int cy, int width, int height); int int void }; getWidth () const {return width;} getHeight () const {return height;} setSize (int w, int h) {width=w; height=h;} (QPainter* p);

void draw

Gauname klasi hierarchij, kuri grafikai vaizduojame taip:


Shape int x int y void draw(QPainter)

Circle int radius void draw(QPainter)

Square int width void draw(QPainter)

Rectangle int width int height void draw(QPainter)

Klas Circle paveldi visus duomenis laukus ir metodus i klass Shape. Tarytumei laukai x ir y, bei metodai draw ir t.t. bt tiesiogiai aprayti klasje Circle. Kompiuterio atmintyje objektai atrodo madaug taip:
Shape x y Circle x y radius Rectangle x y width height Square x y height

Sakoma, kad klas Shape yra klass Circle bazin klas (super-class). Savo ruotu klas Circle paveldi i klass Shape, arba klas Circle yra klass Shape poklas (sub-class). Kiekviena figra save pieia vis kitaip:

83

void Shape::draw(QPainter* p) { p->drawPoint(x, y); } void Circle::draw(QPainter* p) { p->drawEllipse(x-radius, y-radius, radius*2, radius*2); } void Square::draw(QPainter* p) { p->drawRect(x-width/2, y-width/2, width, width); } void Rectangle::draw(QPainter* p) { p->drawRect(x-width/2, y-height/2, width, height); }

Pagalbin klas Screen parpina viet ekrane, kurioje figros gali save nusipieti:
Screen::Screen(QWidget* parent, const char* name) : QWidget(parent, name) { setPalette(QPalette(QColor(250, 250, 200))); shape = new Shape (200, 200); circle = new Circle (200, 200, 100); square = new Square (500, 200, 200); rectangle = new Rectangle (350, 400, 500, 100); } void Screen::paintEvent(QPaintEvent*) { QPainter p(this); shape->draw(&p); circle->draw(&p); square->draw(&p); rectangle->draw(&p);

Ekrane pamatysime:

84

3.3.

Konstruktoriai ir destruktoriai

Paveldta klas yra konstruojama grietai nustatytu bdu, nepriklausomai nuo to, kokia tvarka konstruktoriai surayti inicializatori srae: 1. 2. 3. konstruojama bazinei klasei priklausanti objekto dalis konstruojami paveldtos klass laukai-duomenys vykdomas paveldtos klass konstruktoriaus knas
Circle::Circle (int cx, int cy, int r) : Shape(cx, cy), // 1. radius(r) // 2. { // 3. }

Jei inicializatori srae nra nurodytas konkretus bazins klass konstruktorius, tai kvieiamas konstruktorius pagal nutyljim. Jei tokio nra, kompiliatorius pranea apie klaid. Kaip visuomet: destruktoriai kvieiami tiksliai atvirkia tvarka.

85

3.4.

Bazins klass nari matomumas

Naudojant paveldjim, alia private ir public klass nari matomumo labai plaiai vartojamas tarpinis modifikatorius protected - apsaugotieji nariai. Jie pasiekiami paveldjusios klass metoduose, bet nepasiekiami klass iorje. Taigi, paveldjusios klass metodai gali prieiti prie bazins klass public ir protected nari. Bazins klass private nariai nra pasiekiami paveldjusioje klasje. Klass iorje yra matomi tik public nariai.
nuosavi klass metodai pasiekia paveldjusios klass metodai mato iorins funkcijos pasiekia bazins klass nari matomumas public protected public protected public private

86

3.5.

Metod perkrovimas (overloading) ir pseudo polimorfizmas

Klas Circle su klase Shape sieja vadinamasis is-a ryys: klass Circle objektai gali bti naudojami visur, kur reikalaujama Shape klass objekt. Pvz. jei funkcija reikalauja Shape* tipo parametro, tai galime perduoti Circle* objekt. Paveldjusios klass ne tik pridjo sav duomen ir metod, bet dar ir perkrov metod draw. Kuris i metod bus ikviestas inoma jau kompiliavimo metu ir priklauso tik nuo kintamojo ar rodykls tipo. Tai nra tikrasis polimorfizmas. Pailiustruokime pavyzdiu:
// screen.cpp void Screen::paintEvent(QPaintEvent*) { QPainter p(this); Shape* shapeTmp = shape; shapeTmp->draw(&p); shapeTmp = circle; shapeTmp->draw(&p); shapeTmp = square; shapeTmp->draw(&p); shapeTmp = rectangle; shapeTmp->draw(&p);

Programa nupie keturis takus, atitinkanius shape, circle, square ir rectangle centrus, o ne paias figras. Kompiliatorius sakinyje shapeTmp->draw(&p) mato, jog rodykls shapeTmp tipas yra Shape*, todl kvieia metod Shape::draw. Taigi, ioje vietoje turime paveldjim, bet neturime polimorfizmo. Polimorfizmas - sekaniame skyrelyje.

87

3.6.

Virtuals metodai ir polimorfizmas

Tam, kad isprsti praeito skyrelio problem, pasinaudosime virtualiais metodais. Priraykime raktin od virtual prie vis metodo draw apraym (prie realizacij rayti nereikia):
// shapes.h class Shape { ... virtual void draw(QPainter* p); }; ... class Rectangle : public Shape { ... virtual void draw(QPainter* p); };

Dabar praeito skyrelio metodas Screen::paintEvent() ekrane nupie tak, apskritim, kvadrat ir staiakamp. Virtuals (polimorfiniai) metodai kvieiami ne pagal deklaruot rodykls tip (ms atveju Shape*), o pagal real objekto tip, kuris nustatomas ne kompiliuojant, o programos vykdymo metu. Sakoma, kad virtuals metodai yra ne perkraunami (overload), bet perraomi (override). Vien kart virtualus - visada virtualus! Bazinje klasje paskelbus metod virtualiu, paveldtoje klasje jis bus virtualus nepriklausomai nuo to, ar paraysime od virtual, ar ne. Geras tonas reikalauja prie vis virtuali metod paveldjusiose klasse pakartotinai rayti virtual. Praktinis pastebjimas: bazin klas privalo turti virtual destruktori. Tuomet bsime tikri, jog tokios komandos, kaip delete shape, ikvies destruktori pagal real objekto tip, antraip neivengsime bdos. Danai bna, kad nemenka dalis kodo dirba su rodykle bazin klas ir nesirpina, kokios klass (bazins ar paveldtos) objektas yra i tikrj. Tai bene pats nuostabiausias OOP mechanizmas. Pvz. paraykime globali funkcij draw10Steps(), kuri nupie po 10 duotos figros vaizd po ingsnel juos perstumdama:

88

void draw10steps (QPainter* p, Shape* shape) { int x = shape->getCenterX(); int y = shape->getCenterY(); for (int i=0; i < 10; i++) { shape->setCenter(x + i*5, y + i*5); shape->draw(p); } shape->setCenter(x, y); } void Screen::paintEvent(QPaintEvent*) { QPainter p(this); draw10steps(&p, shape); draw10steps(&p, circle); draw10steps(&p, square); draw10steps(&p, rectangle); }

Ekrane pamatysime:

89

3.7.

Virtuali metod lentels (VMT)

Kaip gyvendinama i virtuali metod magija? Tuo tikslu kiekvienas objektas, turintis bent vien virtual metod, kartu gauna ir nematom rodykl virtuali metod lentel. Tokios klass vadinamos polimorfinmis. Kiekvienai polimorfinei klasei (o ne atskiram objektui) yra lygiai po vien VMT (virtual method table), kur surayti virtuali metod adresai. Pasipaiykime tris objektus atmintyje:
shape1 VMT* vmt int x int y circle1 VMT* vmt int x int y int radius circle2 VMT* vmt int x int y int radius

ShapeVMT 0: Shape::~Shape() 1: Shape::draw() 2: ...

CircleVMT 0: Circle::~Circle() 1: Circle::draw() 2: ...

Kvieiant virtual metod, pradioje objekte surandama rodykl virtuali metod lentel, po to i lentels inomu indeksu imamas metodo adresas. Pvz., vis klasi, paveldjusi nuo Shape, virtuali metod lentelse metodo draw adresas bus patalpintas tuo paiu indeksu (ms pavyzdyje jis lygus 1). Programuotojas kode rao metodo vard, o kopiliatorius pakeiia j indeksu VMT'e. Kompiliavimo metu yra inomas tik metodo indeksas, o konkretus adresas randamas programos vykdymo metu. Taigi, virtualiu metodu kvietimas yra kiek ltesnis, nei nevirtuali. Paprastai beveik visi klass metodai turt bti virtuals, taiau, programos veikimo greiio sumetimais, virtualiais daromi tik tie, kurie klass krjo nuomone bus perrayti paveldtoje klasje. O kas, jei klass Circle metodas draw nori ikviesti klass Shape metod draw, tam, kad padti takel savo centre? Tuo tikslu vartojamas pilnai kvalifikuotas metodo vardas:
void Circle::draw(QPainter* p) { Shape::draw(p); p->drawEllipse(x-radius, y-radius, radius*2, radius*2); }

90

3.8.

Statiniai, paprasti ir virtuals metodai

Dabar mes jau inome visas tris metod ris: 1. 2. 3. statiniai - kvieiami nesusietai su jokiu konkreiu objektu. Metodo adresas inomas kompiliavimo metu. paprasti (ne virtuals) - kvieiami konkreiam klass objektui. Metodo viduje apibrtas odelis this. Metodo adresas inomas jau kompiliavimo metu. virtuals - kvieiami konkreiam klass objektui. Metodo viduje apibrtas odelis this. Metodo adresas randamas tik vykdymo metu.

91

3.9.

Polimorfizmas konstruktoriuose ir destruktoriuose

Panagrinkime pavyzd su trimis paprastutmis klasmis:


// abc.cpp class A { public: A () {print();} virtual ~A () {print();} virtual void print () {cout << "A";} }; class B : public A { public: B () {print();} virtual ~B () {print();} virtual void print () {cout << "B";} }; class C : public B { public: C () {print();} virtual ~C () {print();} virtual void print () {cout << "C";} }; int main () { C c; }

i programa atspausdins:
ABCCBA

C++ konstruktoriuose ir destruktoriuose (skirtingai nuo Java) polimorfizmas neveikia: nors klas C ir perra virtual metod print(), taiau konstruojant klasei B priklausani dal kvieiamas B::print(), o konstruojant klasei A priklausnai dal kvieiamas A::print(). Analogikas Java pavyzdys atspausdint "CCCCCC". Tokiu bd ivengiama nepageidaujam alutini efekt. Jei konstruktoriuose veikt polimorfizmas, tai bt manoma ikviesti paveldjusios klass perrayt metod anksiau, nei paveldtoji klas bus sukonstruota.

92

3.10.

variai virtuals metodai ir abstrakios klass

Kartais bazinje klasje negali bti prasmingos virtualaus metodo realizacijos. Jeigu mes sutarsime, kad klas Shape nusako abstraki figr, neturini jokio vaizdo ekrane, netgi tako, tuomet galime metod draw() padaryti variai virtual (pure virtual), priraydami "= 0" jo antratje:
class Shape { ... virtual void draw(QPainter* p) = 0; };

Klas, turinti bent vien variai virtual metod, vadinama abstrakia. Kompiliatorius neleidia sukurti abstrakios klass objekt. variai virtualus metodas neturi jokio kno, netgi tuio. Tai daugiau paadas, kad paveldtos klass pateiks savo realizacijas. Jei paveldta klas nerealizuoja vis bazins klass variai virtuali metod, tai ir ji pati tampa abstrakia.

93

3.11.

varus interfeisas

Neformalus apibrimas: jei klas neturi savo duomen, turi tui virtual destruktori ir visi jos metodai yra viei (public) ir variai virtuals, tai toki klas vadinsime variu interfeisu. varus interfeisas - puiki priemon labai aikiai atskirti svokas "interfeisas" ir "realizacija". Jei klas paveldi i varaus interfeiso ir realizuoja visus jo metodus, tai tokiu atveju danai sakoma, jog klas "realizuoja interfeis". Pavyzdiui, apraykime grafin figr Shape, kuri nusako jos kairiojo virutinio kampo koordinats, bei plotis ir auktis:
// project: composite, shape.h class Shape { public: virtual ~Shape virtual int getX virtual int getY virtual int getWidth virtual int getHeight virtual void setLocation virtual void paint };

() {} () const () const () const () const (int x, int y) (QPainter* p)

= = = = = =

0; 0; 0; 0; 0; 0;

Klas Shape - varus interfeisas. Klasje Shape nesaugome duomen apie koordinates ekrane, nes paveldjusios klass naudoja skirtingas strategijas. Pvz. staiakampis saugo koordinates ireiktiniu bdu, o apskritimas skaiiuoja pagal savo centr.
Shape
getX() getY() getWidth() getHeight() setLocation() paint()

Rectangle
x :int y :int width :int height :int

Circle
cx :int cy :int radius :int

Composite
addShape()

94

class Rectangle : public Shape { protected: int x; int y; int width; int height; public: Rectangle (int cx, int cy, int width, int height); ... }; class Circle : public Shape { protected: int cx; int cy; int radius; public: Circle (int cx, int cy, int radius); virtual int getX () const {return cx - radius;} virtual int getWidth () const {return radius*2 + 1;} ... };

Treioji paveldjusi klas, Composite, yra sudtin figra:


class Composite : public Shape { protected: vector<Shape*> shapes; public: ... void addShape (Shape* shape); };

Sudtin figra nesaugo savo matmen, bet kiekvien kart skaiiuoja i naujo:
int Composite::getX() const { if (shapes.size() <= 0) return 0; int minX = shapes[0]->getX(); for (unsigned i = 1; i < shapes.size(); if (minX > shapes[i]->getX()) minX = shapes[i]->getX(); return minX; }

i++)

Atkreipkime dmes, jog sudtin figra taip pat yra figra, t.y. sudtin figra gali susidti ir i kit sudtini figr. Sukurkime apskritim, staiakamp, med, susidedant i apskritimo ir staiakampio, bei un. uns galva ir uodega kiek dirbtinai sudsime atskir sudtin figr tam, kad pailiustruoti, jog sudtin figra gali susidti i bet koki figr, tame tarpe ir kit sudtini:

95

Screen::Screen(QWidget* parent, const char* name) : QWidget(parent, name) { circle = new Circle(150, 100, 50); rectangle = new Rectangle(250, 50, 400, 100); tree = new Composite(); tree->addShape(new Rectangle(600, 300, 20, 200)); tree->addShape(new Circle(610, 250, 50)); Composite* tailAndHead = new Composite(); tailAndHead->addShape(new Rectangle(100, 380, 20, 20)); tailAndHead->addShape(new Circle(280, 350, 50)); dog = new Composite(); dog->addShape(new Rectangle(100, 400, 200, 20)); dog->addShape(new Rectangle(100, 420, 20, 40)); dog->addShape(new Rectangle(280, 420, 20, 40)); dog->addShape(tailAndHead);

Schematikai objektus galtume pavaizduoti taip:


tailAndHead

circle tree

rectangle dog

Dar apraykime dvi globalias funkcijas, kuri pirmoje pieia perstumt figr, o antroji pieia punktyrin rmel apie figr:
void paintMoved (QPainter* p, Shape* shape, int dx, int dy) { shape->setLocation(shape->getX()+dx, shape->getY()+dy); shape->paint(p); shape->setLocation(shape->getX()-dx, shape->getY()-dy); } void paintBorder (QPainter* p, Shape* shape) { int x = shape->getX(); int y = shape->getY(); int w = shape->getWidth(); int h = shape->getHeight(); p->setPen(Qt::DotLine); p->drawRect(x-2, y-2, w+4, h+4); p->setPen(Qt::SolidLine); }

96

Nupiekime apskritim, staiakamp, med ir un. Dar nupiekime kairn pastumt med, bei rmelius apie apskritim ir un:
void Screen::paintEvent(QPaintEvent*) { QPainter p(this); circle->paint(&p); rectangle->paint(&p); tree->paint(&p); dog->paint(&p); paintMoved(&p, tree, -150, 0); paintBorder(&p, circle); paintBorder(&p, dog);

Ekrane pamatysime:

Sudtin figra Composite iliustruoja labai paplitusi objektikai orientuoto programavimo architektr: klas susideda i savo paties bazins klass objekt, tiksliau, rodykli juos. Pvz. katalogas yra failas, susidedantis i kit fail, tame tarpe ir katalog. Variklis yra automobilio dalis, susidedanti i kit dali, kurios savo ruotu irgi gali bti sudtins. Tokia pasikartojanti ablonin situacija OOP projektuotoj yra vadinama Composite. Mes jau ne kart susidrme su ja.

97

98

4.

Klaid mtymas ir gaudymas (exception handling)

4.1.

Raktiniai odiai throw, try ir catch

Prisiminkime dinamin sveikj skaii stek:


// dynastack.cpp class Stack { ... public: ... void push int pop int peek bool isEmpty };

(int element); (); (); ();

Metodas pop iima i steko viraus element ir j grina. Jei stekas tuias, grina nul:
int Stack::pop () { if (size > 0) return elements[--size]; return 0; }

Tokiu bdu, klaidingai parayta steko naudojimo programa, kuri netikrina poymio isEmpty, gali skmingai traukti i steko nulius kiek panorjusi. Daugeliu atveju, tokia situacija yra nepageidaujama. ioje vietoje C++ silo patog klaid aptarnavimo mechanizm: klaid mtym ir gaudym (exception handling). Metodas pop gali "mesti" klaid, naudodamasis raktiniu odeliu throw, o kodas, kvieiantis metod pop, gali gaudyti klaidas raktini odi try ir catch pagalba:
int Stack::pop () { if (size > 0) return elements[--size]; throw string("pop() called for empty stack"); }

99

Tokiu bdu, kai stekas tuias, vietoje to, kad grinti beprasm reikm, mes praneame apie klaid mesdami j. Atininkamai funkcijoje main mes tikims metam klaid try-bloko pagalba ir pagaunama jas catch-bloke:
int main () { try { Stack squares; for (int i = 1; i <= 5; i++) squares.push (i*i); for (;;) cout << squares.pop () << endl; } catch (string& ex) { cout << "Exception caught: " << ex << endl; } }

Programa atspausdins:
25 16 9 4 1 Exception caught: pop() called for empty stack

odelis throw turi lygiai vien parametr - klaidos objekt, kuris yra metamas. Ms atveju yra metamas string tipo objektas, neantis savyje praneim apie klaid. Raktinis odis try su figriniais skliaustais ymi blok, kuriame tikimasi, jog gali vykti klaida, t.y. bus "mesta klaida". Po jo i karto privalo sekti catch-blokas su lygiai vienu argumentu, nusakaniu, kokia klaida yra gaudoma. Jei klaida nra metama try-bloko viduje, tai programos vykdymas ignoruoja catch-blok. Klaid gali mesti ne tik tiesiogiai try-bloke kvieiamos funkcijos ir metodai, bet ir pastarj viduje ikviestos funkcijos ir metodai.

100

4.2.

Skirting klaid gaudymas

Metamomis klaidomis gali bti bet kokio tipo objektai, pvz. js apraytos klass ar bazini C++ tip int, char, float ir t.t.. Klasms yra reikalavimas, kad jos turt kopijavimo konstruktori, nes imesta klaida, skriedama per funkcij ir metod kvietimo stek, pakeliui gali bti kopijuojama. Plaiai paplitusi praktika: kiekvienam klaidos tipui apsirayti po klas, neania vis reikiam informacij apie klaid. try-bloko viduje skirtingose vietose gali bti imestos skirting tip klaidos. Jas galima pagauti naudojant vien paskui kit einanius catch-blokus su atitinkamais parametrais. Be to, galima naudoti catch-blok su daugtakiu vietoje parametro, kuris pagauna vis tip klaidas. Pavyzdiui, turime aritmetini iraik parser, kuris tekstin eilut paveria atitinkamu iraikos objektu. Pastarasis turi metod, grinant iraikos rezultat, kaip double tipo reikm. Parseris gali imesti klaid, apie neteisingai urayt aritmetin iraik, o iraikos objektas gali imesti dalybos i nulio klaid:
Expression* exp = NULL; try { Parser parser; exp = parser.parseString("12 + 3*(9-5)"); cout << exp->getValue() << endl; } catch (SyntaxError& ex) { cout << "Syntax error: " << ex.getMessage() << endl; } catch (DivisionByZeroError& ex) { cout << "Division by zero error" << endl; } catch (...) { cout << "Unknown error" << endl; } delete exp;

vykus klaidai, ji bus perduota pirmajam catch-blokui, gaudaniam to tipo klaid. Kiti catch-blokai bus ignoruojami. ia galioja paveldjimas: jei catch-blokas gaudo bazins klaid klass objektus, tai pagaus ir paveldt klasi objektus. Tam, kad is mechanizmas veikt, reikia gaudyti nuorodas objekt, o ne pat objekt. Jei nra atitinkamo catch-bloko ir nra catch-bloko su daugtakiu, tai klaida ilekia toliau i einamosios funkcijos. Jei jos nepagauna joks kitas steke esanios funkcijos catch-blokas, tai ikvieiama globali funkcija terminate, kuri, pagal nutyljima, ubaigia programos darb standartins funkcijos abort pagalba. 101

4.3.

dtiniai try-blokai

Klaida laikoma pagauta ar aptarnauta nuo to momento, kai programos vykdymas eina atitinkam catch-blok. Tokiu bdu, catch-blokas pats savo ruotu gali mesti nauj klaid:
try {

tableData = readTableData(file); } catch (IOException& ex) { throw TableInputError("Can not get table data"); }

Kaip ir bet koks kitas blokas, catch-blokas gali turti savyje kitus try- ir catch-blokus:
List* list = 0; try { list = createMyObjects(); // do some file intput/output } catch (IOException& ex) { try { deleteMyObjects(list); } catch(...) {} }

// do some clean up // catch all exceptions

Kartais catch-blokas pats negali pilnai apdoroti pagautos klaidos ir nori mesti j toliau. Btent catch-bloko viduje galima naudoti raktin od throw be jokio klaidos objekto, kad pagautoji klaida bt metama toliau:
try {

... } catch (SomeError& ex) { if (canHandle(ex)) handleException(ex); else throw; // throw current SomeError object }

102

4.4.

Automatini objekt naikinimas steko vyniojimo metu

Steko vyniojimas, tai procesas, kuomet imesta klaida keliauja funkcij ikvietimo steku, iekodama atitinkamo catch-bloko. Pakeliui yra naikinami visi steke aprayti ir pilnai sukonstruoti objektai. Objektas yra ne pilnai sukonstruotas, jei klaida ilk i jo konstruktoriaus. Panagrinkime pavyzd, kuriame atidarytas failas gali likti neudarytas:
void useFile (const char* fileName) { FILE* file = fopen(fileName, "wt"); fprintf(file, "writting from useFile()\n"); mayThrowAnException(file); fclose(file); }

Funkcija throwsAnException imeta klaid, ir failas lieka neudarytas - kitose programos vietose prijimas prie jo yra udraustas. Dabar pasinaudokime standartins C++ bibliotekos klase ofstream, kuri savo destruktoriuje udaro fail:
void useFile (const string& fileName) { ofstream file (fileName); file << "writting from useFile() << endl; mayThrowAnException(file); }

Tokiu bdu, failas file bus udarytas nepriklausomai nuo to, ar i funkcijos useFile ieinama normaliai, ir sunaikinami visi lokals objektai, ar ieinama dl to, jog buvo imesta klaida, ir sunaikinami visi steke esantys pilnai sukonstruoti objektai.

103

4.5.

Klaid mtymas konstruktoriuose ir destruktoriuose

Klasi destruktoriai neturt mesti klaidos. Tarkime, buvo imesta klaida ir steko vyniojimo metu kvieiami automatini objekt destruktoriai. Jei kuris nors automatinis objektas tuo metu pats imes klaid i savo destruktoriaus, tai bus ikviesta globali funkcija terminate kuri, pagal nutyljim, nutraukia programos vykdym. Jei klaida metama i objekto konstruktoriaus, tai kvieiami destruktoriai toms objekto dalims, skaitant bazines klases, kurios buvo pilnai sukonstruotos, o paio objekto destruktorius nra kvieiamas:
// constrex.cpp class A { public: };

A() {cout << " A()" << endl;} virtual ~A() {cout << "~A()" << endl;}

class B : public A { public: B () {cout << " B()" << endl; throw string("construction of B failed");} ~B () {cout << "~B()" << endl;} }; int main () { try { B b; } catch (string& ex) { cout << "exception caught: " << ex << endl; } }

Programa atspausdins:
A() B() ~A() exception caught: construction of B failed

104

4.6.

Nepagautos klaidos ir funkcija terminate()

Kaip jau buvo minta, jei klaidos nepagauna nei vienas catch-blokas, arba jei vyniojant stek kuris nors destruktorius imet klaid, tuomet kvieiama globali funkcija terminate(), kuri pagal nutyljim ikvieia funkcij abort(). Prisiminkime, jog standartin funkcija abort(), skirtingai nuo funkcijos exit(), nesunaikina globali objekt. Mes galime pateikti savo terminate-funkcij naudodamiesi standartine funkcija set_terminate():
// terminate.cpp void myTerminate() { cout << "my terminate called" << endl; exit(1); } A globalA; int main () { set_terminate(myTerminate); throw string("some error"); }

Programa atspausdins:
A() my terminate called ~A()

105

4.7.

Klaid specifikacija ir netiktos klaidos

Pagal nutyljim, funkcija ir metodas gali imesti bet koki klaid. Nepakent metodo ar funkcijos signatroje nurodyti, kokias klaidas jis gali imesti:
void a() { ... } void b() throw(string, int) { ... } void c() throw() { ... }

Funkcija a() gali imesti bet koki klaid. Funkcija b() imes tik int arba string, arba klaid, paveldt nuo klass string. Funkcija c() pasiada nemesti jokios klaidos. Jei funkcija b() imes kitos ries klaid, arba funkcija c() imes bet koki klaid, bus ikviesta globali funcija unexpected(), kuri, pagal nutyljim, ikvies funkcij terminate. Analogikai, kaip ir terminate() funkcijos atveju, galime pateikti savo unexpected() funkcij pasinaudodami set_unexpected():
// unexpected.cpp void myUnexpected() { cout << "my unexpected called" << endl; exit(1); } void throwsAnException () { throw string ("Some exception"); } void promissedNotToThrow () throw() { throwsAnException(); } int main () { set_unexpected(myUnexpected); promissedNotToThrow(); }

106

4.8.

Standartins klaid klass

Standartin C++ biblioteka pateikia klaid klasi hierarchij:


exception | +----bad_alloc . . . . . . . . | +----bad_cast. . . . . . . . . | +----bad_exception | +----bad_typeid. . . . . . . . | +----ios_base::failure . . . . | +----logic_error | | | +----domain_error | | | +----invalid_argument | | | +----length_error | | | +----out_of_range . . | +----runtime_error | +----overflow_error | +----range_error | +----underflow_error

. new . dynamic_cast

. typeid . ios_base::clear()

. at()

Visos standartins klaid klass i bazins klass exception paveldi metod what(), kuris grina klaidos praneim. Standartins bibliotekos klaid klasi hierarchija puikus klaid grupavimo naudojant paveldjim pavyzdys: utenka gaudyti bazins klass exception klaidas ir kartu pagausime visas paveldtas klaid klases. Prisiminkime: kad is mechanizmas veikt, reikia gaudyti nuorodas objekt, o ne pat objekt. Kartu pamatysime, jog vector<> klass perkrautas operatorius [] netikrina masyvo ri, o analogikas metodas at() meta klaid, jei elemento indeksas neatitinka masyvo element kiekio:

107

// standard_ex.cpp try {

} catch (const exception& ex) { cout << "exception: " << ex.what() << endl; }

vector<int> v; v.push_back(2); v.push_back(4); v.push_back(8); cout << v[0] cout << v[7] cout << v.at(0) cout << v.at(7)

<< << << <<

endl; endl; endl; endl;

Programa atspausdins:
2 1247039744 2 exception: vector [] access out of range

108

5.

Vard erdvs (namespace)

5.1.

Motyvacija

Vard erdvs skirtos sprsti globali besikartojani vard konfliktus. Tarpusavyje susij tip, funkcij ar globali kintamj vardai gali bti apjungti atskir vard erdv. Mes jau susidrme su vard erdve std, kurioje apibrtos visos standartins bilbiotekos klass, funkcijos, globals kintamieji ir t.t.. Panagrinkime pavyzd: dvimaiai ir trimaiai takai bei atstumai tarp j:
// namespace1.cpp namespace Graphics2D { class Point { public: double x, y; Point (double x, double y); }; double distance (const Point& p1, const Point& p2); } namespace Graphics3D { class Point { public: double x, y, z; Point (double x, double y, double z); }; double distance (const Point& p1, const Point& p2); }

Funkcij ir konstruktori realizacija:


double Graphics2D::distance (const Point& a, const Point& b) { return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y)); } Graphics2D::Point::Point (double _x, double _y) : x (_x), y (_y) {} // Graphics3D - analogikai

109

Tokie vard erdvse apskliausti tipai naudojami prastiniu bd, tik j vardai prasideda talpinaniosios vard erdvs vardu:
int main () { Graphics2D::Point a(0, 0); Graphics2D::Point b(2, 2); Graphics3D::Point c(0, 0, 0); Graphics3D::Point d(2, 2, 2); cout << "a..b = " << Graphics2D::distance(a, b) << endl; cout << "c..d = " << Graphics3D::distance(c, d) << endl; }

Programa atspausdins:
a..b = 2.82843 c..d = 3.4641

110

5.2.

Raktinis odis "using"

Raktinio odio using pagalba galima naudoti ir trumpesnius vardus:


// namespace2.cpp int main () { using namespace Graphics2D; using Graphics2D::distance; // preffer to std::distance Point a(0, 0); Point b(2, 2); cout << "a..b = " << distance(a, b) << endl; }

Uraas "using namespace Graphics2D" reikia, kad visi vard erdvs Graphics2D identifikatoriai gali bti naudojami tiesiogiai, be vard erdvs priedlio. Galima ivardinti ir atskirus vard erdvs identifikatorius, kuriuos norime vartoti tiesiogiai. Pavyzdiui, uraas "using Graphics2D::distance" atlieka dvi funkcijas: 1. 2. leidia tiesiogiai naudoti identifikatori distance be Graphics2D:: prefikso; jei urao vietoje yra matomas kitas identifikatorius distance, tai pirmenyb teikti Graphics2D vard erdvs identifikatoriui.

Ms atveju naudotas antrasis variantas, nes antratinio failo iostream viduje jau yra identifikatorius distance. Naudojant using konkreiam funkcijos identifikatoriui, tampa matomos visos funkcijos su nurodytu vardu, nekreipiant dmesio argument sra. Be to, raktinis odis using takoja tik t kodo blok, kuriame yra naudojamas.

111

5.3.

Vard erdvi apjungimas

Vard erdvs yra atviros, t.y. jas galima papildyti: kompiliatorius visas rastas vard erdves su vienodu pavadinimu apjungia vien:
// namespace3.cpp namespace Graphics2D { class Point {...}; } namespace Graphics2D { double distance (const Point& p1, const Point& p2); }

5.4.

Vard erdvi sinonimai

Vard erdvms galima sukurti sinonimus. Daniausiai tai bna sutrumpinimai:


// namespace4.cpp namespace G2 = Graphics2D; int main () { G2::Point a (0, 0); G2::Point b (2, 2); cout << "a..b = " << G2::distance(a, b) << endl; }

112

5.5.

Vard erdvs be pavadinimo

Vard erdvs be pavadinimo naudojamos tik cpp-failuose (o ne pvz. h-failuose). Tarkime, turime du modulius m1.cpp ir m2.cpp priklausnaius tam paiam projektui, t.y. kartu susiejamus vien program:
// m1.cpp class Date {...}; int f(int i) {...}

// m2.cpp class Date {...}; int f(int i) {...}

Klas Date ir funkcija f yra pagalbiniai, todl jie ir aprayti ir apibrti ir naudojami tik savo moduli viduje, t.y. jie nepaminti h-faile. Kompiliatorius tvarkingai kompiliuos. Taiau linkeris imes klaid apie pasikartojanius identifikatorius skirtinguose moduliuose. Naudojant vard erdves be pavadinimo, galime priversti kompiliatori sugeneruoti unikalius vard erdvs pavadinimus kiekvienam moduliui atskirai:
// m1.cpp namespace { class Date {...}; int f(int i) {...} }

Toks uraas yra ekvivalentikas uraui:


namespace $$$ { class Date {...}; int f(int i) {...} } using namespace $$$;

Kur $$$ yra unikalus pavadinimas, galiojantis tik viename modulyje. Nevardintos erdvs pakeiia odel static kuomet jis naudojamas su prasme "lokalus linkavimas". Raktinis odis static turt bti naudojamas tik statiniams klass nariams ir funkcij statiniams vidiniams kintamiesiems aprayti.

113

114

6.

Operatori perkrovimas

6.1.

Motyvacija

Realizuokime kompleksinius skaiius su realija ir menamja dalimis. Apibrkime kompleksini skaii sudt, daugyb, ivedim sraut:
// operator1.cpp class Complex { private: double r; double i; public: Complex (double re=0, double im=0) : r(re), i(im) {} Complex add (const Complex& c); Complex multiply (const Complex& c); void print (ostream& os); };

Apskaiiuokime reikin d=a + b*c:


int main () { Complex a (2.5, 1.5); Complex b (10, 2); Complex c (4); Complex d; d = b.multiply(c); d = a.add(d); d.print(cout); }

Rezultatas:
(42.5+i9.5)

O ar negaltume mes to paties reikinio urayti prastiniu matematiniu pavidalu?


int main () { ... d = a + b*c; cout << d << endl; }

115

Galime! Tuo tikslu perkraukime tris C++ operatorius +, - ir <<:


// operator2.cpp class Complex { private: double r; double i; public: Complex (double re=0, double im=0) : r(re), i(im) {} Complex operator+ (const Complex& c); Complex operator* (const Complex& c); friend ostream& operator<< (ostream& os, const Complex& c); }; Complex Complex::operator+ (const Complex& c) { return Complex(r + c.r, i + c.i); } Complex Complex::operator* (const Complex& c) { return Complex(r*c.r - i*c.i, r*c.i + i*c.r); } ostream& operator<< (ostream& os, const Complex& c) { os << "(" << c.r << "+i" << c.i << ")"; return os; } int main () { Complex a (2.5, 1.5); Complex b (10, 2); Complex c (4); Complex d; d = a + b*c; cout << d << endl; }

Rezultatas bus tas pats:


(42.5+i9.5)

116

6.2.

Perkraunami operatoriai

Perkrauti galima iuos C++ operatorius:


+ | -= << >= -> ~ *= >> && [] * ! /= >>= || () / = %= <<= ++ new % < ^= == -new[] ^ > &= != ->* delete & += |= <= , delete[]

Daug lengviau atsiminti vienintelius keturis operatorius, kuri perkrauti negalima:


:: . .* ?:

Galime perkrauti operatori, t.y. veiksmus, kuriuos atlieka operatorius, bet j vykdymo prioritetai bei narikumai (unirinis ar binarinis) ilieka tie patys. Perkrautam operatoriui galime suteikti bet koki prasm, taiau geriau tuom nepiktnaudiauti: tegu suma ir reikia sum, o sandauga sandaug ir pan.. Beje, operatorius galima kviesti ir ireitiniu bdu, kaip prastas funkcijas ar metodus. Pvz., vietoje:
d = a + b*c; cout << d << endl;

Galime parayti taip, kaip kompiliatorius "padaro mintyse":


// operator2.cpp d = a.operator+(b.operator*(c)); operator<<(cout, d).operator<<(endl);

Matyt, nereikia ir sakyti, jog ireiktiniu bdu operatoriai nra naudojami - juk jie todl ir perkrauti, kad netu aikumo, o ne painiavos.

117

6.3.

Unariniai ir binariniai operatoriai

Binarinis (dviej argument) operatorius gali bti apraytas kaip klass metodas su vienu parametru, arba kaip globali funkcija su dviem parametrais:
class Vector { Vector operator* (double k) }; Vector operator* (double k, Vector& v); void f (Vector& v) { Vector a = v * 2.0; Vector b = 2.0 * v; ... }

// v.operator*(2.0) // ::operator*(2.0, v)

Kartais privaloma aprayti binarin operatori kaip funcij ne nar, jei pirmasis jo argumentas yra ne tos klass, kuriai skiriamas is operatorius, objektas. Pvz:
ostream& operator<< (ostream& os, const Complex& c)

Tokie operatoriai danai bna klass draugais (friend). Unarinis (vieno argumento) operatorius gali bti apraytas kaip klass metodas be parametr, arba kaip globali funkcija su vienu parametru:
class ListIterator { ListElement& operator++ (); bool hasElements(); };

// returns next element

List operator~ (List& list); // reverse list order void printAll (List& list) { ListIterator iterator = list.getFirst(); while (iterator.hasElements()) cout << ++iterator << endl; List reversed = ~list; ... }

Prisiminkime, jog operatoriai ++ ir -- gali bti prefiksiniai, pvz. ++a, kuomet jie pradioje padidina kintamojo a reikm vienetu ir vliau j grina, arba postfiksiniai, pvz. a++, kuomet pradioje gaunama kintamojo a reikm, o vliau ji padidinama vienetu. Aukiau yra apraytas prefiksinis operatorius ++. Postfiksiniai operatoriai ++ ir -- yra apraomi dirbtinai pridedant nenaudojam int-tipo argument: 118

class ListIterator { ListElement& operator++ (int); };

// postfix

void printAll (List& list) { ListIterator iterator = list.getFirst(); while (iterator.hasElements()) cout << iterator++ << endl; }

Dar kart nepamirkite, kad perkraunant operatorius, programuotojas nustato, k jie i ties darys. Tik jis gali (ir privalo) utikrinkti, kad prefiksinis operatorius veiks kaip prefiksinis, o postfiksinis - kaip postfiksinis.

119

6.4.

Tip konversijos operatoriai

Prisiminkime, kad tip konversija uraoma taip:


double d = 8.15; int i = (int) d;

Mes jau matme, jog galima aprayti koki nors klas X su konstruktoriumi, leidianiu ireiktiniu arba automatiniu bdu kito tipo T objekt konvertuoti klass X tipo objekt (T -> X). Tipas T gali bti kita klas ar bazinis C++ kalbos tipas. Galima ir atvirktin konversija: klasje X aprayti operatori, kuris klass X objekt konvertuot tipo T objekt (X -> T). Pvz., standartinje bibliotekoje ifstream tipo objekt galima konvertuoti int tipo reikm:
class ifstream // input file stream { ... operator int () const {return errorOccured == false;} };

Tokiu bdu, klass ifstream objektai gali bti naudojami visur, kur ir sveikieji skaiiai:
// operator3.cpp int main () { ifstream file ("integers.txt"); int i; file >> i; while (file) // calles: file.operator int () { cout << " " << i; file >> i; } cout << endl; file.clear(); file.close(); }

Apraant klass viduje tip konvertavimo operatori, nereikia nurodyti grinamos reikms tipo - jis visuomet yra toks, kur konvertuojama. Matyt, kad toki tipo konvertavimo operatori naudojimas turt bti labai saikingas, o gal net ir vengtinas. Juk t pat programos cikl galjome urayti ir suprantamiau:
while (file.good()) {...}

120

7.

Apibendrintas programavimas (generic programming)

7.1.

Programavimo stiliai

Skirtingi programavimo stiliai akcentuoja skirtingas svokas:


struktrinis (sturctured): struktros ir funkcijos, manipuliuojanios jomis objektais paremtas (object based): duomenys ir funkcijos drauge objektikai orientuotas (object oriented): paveldjimas ir polimorfizmas apibendrintas (generic): tipas, o ne kintamasis, gali bti kito tipo parametras.

Skaitytojas, matyt, galt ivardinti ir daugiau programavimo stili, pvz., loginis programavimas (tipinis programavimo kalbos atstovas - PROLOG) besiriamiantis logine dedukcija, funkcinis programavimas, paremtas sraais ir rekursija, kur jau klasika tapusi programavimo kalba LISP (list programming) dar kartais vadinama dirbtinio intelekto asembleriu, ir t.t.. Ms nagrinjamam apibendrintam programavimui palaikyti C++ kalba pateikia ablono (template) svok. Susipastant su ablonais pagrindinis tikslas yra gauti pakankamai ini, kad naudotis standartine C++ ablon biblioteka (STL - Standard Tamplate Library).

121

7.2.

Funkcij ablonai

Paioje ablon atsiradimo pradioje kai kurie programuotojai juos laik tik griozdiku programavimo kalbos C preprocesoriaus makros pakaitalu. Standartinis pavyzdys - funkcijos min ir max:
// minmax.cpp #define MIN(x, y) #define MAX(x, y) ((x) < (y) ? (x) : (y)) ((x) > (y) ? (x) : (y))

Pakeiskime iuos C makrosus C++ funkcij ablonais:


template<typename T> T min (T x, T y) { return x < y ? x : y; } template<class T> T max (T x, T y) { return x > y ? x : y; }

Prie kiekvien funkcijos ablon eina uraas template<typename AAA> arba template <class AAA>, kur AAA yra tipo parametro vardas. Funkcijos min ablono parametras yra tipas T, kuris nebtinai turi bti klass vardas. Reikalaujama, kad tipas turt konkreias savybes, bet nereikalaujame, kad priklausyt konkreiai klasi hierarchijai. Ms pavyzdyje tipui T turi bti apibrtos palyginimo operacijos > ir <. Tai gali bti bazinis C++ kalbos tipas, kuriam automatikai apibrtos palyginimo operacijos arba pai apsirayta klas, su perkrautais palyginimo operatoriais. emiau pateikiamas makros ir ablon panaudojimo pavyzdys:
int a = int b = cout << cout << cout << cout << 10; 20; "MIN(a, "MAX(a, "min(a, "max(a,

b) b) b) b)

= = = =

" " " "

<< << << <<

MIN(a, MAX(a, min(a, max(a,

b) b) b) b)

<< << << <<

endl; endl; endl; endl;

Funkcijos ablono apraymas nesugeneruoja jokio kodo objektin fail. Kad funkcijos ablonas gaut pavidal, reikia funkcij ikviesti. Funkcijos ablonas kvieiamas taip pat, kaip ir prastin funkcija. Kompiliavimo metu kompiliatorius pats sukonstruos reikiam funkcijos kn pagal funkcijos argument tipus. Jei skirtingose programos vietose kviesime su skirting tip argumentais, tai kiekvienam tip rinkiniui kompiliatorius sugeneruos po vien funkcijos ablono kn. Galima kompiliatoriui ir ireiktiniu bdu pasakyti, kokios funkcijos mes pageidaujame. emiau esantys 122

sakiniai yra ekvivalents, nes kintamieji a ir b yra tipo int:


cout << "min(a, b) = " << min(a, b) << endl; cout << "min<int>(a, b) = " << min<int>(a, b) << endl;

Nepamirkime, kad preprocesoriaus makrosai - tai "bukas" vien simboli pakeitimas kitais dar prie kompiliavim. Nevykdomas tip atitikimo patikrinimas, be to galimi alutiniai efektai, pvz.:
a = 10; b = 20; cout << "MIN(++a, ++b) a = 10; b = 20; cout << "min(++a, ++b)

= " << MIN(++a, ++b) << endl; = " << min(++a, ++b) << endl;

is kodo fragmentas atspausdins:


MIN(++a, ++b) min(++a, ++b) = 12 = 11

Pirmoje rezultato eilutje iliustruotas nepageidaujamas efektas: kintamasis a buvo inkrementuotas du kartus, nes uraas MIN(++a, ++b) po makroso ipltimo prie kompiliavim yra ekvivalentus tokiam uraui:
((++a) < (++b) ? (++a) : (++b))

Kai kas argumentuoja, jok makros naudojimas veikia greiiau nei funkcijos ablonas, nes sutaupomas funkcijos kvietimas. Taiau panaudojus raktin odel inline galime leisti kompiliatoriui i nelygyb itaisyti:
template<typename T> inline T min (T x, T y) { return x < y ? x : y; }

Taip pat atkreipkime dmes ura template<typename T>, kuris yra ekvivalentus (su ypatingai retomis iimtimis) uraui template<class T>, tik istorikai atsirado kiek vliau. Mat C++ krjai eilin kart be reikalo taup naujo raktinio odio vedim. Toliau mes vartosime odel typename, kadangi jis geriau atspindi prasm: T - yra bet koks tipas, nebtinai klas. Funkcijos min ir max yra labai paprastas pavyzdys. Standartinje bibliotekoje yra gerokai sudtingesni funkcij ablon, pvz. sort ir stable_sort ablonai, kurie suriuoje bet kur masyvo stiliaus konteiner turint savyje bet kokio tipo elementus, kuriems apibrtas operatorius <. Prisiminkime, jog paprast funkcij antrats talpinamos h-failus, o knai cpp123

failus, taip atskiriant interfeis nuo realizacijos. Su funkcij ablonais popieriai yra gerokai prastesni: visas funkcijos knas turi bti matomas kompiliatoriui kompiliavimo metu, todl jis talpinamas h-fail. Tas pats galioja ir klasi ablonams. Tokiu bdu ne tik atskleidiamos realizacijos detals, bet ir labai ymiai padaugje kompiliuojam eilui kiekis. Bene pagrindinis kompiliatoriaus rpestis kompiliuojant keliasdeimties eilui program, naudojani STL konteinerius, yra sukompiliuoti keliasdeimt tkstani eilui i standartini h-fail, turini savyje ablonus.

124

7.3.

Klasi ablonai

io kurso metu mes danai naudojome sveikj skaii stek. Nesunku suvokti, kad lygiai taip pat galime apsirayti ir kit bazini tip stekus: char, float, double, long ir t.t.. Bt labai varginantis darbas apsiratyi nauj steko klas kiekvienam baziniam ar pai sukonstruotam tipui. Milinikas kodo pasikartojimas. ia mes galime pasinaudoti klasi ablonais. Visur, kur naudojome odel int nusakyti steko elemento tipui, dabar naudokime ablono parametr, tip T:
// stack_template.h template<typename T> class Stack { private: T* elements; int size; int capacity; public: Stack (int initialCapacity = 4); ~Stack (); void T T bool push pop peek isEmpty (T element); (); (); () const;

};

Dabar pilnas klass vardas yra Stack<T>. Jos metod realizacijos reikalauja prierao template<typename T>:
template<typename T> Stack<T>::Stack (int initialCapacity) { if (initialCapacity < 1) initialCapacity = 1; capacity = initialCapacity; size = 0; elements = new T [capacity]; } ... template<typename T> T Stack<T>::pop () { if (size > 0) return elements[--size]; throw std::string("pop() called on empty stack"); }

125

Dabar mes galime turti stekus su norimo tipo elementais:


int main () { Stack<double> squares; for (int i = 1; i <= 10; i++) squares.push (i*i / 100.0); while (!squares.isEmpty()) cout << squares.pop() << endl; Stack<char> word; word.push('s'); word.push('u'); word.push('l'); word.push('a'); while (!word.isEmpty()) cout << word.pop(); cout << endl; Stack<string> names; names.push("Aldona"); names.push("Steponas"); names.push("Zigmas"); names.push(string("Birute")); while (!names.isEmpty()) cout << names.pop() << endl;

Atkreipkime dmes tai, kad klas Stack iskirinja element masyvus ir grina elementus pagal reikm. Tai reikia, jog elementai privalo turti konstruktori pagal nutyljim, korektik priskyrimo operatori bei kopijavimo konstruktori. Standartin klas string pasiymi visomis iomis savybmis. Aukiau esantis kodo fragmentas atspausdins:
1 0.81 0.64 0.49 0.36 0.25 0.16 0.09 0.04 0.01 alus Birute Zigmas Steponas Aldona

126

7.4.

Trumpai apie sudtingesnes ablon savybes

Ankstesni skyreli turt pakakti, kad galtumti naudotis stadartins C++ bibliotekos ablonais. Tuo tarpu iame skyrelyje trumpai paminsime kiek subtilesnes ablon galimybes. ablonas gali turti keleta parametr: Be to, parametrais gali bti ne tik tip vardai, bet ir konstantos. Pvz., fiksuoto dydio buferis:
template<typename T, int size> class Buffer {...}; Buffer<char, 12> charBuffer; Buffer<Record, 8> recordBuffer;

ablono parametrai gali nusakyti algoritm: Jie gali nurodyti, koki operacij atlikti su argumentais. Pvz., lyginant simbolines eilutes tenka atsivelgti kokreiai kalbai bding abecl: raid "" (A nosin) eina po raids "A" ir prie raid "B", kas nra tiesa, jei lygintume tik simboli kodus. Tuo tikslu galime apsirayti palyginimo klas su dviem statiniais metodais:
class ComparatorLT { public: static bool equal (char a, char b) {...} static bool less (char a, char b) {...} };

Kartu aprayti eilui palyginimo funkcijos ablon, kuris ima kaip parametr palyginimo klas. i funkcija grina reikm 0, kai eiluts lygios, neigiam skaii, kai pirma maesn u antr, ir teigiam skaii kai pirma eilut didesn u antr:
template<typename Comparator> int compare (const string& a, const string& b) { for (int i = 0; i<a.length() && i<b.length(); i++) if (!Comparator::equal(a[i], b[i])) return Comparator::less(a[i], b[i]) ? -1 : 1; return a.length() - b.length(); }

Tok funkcijos ablon galime ikviesti tokiu bdu:


int result = compare<CompareLT>(lithuanianName, n2);

Tokie ablono parametr panaudojimai algoritmui nusakyti vadinamas "bruoais" (traits). 127

ablonas gali turti parametrus su reikmmis pagal nutyljim: Pvz., apsiraykime bendr simboli palyginimo klass ablon, parametrizuojam simbolio tipu:
template<typename T> class Comparator { public: static bool equal (T a, T b) {return T == T;} static bool less (T a, T b) {return T < T;} };

J galime panaudoti, kaip parametr pagal nutyljim kitame ablone - funkcijoje compare. Atkreipkime dmes, kad emiau esantys tekstini eilui ablonai naudoja simbolio tipo parametr T. ia tam atvejui, jei mums reiks vieno baito simboli (char) eilui arba dviej bait simboli (wchar_t) eilui:
template<typename T, typename C = Comparator<T> > int compare (const String<T>& a, const String<T>& b) {...}

Tuomet funkcij compare galime naudoti keliais bdais:


int result1 = compare(str1, str2); // Comparator<char> int result2 = compare<char>(str1, str2); // Comparator<char> int result3 = compare<char, ComparatorLT>(str1, str2);

Pavyzdiui, standartinje bibliotekoje, kuomet naudojami vairi tip element konteineriai, naudojamas ablono parametras allocator, atsakingas u nauj element iskyrim ir sunaikinim. Pagal nutyljim jis realizuotas operatori new ir delete pagalba. ablonus galima specializuoti: Pvz., turint steko ablon su elemento tipo parametru T, galime apsirayti jo specializacij, kuomet parametras yra rodykl T. Arba galime pateikti jo efektyvesn specializacij konkreiam tipui, kai T yra kakoks MyType:
template<typename T> class Stack {...}; // bedras visiems template<> class Stack<MyType> {...}; // specializacija tipui // MyType

Specializuoti galima ir funkcij ablonus. Klass nariai-ablonai: Klass-ablono nevirtuals metodai patys savo ruotu gali bti metodai-ablonai su papildomu nuosavu ablono parametru:

128

template<typename T> class MyClass { template<typename W> double myMethod(W w); ... };

Tokio metodo realizacija vliau apipavidalinama taip:


template<typename T> template<typename W> double MyClass<T>::myMethod(W w) { ... }

Tokiu bdu klas turs po vien egzempliori kiekvienam tipui T ir dar priedo konkreiam tipui T po kelet metodo myMethod kn kiekvienam metodo argumento tipui W. Ivada ir patarimas: Bendru atveju, naudojant ablonus, galima uvirti tikr makalyn. Utenka vien pavelgti standartins bibliotekos konteineri h-failus. Sakoma, kad ablon instancijavimas kompiliavimo metu yra ekvivalentus Tiuringo mainai. emiau pateiktas pavyzdys, kaip naudojant ablonus priversti kompiliatori kompiliavimo metu (o ne programos vykdymo metu!) traukti kvadratin akn:
// turing.cpp const int N = 132 * 132; template <int Size, int Low = 1, int High = Size> struct Root; template <int Size, int Mid> struct Root<Size, Mid, Mid> { static const int root = Mid; }; template <int Size, int Low, int High> struct Root { static const int mean = (Low + High)/2; static const bool down = (mean * mean >= Size); static const int root = Root<Size, (down?Low:mean+1), (down?mean:High)>::root; }; int main() { cout << "ceil(sqrt(" << N << "))=" << Root<N>::root << endl; }

i programa kompiliavimo metu apskaiiuos konstantas ir atspausdins:


ceil(sqrt(17424))=132

Taigi, ablonus reikt paiam programuoti tik ten, kur jie yra prasmingi. Daug geriau - pasinaudoti kit paraytais ir gerai itestuotais ablonais, pvz. stadartine C++ ablon biblioteka (STL). 129

130

8.

Daugialypis paveldjimas

8.1.

Daugialypio paveldjimo pavyzdys

C++ kalba turi daugialyp paveldjim: klas vienu metu gali paveldti duomenis ir metodus i keletos bazini klasi. Pavyzdiui, neiojamas kasei kompaktini plokteli grotuvas ("ausinukas", "volkmanas") danai turi ir radijo imtuv, todl galime sakyti, jog ausinukas yrakasei grotuvas ir ausinukas taip pat yra radijo imtuvas:
class MCPlayer { ... }; class FMReceiver { ... }; class Walkman : public MCPlayer, public FMReceiver { ... };

Grafikai tok paveldjim galime pavaizduoti taip:


MCPlayer FMReceiver

Walkman

Klass Walkman objektus dabar galime naudoti visur ten, kur reikia MCPlayer ar FMReceiver objekt:
void tuneReceiver (FMReceiver* fm) { ... }

131

void playMC (MCPlayer* { ... }

mc)

int main () { Walkman* walkman = new Walkman(); tuneReceiver (walkman); playMC (walkman); }

132

8.2.

Pasikartojanios bazins klass ir virtualus paveldjimas

Tiek kasei grotuvas, tiek ir radijo imtuvas yra elektroniniai renginiai. Natralu, kad jie savo ruotu paveldt nuo elektroninio renginio klass EUnit:
class class class class EUnit {...}; MCPlayer : public EUnit {...}; FMReceiver : public EUnit {...}; Walkman : public MCPlayer, public FMReceiver {...};

Dabar klass Walkman objektai turs du EUnit poobjekius:


EUnit EUnit

MCPlayer

FMReceiver

Walkman

Pasikartojanti bazin klas EUnit gali sukelti tam tikr problem, nes abu jos egzemplioriai klasje Walkman gyvens nepriklausomai, o tip konversija i Walkman EUnit bus nevienareikm. I ties Walkman yra EUnit pagal paveldjimo linij, nors ir netiesiogin, taiau kuriuo i dviej EUnit' yra Walkman'as? Galime kompiliatori priversti sugeneruoti tik vien EUnit poobjekt klass Walkman objekte. Tuo tikslu klass MCPlayer ir FMReceiver turi naudoti virtual paveldjim:
class class class class EUnit {...}; MCPlayer : virtual public EUnit {...}; FMReceiver : virtual public EUnit {...}; Walkman : public MCPlayer, public FMReceiver {...};

Virtualus paveldjimas neturi nieko bendro su virtualiais polimorfiniais metodais, nebent tik tai, jog abiem atvejais kiek sultja programos veikimas. Eilin kart taupytas naujo raktinio odio vedimas. Bet kokiu atveju gauname klasikin romb:
EUnit

MCPlayer

FMReceiver

Walkman

133

8.3.

Virtualios bazins klass konstravimo problema

Virtualios bazins klass isprendia pasikartojanios bazins klass problem, taiau atsinea kit, bendru atveju neisprendiam, virtualios bazins klass konstravimo problem. Kai mes konstruojame klass Walkman objekt, tai pradioje yra kvieiami klasi MCPlayer ir FMReceiver poobjeki konstruktoriai. Pastarieji savo ruotu kvieia klass EUnit konstruktori, perduodami kiekvienas savo parametrus. Taiau klass Walkman objektas savyje turi tik vien EUnit poobjekt, kuris yra konstruojamas tik vien kart naudojant konstruktori pagal nutyljim. emiau esantis pavyzdys demonstruoja i virtualios bazins klass konstravimo problem:
// multi_inher.cpp class class class class A B : virtual public A C : virtual public A D : public B, public { { { C A (int i = 0) {...} }; B () : A(1) {...} }; C () : A(2) {...} } {}

int main () { A a; B b; C c; D d; }

Kiekvienos klass konstruktorius atspausdins po vien eilut ekrane:


A() i=0 A() i=1 B() A() i=2 C() A() i=0 B() C() D()

Grafikai turime jau matyt romb:


A

134

Matome, jog klass B objektas nort, jog jo poobjektis A bt sukonstruotas su argumentu 1, o klass C objektas nori, kad jo poobjektis A bt sukonstruotas su argumentu 2. Tuo tarpu klass D objekto poobjekiai B ir C dalinasi vieninteliu poobjekiu A. Kompiliatorius ioje vietoje nutaria nekreipti objekto D poobjeki B ir C norus, irkvieia bendro poobjekio A konstruktori pagal nutyljim. Bendru atveju virtualios bazins klass konstravimo problemos isprsti nemanoma. Egzistuoja tik dalinis ios problemos sprendimas, kuomet klass D konstruktoriuje galima nurodyti, kur netiesiogins bazins klass A konstruktori ikviesti, kad "daugma tikt" ir poobjekiui B ir C:
class D2 : public B, public C { public: D2() : A(15) {cout << "D2()" << endl;} }; int main () { D2 d2; }

is kodo fragmentas atspausdins:


A() i=15 B() C() D2()

Praktikoje yra geriau paveldti tik i vienos prastins klass su duomenimis ir i kiek norime daug "vari interfeis". Prisiminkime, jog varus interfeisas, tai klas, kuri neturi joki duomen ir metod, o tik variai virtualius metodus ir tui virtual destruktori. Kadangi varus interfeisas neturi duomen, tai neegzistuoja ir jo konstravimo problema.

135

8.4.

Objekt tip identifikacija programos vykdymo metu

Objekt tip identifikacija programos vykdymo metu (run-time type identification) galioja tiek vienalypiam, tiek ir daugialypiam paveldjimui. Prisiminkime, jog klas, turinti virtual destruktori arba bent vien virtual metod, vadinama polimorfine klase. Kaip jau inome, vis paveldjusi klasi objektai kartu yra ir bazins klass objektai, todl galime "natraliai" atlikti tip konversij i paveldjusios klass bazin:
// rtti.cpp class A { public: virtual ~A() {} }; class B : public A {}; int main () { A* a = new A(); B* b = new B(); a = b; // natrali tip konversija }

Jau programos kompiliavimo metu yra inoma, kad rodykl b rodo klass B tipo objekt, kuris paveldi i klass A. Todl priskyrimo sakinys a = b yra korektikai isprendiamas dar kompiliavimo metu. Kartais reikia atlikti atvirki tip konversij: turime rodykl A, bet i anksto tikims, kad ji bus nukreipta objekt B. Ar tai tiesa ar ne galime patikrinti tik programos vykdymo metu. Polimorfins klass ar nuo jos paveldjusi klasi objektams programos vykdymo metu saugoma informacija apie objekto tip. iuo atveju, naudojantis raktiniu odeliu dynamic_cast, mes galime atlikti saugi tip konversij i bazins klass A, paveldjusi klas B, nes klas A turi virtual destruktori:
void safeCastFromAtoB (A* a) { B* b = dynamic_cast<B*>(a); if (b == 0) cout << "cast A -> B failed" << endl; else cout << "cast A -> B succeeded" << endl; } int main () { A* a = new A(); B* b = new B(); safeCastFromAtoB(a); safeCastFromAtoB(b); }

is kodo fragmentas atspausdins:


cast A -> B failed cast A -> B succeeded

136

Raktinis odelis dynamic_cast tarp paprast skliaust reikalauja rodykls (kintamojo ar reikinio), o tarp enkl <> - rodykls tipo, kur konvertuosime duot rodykl. Jei pavyko, grinama saugiai konvertuota rodykl, jei ne - grinamas nulis. dynamic_cast galime naudoti ir nuorod konversijai, tik ia, klaidos atveju, metama standartin klaida bad_cast. Raktinio odelio typeid pagalba galime gauti informacij apie polimorfins klass objekto tip. is odelis reikalauja objekto, kaip savo vienintelio argumento, ir grina nuorod standartin struktr type_info, apibrt h-faile typeinfo. Mes naudosime ios struktros metod name:
void printTypeName (A* a) { cout << "type name: " << typeid(*a).name() << endl; } int main () { A* a = new A(); B* b = new B(); printTypeName(a); printTypeName(b); a = b; printTypeName(a); }

is kodo fragmentas atspausdins:


type name: 1A type name: 1B type name: 1B

137

138

9.

OOP receptai

9.1.

Objektikai orientuotos visuomens folkloras

Ne vienas pradedantis, o danai ir toliau paengs programuotojas, savo kailiu patyr, kad konkreios programavimo kalbos sintakss inojimas tik i dalies padeda sprsti realaus gyvenimo problemas. Gal gale, objektikai orientuota programavimo kalba, kokia ji bebt, yra tik rankis programoms kurti. Toliau sprendiami auktesnio lygio udaviniai: klasi struktr ir tarpusavio sveikos projektavimas, pusiausvyros tarp kodo lankstumo, vykdymo greiio ir kodo aikumo iekojimas. Kadangi objektikai orientuotas programavimas gyvuoja kelet deimtmei, tai ir patirties daugeliui udavini sprsti yra sukaupta tikrai daug. J galtume pavadinti objektikai orientuotos visuomens folkloru. Tolesnieji trys skyreliai atitinkamai remiasi trimis puikiomis knygomis: 1. Erich Gamma, Richard Helm, Raph Johnson, John Vlissides (GOF - Gang Of Four): Design Patterns; Elements of Reusable Object-Oriented Software; Addison Wesley Longman, Inc., 1995 Martin Fowler: Refactoring; Improving the Design of Existing Code; Addison Wesley Longman, Inc., 1999 Kent Beck: eXtreame Programming eXplained; Embrace Change; AddisonWesley, 2000

2. 3.

iose knygose vietoje paini teorini ivediojim pateikta krvos praktikoje patikrint patarim ir sprendim. Mes tik trumpai paliesime kai kuriuos folkloro perliukus. Programuotojui primygtinai rekomenduojama susupainti su aukiau ivardintomis knygomis. Syk perskait, vliau dar ne kart prie j grite. Laikui bgant augs ir js kaip programuotoj patirtis, tuomet galsite naujai vertinti koleg receptus, palyginti su nuosavais pastebjimais ir pateikti sav idj. Beje, aukiau ivardintos knygos turi omenyje, jog skaitytojas jau gijo pakankamai gilias programavimo kalbos inias (C++ arba Java). Jei trksta kasdieni programavimo gdi, rekomenduojama paskaityti labai auni Majerso knyg: 139

Scott Meyers: Effective C++, Second Edition; 50 Specific Ways to Improve Your Programs and Designs; Addison Wesley, 1998 Scott Meyers: More Effective C++: 35 New Ways to Improve Your Programs and Designs; Addison Wesley, 1995

140

9.2.

Projektavimo ablonai (design patterns)

Erich Gamma, Richard Helm, Raph Johnson, John Vlissides (GOF - Gang Of Four): Design Patterns; Elements of Reusable Object-Oriented Software; Addison Wesley Longman, Inc., 1995 Objektikai orientuot (OO) program krimas yra sunkus udavinys, o daug kart panaudojam programini komponent krimas - dar sunkesnis. Patyr OO projektuotojai ino, kad lankstaus ir ateityje naudingo dizaino sukrimas i pat pirmo karto yra sudtingas, o daniausiai ir nemanomas dalykas. Ir visgi ekspertai sumeistrauja puikius program modelius. K gi ino ekspertai, ko neinotu naujokai? Vienas dalykas, kurio tikrai nedaro ekspertai, yra program krimas nuo nulio. Jie veriau pasiieko gatav sprendim, kurie puikiai veik praeityje, ir, labai tiktina, toliau puikiai veiks. Projektavimo ablonai - tai bdas nuosekliai dokumentuoti projektuotoj patirt, t.y. programuotoj folklor - legendas ir padavimus. Prie kiekvieno projektavimo ablono, be kit dalyk, yra paymima: problema, kuri norime isprsti, sprendimas, susidedantis i keletos klasi ir j tarpusavio sveikos, sprendimo pritaikymo paskms, t.y. jo privalumai ir trkumai.

Autori kolektyvas, ketverto gauja, sudar 23-j projektavimo ablon katalog. Kiekvienas ablonas turi pavadinim su uuomina apie jo paskirt. iame kurse mes jau palietme maiausiai tris projektvimo ablonus: Singleton - utikrina, jog klas turs tik vien egzempliori, kur galima pasiekti globaliai Composite - komponuoja objektus medio pavidalo hierarchijas; pavieniai ir sudtiniai objektai traktuojami vienodai Iterator - leidia paeiliui perbgti konteinerio elementus, kartu paslepiant vidin konteinerio realizacij

Composite ablonas yra vienas populiariausi. Grafins lang sistemos yra puikus panaudojimo pavyzdys: langai susideda i grafini komponent, kuri kiekvienas savo ruotu gali susidti i smulkesni ir t.t.. emiau pateikta apibendrinta Composite ablono klasi hierarchija:

141

Client

Component operation()

Leaf operation()

Composite operation() add(Component) remove(Component) getChild(int) forall g in children g.operation()

Tipin Composite objekt struktra:


aComposite

aLeaf

aLeaf

aComposite

aLeaf

aLeaf

aLeaf

aLeaf

142

9.3.

Program pertvarkymas (refactoring)

Martin Fowler: Refactoring; Improving the Design of Existing Code; Addison Wesley Longman, Inc., 1999 Tiek dideli, tiek ir mai projektai danai susiduria su tokia problema: kodas nepaliaujamai auga ir puiasi, pridedant nauj funkcij pradeda grivinti anksiau parayti moduliai, niekas normaliai nesusigaudo, kurios dalys takoja viena kit ir pan.. Tokiu atveju pakvimpa program pertvarkymu (refactoring): tenka pertvarkyti (sakoma, "ivalyti") egzistuojant kod, isaugant ankstesn funkcionalum. T.y. programos pertvarkymo metu nra diegiamas naujas ar keiiamas senas funkcionalumas, tiesiog ieities tekstai tampa tvarkingesni, geriau struktrizuoti ir labiau suprantami. Nereikia n sakyti, jog dar kart perrayti t pat kod mgsta nedaugelis. Labai nedaugelis. Taiau nebtina keisti visk i karto. Martinas Fauleris moko, jog visk reikia daryti mayiais, aikiai apibrtais ir gerai kontroliuojamais ingsneliais. Galbt vienas modulis bus pertvarkytas per valand, o kitam prireiks keli mnesi. Kada reikia program pertvarkyti? Paprastai, palaipsniui projektuojant ir programuojant, patyrs programuotojas kartu pertvarkinja ir neseniai parayt kod. Kitas atvejis, kai reikia diegt nauj funkcij, taiau egzistuojantis kodas joms nepritaikytas. Kaip inoti, kurias programos vietas reikia keisti? O gi tas, kurios "nemaloniai kvepia". Martinas parodo, kaip aptikti vis eil "nemaloni kvap" ir kaip juos paingsniui paalinti. Rezultate gauname program, kuri daro t pat, tik "maloniau kvepia". Pertvarkant svarbu prisilaikyti bent dviej princip: turti pasiraius automatinius programos testus, kurie patikrina, ar pertvarkyta programa elgiasi taip, kaip ir anksiau pertvarkyti tik labai maai ingsneliais ir atlikti automatinius testus - jei kuris neveiks, bus aiku, kad klaida slypi naujausiuose pakeitimuose

iame skyrelyje apsiribosime vienu pavyzdiu: video nuoma. Tai programa, kuri duotam klientui atspausdina, kokius jis filmus yra isinuomavs ir kiek jis turi mokti. Mokestis priklauso nuo to, kuriam laikui filmas yra inuomotas ir nuo filmo tipo. Tra trys film tipai: reguliars, vaikiki ir nauji filmai. Tuo tikslu konstruojama paprastut klasi diagrama, leidianti atspausdinti tekstin ataskait apie klient:

143

Customer name: string printStatement()

Rental daysRented: int

1 title: string priceCode: int

Movie

Smulkesns detals pateikiamos Faulerio knygoje. Beje, tokiai paprastutei programlei is dizainas puikiausiai tinka. Ir nors i klasi diagramos sunku pamatyti, taiau visgi atsiranda tam tikr problem, kuomet mes usimanome nauj dalyk: ataskaitos ne tik tekstiniame, bet ir HTML formate laisvai kaitaliojamos film kain politikos ir nuolaid skaiiavimo daniems videotekos klientams nuomuojantis nauj film (udirbt tak skaiius)

Fauleris parodo, kaip ingsnis po ingnio pertvarkyti i diagram lydint kod ir gauti nauj klasi hierarchij:
Customer name: string printStatement() printHTMLStatement() getTotalCharge() getTotalFrequentRenterPoints() 1 * Rental daysRented: int getCharge() getFrequentRenterPoints() * 1 title: string getCharge(days: int) getFrequentRenterPoints(days: int) Movie

Price getCharge(days: int) getFrequentRenterPoints(days: int)

RegularPrice getCharge(days: int)

ChildrenPrice getCharge(days: int)

NewReleasePrice getCharge(days: int) getFrequentRenterPoints(days: int)

Kaip sako Fauleris: svarbiausia pertvarkymo ritmas - maas pakeitimas, testas, maas pakeitimas, testas ir t.t..

144

9.4.

Ekstremalus programavimas (eXtreame programming)

Kent Beck: eXtreame Programming eXplained; Embrace Change; Addison-Wesley, 2000 (http://www.xprogramming.com) Ekstremalus programavimas - tai recept rinkinys viso programavimo proceso organizavimui. Jame nra nieko ekstremalaus, nebent tai, jog jis gerai veikia tik tuomet, kai didesn dalis recept yra naudojama ir turi maai prasms, kai naudojamas atmestinai. Be to, jame yra keletas patarim, su kuriais ne kiekvienas programuotojas i karto sutiks - ekstremalumas slypi tame, jog programuotojui tenka priversti save keisti netikusius proius. emiau pateikti kai kurie bendrai pripainti geri proiai, tik jie yra hiperbolizuoti iki ekstremalumo: jei kodo perirjimas yra gerai, tai mes j perirsime nepertraukiamai (pair programming: programavimas poromis - du mons prie kiekvieno kompiuterio); jei testavimas yra gerai, tai mes testuosime nuolatos (unit testing: automatiniai moduli testai, kurie grina arba OK, arba ivardina rastas klaidas); jei projektavimas yra gerai, tai tuomet mes j darysime kasdien (refactoring: nuolatinis program pertvarkymas); jei paprastumas yra gerai, mes visuomet paliksime pat paprasiausi programos dizain, kuris palaiko visas reikiamas funkcijas; jei atskir moduli tarpusavio integracija ir testavimas yra gerai, mes apjunginsime savo modulius kelis kartus per dien; jei darb iteracijos yra gerai, mes planuosime labai maus darbus net valandomis, o ne savaitmis ar mnesiais.

Tai nra visi receptai. Plaiau apie juos paius bei j argumentacij galima rasti Kento Beko knygoje. Taigi, programuotojai sdi poromis, parao testus klasms anksiau, negu kad paias klases, jie daug kalbasi, kelet kartu per dien padeda savo kod bendr server ir pasiima i jo kit programuotoj darb, perkompiliuoja ir testuoja, vl kalbasi, keiiasi poromis, keiia kit parayt koda ir t.t.. Gal tik ne taip chaotikai. Reikt pastebti, jog ekstremalus programavimas tinka tik maiems ar vidutiniems projektams. Jis reikalauja auktos programuotojo, kaip kolektyvo nario, kultros ir disciplinos.

145

Labai svarbi darbo atmosfera. Visuomet privalo bti pakankamai ramu, gaivu ir prikrauta pakankamai ukandi. Syk Kentas Bekas konsultavo vienos firmos programuotojus. Primok visoki recept. Vliau praktika parod, jog didiausi taka programuotojams padar stal perstumdymas: anksiau visi keturi stalai buvo prie skirting sien, o po to - visi kambario viduryje. Kiekvienas mat kito veid ir bendravimas vyko mieliau ir efektyviau. Pats Kentas Bekas apie save sako: a nesu fantastikas programuotojas, a tiesiog geras programuotojas su fantastikais proiais.

146

También podría gustarte