452
Skripta Informatičke Škole Proanima

C++ PRIRUCNIK

Embed Size (px)

DESCRIPTION

c++

Citation preview

Page 1: C++ PRIRUCNIK

Skripta Informatičke Škole Proanima

Page 2: C++ PRIRUCNIK

2 C++ programer

Page 3: C++ PRIRUCNIK

3

Lekcija 1 Uvod u C++

Danas ćemo naučiti:

Zašto je C++ postao industrijski standard za razvoj softwarea Korake potrebne za razvoj C++ programa Kako unijeti, kompajlirati i povezati naš prvi C++ program

Kratka povijest jezika C++Programski jezici su prošli kroz dramatičnu evoluciju od vremena prvih elektronskih računala koja su se koristila za proračune i razbijanje šifri za vrijeme Drugog svjetskog rata. U ranim danima, programeri su radili u najprimitivnijem programskom jeziku: strojnom jeziku. Te instrukcije su bile prikazane slijedom dugačkih brojeva prikazanih nulama i jedinicama. Uskoro je razvijen assembler kako bi povezao teško pamtljive brojeve za svaku naredbu i asocirao ih sa engleskim riječima, tzv. mnemonicima poput ADD ili MOV.

S vremenom, razvili su se viši programski jezici, poput BASICa i COBOLa, Ti jezici su omogućavali programerima da rade s nečim što aproksimira riječi i rečenice, npr. Let A = 100. Te instrukcije se prevode nazad u strojni jezik uz pomoć programa koje nazivamo interpreteri i kompajleri. Interpreter prevodi program dok ga čita, pretvarajući programsku instrukciju, odnosno kod direktno u akcije. Kompajler prevodi kod u neku vrstu prijelaznog oblika, mješanca između strojnog jezika i jezika u kojem razvijamo program. Taj proces se zove kompajliranje i proizvodi objektni kod. Kompajler nakon toga obično poziva linker koji objektni kod pretvara u izvršnu datoteku, odnosno u program.

ProgramiObratite pažnju na to da se riječ program koristi za dve potpuno različite vrste datoteka. Programom nazivamo i naš izvorni kod (engl. source kod), ali i izvršnu datoteku koja je nastala kao proces kompajliranja i linkanja izvršne datoteke.

Proceduralno, strukturirano i objektno-orjentirano programiranjeDo nedavno, programi su bili smatrani slijedom procedura koje vrše zadane radnje nad podacima. Procedura, ili funkcija je kruto definiran slijed specifičnih instrukcija. Sami podaci su bili potpuno odvojeni od procedura, i čitav trik programiranja se sastoji u praćenju koja funkcija je zvala koju i koji podaci su bili promjenjeni u tom procesu. Kako bi se unio nekakav red u potencijalno konfuznu situaciju, izmišljeno je strukturirano programiranje.

Osnovna ideja koja se krije iza pojma strukturiranog programiranja jest stara «podjeli pa vladaj». Računalni program je smatran nizom zadaća koje treba izvršiti. Svaka zadaća koje je prekompleksna za jednostavan opis bi trebala biti «razbijena» u niz manjih zadataka, sve dok zadaci nisu zadovoljavajuće mali ali i «samodostatni» u smislu da su takvi dijelovi koda lako razumljivi.

Page 4: C++ PRIRUCNIK

4 C++ programer

Kao primjer, računanje prosječne plaće svih uposlenika u tvrtci je prilično kompleksan zadatak. Možemo ga, međutim razbiti na podzadatke:

1. Saznati koliko svaka osoba zarađuje. 2. Zbrojiti sve uposlene. 3. Zbrojiti sve plaće. 4. Podijeliti sa brojem zaposlenih.

Zbrojiti sve plaće može biti rastavljeno na:

1. Uzimanje karton uposlenog. 2. Pristupiti podatku o iznosu plaće3. Dodati plaću u ukupnu sumu. 4. Uzeti karton od slijedećeg zaposlenog.

Strukturirano programiranje ostaje enormno koristan i efikasan pristup za rješavanje kompleksnih problema. U kasnim 1980im su se, svejedno, počeli otkrivati nedostaci takvog pristupa i s vremenom su postajali sve očitiji.

Prvo, potpuno je neprirodno razmišljati o našim podacima (npr., popis zaposlenih) i onome što možemo učiniti s njima (sortirati, uređivati, itd.) kao o odvojenim procesima.

Drugo, programeri su se često nalazili u situaciji da traže rješenja za stare probleme, ili kako mi volimo to reći, «otkrivali su toplu vodu». Rodila se «Ponovno upotrijebi» (engl. reusability) ideja za stvaranjem komponenti koje imaju poznata svojstva, te o mogućnosti da takve komponente jednostavno umećemo u naš kod. To je učinjeno po analogiji hardwareskog svijeta. Kad vam se pokvari procesor u računalu, jednostavno zamijenite procesor, ne morate ga projektirati, niti dobro poznavati njegovu internu arhitekturu kako bi on ipak bio u stanju obavljati svoju funkciju. U softwareskom svijetu se do tada takva analogija nije mogla povući.

Nadalje, staromodni programi su forsirali korisnika da prolazi kroz «korak-po-korak» seriju ekrana. Moderni događajem pokretani (engl. event driven) programi predstavljaju sve izbore odmah i reagiraju na odgovarajuću korisnikovu akciju.

Objektno-orijentirano programiranje pokušava odgovoriti na te potrebe, pružajući tehnike za upravljanje kompleksnim problemima, podržavanjem ponovnog korištenja komponenti i grupiranjem podataka i zadacima koji manipuliraju tim podacima.

Srž objektno orijentiranog programiranja je u tretiranju podataka i procedura koje djeluju na podatke kao jednoga «objekta», samodostatne jedinke koja ima vlastiti identitet i određene karakteristike.

Page 5: C++ PRIRUCNIK

5

Razvojni ciklusDa je svaki program kojeg ste ukucali ikad radio iz prve, to bi bio potpun razvojni ciklus. Ukucate program, kompajlirate ga, linkate i pokrenete izvršnu datoteku. Na žalost, svaki program, ma kako trivijalan on bio, će najvjerovatnije sadržavati određene pogreške, odn. bugove. Neke pogreške će uzrokovati greške prilikom kompajliranja, neke tek prilikom linkanja, dok će se neke pojaviti tek u izvršnoj datoteci. Bilo kada da se to dogodi, bit će potrebno ukloniti pogrešku iz koda, te ponovo kreirati izvršnu datoteku i pokrenuti ju. Razvojni ciklus je okvirno prikazan na slijedećoj shemi:

hello.cpp --- naš prvi C++ program

1: #include <iostream.h>2:3:4: int main();5: {6: cout <<»Hello World!\n»;7: return 0;8: }

Page 6: C++ PRIRUCNIK

6 C++ programer

Nakon što smo unesli program i kreirali izvršnu datoteku, pokušajmo ju pokrenuti. Na ekranu bi se trebalo pojaviti:

Hello World!

Kviz1. Koja je razlika između interpretera i kompajlera? 2. Kako kompajlirate izvršni kod sa svojim kompajlerom? 3. Što čini linker? 4. Koji su koraci u razvojnom ciklusu?

Vježbe1. pogledajte u slijedeći program i pokušajte ustanoviti što on radi bez njegova pokretanja.

1: #include <iostream.h>2: int main()3: {4: int x = 5;5: int y = 7;6: cout «\n»;7: cout << x + y << « « << x * y;8: cout «\n»;9:return 0;10: }

2. Unesite program sa 1. vježbe, kompajlirajte ga i povežite. Što on čini ? Da li radi ono što ste mislili?

3. Unesite slijedeći program i kompajlirajte ga. Koja će se greška pojaviti?

1: include <iostream.h>2: int main()3: {4: cout << «Hello World\n»;5: return 0;6: }

4. Popravite grešku u 3. vježbi, rekompajlirajte , linkajte i pokrenite program. Što on radi?

Page 7: C++ PRIRUCNIK

7

Lekcija 2 Dijelovi C++ programa

C++ programi se sastoje od objekata, funkcija, varijabli i drugih komponenti. Većina ovih lekcija se bavi detaljnim objašnjavanjem tih djelova, ali kako bi dobili predožbu kako program radi, moramo analizirati neki program koji radi. Danas ćemo proučavati dijelove C++ programa.

Čak i jednostavan program poput Hello.cpp iz 1. lekcije ima mnoge zanimljive dijelove. Za početak prisjetimo se kako je Hello.cpp izgledao:

Listing 2.1. HELLO.CPP demonstracija dijelova C++ programa

1: #include <iostream.h>2:3: int main()4: {5: cout << "Hello World!\n";6: return 0;7: }

Hello World!

U liniji 1, datoteka iostream.h je uključena u naš program. Prvi znak je # simbol, što je zapravo signal pretprocesoru. Svaki put kad startate kompajler, pretprocesor se pokreće. On "čita" vaš source kod, tražeći linije koje počinju s simbolom povisilice (#), i obavlja zadane radnje prije nego se kompajler pokrene.

include je pretprocesorska instrukcija koja kaže, "Ono što slijedi je ime datoteke. Pronađi tu datoteku i pročitaj ju prije nego kreneš s prevođenjem programa." kose zagrade oko imena datoteke kažu pretprocesoru da traži u svim definiranim mjestima tu datoteku. Ako vam je kompajler pravilno podešen, uglate zagrade će uzrokovati da pretprocesor traži datoteku iostream.h u direktoriju koji sadrži sve H datoteke za vaš kompajler. Datoteka iostream.h (Input-Output-Stream) je upotrebljena prilikom ukucavanja cout, koja pomaže prilikom ispisa na ekran. Efekt linije 1 je isti kao da ste cijelu datoteku iostream prepisali u program prije nego što ste počeli unositi vaš program.

Linija 3 počinje s pravim programom, odnosno s funkcijom nazvanom main(). Svaki C++ program mora imati main() funkciju. Uopćeno govoreći, funkcija je blok naredbi koja obavlja jednu ili više radnji. Obično se funkcije pozivaju unutar drugih funkcija, ali main() je drugačiji. Kad pokrenete program, main() se poziva automatski.

Sve funkcije počinju s otvorenom zagradom ({) a završavaju, naravno, sa zatvorenom zagradom (}). Zagrade od main() funkcije su na linijama 4 i 7. Sve između njih se smatra dijelom funkcije.

Srž ovog programa sadržana je u liniji 5. Objekt cout je upotrebljen za ispis poruke na ekran. Baviti ćemo se objektima više u lekciji 6.

Evo kako se cout koristi: Otipkate riječ cout, prateći je s operatorom koji se naziva output redirection operator (<<). Sve što slijedi taj operator, preusmjerava (engl. redirection=preusmjeravanje) se na ekran. ako želite nizove znakova ispisati na ekran, svakako ih zatvorite navodnicima ("), kako je prikazano u liniji 5.

Page 8: C++ PRIRUCNIK

8 C++ programer

Zadnja dva znaka, \n, kažu cout objektu da stave novu liniju nakon što se ispiše poruka Hello World!. Taj posebni kod će biti detaljno analiziran u 17 lekciji, kad ćemo kompletno obrađivati cout objekt.

Svi ANSI uskladivi programi deklariraju main() kao cjelobrojnu funkciju (int). Ta vrijednost se "vraća" operativnom sistemu kad program dođe do kraja.

Listing 2.2.Upotreba cout.

1: // Listing 2.2 using cout2:3: #include <iostream.h>4: int main()5: {6: cout << "Hello there.\n";7: cout << "Here is 5: " << 5 << "\n";8: cout << "The manipulator endl writes a new line to the screen." << endl;9: cout << "Here is a very big number:\t" << 70000 << endl;10: cout << "Here is the sum of 8 and 5:\t" << 8+5 << endl;11: cout << "Here's a fraction:\t\t" << (float) 5/8 << endl;12: cout << "And a very very big number:\t" << (double) 7000 * 7000 << endl;13: cout << "Don't forget to replace Jesse Liberty with your name...\n";14: cout << "Jesse Liberty is a C++ programmer!\n";15: return 0;16: }

Hello there.Here is 5: 5The manipulator endl writes a new line to the screen.Here is a very big number: 70000Here is the sum of 8 and 5: 13Here's a fraction: 0.625And a very very big number: 4.9e+07Don't forget to replace Jesse Liberty with your name...Jesse Liberty is a C++ programmer!

KomentariKad pišete program, uvijek vam je jasno što pokušavate napraviti u kojem dijelu. Smješno je to da—mjesec poslije, kad se vratite programu, može nam djelovati dosta zbunjujuće i nejasno. Za borbu protiv zbunjenosti, a i za olakšavanje drugima da razumiju naš kod, željeti ćete koristiti komentare. Komentari su običan tekst, u potpunosti ignoriran od strane kompajlera, ali koji obavještava potencijalnog programera (ili nas same) što radimo u određenim dijelovima programa.

Tipovi komentaraC++ komentari dolaze u dva oblika: the double-slash (//) komentar, i slash-star (/*) komentar. Double-slash komentar, kojeg ćemo često zvati C++ komentarom kaže kompajleru da ignorira sve što slijedi do kraja linije.

Page 9: C++ PRIRUCNIK

9Slash-star komentar kaže kompajleru da ignorira sve iza (/*) dok ne naleti na star-slash (*/) oznaku. Ovakvi komentari se često nazivaju i C komentarima. Svaki /* mora imati i zatvarajući */.

Listing 2.3 demonstracija upotrebe komentara, pokazuje da oni ne utječu na procesiranje programa.

1: #include <iostream.h>2:3: int main()4: {5: /* this is a comment6: and it extends until the closing7: star-slash comment mark */8: cout << "Hello World!\n";9: // this comment ends at the end of the line10: cout << "That comment ended!\n";11:12: // double slash comments can be alone on a line13: /* as can slash-star comments */14: return 0;15: }Hello World!That comment ended!

Komentari na vrhu datotekeDobra je ideja staviti blok komentara na vrh svake datoteke koju stvarate. Točan stil takvog komentara je stvar osobnog ukusa, ali bi svako zaglavlje takve vrste trebalo sadržavati barem slijedeće informacije:

Ime funkcije ili programa. Ime datoteke. Što funkcija ili program radi. Opis kako program radi. Ime autora. Povijest prepravki (engl. revision history)—zabilješke o svakoj napravljenoj promjeni. Koji kompajleri, linkeri, i ostali aliti su korišteni prilikom stvaranja programa. Eventualne dodatne zabilješke.Npr., slijedeći blok komentara bi mogao stajati na vrhu Hello World programa.

/************************************************************Program: Hello WorldFile: Hello.cppFunction: Main (complete program listing in this file)Description: Prints the words "Hello world" to the screenAuthor: Jesse Liberty (jl)Environment: Turbo C++ version 4, 486/66 32mb RAM, Windows 3.1

DOS 6.0. EasyWin module.

Notes: This is an introductory, sample program.Revisions: 1.00 10/1/94 (jl) First release 1.01 10/2/94 (jl) Capitalized "World"************************************************************/

Page 10: C++ PRIRUCNIK

10 C++ programer

FunkcijeIako je i main() funkcija, zbog svoje (ne)običnosti nam to i nije dobar primjer. Tipične funkcije se zovu i stvaraju prilikom izvršenja vašega programa. Program se izvršava liniju po liniju onako kako se pojavljuje u vašem izvršnom kodu sve dok ne "naleti" na funkciju. Kad se to dogodi, program iskoči iz svog redoslijeda, te izvršava funkciju.. Kada funkcija obavi svoj posao, program ponovo preuzima kontrolu u liniji koda koji direktno slijedi iza poziva funkcije.

Dobra analogija za ovo je oštrenje olovke. Ako crtate sliku, i vrh olovke se slomi, morate prestati crtati, naoštriti olovku, te se potom vratiti crtanju. Kad program treba uslugu, može pozvati funkciju koja tu uslugu pruža, te potom nastaviti gdje je stao sa radom. Slijedeći program demonstrira tu ideju.

Listing 2.4. Demonstracija poziva funkcije.

1: #include <iostream.h>2:3: // function Demonstration Function4: // prints out a useful message5: void DemonstrationFunction()6: {7: cout << "In Demonstration Function\n";8: }9:10: // function main - prints out a message, then11: // calls DemonstrationFunction, then prints out12: // a second message.13: int main()14: {15: cout << "In main\n" ;16: DemonstrationFunction();17: cout << "Back in main\n";18: return 0;19: }

In mainIn Demonstration FunctionBack in main

Upotreba funkcijaFunkcije ili vraćaju vrijednost ili vraćaju prazninu (engl. void), što u biti znači da ne vraćaju ništa. Funkcija koja zbraja dva broja bi mogla vraćati sumu, i prema tome bila bi definirana kao cjelobrojna funkcija (engl. integer, int). Funkcija koja samo ispisuje poruku i nema šta za vratiti bi bila deklarirana kao void funkcija.

Funkcija se sastoji od zaglavlja (header) i tijela. Zaglavlje se sastoji od povratnog tipa, imena funkcije i ulaznih parametara te funkcije. Tako bi, npr. funkcija za zbrajanje dva broja bila deklarirana kao:

int Sum(int a, int b)

Parametar je deklaracija tipa vrijednosti kojeg dajemo funkciji. Stvarne vrijednosti koje će biti predane prilikom izvršenja nazivamo argumenti. Često se parametri i argumenti koriste i kao sinonimi.

Page 11: C++ PRIRUCNIK

11Tijelo funkcije se sastoji od vitičaste zagrade, nitijedne ili više naredbi, te zatvarajuće zagrade. Funkcija također može vratiti vrijednost, koristeći return naredbu. Ta naredba će također uzrokovati izlazak iz funkcije. Ako ne stavite return naredbu, funkcija će automatski vraćati void. Povratna vrijednost mora biti istoga tipa kao deklaracija funkcije.

Listing 2.5 demonstrira funkciju koja uzima dva cjelobrojna parametra i vraća cjelobrojnu vrijednost. Za sada se ne opterećujte previše o tipovima podataka, njih ćemo proučavati u lekciji 3.

Listing 2.5. FUNC.CPP demonstracija jednostavne funkcije.

1: #include <iostream.h>2: int Add (int x, int y)3: {

4:5: cout << "In Add(), received " << x << " and " << y << "\n";6: return (x+y);7: }

8:9: int main()10: {11: cout << "I'm in main()!\n";12: int a, b, c;13: cout << "Enter two numbers: ";14: cin >> a;15: cin >> b;16: cout << "\nCalling Add()\n";17: c=Add(a,b);18: cout << "\nBack in main().\n";19: cout << "c was set to " << c;20: cout << "\nExiting...\n\n";21: return 0;22: }

I'm in main()!Enter two numbers: 3 5Calling Add()In Add(), received 3 and 5Back in main().c was set to 8Exiting...

Kviz1. Koja je razlika između kompajlera i pretprocesora?2. Zašto je funkcija main() posebna?3. Koja su dva tipa komentara i po čemu se razlikuju?4. Mogu li komentari biti duži od jedne linije?

Vježbe1. Napišite program koji ispisuje "Ja volim C++" na ekran. 2. Napišite najmanji program koji se može prevesti, linkati i pokrenuti. 3. Unesite slijedeći program i kompajlirajte ga. Gdje je greška? Možete li ju popraviti?

1: #include <iostream.h>2: void main()

Page 12: C++ PRIRUCNIK

12 C++ programer

3: {4: cout << Is there a bug here?";5: }

4. Popravite pogrešku sa 3. vježbe i pokrenite program.

Lekcija 3 Varijable i konstante

Programi često trebaju pohraniti podatke koje koriste. Varijable i konstante nude različite načine za predstavljanje podataka i njihovo manipuliranje.

Danas ćemo naučiti

Kako deklarirati i definirati varijable i konstante. Kako dodijeliti vrijednosti varijablama i manipulirati s tim vrijednostima. Kako ispisati vrijednost varijable na ekran.

Što je varijabla?U C++, kao i u većini programsih jezika, varijabla je mjesto za pohranjivanje informacije. Varijabla je lokacija u memoriji računala u koju možete pohraniti vrijednost i kasnije je odatle pročitati.

Računalna memorija se može zamisliti kao niz kutijica. Svaka kutijica je jedna u nizu mnoštva poredanih kutijica. Svaka kutijica ima pridružen broj i na svakoj slijedećoj kutijici taj broj je uvećan za 1. Te brojeve nazivamo memorijskim adresama. Varijabla rezervira jednu ili više kutijica u koje smješta vrijednost.

Ime vaše varijable (na primjer, myVariable) je etiketa na jednoj od tih kutijica, tako da ju možete jednostavno naći bez potrebe za znanjem njene stvarne adrese. Slika 3.1 je shematski prikaz te ideje. Kao što vidite sa slike, myVariable počinje na adresi 103. Ovisno o veličini myVariable, ona može zauzeti jednu ili više memorijskih adresa.

Slika3.1. Shematski prikaz memorije.

Rezerviranje memorijeKad definirate varijablu u C++, morate reći kompajleru koju podatka će sadržavati: cijeli broj, karakter, itd. Ta informacija govori kompajleru koliko prostora da rezervira u memoriji ovisno o vrijednosti koju želite pohraniti u varijabli.

Svaka kutijica je velika jedan byte. Ako vrsta podatka kojeg spremate treba 2 bytea, kompajler će ih rezervirati uz ime vaše varijable. Tip varijable (npr., integer—cijeli broj) govori kompajleru upravo to: koliko memorije treba sačuvati za varijablu.

Budući da računala koriste bitove i byteove za predstavljanje vrijednosti, te budući da se računalna memorija mjeri u byteovima, bitno je da razumijete osnovne koncepte

Varijabla

RAM

Adresa

100 101 102 103 104 105 106 107

myVariable

Page 13: C++ PRIRUCNIK

13vezane uz te termine. Ne bi bilo loše ni podsjetiti se matematike vezane uz dekadske, binarne i heksadecimalne brojevne sustave.

Veličina varijabliNa istoj vrsti računala, svaka varijabla zauzima istu količinu prostora. Sama implementacija ovisi o arhitekturi računala, njegovu procesoru, operativnom sistemu i kompajleru kojeg koristimo.

Char varijabla (služi za čuvanje znaakova) najčešće zauzima jedan byte. Short integer je dva bytea na većini računala, dok je long integer obično četiri bytea dug. Int varijable (bez oznake short i long) može biti ili 2 bytea ili 4. Listing 3.1 bi trebao pomoći prilikom određivanja točne veličine svih tipova na vašem računalu.

Listing 3.1. Određivanje veličine tipova varijabli na vašem računalu.

1: #include <iostream.h>2:3: int main()4: {5: cout << "The size of an int is:\t\t" << sizeof(int) << " bytes.\n";6: cout << "The size of a short int is:\t" << sizeof(short) << " bytes.\n";7: cout << "The size of a long int is:\t" << sizeof(long) << " bytes.\n";8: cout << "The size of a char is:\t\t" << sizeof(char) << " bytes.\n";9: cout << "The size of a float is:\t\t" << sizeof(float) << " bytes.\n";10: cout << "The size of a double is:\t" << sizeof(double) << " bytes.\n";11:12: return 0;13: }Output: The size of an int is: 2 bytes.The size of a short int is: 2 bytes.The size of a long int is: 4 bytes.The size of a char is: 1 bytes.The size of a float is: 4 bytes.The size of a double is: 8 bytes.

Analiza: Većina stvari na listingu 3.1 bi trebala biti prilično poznata. Jedino novo svojstvo koje koristimo je sizeof() funkcijama u linijama 5 do 10. sizeof() je standardna funkcija koja dolazi s vašim kompajlerom i ona nam govori veličinu objekta kojeg joj prosljedimo kao parametar. Npr., u liniji 5 ključna riječ int je proslijeđena u sizeof(). Koristeći sizeof(), u stanju smo odrediti da je na našem računalu int jednak short int-u, što iznosi 2 bytea.

Integeri s predznakom i bez njega (engl. signed i unsigned)

Dodatno, svi cijeli brojevi dolaze u dva oblika: signed i unsigned. Ideja koja se krije iza toga je da ponekad trebate negativne brojeve, a ponekad ne trebate. Cijeli brojevi (i short i long) bez riječi "unsigned" su pretpostavljeni kao signed. Signed integeri su ili negativni ili pozitivni. Unsigned integeri su uvijek pozitivni.

Budući da je isti broj byteova dodijeljen i za signed i za unsigned brojeve, najveći broj koji možemo pohraniti u unsigned integer je dvostruko veći od onoga pohranjenog u

Page 14: C++ PRIRUCNIK

14 C++ programer

signed integer.. Npr., unsigned short integer može baratati brojevima od 0 do 65535. Pola od te količine će otići na negativne brojeve, pa stoga signed short može predstavljati brojeve samo od –32,768 do 32,768.

Osnovni tipovi varijabliJoš nekoliko tipova varijabli je ugrađeno u C++. Oni mogu biti podijeljeni na cjelobrojne varijable (tip o kojem smo da sad govorili), realne odn. floating point varijable, te znakovne varijable.

Floating-point varijable sadržavaju brojeve koji mogu biti prikazani kao razlomci, odnosno to su realni brojevi. Znakovne varijable drže jedan byte i služe za pohranu jednog od 256 znakova i simbola ASCII seta znakova.

Novi izraz: ASCII set znakova je standardizirani set znakova koji se koristi na računalima. ASCII je akronim od American Standard Code for Information Interchange.

Tipovi varijabli korišteni u C++ programima su opisani u tablici 3.1. Ova tablica prikazuje tip varijable, koliko mjesta zauzima u računalu (ovisi o tipu računala—okvirno), te koje vrijednosti mogu biti pohranjene u te varijable. Vrijednosti koje mogu biti pohranjene su određene veličinom tipa varijable, pa provjerite izlaz za program sa listinga 3.1.

Tablica 3.1. Tipovi varijabli.

Type Size Values unsigned short int 2 bytea 0 do 65,535 short int 2 bytea -32,768 do 32,767 unsigned long int 4 bytea 0 do 4,294,967,295 long int 4 bytea -2,147,483,648 do 2,147,483,647 int (16 bit) 2 bytea -32,768 do 32,767 int (32 bit) 4 bytea -2,147,483,648 do 2,147,483,647 unsigned int (16 bit) 2 bytea 0 do 65,535 unsigned int (32 bit) 4 bytea 0 do 4,294,967,295 char 1 byte 256 znakovnih vrijednosti float 4 bytea 1.2e-38 do 3.4e38 double 8 bytea 2.2e-308 do 1.8e308

Definiranje varijableMožete kreirati ili definirati varijablu ispisujući prvo njezin tip, te nakon jednog ili više razmaka njezino ime praćeno znakom ";". Ime varijable može biti praktički bilo kakva kombinacija slova i brojeva, ali ne smije sadržavati prazna mjesta. Legalna imena varijabli su npr., x, J23qrsnf, i mojeGodine. Dobro ime varijable nam govori čemu služi varijabla, te nam samim tim olakšava praćenje tijeka našeg programa.Slijedeća izjava definira cjelobrojnu varijablu zvanu myAge:

int myAge;

Izbjegavajte imena poput J23sqfrn za vaše varijable, te ograničite upotrebu varijabli od samo jednog znaka (kao x ili i) na one koje se koriste samo kratko. Pokušavajte koristiti opisna imena poput mojeGodine ili kolikoDana. Takva imena se lakše razumijevaju tri

Page 15: C++ PRIRUCNIK

15tjedna kasnije dok se lupate po glavi pokušavajući shvatiti što ste željeli postići u toj i toj liniji koda.

Pokušajmo s malim eksperimentom: Pogodite što ovaj program radi bazirano na prvih par linija koda:

Primjer 1:

main()

{unsigned short x;unsigned short y;ULONG z;z = x * y;

}

Primjer 2:

main ()

{unsigned short Width;unsigned short Length;unsigned short Area;Area = Width * Length;

}

Jasno, drugi program je mnogo lakše razumljiv od prvog, a nepogodnost ukucavanja dugačkih imena varijabli brzo blijedi s rastom čitljivosti programa.

Osjetljivost na velika i mala slovaC++ razlikuje velika i mala slova. Drugim rječima, varijable imena Godine i GODINE su dvije različite varijable.

Postoje različite konvencije prilikom imenovanja varijabli, i iako nije važno koju metodu koristite, bitno je da budete dosljedni u svom programu. Mnogi programeri koriste samo mala slova za varijable. Ako je opis varijable sadržan u dvije riječi, popularni načini imenovanja su moj_auto ili mojAuto.

Ključne riječiNeke riječi su rezervirane od strane jezika C++ i zato ih ne smijemo koristiti kao imena varijabli. To su tzv., ključne riječi koje se koriste za kontrolu vašega programa, poput if, while, for, ili main. Priručnik vašeg kompajleera sigurno sadrži popis svih ključnih riječi, ali općenito, svako razumno ime za varijablu gotovo sigurno neće biti ključna riječ.

Stvaranje više varijabli odjednomMoguće je kreirati više varijabli istoga tipa u jednoj naredbi, navodeći tip, te zatim pobrojati sve varijable tog tipa odvojene zarezom. Npr. :

Page 16: C++ PRIRUCNIK

16 C++ programer

unsigned int myAge, myWeight; long area, width, length;

Kao što vidite, myAge i myWeight su obje deklarirane kao unsigned integer varijable. Druga linija deklarira tri individualne long varijable imena area, width, i length. Tip (long) je dodijeljen svim varijablama, pa ne možete miješati tipove u jednoj definicijskoj naredbi.

Dodjeljivanje vrijednosti vašim varijablamaVrijednost dodjeljujemo varijabli koristeći operator pridruživanja (=). Tako bi, primjerice, dodjelili 5 varijabli imena Width tipkajući:

unsigned short Width;Width = 5;

Moguće je i kombinirati te korake i inicijalizirati Width prilikom definicije unoseći:

unsigned short Width = 5;

Evo još nekoliko primjera deklaracije i inicijalizacije varijabli:

long width = 5, length = 7; int myAge = 39, yourAge, hisAge = 40;

Listing 3.2 pokazuje gotov program, spreman za prevođenje, koji računa površinu pravokutnika i ispisuje ju na ekran.

1: // Demonstration of variables2: #include <iostream.h>3:4: int main()5: {6: unsigned short int Width = 5, Length;7: Length = 10;8:

9: // create an unsigned short and initialize with result10: // of multiplying Width by Length11: unsigned short int Area = Width * Length;12:

13: cout << "Width:" << Width << "\n";14: cout << "Length: " << Length << endl;15: cout << "Area: " << Area << endl;16: return 0;17: }Width:5Length: 10Area: 50

typedef

Zamorno je i podložno greškama stalno ponavljati npr. unsigned short int. C++ nam omogućuje kreiranje aliasa za tu frazu korištenjem ključne riječi typedef, koja dolazi od "definicija tipa" (engl. type definition).

Page 17: C++ PRIRUCNIK

17U stvarnosti, zapravo kreiramo sinonim, važno je to razlikovati od stvaranja novog tipa (što ćemo raditi u lekciji 6). typedef se upotrebljava upisivanjem riječi typedef, praćene postojećim tipom i potom novim imenom. Na primjer:

typedef unsigned short int USHORT

stvara novo ime USHORT koje možemo koristiti bilo gdje gdje smo dosad pisali unsigned short int. Listing 3.3 je prepisani listing 3.2, ali koji koristi definiciju tipa USHORT umjesto unsigned short int.

Listing 3.3. Demonstracija typedef.

1: // *****************2: // Demonstrates typedef keyword3: #include <iostream.h>4:5: typedef unsigned short int USHORT; //typedef defined6:7: void main()8: {9: USHORT Width = 5;10: USHORT Length;11: Length = 10;12: USHORT Area = Width * Length;13: cout << "Width:" << Width << "\n";14: cout << "Length: " << Length << endl;15: cout << "Area: " << Area <<endl;16: }Output: Width:5Length: 10Area: 50

Kada koristiti short a kada longVeliki izvor konfuzije za novopečenog C++ programera je kada deklarirati varijablu tipa long, a kada kao short. Pravilo je da, ako postoji ikoja šansa da će vrijednost premašiti doseg varijable, koristimo veći tip.

Kada unsigned integer dosegne maksimalnu vrijednost, on jednostavno, počne brojati od početka, slično brojaču kilometara u automobilu. Listing 3.4 pokazuje što se događa kad stavimo preveliku vrijdnost u short integer.

Listing 3.4. Domonstracija stavljanja prevelikog broja u unsigned integer.

1: #include <iostream.h>2: int main()3: {4: unsigned short int smallNumber;5: smallNumber = 65535;6: cout << "small number:" << smallNumber << endl;7: smallNumber++;8: cout << "small number:" << smallNumber << endl;9: smallNumber++;10: cout << "small number:" << smallNumber << endl;

Page 18: C++ PRIRUCNIK

18 C++ programer

11: return 0;12: }Output: small number:65535small number:0small number:1

Signed integer je drugačiji cjelobrojni oblik u kojem se pola vrijednosti čuva za pozitivne, a pola za negativne brojeve. Listing 3.5 pokazuje što se događa kad dodamo 1 na najveći pozitivni broj u unsigned short integer varijabli.

Listing 3.5. Demonstarcija stavljanja prevelikog broja u signed integer.

1: #include <iostream.h>2: int main()3: {4: short int smallNumber;5: smallNumber = 32767;6: cout << "small number:" << smallNumber << endl;7: smallNumber++;8: cout << "small number:" << smallNumber << endl;9: smallNumber++;10: cout << "small number:" << smallNumber << endl;11: return 0;12: }Output: small number:32767small number:-32768small number:-32767

ZnakoviZnakovne varijable (tip char) su obično 1 byte, dovoljno za čuvanje 256 varijednosti. Znak može biti interpertiran malim brojem (0-255) kao pripadajući član ASCII seta.

Unutar ASCII tablice, slovo "a" iima dodjeljen broj 97. Sva mala i velika slova, svi brojevi, te interpunkcijski znakovi imaju dodijeljene vrijednosti između 1 i 128.

Listing 3.6. Ispis znakova baziran na njihovim brojevima u ASCII tablici

1: #include <iostream.h>2: int main()3: {4: for (int i = 32; i<128; i++)5: cout << (char) i;6: return 0;7: }Output: !"#$%G'()*+,./0123456789:;<>?@ABCDEFGHIJKLMNOP_QRSTUVWXYZ[\]^'abcdefghijklmnopqrstuvwxyz<|>~s

Page 19: C++ PRIRUCNIK

19Specijalni znakovi\n new line \t tab \b backspace \" double quote \' single quote \? question mark \\ backslash

KonstanteKao i varijable, i konstante su mjesta za pohranu podataka. Za razliku od varijabli, konstante se ne mogu mijenjati, odn., ne možemo im tijekom izvođenja programa dodavati novu vrijednost.

Postoje dva načina za deklariranje konstanti u C++ jeziku. Stari način je korištenje pretprocesorske direktive #define, npr:

#define studentsPerClass 15

Primjetite da studentsPerClass is nema određeni tip (int, char, itd.). #define radi jednostavnu zamjenu teksta svaki put kad vam se u programu pojavi riječ studentsPerClass, pretprocesor stavlja 15 umjesto toga.

Noviji način za definiranje konstanti glasi:

const unsigned short int studentsPerClass = 15;

Enumerirane konstanteEnumerairane konstante omogućuju nam kreiranje novih tipova , te potom definiranje varijabli čija je sadržina ograničena na niz mogućih vrijednosti. Npr, možemo deklarirati COLOR kao enumeraciju, i potom definirati pet vrijednosti za boju: RED, BLUE, GREEN, WHITE, i BLACK. Npr:

enum COLOR { RED, BLUE, GREEN, WHITE, BLACK };

Ova naredba izvodi dvije zadaće:

1. Stvara novu enumeraciju zvanu COLOR.2. Pravi RED simboličkom konstantom s vrijednosti 0, BLUE ima vrijednost 1, itd...svaka enumerirana konstanta ima cjelobrojnu vrijednost. Ako drugačije ne navedete, prva konstanta ima vrijednost 0, a ostale rastu za 1. Svaka konstanta može biti i inicijalizirana na vlastitu vrijednost, a one koje nisu će biti za 1 veće od prethodno navedene. Pa ako napišete

enum Color { RED=100, BLUE, GREEN=500, WHITE, BLACK=700 };

RED će imati vrijednost 100; BLUE, 101; GREEN, 500; WHITE, 501; i BLACK, 700.

Listing 3.7. Demonstracija enumeriranih konstanti.

1: #include <iostream.h>2: int main()3: {4: enum Days { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday,

Saturday };5:

Page 20: C++ PRIRUCNIK

20 C++ programer

6: Days DayOff;7: int x;8:

9: cout << "What day would you like off (0-6)? ";10: cin >> x;11: DayOff = Days(x);12:

13: if (DayOff == Sunday || DayOff == Saturday)14: cout << "\nYou're already off on weekends!\n";15: else16: cout << "\nOkay, I'll put in the vacation day.\n";17: return 0;18: }Output: What day would you like off (0-6)? 1Okay, I'll put in the vacation day.What day would you like off (0-6)? 0You're already off on weekends!

Kviz1. Koja je razlika između integer i floating point varijable? 2. Koje su razlike među unsigned short int i long int?3. Što je prednost kod upotrebe const ključne riječi u odnosu na #define? 4. Kako razlikujemo dobro od lošeg imena varijable?6. Uz slijedeći enum, koja će biti vrijednost za BLUE?

enum COLOR { WHITE, BLACK = 100, RED, BLUE, GREEN = 300 };

7. Koje od slijedećih varijabli su dobre, koje loše, a koje nelegalne? a. Ageb. !exc. R79Jd. TotalIncomee. __Invalid

Vježbe1. Koji bi bio pravi tip varijable za pohranu slijedećih informacija?

a. Vaše godine.b. Poštanski broj vašega grada. c. Broj zvijezda u galaksiji. d. Prosječna količina padalina za siječanj.

2. Izmislite dobra imena varijabli za te informacije. 3. Deklarirajte konstantu za pi kao 3.14159. 4. Deklarirajte float varijablu i inicijalizirajte ju koristeći pi konstantu.

Page 21: C++ PRIRUCNIK

21

Lekcija 4 Izrazi i naredbe

NaredbeU C++ jeziku, naredba kontrolira slijedom izvršavanja, procjenjuje izraze, ili ne čini ništa (null statement). Sve C++ naredbe završavaju sa točka-zarezom, čak i null statement, koji je u stvari samo točka-zarez. Jedna od najuobičajenijih naredbi je slijedeće pridruživanje:

x = a + b;

Za razliku od algebre, ova naredba ne znači da je x jednako a+b. Ovo se čita "pridruži vrijednost sume od a i b u x", ili "pridruži u x, a+b". Iako ova naredba čini dve stvari, to je jedna naredba i zato ima jedan ";". Operator pridruživanja ("=") pridružuje što god je na desnoj strani znaka jednakosti, onome što se nalazi s njegove lijeve strane.

Umetanje prazninaPraznine (tabovi, razmaci, i nove linije) se obično ignoriraju u naredbama. Naredba pridruživanja o kojoj smo maloprije raspravljali može biti napisana kao

x=a+b;

ili kao

x =a

+ b ;

Iako je ova posljednja varijanta potpuno ispravna glede valjanosti koda, isto tako je i potpuno blesava. Praznine bi trebale vaš program činiti čitljivijim i lakšim za održavanje, a ne za kreiranje zastrašujućeg potpuno nečitkog koda. U ovome, kao i u svemu ostalome, C++ pruža vam moć; na vama je da ju iskoristite razboritim odlukama.

Blokovi naredbiNa svako mjesto na koje možete staviti naredbu, možete staviti i složenu naredbu također zvanu i blok. Blok počinje s vitičastom zagradom ({) i završava s njom (}). Iako svaka naredba u bloku mora završiti s ";", sam blok ne završava s točka-zarezom. Na primjer

{temp = a;a = b;b = temp;

}

Ovaj blok djeluje kao jedna naredba i zamjenjuje vrijednosti u varijablama a i b.

Page 22: C++ PRIRUCNIK

22 C++ programer

IzraziU C++ izrazom smatramo sve što vraća neku vrijednost. Tako, na primjer, 3+2 vraća vrijednost 5 i time je izraz. Svi izrazi su naredbe.

Raznolikost koda koji se naziva izrazom mogla bi vas iznenaditi. Evo tri primjera:

3.2 // vraća vrijednost 3.2PI // float const koja vraća vrijednost 3.14SecondsPerMinute // int const koja vraća 60

Složeni izrazix = a + b;

ne samo da zbraja a i b nego i dodjeljuje rezultat u x, ali i vraća vrijednost toga pridruživanja (vrijednost od x). Tako je i ova naredba zapravo izraz. Budući da je izraz, ona može biti i na desnoj strani operatora pridjeljivanja:

y = x = a + b;

Ova se linija izvršava slijedećim redosljedom: zbrajaju se a i b.Rezultat izraza a+b se pridružuje varijabli x.Pridružuje se rezultat izraza pridruživanja x = a + b u y. Ako su a, b, x, i y cijeli brojevi, te ako a ima vrijednost 2, a b ima vrijednost 5, i u x i u y će biti pridružena vrijednost 7.

Listing 4.1. Izvršavanje složenih izraza.

1: #include <iostream.h>2: int main()3: {4: int a=0, b=0, x=0, y=35;5: cout << "a: " << a << " b: " << b;6: cout << " x: " << x << " y: " << y << endl;7: a = 9;8: b = 7;9: y = x = a+b;10: cout << "a: " << a << " b: " << b;11: cout << " x: " << x << " y: " << y << endl;12: return 0;13: }Output: a: 0 b: 0 x: 0 y: 35a: 9 b: 7 x: 16 y: 16

OperatoriOperator je simbol koji uzrokuje da kompajler poduzme akciju. Operatori djeluju na operande, a u C++ svi operandi su izrazi. U C++ postoji nekoliko različitih kategorija operatora. Dvije od tih kategorija su operatori pridruživanja i matematički operatori.

Page 23: C++ PRIRUCNIK

23

Operator pridruživanjaOperator pridruživanja (=) uzrokuje da operand s lijeve strane operatora pridruživanja promjeni svoju vrijednost u onu koja se nalazi s njegove desne strane. Izraz

x = a + b;

pridružuje vrijednost koja je rezultat zbrajanja a i b operandu x.

Operand koji legalno može biti na lijevoj strani operatora pridruživanja se zove lvalue. Onaj koji može biti na desnoj strani se zove (pogodili ste) rvalue.

Konstante su r-value. One ne mogu biti l-value. Tako, možete pisati

x = 35; // ok

ali nije dozvoljeno

35 = x; // error, not an lvalue!

Matematički operatoriPostoji pet matematičkih operatora: zbrajanje (+), oduzimanje (-), množenje (*), dijeljenje (/), i modul (%).

Zbrajanje i oduzimanje rade onako kako bi i očekivali, iako oduzimanje s neoznačenim cjelim brojevima može dati neočekivane rezultate, ako je rezultat negativan broj. Vidjeli smo nešto tako u prošloj lekciji kad je dolazilo do preopterećenja varijabli.

Listing 4.2. Demonstracija oduzimanja i preopterećenje.

1: // Listing 4.2 - demonstrates subtraction and2: // integer overflow3: #include <iostream.h>4:5: int main()6: {7: unsigned int difference;8: unsigned int bigNumber = 100;9: unsigned int smallNumber = 50;10: difference = bigNumber - smallNumber;11: cout << "Difference is: " << difference;12: difference = smallNumber - bigNumber;13: cout << "\nNow difference is: " << difference <<endl;14: return 0;15: }Output: Difference is: 50Now difference is: 4294967246

Cjelobrojno dijeljenje i moduliCjelobrojno dijeljenje je malo drugačije od uobičajenog. Kad podjelite 21 sa 4, rezultat je realan broj (broj sa ostatkom). Cijeli brojevi nemaju ostatke, pa je on izgubljen. Rezultat bi prema tome bio 5. Za dobivanje ostatka , radimo 21 modul 4 (21 % 4) i rezultet je 1. Prema tome, modul nam daje ostatak kod cjelobrojnog dijeljenja.

Traženje modula može biti vrlo korisno. Na primjer, želite ispisati naredbu na svakoj desetoj akciji. Svaki broj čija vrijednost je 0 kad vadimo modul 10 i tog broja je višekratnik broja 10. Tako je 1 % 10 jednako 1, 2 % 10 je 2 itd., sve do 10 % 10, čiji

Page 24: C++ PRIRUCNIK

24 C++ programer

je rezultat 0. 11 % 10 je ponovo 1 i uzorak se ponavlja sve do slijedećeg višekratnika a to je 20. Tu ćemo tehniku često koristiti, a više o njoj u lekciji 7.

Kombiniranje pridruživanja i matematičkih operatora.

Nije neuobičajeno željeti dodati vrijednost varijabli i rezultat dodijeliti istoj varijabli. Ako imate varijablu myAge i želite joj povećati vrijednost za 2, možete napisati

int myAge = 5;int temp;temp = myAge + 2; // add 5 + 2 and put it in tempmyAge = temp; // put it back in myAge

Ova je metoda, zapravo gubitak vremena i prostora. U C++, možete istu varijablu koristiti na obje strane operatora pridruživanja, pa prethodno postaje

myAge = myAge + 2;

što je već mnogo bolje. U algebri bi ovakav izraz bio besmislen, ali ga C++ tumači ka "dodaj 2 u vrijednost od myAge i pridruži rezultat u myAge.

Još jednostavnije za pisati, ali malo teže za čitati jemyAge += 2;

Operator obnavljajućeg pridruživanja (engl. "self-assigned addition")--(+=) dodaje rvalue u lvalue i zatim pridružuje rezultat u lvalue. Ako je myAge imao vrijednost 4 na početku, imao bi 6 nakon ove naredbe.

Također postoje i operatori obnavljajućeg oduzimanja (-=), dijeljenja (/=), množenja (*=), i modula (%=).

Inkrement i dekrementNajčešća vrijednost za zbrajanje i potom ponovno pridruživanje je 1. U C++ je povećanje vrijednosti za 1 zvano inkrementiranje, a smanjenje za 1 je zvano dekrementiranje. Postoje i specijalni operatori za izvođenje tih akcija.

Tako, ako želite varijablu C inkrementirati, koristili bi slijedeću naredbu:

C++; // Start with C and increment it.

Ova naredba je ekvivalentna čitljivijoj naredbi

C = C + 1;što se može pisati i kao, već sada to i znate

C += 1;

Prefiks i postfiksI inkrement operator (++) i dekrement operator (--) dolaze u dva oblika; prefiks i postfiks. Prefiks varijanta se piše prije imena varijable (++myAge); postfiks se piše nakon varijable (myAge++).

U jednostavnoj naredbi, nije bitno koji će te koristiti, ali u složenoj, kad inkrementirate (ili dekrementirate) varijablu i rezultat pridružujete drugoj varijabli, ima bitnu razliku. Prefiks operator se izvodi prije pridruživanja, a postfiks nakon.

Semantika prefiksa je: inkrementiraj vrijednost i onda ju pošalji. Semantika postfiksa je drugačija: Pošalji vrijednost i onda inkrementiraj original.

To na početku može biti konfuzno, ali ako je x cijeli broj čija je vrijednost 5 i vi napišete

Page 25: C++ PRIRUCNIK

25int a = ++x;

rekli ste kompajleru da inkrementira x (čineći ga 6) i zatim tu vrijednost dodjeljuje varijabli a. Prema tome, sada su i a i x jednake 6.

Ako nakon toga napišemoint b = x++;

sada smo rekli kompajleru da dodijeli vrijednost od x (6) varijabli b, vrati se nazad i inkrementira x. Sada nam je b jednako 6, ali x ima vrijednost 7. Listing 4.3 prikazuje upotrebu i implikacije oba tipa.

Listing 4.3. Demonstarcija prefiks i postfiks operatora.

1: // Listing 4.3 - demonstrates use of2: // prefix and postfix increment and3: // decrement operators4: #include <iostream.h>5: int main()6: {7: int myAge = 39; // initialize two integers8: int yourAge = 39;9: cout << "I am: " << myAge << " years old.\n";10: cout << "You are: " << yourAge << " years old\n";11: myAge++; // postfix increment12: ++yourAge; // prefix increment13: cout << "One year passes...\n";14: cout << "I am: " << myAge << " years old.\n";15: cout << "You are: " << yourAge << " years old\n";16: cout << "Another year passes\n";17: cout << "I am: " << myAge++ << " years old.\n";18: cout << "You are: " << ++yourAge << " years old\n";19: cout << "Let's print it again.\n";20: cout << "I am: " << myAge << " years old.\n";21: cout << "You are: " << yourAge << " years old\n";22: return 0;23: }Output: I am 39 years oldYou are 39 years oldOne year passesI am 40 years oldYou are 40 years oldAnother year passesI am 40 years oldYou are 41 years oldLet's print it againI am 41 years oldYou are 41 years old

Page 26: C++ PRIRUCNIK

26 C++ programer

Redosljed izvođenja operatora u složenim naredbamax = 5 + 3 * 8;

što se prvo obavlja, zbrajanje ili množenje? Ako se prvo obavlja zbrajanje, rješenje je 8 * 8, ili 64. Ako se prvo izvodi množenje, odgovor je 5+24, odnosno 29.

Svaki operator ima svoj prioritet izvršavanja. Množenje ima veći prioritet nego zbrajanje, pa je vrijednost gornjeg izraza 29.

Kad dva matematička operatora imaju isti prioritet, oni se izvršavaju s lijeva na desno. Tako se u

x = 5 + 3 + 8 * 9 + 6 * 4;

prvo provodi množenje, s lijeva na desno. 8 * 9 = 72, i 6 * 4 = 24. Sada je izraz u biti

x = 5 + 3 + 72 + 24;

Sada zbrajamo, s lijeva na desno 5 + 3 = 8; 8 + 72 = 80; 80 + 24 = 104.

Budite pažljivi prilikom pisanja izraza. Neki operatori, poput pridruživanja se izvršavaju s desna na lijeva. Pogledajte izraz

TotalSeconds = NumMinutesToThink + NumMinutesToType * 60

U ovom izrazu ne želite pomnožiti NumMinutesToType varijablu sa 60 i potom ju zbrojiti sa NumMinutesToThink. Vi želite zbrojiti dvije varijable i potom ih pomnožiti sa 60 kako bi dobili rezultat u sekundama. U tom slučaju, koristimo zagrade da promjenimo redosljed prioriteta. Elementi unutar zagrada se izvršavaju s višim prioritetom nego bilo koji drugi matematički operatori. Prema tome

TotalSeconds = (NumMinutesToThink + NumMinutesToType) * 60

će postići ono što želite.

Gnježdenje zagradaZa kompleksne izraze, možda ćete morati ugnjezditi zagrade jedne unutar drugih. Npr:

TotalPersonSeconds = ( ( (NumMinutesToThink + NumMinutesToType) * 60) * (PeopleInTheOffice + PeopleOnVacation) )

Složeni izrazi se čitaju iznutra prema van. Prvo se zbrajaju NumMinutesToThink i NumMinutesToType, budući da su u "najunutrašnjijim" zagradama. Nakon toga njihova se suma množi sa 60. Slijedeće, PeopleInTheOffice se zbraja s PeopleOnVacation. Konačno, ukupan broj ljudi se množi s ukupnim brojem sekundi.

Ovaj primjer povlači za sobom važnu temu. Ovaj izraz je jednostavan za razumijevanje računalu, ali vrlo težak za čitanje, razumjevanje i mijenjanje. Evo istog izraza ponovo napisanog, koji koristi privremene integer varijable:

TotalMinutes = NumMinutesToThink + NumMinutesToType;TotalSeconds = TotalMinutes * 60;TotalPeople = PeopleInTheOffice + PeopleOnVacation;TotalPersonSeconds = TotalPeople * TotalSeconds;

Ovaj primjer je duži za ukucavanje i koristi više privremenih varijabli, ali je mnogo jednostavniji za razumijevanje. Dodajmo još komentar s objašnjenjem koda i promjenimo 60 u konstantu, te smo dobili kod koji je jednostavan za razumijevanje i, što je još važnije, održavanje.

Page 27: C++ PRIRUCNIK

27Priroda IstineU C++, nula se smatra laži, a sve ostale vrijednosti se smatraju istinama, iako se istina obični predstavlja brojem 1. Prema tome ako je izraz neistinit (false), on je jednak nuli.

Relacijski operatoriRelacijski operatori se koriste za određivanje kada su dva broja jednaka, ili dali je jedan broj veći od drugog. Svaka relacijska naredba poprima ili 1(TRUE) ili 0(FALSE).

Ako cjelobrojna varijabla ima vrijednost 39, a yourAge ima vrijednost 40, možete odrediti jesu li oni jednaki koristeći relacijski operator jednakosti:

myAge == yourAge; // is the value in myAge the same as in yourAge?

Ovaj izraz poprima vrijednost 0, odnosno laž, budući da varijable nisu jednake. Izraz

myAge > yourAge; // is myAge greater than yourAge?

poprima 0 odn. laž.

Upozorenje: Mnogi C++ programeri početnici brkaju operator pridruživanja (=) s operatorom jednakosti (==). To može prouzročiti gadne bugove u programu.

Postoji šest relacijskih operatora: jednako (==), manje od (<), veće od (>), manje ili jednako (<=), veće ili jednako (>=), te različito (!=).

Tablica 4.1. Relacijski operatori.

Ime Operator Primjer Vrijednost Equals == 100 == 50;

50 == 50;falsetrue

Not Equals

!= 100 != 50;50 != 50;

true false

Greater Than > 100 > 50;50 > 50;

true false

Greater Than or Equals

>= 100 >= 50;50 >= 50;

true true

Less Than < 100 < 50;50 < 50;

false false

Less Thanor Equals

<= 100 <= 50;50 <= 50;

false true

if naredbaObično vaš program izvršava liniju po liniju redosljedom kojim se pojavljuje u vašem izvornom kodu. if naredba omogućuje vam testiranje uvjeta (npr. da li su dve varijable jednake) i grana se u odvojene dijelove koda ovisno o rezultatu.

Najjednostavniji oblik if naredbe glasi:

if (izraz)

naredba;

Izraz unutar zagrada može biti bilo koji izraz, ali obično sadrži jedan od relacijskih izraza. Ako izraz poprimi vrijednost 0, smatra se neistinom, i naredba se preskače. Ako ima bilo koju drugu vrijednost, smatramo ga istinitim, i naredba se izvršava. Pogledajmo slijedeći primjer:

if (bigNumber > smallNumber)

Page 28: C++ PRIRUCNIK

28 C++ programer

bigNumber = smallNumber;

Ovaj kod uspoređuje bigNumber i smallNumber. Ako je bigNumber veći, slijedeća linija postavlja njegovu vrijednost na onu od koju ima smallNumber.

Budući da je blok naredbi omeđen vitičastim zagradama potpuno jednak jednoj naredbi, slijedeći oblik grananja je vrlo čest i moćan:

if (expression)

{statement1;statement2;statement3;

}

Evo primjera takve upotrebe:

if (bigNumber > smallNumber)

{bigNumber = smallNumber;cout << "bigNumber: " << bigNumber << "\n";cout << "smallNumber: " << smallNumber << "\n";

}

Listing 4.4. Demonstracija grananja baziranog na relacijskim operatorima.

1: // Listing 4.4 - demonstrates if statement2: // used with relational operators3: #include <iostream.h>4: int main()5: {6: int RedSoxScore, YankeesScore;7: cout << "Enter the score for the Red Sox: ";8: cin >> RedSoxScore;9:

10: cout << "\nEnter the score for the Yankees: ";11: cin >> YankeesScore;12:13: cout << "\n";14:15: if (RedSoxScore > YankeesScore)16: cout << "Go Sox!\n";17:

18: if (RedSoxScore < YankeesScore)19: {20: cout << "Go Yankees!\n";21: cout << "Happy days in New York!\n";22: }23:

24: if (RedSoxScore == YankeesScore)25: {26: cout << "A tie? Naah, can't be.\n";27: cout << "Give me the real score for the Yanks: ";

Page 29: C++ PRIRUCNIK

2928: cin >> YankeesScore;29:

30: if (RedSoxScore > YankeesScore)31: cout << "Knew it! Go Sox!";32:

33: if (YankeesScore > RedSoxScore)34: cout << "Knew it! Go Yanks!";35:

36: if (YankeesScore == RedSoxScore)37: cout << "Wow, it really was a tie!";38: }39:

40: cout << "\nThanks for telling me.\n";41: return 0;42: }Output: Enter the score for the Red Sox: 10Enter the score for the Yankees: 10A tie? Naah, can't beGive me the real score for the Yanks: 8Knew it! Go Sox!Thanks for telling me.

elsečesto će naš program ići jednom granom ako je uvijet istinit, a drugom ako je lažan. Na listingu 4.3 mogli smo ispisati poruku (Go Sox!) ako je prvi test (RedSoxScore > Yankees) davao TRUE, a drugačiju poruku (Go Yanks!) ako je bio FALSE.

Dosad prikazana metoda radi fino, ali je pomalo nepraktična. Ključna riječ else može generirati mnogo čitljiviji kod:

if (izraz)naredba;

elsenaredba;

Listing 4.5. Demonstracija ključne riječi else.

1: // Listing 4.5 - demonstrates if statement2: // with else clause3: #include <iostream.h>4: int main()5: {6: int firstNumber, secondNumber;7: cout << "Please enter a big number: ";8: cin >> firstNumber;9: cout << "\nPlease enter a smaller number: ";10: cin >> secondNumber;11: if (firstNumber > secondNumber)12: cout << "\nThanks!\n";13: else14: cout << "\nOops. The second is bigger!";15:16: return 0;

Page 30: C++ PRIRUCNIK

30 C++ programer

17: }

Output: Please enter a big number: 10

Please enter a smaller number: 12

Oops. The second is bigger!

Kompleksne if naredbe

Listing 4.6. Složena, ugnježdena if naredba.

1: // Listing 4.5 - a complex nested2: // if statement3: #include <iostream.h>4: int main()5: {6: // Ask for two numbers7: // Assign the numbers to bigNumber and littleNumber8: // If bigNumber is bigger than littleNumber,9: // see if they are evenly divisible10: // If they are, see if they are the same number11:

12: int firstNumber, secondNumber;13: cout << "Enter two numbers.\nFirst: ";14: cin >> firstNumber;15: cout << "\nSecond: ";16: cin >> secondNumber;17: cout << "\n\n";18:

19: if (firstNumber >= secondNumber)20: {21: if ( (firstNumber % secondNumber) == 0) // evenly divisible?22: {23: if (firstNumber == secondNumber)24: cout << "They are the same!\n";25: else26: cout << "They are evenly divisible!\n";27: }28: else29: cout << "They are not evenly divisible!\n";30: }31: else32: cout << "Hey! The second one is larger!\n";33: return 0;34: }

Output: Enter two numbers.First: 10Second: 2They are evenly divisible!

Page 31: C++ PRIRUCNIK

31

Upotreba zagrada unutar ugnježdenih if-ovaif (x > y) // if x is bigger than y if (x < z) // and if x is smaller than z x = y; // then set x to the value in z

Kod pisanja velikih ugnježdenih naredbi izostavljanje vitičastih zagrada može uzrokovati popriličnu zbrku. Listing 4.7 ilustrira problem:

1: // Listing 4.7 - demonstrates why braces2: // are important in nested if statements3: #include <iostream.h>4: int main()5: {6: int x;7: cout << "Enter a number less than 10 or greater than 100: ";8: cin >> x;9: cout << "\n";10:

11: if (x > 10)12: if (x > 100)13: cout << "More than 100, Thanks!\n";14: else // not the else intended!15: cout << "Less than 10, Thanks!\n";16:17: return 0; 18: }Output: Enter a number less than 10 or greater than 100: 20Less than 10, Thanks!

Listing 4.8. Demonstracija pravilne upotrebe vitičastih zagrada unutar if naredbe1: // Listing 4.8 - demonstrates proper use of braces2: // in nested if statements3: #include <iostream.h>4: int main()5: {6: int x;7: cout << "Enter a number less than 10 or greater than 100: ";8: cin >> x;9: cout << "\n";10:

11: if (x > 10)12: {13: if (x > 100)14: cout << "More than 100, Thanks!\n";15: }16: else // not the else intended!17: cout << "Less than 10, Thanks!\n";18: return 0;19: }Output: Enter a number less than 10 or greater than 100: 20

Page 32: C++ PRIRUCNIK

32 C++ programer

Logički operatoriČesto nam je potrebno postaviti više relacijskih pitanja odjednom. "Jeli istina da je x veći od y i da je također y veći od z?". Zamislite sofisticirani alarm sa slijedećom logikom: "Ako se aktivira alarm na vratima I više je od šest popodne I NIJE praznik, ILI vikend, tada zovi policiju." U C++ tri logička operatora se koriste prilikom takvih ispitivanja. To su:

AND && izraz1 && izraz2 OR || izraz1 || izraz2 NOT ! !izraz

Logički I (engl. AND)Logička AND naredba procjenjuje dva izraza, te ako su obadva istinita, i logička AND naredba je istinita. Ako je istina da ste gladni I ako je istina da imate novaca, TADA je istina da možete kupiti ručak. Prema tome,

if ( (x == 5) && (y == 5) )

će dati TRUE ako su i x i y varijable jednake 5, a dat će FALSE u bilo kojem drugom slučaju.

Logički ILILogička OR naredba procjenjuje dva izraza. Ako je bilo koji istinit, izjava je istinita. Ako imate novce ILI imate kreditnu karticu, možete platiti račun. Ne trebaju vam i novci i kreditna kartica; dovoljno je samo jedno iako ne bi bilo loše imati i jedno i drugo. Prema tome,

if ( (x == 5) || (y == 5) )

daje TRUE ako bilo x bilo y sadrži vrijednost 5, ili ako su obadva jednaka 5.

Logički NOTLogička NOT naredba daje istinu ako je izraz kojeg pručavamo lažan. ako je izraz lažan, vrijednost testa je TRUE!. Prema tome,

if ( !(x == 5) )

je istina ako je x različit od 5. Ovo je isto kao da smo napisali

if (x != 5)

Relacijski prioritetiRelacijski operatori i logički operatori u C++ izrazima vraćaju vrijednost 1(TRUE) ili 0(FALSE). Kao i svi izrazi, oni imaju svoje prioritete koji određuju koja će se relacija ispitivati prva. Ta činjenica je bitna prilikom određivanja vrijednosti naredbe

if ( x > 5 && y > 5 || z > 5)

Može biti da je programer želio da dobije TRUE ako su i x i y veći od 5 ili je z veći od 5. Ili je možda ideja da dobijemo TRUE samo ako je x veći od 5 i ako je istina da je ili y veći od 5 ili je z veći od 5.

Iako prioriteti određuju koje će se operacije izvršavati prve, korištenje zagrada može pojednostavniti izraze:

if ( (x > 5) && (y > 5 || z > 5) )

Page 33: C++ PRIRUCNIK

33

često koristimo

if (x) // if x is true (nonzero)

x = 0;

što je u principu

if (x != 0) // if x is nonzero

x = 0;

I ove dvije naredbe su ekvivalentne:

if (!x) // if x is false (zero)if (x == 0) // if x is zero

Druga je ipak jednostavnija za razumijevanje i eksplicitnija.

Kondicionalni operatorKondicionalni operator (?:) je jedini operator u C++ koji koristi tri izraza:

(izraz1) ? (izraz2) : (izraz3)

Ova linija se čita kao "Ako je izraz 1 istina, vrati vrijednost u izraz 2, inače ju vrati u izraz3. Tipično se ta vrijednost dodjeljuje varijabli.

Listing 4.9. Demonstarcija kondicionalnog operatora.

1: // Listing 4.9 - demonstrates the conditional operator2: //3: #include <iostream.h>4: int main()5: {6: int x, y, z;7: cout << "Enter two numbers.\n";8: cout << "First: ";9: cin >> x;10: cout << "\nSecond: ";11: cin >> y;12: cout << "\n";13:

14: if (x > y)15: z = x;16: else17: z = y;18:

19: cout << "z: " << z;20: cout << "\n";21:22: z = (x > y) ? x : y;23:24: cout << "z: " << z;25: cout << "\n";26: return 0;

Page 34: C++ PRIRUCNIK

34 C++ programer

27: }Output: Enter two numbers.First: 5Second: 8z: 8z: 8

Kviz1. Što je izraz?2. Da li je x = 5 + 7 izraz? Koja mu je vrijednost?3. Kolika je vrijednost od 201 / 4?4. Kolika je vrijednost od 201 % 4?5. Ako su myAge, a, i b svi int varijable, koje su njihove vrijednosti nakon:

myAge = 39;a = myAge++;b = ++myAge;

6. Kolika je vrijednost od 8+2*3?7. Koja je razlika između x = 3 i x == 3?8. Da li slijedeće vrijednosti poprimaju TRUE ili FALSE?

a. 0b. 1 c. -1 d. x = 0 e. x == 0 // pretpostavimo da x ima vrijednost 0

Vježbe1. Napišite jednu naredbu koja ispituje dve cjelobrojne vrijednosti i mijenja veću u

manju, koristeći samo jedan else uvjet.2. Proučite slijedeći program. Zamislite unošenje tri broja, i napišite kakav izlaz

očekujete. 1: #include <iostream.h>2: int main()3: { 4: int a, b, c;5: cout << "Please enter three numbers\n";6: cout << "a: ";7: cin >> a;8: cout << "\nb: ";9: cin >> b;10: cout << "\nc: ";11: cin >> c;12:13: if (c = (a-b))14: {cout << "a: ";15: cout << a;16: cout << "minus b: ";17: cout << b;18: cout << "equals c: ";19: cout << c << endl;}20: else21: cout << "a-b does not equal c: " << endl;22: return 0;23: }

Page 35: C++ PRIRUCNIK

353. Upišite program iz vježbe 2, te kreirajte exe datoteku. Unesite brojeve 20, 10 i 50 .

Jeste li dobili ono što ste očekivali? Zašto niste?

Page 36: C++ PRIRUCNIK

36 C++ programer

4. Pogledajte program i anticipirajte njegov izlaz: 1: #include <iostream.h>2: int main()3: {4: int a = 1, b = 1, c;5: if (c = (a-b))6: cout << "The value of c is: " << c;7: return 0;8: }

5. Unesite, kompajlirajte, povežite i pokrenite program sa vježbe 4. Što je izlaz i zašto?

Page 37: C++ PRIRUCNIK

37

Lekcija 5 Funkcije

Iako je objekto orjentirano programiranje svoju pažnju sa funkcija usmjerilo prema objektima, funkcije i dalje ostaju ključne komponente svakog programa. Danas ćete naučiti

Što je funkcija i koji su njezini dijelovi Kako deklarirati i definirati funkcije Kako proslijediti parametre u funkciju Kako vratiti vrijednost iz funkcije

Što je funkcija?Funkcija je vrsta potprograma koji djeluje na podacima i vraća određenu vrijednost. Svaki C++ program ima bar jednu funkciju, main(). Kad pokrenete vaš program, main() se pokreće automatski. main() može zvati druge funkcije koje opet mogu pozivati druge.

Svaka funkcija ima svoje ime, i kad se to ime pojavi unutar programa, izvršenje programa se premješta u tijelo funkcije. Kada funkcija završi, program nastavlja s radom na mjestu gdje je i prekinuo, odn. u liniji koja slijedi nakon poziva funkcije.

Dobro dizajnirane funkcije izvršavaju specifične i lako razumljive zadaće. Komplicirani zadaci bi trebaki biti razbijeni u višestruke funkcije, gdje jedna funkcija po potrebi može pozivati drugu.

Funkcije dolaze u dva oblika: korisnički definirane i ugrađene. Ugrađene su one koji standardno dolaze u paketu sa vašim kompajlerom.

Deklaracija i definicija funkcije

Upotreba funkcija u vašem programu uvjetuje da prvo deklarirate funkciju, a potom da definirate funkciju. Deklaracija kazuje kompajleru ime, povratni tip funkcije, te njezine parametre. Definicija kazuje kompajleru kako funkcija radi. Niti jedna funkcija ne može biti pozvana iz programa, odn. bilo koje druge funkcije ako prethodno nije bila deklarirana. Deklaracija funkcije se često naziva i njenim prototipom.

Deklariranje funkcijePostoje tri načina za deklariranje funkcije:

Napišete svoju datoteku prototipa i potom koristite #include direktivu da ju uključite u vaš program

Napišete prototip u datoteku u kojoj se funkcija koristi Definirate funkciju prije nego što ju pozove bilo koja druga funkcija. Kad to

učinite definicija postaje ujedni i njezina deklaracija.

Iako možete definirati funkciju prije njezine uporabe, i time izbjeći neophodno kreiranje funkcijskog prototipa, to nije dobra programerska praksa iz tri razloga:

Prvo, loša je ideja zahtjevati da se funkcije unutar datoteke pojavljuju određenim redosljedom. Čineći to otežavate si održavanje i daljnje izmjene programa.

Page 38: C++ PRIRUCNIK

38 C++ programer

Drugo, moguće je da funkcija A() mora pozvati funkciju B(), ali u određenim okolnostima i B() također mora zvati funkciju A(). Nije moguće definirati funkciju A() prije funkcije B() i također definirati funkciju B() prije definicije funkcije A(), pa barem jedna od njih mora biti deklarirana u svakom slučaju.

Treće, funkcijski prototipovi olakšavaju otkrivanje i uklanjanje grešaka. Ako vaš prototip deklarira da funkcija uzima određen niz parametara, ili da vraća određeni tip podatka, te ako potom vaša funkcija to ne učini, kompajler će primjetiti nepravilnost i označiti ju.

Funkcijski prototipoviMnoge ugrađene funkcije koje koristite imaju svoje prototipe već upisane u datoteke koje uključujete u programe s #include direktivom. Za funkcije koje sami napišete, vi morate upisati i funkcijski prototip. Funkcijski prototip je naredba, što znači da završava s točka-zarezom. Sastoji se od povratnog tipa funkcije, imena i liste parametara.

Lista parametara je lista svih parametara i njihovih tipova, odvojenih zarezima.

Funkcijski prototip i definicija funkcije moraju imati potpuno jednake povratne tipove, ime i listu parametara. Ako dođe do razlike, kompajler će nam javiti grešku. Primjetite, da funkcijski prototip ne mora sadržavati imena parametara, već samo njihove tipove, npr:

long Area(int, int);

Ovaj prototip deklarira funkciju imena Area() koja vraća long vrijednost i ima dva parematra, obadva cjelobrojna. Iako je ovo potpuno legalno, dodavanje imena parametrima čini funkciju mnogo čitljivijom:

long Area(int length, int width);

Sad je očitije što funkcija radi i koji su joj parametri.

Primjetite također da svaka funkcija ima svoj povratni tip. Ako on nije naveden, funkcija se po defaultu dekalrira kao int. Program će vam ipak biti čitljiviji ako ekplicitno navedete povratni tip za svaku funkciju, uključujući i main().

Listing 5.1. Deklaracija i definicija funkcije te upotreba iste.

1: // Listing 5.1 - demonstrates the use of function prototypes2:3: typedef unsigned short USHORT;4: #include <iostream.h>5: USHORT FindArea(USHORT length, USHORT width); //function prototype6:7: int main()8: {9: USHORT lengthOfYard;10: USHORT widthOfYard;11: USHORT areaOfYard;12:

13: cout << "\nHow wide is your yard? ";14: cin >> widthOfYard;15: cout << "\nHow long is your yard? ";16: cin >> lengthOfYard;17:18: areaOfYard= FindArea(lengthOfYard,widthOfYard);19:20: cout << "\nYour yard is ";

Page 39: C++ PRIRUCNIK

3921: cout << areaOfYard;22: cout << " square feet\n\n";23: return 0;24: }25:26: USHORT FindArea(USHORT l, USHORT w)27: {28: return l * w;29: }Output: How wide is your yard? 100How long is your yard? 200Your yard is 20000 square feet

Primjeri funkcijskih prototipalong FindArea(long length, long width);void PrintMessage(int messageNumber);int GetChoice(); BadFunction();

Primjeri definicija funkcijelong Area(long l, long w)

{return l * w;

}

void PrintMessage(int whichMsg)

{if (whichMsg == 0)

cout << "Hello.\n";

if (whichMsg == 1)

cout << "Goodbye.\n";

if (whichMsg > 1)

cout << "I'm confused.\n";

}

Page 40: C++ PRIRUCNIK

40 C++ programer

Lokalne varijableNe samo da možete dodjelivati varijable funkciji, nego također možete deklarirati varijable u tijelu same funkcije. Tako deklarirane varijable nazivamo lokalnim varijablama, stoga što se mogu koristiti samo u funkcijama u kojima su deklarirane, znači imaju ograničenu, lokalnu upotrebu. Čim izađemo iz funkcije, takve varijable nam nisu dostupne.

Lokalne varijable su definirane kao i sve druge varijable. Parametri proslijeđeni funkciji se također smatraju lokalnim varijablama i mogu biti upotrebljeni isto kao da su definirani unutar tijela same funkcije. Listing 5.2 je primjer upotrebe parametara i lokalno definiranih varijabli unutar funkcije.

Listing 5.2. Upotreba lokalnih varijabli i parametara.

1: #include <iostream.h>2:3: float Convert(float);4: int main()5: {6: float TempFer;7: float TempCel;8:

9: cout << "Please enter the temperature in Fahrenheit: ";10: cin >> TempFer;11: TempCel = Convert(TempFer);12: cout << "\nHere's the temperature in Celsius: ";13: cout << TempCel << endl;14: return 0;15: }16:

17: float Convert(float TempFer)18: {19: float TempCel;20: TempCel = ((TempFer - 32) * 5) / 9;21: return TempCel;22: }Output: Please enter the temperature in Fahrenheit: 212Here's the temperature in Celsius: 100Please enter the temperature in Fahrenheit: 32Here's the temperature in Celsius: 0Please enter the temperature in Fahrenheit: 85Here's the temperature in Celsius: 29.4444Evo iste verzije sa drugačijim nazivima varijabli:

1: #include <iostream.h>2:3: float Convert(float);4: int main()5: {6: float TempFer;7: float TempCel;8:

9: cout << "Please enter the temperature in Fahrenheit: ";

Page 41: C++ PRIRUCNIK

4110: cin >> TempFer;11: TempCel = Convert(TempFer);12: cout << "\nHere's the temperature in Celsius: ";13: cout << TempCel << endl;14: }15:

16: float Convert(float Fer)17: {18: float Cel;19: Cel = ((Fer - 32) * 5) / 9;20: return Cel;21: }

Novi izraz: Varijable imaju svoj doseg (engl. scope), koji određuje koliko dugo će ta varijabla biti dostupna unutar programa i gdje joj se može pristupiti. Varijable deklarirane unutar bloka su dostupne samo u tom bloku, in "ne postoje" čim blok završi. Globalne varijable imaju globalni doseg i dostupne su bilo gdje unutar našega programa.

Globalne varijableVarijable definirane izvan bilo koje funkcije imaju globalan doseg i time su dostupne u bilo kojoj funkciji našeg programa, uključujući i main().

Lokalne varijable istog imena kao i globalne ne mijenjaju globalne. Ipak, one ih skrivaju unutar tijela funkcije u kojima su definirane.

Listing 5.3. Demonstracija globalnih i lokalnih varijabli.

1: #include <iostream.h>2: void myFunction(); // prototype3:4: int x = 5, y = 7; // global variables5: int main()6: {7:8: cout << "x from main: " << x << "\n";9: cout << "y from main: " << y << "\n\n";10: myFunction();11: cout << "Back from myFunction!\n\n";12: cout << "x from main: " << x << "\n";13: cout << "y from main: " << y << "\n";14: return 0;15: }16:17: void myFunction()18: {19: int y = 10;20:21: cout << "x from myFunction: " << x << "\n";22: cout << "y from myFunction: " << y << "\n\n";23: }

Output: x from main: 5y from main: 7

Page 42: C++ PRIRUCNIK

42 C++ programer

x from myFunction: 5y from myFunction: 10

Back from myFunction!

x from main: 5y from main: 7

Globalne varijable: UpozorenjeU C++, globalne varijable su dozvoljene ali se gotovo nikada ne koriste. C++ je izrastao iz C, a u C-u se globalne varijable ono što nazivamo "nužno zlo". Neophodne su stoga što programer ponekad želi učiniti podatke dostupnima svim funkcijama.

Globalne varijable su opasne stoga što su to dijeljeni podaci, i jedna ih funkcija može promjeniti na način koji je nevidljiv drugoj. To može i najčešće stvarno i uzrokuje pogreške koje je jako teško otkriti.

Listing 5.4. Variajable dostupne unutar bloka.

1: // Listing 5.4 - demonstrates variables2: // scoped within a block3:4: #include <iostream.h>5:6: void myFunc();7:8: int main()9: {10: int x = 5;11: cout << "\nIn main x is: " << x;12:13: myFunc();14:15: cout << "\nBack in main, x is: " << x;16: return 0;17: }18:

19: void myFunc()20: {21:

22: int x = 8;23: cout << "\nIn myFunc, local x: " << x << endl;24:

25: {26: cout << "\nIn block in myFunc, x is: " << x;27:28: int x = 9;29:30: cout << "\nVery local x: " << x;31: }32:33: cout << "\nOut of block, in myFunc, x: " << x << endl;

Page 43: C++ PRIRUCNIK

4334: }Output: In main x is: 5In myFunc, local x: 8In block in myFunc, x is: 8Very local x: 9Out of block, in myFunc, x: 8Back in main, x is: 5

Nepotrebno je reći, program bi bio mnogo razumljiviji da su tim varijablama zadana različita imena! Iako nema limita na veličinu same funkcije, dobro dizajnirane funkcije su obično malene. Mnogi programeri savjetuju da veličina jedne funkcije nikad ne smije prelaziti veličinu ekrana. Pogotovo u našim prvim koracima, moramo se truditi da funkcije budu što je kraće moguće kako bi ih lakše razumjeli i održavali.

Upotreba funkcija kao parametara drugim funkcijamaIako je legalno za jednu funkciju da kao parametar uzima drugu funkciju, to nam kod može napraviti nečitkim i teško razumljivim. Kao primjer, recimo da imate funkcije double(), triple(), square(), i cube(), od kojih svaka vraća vrijednost. Mogli biste napisati

Answer = (double(triple(square(cube(myValue)))));

Alternativa je dodjelivanje varijable prilikom svakog koraka:

unsigned long myValue = 2;unsigned long cubed = cube(myValue); // cubed = 8unsigned long squared = square(cubed); // squared = 64unsigned long tripled = triple(squared); // tripled = 196unsigned long Answer = double(tripled); // Answer = 392

Parameteri su lokalne varijableArgumenti predani funkciji su lokalni za tu funkciju. Promjene napravljene na argumentima ne utječu na vrijednosti iz pozivne funkcije. To se još i naziva "passing by value", što znači da se lokalna kopija svakog argumenta pravi unutar funkcije. Te lokalne kopije se tretiraju kao i sve ostale lokalne varijable. Listing 5.5 nam to ilustrira.

Listing 5.5. Demonstracija prenošenja po vrijednosti.

1: // Listing 5.5 - demonstrates passing by value2:3: #include <iostream.h>4:5: void swap(int x, int y);6:7: int main()8: {9: int x = 5, y = 10;10:

11: cout << "Main. Before swap, x: " << x << " y: " << y << "\n";12: swap(x,y);13: cout << "Main. After swap, x: " << x << " y: " << y << "\n";14: return 0;15: }16:

17: void swap (int x, int y)

Page 44: C++ PRIRUCNIK

44 C++ programer

18: {19: int temp;20:21: cout << "Swap. Before swap, x: " << x << " y: " << y << "\n";22:23: temp = x;24: x = y;25: y = temp;26:27: cout << "Swap. After swap, x: " << x << " y: " << y << "\n";28:29: }Output: Main. Before swap, x: 5 y: 10Swap. Before swap, x: 5 y: 10Swap. After swap, x: 10 y: 5Main. After swap, x: 5 y: 10

Povratne vrijednostiFunkcija vraća ili neku vrijednost ili void. Void je signal kompajleru da nikakva vrijednost neće biti vraćena.

Za vraćanje neke vrijednosti iz funkcije, napišemo ključnu riječ return koju slijedi vrijednost koju želimo vratiti. Sama vrijednost može biti i izraz koji vraća vrijednost. Na primjer, ovo su sve legalne return naredbe

return 5;return (x > 5);return (MyFunction());

Legalno je imati više od jedne return naredbe unutar funkcije. Listing 5.6 demonstrira tu ideju.

Listing 5.6. Demonstracija višestrukih return naredbi.

1: // Listing 5.6 - demonstrates multiple return2: // statements3:4: #include <iostream.h>5:6: int Doubler(int AmountToDouble);7:8: int main()9: {10:11: int result = 0;12: int input;13:14: cout << "Enter a number between 0 and 10,000 to double: ";15: cin >> input;16:17: cout << "\nBefore doubler is called... ";18: cout << "\ninput: " << input << " doubled: " << result << "\n";19:20: result = Doubler(input);21:22: cout << "\nBack from Doubler...\n";

Page 45: C++ PRIRUCNIK

4523: cout << "\ninput: " << input << " doubled: " << result << "\n";24:25:26: return 0;27: }28:29: int Doubler(int original)30: {31: if (original <= 10000)32: return original * 2;33: else34: return -1;35: cout << "You can't get here!\n";36: }Output: Enter a number between 0 and 10,000 to double: 9000Before doubler is called...input: 9000 doubled: 0Back from doubler...input: 9000 doubled: 18000Enter a number between 0 and 10,000 to double: 11000Before doubler is called...input: 11000 doubled: 0Back from doubler...input: 11000 doubled: -1

Default parametriZa svaki parametar koji deklarirate u funkcijkom prototipu i definiciji, pozivajuća funkcija mora predati vrijednost. Predana vrijednost mora biti deklariranog tipa. Prema tome, ako imate funkciju deklariranu kao

long myFunction(int);

funkcija zapravo mora uzeti cjelobrojnu varijablu. Ako se definicija funkcije razlikuje ili ako ne predate cijeli broj funkciji, dobit ćete grešku prilikom kompajliranja.

Jedina iznimka tome pravilu je kad funkcijski prototip deklarira podrazumijevanu vrijednost za taj parametar. Podrazumijevana vrijednost je ona koja će se koristiti ako ne proslijedimo nikakvu vrijednost funkciji. Prethodna deklaracija mogla bi biti napisana

long myFunction (int x = 50);

Ovaj prototip govori, "myFunction() vraća long i uzima integer parametar. Ako argument nije isporučen, koristi default vrijednost 50." Budući da imena parametara nisu obavezna u funkcijskim prototipima, ova deklaracija bi mogla biti napisana i kao

long myFunction (int = 50);

Svi ili samo neki funkcijski parametri mogu imati dodjeljene podrazumijevane vrijednosti. Jedino ograničenje glasi: Ako neki parametar nema deklariranu default vrijednost, ne može ga imati niti njemu prethodni parametar.

U funkcijskom prototipu poput ovog

long myFunction (int Param1, int Param2, int Param3);

možete dodjeliti default vrijednost u Param2 samo ako ste ju dodjelili i za Param3. Param1 ju može imati samo ako su dodijeljene i za Param2 i za Param3. Listing 5.7 je demonstracija upotrebu podrazumijevanih vrijednosti.

Page 46: C++ PRIRUCNIK

46 C++ programer

Listing 5.7. Demonstracija podrazumijevanih vrijednosti parametara.

1: // Listing 5.7 - demonstrates use2: // of default parameter values3:4: #include <iostream.h>5:6: int AreaCube(int length, int width = 25, int height = 1);7:8: int main()9: {10: int length = 100;11: int width = 50;12: int height = 2;13: int area;14:15: area = AreaCube(length, width, height);16: cout << "First area equals: " << area << "\n";17:18: area = AreaCube(length, width);19: cout << "Second time area equals: " << area << "\n";20:21: area = AreaCube(length);22: cout << "Third time area equals: " << area << "\n";23: return 0;24: }25:26: AreaCube(int length, int width, int height)27: {28:29: return (length * width * height);30: }

Output: First area equals: 10000Second time area equals: 5000Third time area equals: 2500

Preopterećenje funkcijaC++ nam omogućuje kreiranje više funkcija sa istim imenom. To se zove preopterećenje funkcija (engl. function overloading). Funkcija se mora razlikovati po listi parametara, različitim tipovima parametara, ili oboje. Ovo je primjer:

int myFunction (int, int);int myFunction (long, long);int myFunction (long);

myFunction() je preopterećena s tri različite liste paramatara. Prva i druga se razlikuju po tipovima parametara, a treća po broju parametara.

Povratni tip može biti isti ili različit za preopterećene funkcije. Primjetite da dve funkcije istog imena i liste parametara a različitih povratnih tipova uzrokuju grešku prilikom kompajliranja.

Page 47: C++ PRIRUCNIK

47Novi izraz: Preopterećenje funkcija još i nazivamo funkcijskim polimorfizmom. Poli znači više, a morf znači oblik. Dakle, ta funkcija ima više oblika.

Polimorfizam nam omogućuje, npr., da napravimo funkciju Prosjek() koja može računati prosjek i za integere, i za double vrijednosti, i za sve ostale, a sve to bez potrebe za kreiranjem individualnih imena poput ProsjekInt(), ProsjekFloat(), ProsjekDouble()...

Recimo da napišete funkciju koja duplira koliki joj god ulazni podatak date. Želite joj moći predati int, long, float, ili double. Bez preopterećenja, morali biste napraviti četiri različita imena funkcije:

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

S preopterećenjem, napraviti ćete slijedeću deklaraciju:

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

To je lakše za čitanje i lakše za upotrebu. Ne morate brinuti o tome koju funkciju pozvati; vi samo predate varijablu, i prava funkcija se poziva automatski. Listing 5.8 ilustrira upotrebu preopterećenja funkcija.

Listing 5.8. Demonstracija polimorfizma funkcija.

1: // Listing 5.8 - demonstrates2: // function polymorphism3:4: #include <iostream.h>5:6: int Double(int);7: long Double(long);8: float Double(float);9: double Double(double);10:11: int main()12: {13: int myInt = 6500;14: long myLong = 65000;15: float myFloat = 6.5F;16: double myDouble = 6.5e20;17:18: int doubledInt;19: long doubledLong;20: float doubledFloat;21: double doubledDouble;22:23: cout << "myInt: " << myInt << "\n";24: cout << "myLong: " << myLong << "\n";25: cout << "myFloat: " << myFloat << "\n";26: cout << "myDouble: " << myDouble << "\n";

Page 48: C++ PRIRUCNIK

48 C++ programer

27:28: doubledInt = Double(myInt);29: doubledLong = Double(myLong);30: doubledFloat = Double(myFloat);31: doubledDouble = Double(myDouble);32:33: cout << "doubledInt: " << doubledInt << "\n";34: cout << "doubledLong: " << doubledLong << "\n";35: cout << "doubledFloat: " << doubledFloat << "\n";36: cout << "doubledDouble: " << doubledDouble << "\n";37:38: return 0;39: }40:41: int Double(int original)42: {43: cout << "In Double(int)\n";44: return 2 * original;45: }46:47: long Double(long original)48: {49: cout << "In Double(long)\n";50: return 2 * original;51: }52:53: float Double(float original)54: {55: cout << "In Double(float)\n";56: return 2 * original;57: }58:59: double Double(double original)60: {61: cout << "In Double(double)\n";62: return 2 * original;63: }Output: myInt: 6500myLong: 65000myFloat: 6.5myDouble: 6.5e+20In Double(int)In Double(long)In Double(float)In Double(double)DoubledInt: 13000DoubledLong: 130000DoubledFloat: 13DoubledDouble: 1.3e+21

Page 49: C++ PRIRUCNIK

49

Umetnute (engl. inline) funkcijeKad definirate funkciju, kompajler obično kreira samo jedan niz pripadajućih instrukcija u memoriji. Kad pozovete funkciju, izvršenej programa skače na te instrukcije, te kad funkcija obavi radnju, skače se nazad u slijedeći red pozivne funkcije. Ako pozovete funkciju 10 puta, vaš program svaki puta skače na isti niz instrukcija. To znači da postoji samo jedna kopija vaše funkcije ane 10.

Prilikom skakanja u i iz funkcija dolazi do malog gubitka performansi. Za malene funkcije se može dobiti na brzini ako možemo izbjeći te skokove. Ako je funkcija deklarirana s ključnom riječi inline, kompajler ne kreira "pravu" funkciju: on kopira kod iz inline funkcije direktno u pozivnu funkciju. Ne radi se nikakav skok; kao da smo napisali cijelu funkciju unutar pozivne funkcije.

Primjetite da inline funkcije mogu donesti brzinu ali uz popriličnu cijenu. Cijena je povećanje duljine izvršne datoteke, budući da sad možemo imati isti kod dupliran unutar našeg programa onoliko puta koliko se poziva određena funkcija.

Listing 5.9. Demonstracija inline funkcije.

1: // Listing 5.9 - demonstrates inline functions2:3: #include <iostream.h>4:5: inline int Double(int);6:7: int main()8: {9: int target;10:11: cout << "Enter a number to work with: ";12: cin >> target;13: cout << "\n";14:15: target = Double(target);16: cout << "Target: " << target << endl;17:18: target = Double(target);19: cout << "Target: " << target << endl;20:21:22: target = Double(target);23: cout << "Target: " << target << endl;24: return 0;25: }26:27: int Double(int target)28: {29: return 2*target;30: }Output: Enter a number to work with: 20Target: 40Target: 80Target: 160

Page 50: C++ PRIRUCNIK

50 C++ programer

RekurzijaFunkcija može pozivati i samu sebe. To se zove rekurzija, i ona može biti direktna ili indirektna. Direktna je kad funkcija poziva samu seba. Indirektna je kad funkcija poziva funkciju koja potom poziva ovu prvu.

Neki problemi se puno lakše rješavaju rekurzijom, obično on u kojima djelujemo na podatke i na isti način i razultat. Oba tipa rekurzije dolaze u dva podoblika: oni koji kad tad završe i proizvedu odgovor, te oni koji nikad ne završe i proizvedu grešku prilikom izvršenja. Programeri smatraju da su ove druge prilično zabavne (dok god se događaju nekom drugom).

Važno je napomenuti da kad god funkcija pozove samu sebe, pokrene se nova kopija funkcije. Lokalne varijable u drugoj funkciji su neovisne od onih u prvoj, i ne mogu direktno utjecati jedne na druge.

Za ilustraciju rješenja problema upotrebom rekurzije, pogledati ćemo Fibonaccijev niz:

1,1,2,3,5,8,13,21,34...

Svaki broj, poslije drugog, je suma dva prethodna broja. Fibonaccijev problem bi bio određivanje koji je dvanaesti broj u nizu.

Jedan način za rješenje tog problema je pažljivo proučavanje niza. Prva dva broja su 1. Svaki slijedeći broj je suma prethodna dva broja. Prema tome, sedmi broj je zbroj petog i šestog broja niza. Uopćeno, n-ti broj je suma od n-2 i n-1 dok god je n>2.

Rekurzivna funkcija treba uvijet za zaustavljanje. Nešto se mora dogoditi što bi uzrokovalo program da prestane s rekurzijom, ili neće nikad zavšiti s radom. U Fibonaccijevom nizu, n<3 je uvjet za zaustavljanje.

Algoritam bi bio slijedeći:

1. Zamolite korisnika za položaj u nizu. 2. Pozovemo fib() funkciju s tom pozicijom, dajući joj unešenu vrijednost.3. fib() funkcija ispituje argument (n). Ako je n<3 vraća 1; inače, fib() poziva samu sebe

(rekurzivno) dodjeljujući vrijednost n-2, poziva se ponovo dodjelujući n-1, i vraća nam sumu. Ako pozovete fib(1), vraća nam 1. Ako pozovete fib(2), vraća 1, Ako pozovete fib(3), vraća nam sumu od poziva fib(2) i fib(1). Budući da da fib(2) vraća 1 i fib(1) vraća 1, fib(3)će vratiti 2.

Ako pozovete fib(4), vratiti će nam sumu od fib(3) i fib(2). Već smo ustanovili da fib(3) vraća 2 (pozivajući fib(2) i fib(1)), pa će fib(4) zbrojiti vrrijednosti i time vratiti 3, što je četvrti broj niza.

Ova metoda nije najefikasniji način za rješenje toga problema (u fib(20) funkcija fib se poziva 13 529 puta!!!), ali radi. Svaki put kad se pozove fib(), rezervira se određena količina memorije. Kad izađe iz nje, memorija se oslobodi. Prilikom rekurzije, memorija se rezervira prije nego što se oslobađa, pa ovaj program može brzo "potrošiti" svu rezerviranu memoriju. Listing 5.10 implementira fib() funkciju.

Upozorenje: Prilikom pokretanja listinga 5.10, koristite malen broj (manji od 20). Zbog upotrebe rekurzije, ovaj program zauzima mnogo memorije.

Page 51: C++ PRIRUCNIK

51Listing 5.10. Demonstracija rekurzije za račun Fibonaccijevog niza.

1: // Listing 5.10 - demonstrates recursion2: // Fibonacci find.3: // Finds the nth Fibonacci number4: // Uses this algorithm: Fib(n) = fib(n-1) + fib(n-2)5: // Stop conditions: n = 2 || n = 16:7: #include <iostream.h>8:9: int fib(int n);10:11: int main()12: {13:14: int n, answer;15: cout << "Enter number to find: ";16: cin >> n;17:18: cout << "\n\n";19:20: answer = fib(n);21:22: cout << answer << " is the " << n << "th Fibonacci number\n";23: return 0;24: }25:26: int fib (int n)27: {28: cout << "Processing fib(" << n << ")... ";29:30: if (n < 3 )31: {32: cout << "Return 1!\n";33: return (1);34: }35: else36: {37: cout << "Call fib(" << n-2 << ") and fib(" << n-1 << ").\n";38: return( fib(n-2) + fib(n-1));39: } 40: }Output: Enter number to find: 5Processing fib(5)... Call fib(3) and fib(4).Processing fib(3)... Call fib(1) and fib(2).Processing fib(1)... Return 1!Processing fib(2)... Return 1!Processing fib(4)... Call fib(2) and fib(3).Processing fib(2)... Return 1!Processing fib(3)... Call fib(1) and fib(2).Processing fib(1)... Return 1!Processing fib(2)... Return 1!5 is the 5th Fibonacci number

Page 52: C++ PRIRUCNIK

52 C++ programer

Rekurzija se ne koristi često u C++ programiranju, ali može biti moćno i elegantno oruđe za određene potrebe.

Kviz1. Koja je razlika između prototipa funkcije i definicije funkcije?2. Moraju li se imena parametara podudarati u prototipu, definiciji i pozivu funkcije?3. ako funkcije ne vraća nikakvu vrijednost, kako ćemo ju deklarirati?4. Ako ne deklariramo povratni tip funkcije, koji tiš će biti podrazumijevan?5. Što je lokalna varijabla?6. Što je doseg (engl. scope)? 7. Što je rekurzija? 8. Kad koristimo globalne varijable? 9. Što je preopterećenje funkcija? 10. Što je polimorfizam?

Vježbe1. Napišite prototip za funkciju imena Perimeter(), koja vraća unsigned long int i ima

dva parametra, oba unsigned short int.

2. Napišite definiciju za funkciju Perimeter() iz vježbe 1. Dva parametra predstavljaju dužinu i širinu pravokutnika. Neka funkcija vraća perimetar (dvostruka dužina pomnožena dvostrukom širinom).

3. BUG BUSTER: Što ne valja s funkcijom u slijedećem kodu? #include <iostream.h>void myFunc(unsigned short int x);int main()

{unsigned short int x, y;y = myFunc(int);cout << "x: " << x << " y: " << y << "\n";

}

void myFunc(unsigned short int x)

{return (4*x);

}

4. BUG BUSTER: Što ne valja s funkcijom u slijedećem kodu? #include <iostream.h>int myFunc(unsigned short int x);int main()

{unsigned short int x, y;y = myFunc(x);cout << "x: " << x << " y: " << y << "\n";

}

int myFunc(unsigned short int x);

{return (4*x);

Page 53: C++ PRIRUCNIK

53}

5. Napišite funkciju koja uzima dva unsigned short integer argumenta i vraća rezultat dijeljenja prvog sa drugim. Ako je drugi broj nula, ne radi dijeljenje nego vrati –1.

6. Napišite program koji traži korisnika da unese dva broja i zove funkciju iz prethodne vježbe. Neka ispiše rješenje, odnosno poruku o grešci ako dobije –1.

7. Napišite program koji traži unos broja i potencije. Napišite rekurzivnu funkciju koja računa potenciju zadanog broja. Npr., ako je broj 2, a potencija 4, funkcija treba vratiti 16.

Page 54: C++ PRIRUCNIK

54 C++ programer

Lekcija 6 Osnovne klase

Klase proširuju ugrađena svojstva jezika C++ kako bi vam pomogle u predstavljanju i rješavanju složenih problem. Danas ćete naučiti:

Što su klase i objekti. Kako definirati ovu klasu i stvarati objekte te klase. Što su funkcijski članovi i podatkovni članovi (engl member function & member

data). Što su konstruktori i kako se koriste.

Stvaranje novih tipovaVeć ste naučili i različitim tipovima varijabli, uključujući i unsigned integer i charakter tipove. Tip varijable nam govori podosta o njoj. Npr., ako deklarirate Height i Width kao unsigned integer, tada znate da svaka od njih može sadržavati broj između 0 i 65535, pod pretpostavkom da je integer dvobajtni podatak. To je pravo značenje kad kažemo da je varijabla unisgned integer. Pokušavanje spremanja drugačijeg tipa podatka u varijablu uzrokuje grešku. Vi ne možete spremiti svoje ime u unsigned integer i ne biste trebali niti pokušavati.

Uopćeno govoreći, tip je kategorija. U C++ jeziku program može stvarati nove tipove podatka, i svaki od ti novih tipova može imati svu funkcionalnost i snagu ugrađenih tipova.

Zašto stvarati novi tip?Programi se obični pišu kako bismo riješili neke probleme iz realnog svijeta, kao npr., praćenjem osobnih dohodaka uposlenika ili simuliranjem rada sustava za grijanje. Iako je moguće rješavati složene probleme koristeći programe koji se koriste ugrađenim tipovima podataka, puno je lakše nositi se sa kompleksnim problemima ako možete stvarati "opise" objekata o kojima govorite. Drugim riječima, simuliranje rada sustava za grijanje je jednostavnije ako stvorite varijable koje predstavljaju sobe, senzore za grijanje, termostate i bojlere. Što bolje varijable opisuju stvarnost, lakše nam je napisati program.

Klase i članoviNove tipove stvarate deklarirajući klasu. Klasa je kolekcija varijabli—često različitih tipova—kombinirana sa grupom pripadnih funkcija.

Jedan način za razmišljanje o autu je kao o kolekciji kotača, vrata, sjedala, prozora i tako dalje. Drugi način razmišljanja o autu je što on može: može se gibati, ubrzavati, usporavati, stati, parkirati, itd. Klasa nam omogućuje da enkapsuliramo, odn. grupiramo, te dijelove i različite funkcije u jednu kolekciju, koju nazivamo objekt.

Enkapsulacija svega što znamo o autu u jednu klasu ima brojne prednosti za programera. Sve se nalazi na jednom mjestu, što olakšava pozivanje, kopiranje i manipuliranje podacima. Isto tako korisnici klase—odnosno dijelovi programa koji ju pozivaju, mogu koristiti objekt bez brige o tome šta se nalazi unutra i kako radi.

Klasa može biti načinjena od bilo koje kombinacije tipova varijabli i ostalih tipova klasa. Varijable unutar klase nazivamo podatkovnim članovima (engl. data members, ili data variables). Klasa Automobil mogla bi imati podatkovne članove koji predstavljaju sjedala, tip radio uređaja, gume, itd.

Page 55: C++ PRIRUCNIK

55Funkcije unutar klase obično manipuliraju podatkovnim članovima. Njih nazivamo funkcijskim članovima ili metodama (engl. member functions, methods). Metode klase Automobil mogle bi biti Start(), Koči(). Klasa Mačak bi mogla imati podatkovne članove koji predstavljaju godine i težinu; njezine metode mogle bi biti Spavaj(), Mjau(), LoviMiša().

Deklariranje klaseZa deklariranje klase, koristimo ključnu riječ class, koju slijedi vitičasta zagrada, te potom lista podatkovnih članova i metoda te klase. Zatvaramo deklaraciju sa zatvarajućom vitičastom zagradom i točka-zarezom. Evo deklaracija klase zvane Cat:

class Cat

{unsigned int itsAge;unsigned int itsWeight;Meow();};

Deklaracija ove klase ne alocira memoriju za samu klasu. Ona samo govori kompajleru što je to Cat, koje sadrži podatke (itsAge i itsWeight), te što može raditi (Meow()). Također govori kompajleru koliko je velik Cat—odnosno koliko mjesta kompajler treba rezervirati za svaku mačku koju stvorimo.

Definiranje objektaObjekt vašeg novog tipa definirate isto kao i cjelobrojnu varijablu:

unsigned int GrossWeight; // definicija neoznačenog cijelog brojaCat Frisky; // definicija Mačke

Ovaj kod definira variablu zvanu Gross Weight koja je cjelobrojnog neoznačenog tipa. Također definira Frisky, što je zapravo objekt čija klasa (ili tip) je Cat.

Klase i objektiVi se niti u stvarnom životu ne igrate s definicijom mačke; igrate se s individualnim mačkama. Naravno da postoji razlika između poimanja mačke, te one određene mačke koja se u tome trenutku izležava u vašoj dnevnoj sobi. Na isti način C++ pravi razliku između klase Cat, što je u stvari ideja mačke, te svakog individualnog Cat objekta. Prema tome, Frisky je objekt tipa Cat na isti način na koji je GrossWeight varijabla tipa unisgned int.

Pristupanje članovima klaseJednom kad definirate Cat objekt..na primjer, Frisky—koristeći operator točke (engl. dot) (.) pristupate članovima toga objekta. Prema tome, za dodjeljivanje broja 50 u Friskijev Weight podatkovni član, napisali biste

Frisky.Weight = 50;

Na isti način, za poziv Meow() funkcije, napisali biste

Frisky.Meow();

Kad se koristite metodama klase, vi pozivate metodu. U ovom primjeru, vi pozivate Meow() na mačku Frisky.

Page 56: C++ PRIRUCNIK

56 C++ programer

Pridruživanje objektima, a ne klasama

U C++, vi ne pridružujete vrijednosti tipovima, nego varijablama. Npr., ne biste nikad napisali

int = 5; // pogrešno

Kompajler bi ovo proglasio pogreškom, budući da ne možete dodijeliti 5 integeru. Umjesto toga, morate definirati varijablu i dodjeliti 5 toj varijabli. Na primjer,

int x; // definira x kao cijeli brojx = 5; // postavi vrijednost varijable x na 5

Isto tako, ne biste nikad napisali

Cat.age=5; // pogreška

???

Kompajler bi ovo proglasio pogreškom budući da nemožete dodjeliti 5 age dijelu klase Cat. Umjesto toga, moramo definirati Cat objekt i dodjeliti 5 tome objektu. Na primjer,

Cat Frisky; // isto kao int x;Frisky.age = 5; // isto kao x = 5;

Ako ne deklarirate, klasa neće imatiPokušajte sa slijedećim eksperimentom: Pronađite nekog trogodišnjaka i pokažite mu mačka. Tada recite, "Ovo je Tom. Tom zna jedan trik. Tom, viči vau." Dijete će se nasmijati i reći, "Ne, ludo, mačke ne laju."

Da ste napisali

Cat Frisky; // napravi mačku imena FriskyFrisky.Bark() // reci Friskiju da laje

kompajler ne bi rekao, mačke ne laju. Kompajler znna da Frisky ne laje budući da klasa Cat nema Bark() funkciju.

Privatni protiv javnihDodatne ključne riječi se koriste u deklaraciji klase. Dvije najvažnije su public i private.

Svi članovi klase—i podaci i metode—su privatni po defaultu. Privatni članovi mogu biti pristupani samo iz metoda klase u kojoj se nalaze. Javni članovi su dostupni svakom objektu te klase. Ova razlika je i važna i zbunjujuća. Kako bismo to malo razjasnili, pogledajmo primjer iz ranijeg dijela lekcije:

class Cat

{unsigned int itsAge;unsigned int itsWeight;Meow();};

U ovoj deklaraciji, itsAge, itsWeight, i Meow() su privatni, budući da su svi pripadni članovi klase privatni po defaultu. To znači da, ukoliko ne navedete drugačije, oni su privatni.

Kako bilo, ako napišete

Page 57: C++ PRIRUCNIK

57Cat Boots;

Boots.itsAge=5; // greška! ne može se pristupati privatnim podacima!

kompajler će to proglasiti greškom. U stvari vi ste gornjom deklaracijom rekli kompajeru, "pristupat ću itsAge, itsWeight i Meow() samo iz funkcijskih članova same klase." A ovdje ste pristupili itsAge podatkovnom članu Boots objekta izvan Cat metode. Samo zato što je Boots objekt klase Cat, ne znači da možete pristupat njegovim privatnim dijelovima.

Ovo je izvor neopisive konfuzije za novopečenog C++ programera. Već vas čujem kako urlate, "Hej! Upravo sam rekao da je Boots mačka. Zašto Boots ne može pristupiti vlatitim godinama?" Odgovor je da Boots može, ali vi ne možete. Boots, sa vlastitim metodama može pristupati svim svojim dijelovima—i javnim i privatnim. Iako ste vi kreirali klasu Cat, to ne znači da možete vidjeti ili mijenjati one dijelove koji su privatni.

Način da koristite klasu Cat i da joj možete pristupati podatkovnim članovima je

class Cat

{public:unsigned int itsAge;unsigned int itsWeight;Meow();};

Sada su itsAge, itsWeight, i Meow() svi postali javni. Boots.itsAge=5 kompajlira se bez problema.

Listing 6.1. Pristupanje javnim članovima jednostavne klase.

1: // Demonstrates declaration of a class and2: // definition of an object of the class,3:4: #include <iostream.h> // for cout5:6: class Cat // declare the class object7: {8: public: // members which follow are public9: int itsAge;10: int itsWeight;11: };12:13:14: void main()15: {16: Cat Frisky;17: Frisky.itsAge = 5; // assign to the member variable18: cout << "Frisky is a cat who is " ;19: cout << Frisky.itsAge << " years old.\n";20:

Output: Frisky is a cat who is 5 years old.

Pažnja: Pokušajte staviti liniju 8 u komentar (//) i rekompajlirajte. Dobit ćete grešku u liniji 17 budući da itsAge više neće imati javni pristup. Podrazumijevana vrijednost za klase je privatan pristup.

Page 58: C++ PRIRUCNIK

58 C++ programer

Načiniti podatkovne članove privatnimaKao opće pravilo dizajna, trebali biste učiniti navedeno u naslovu. Prema tome, morate kreirati javne funkcije znane kao metode pristupa (engl. accessor methods) za postavljanje i dobivanje privatnih varijabli. Ove pristupne metode su funkcijski članovi koji drugi dijelovi programa zovu za postavljanje i dobivanje privatnih varijabli.

Zašto se gnjaviti s ovim dodatnim nivoom indirektnog pristupa? Na kraju, nije li jednostavnije koristiti se podacima nego pristupati im preko pristupnih funkcija.

Pristupne funkcije omogućuju nam da razdvojimo detalje o tome kako su podaci pohranjeni i kako se koriste. To vam omogućuje da promjenite način spremanja podataka bez potrebe za ponovnim pisanjem funkcija koje koriste podatke.

Ako funkcija koja treba znati starost mačka pristupa varijabli itsAge direktno, ta bi funkcija morala biti ponovno napisana ako vi kao autor Cat klase odlučite promijeniti način na koji su podaci spremljeni. Međutim, ako koristite funkciju GetAge(), vaša Cat clasa će jednostavno vratiti vrijednost bez obzira kako ćete doći do starosti. Pozivna funkcija ne mora znati spremate li taj podatak kao unsigned integer ili long, ili računate li sve kako bi trebalo.

Ta tehnika bitno olakšava održavanje programa. Daje vašem kodu dulji život budući da promjene u samom dizajnu ne čine vaš program zastarjelim.

Listing 6.2 pokazuje Cat klasu modificiranu tako da sadrži privatne podatkovne članove i javne metode pristupa. Primjetite da ovo nije komletan program nego samo klasa.

Listing 6.2. Klasa sa metodama pristupa.

1: // Cat class declaration2: // Data members are private, public accessor methods3: // mediate setting and getting the values of the private data4:5: class Cat6: {7: public:8: // public accessors9: unsigned int GetAge();10: void SetAge(unsigned int Age);11:

12: unsigned int GetWeight();13: void SetWeight(unsigned int Weight);14:

15: // public member functions16: Meow();17:18: // private member data19: private:20: unsigned int itsAge;21: unsigned int itsWeight;22:

23: };

Za postavljanje Friskijevih godina, pridružili biste vrijednost SetAge() metodi, kao u

Cat Frisky;

Page 59: C++ PRIRUCNIK

59Frisky.SetAge(5); // postavi Friskijevu starost koristeći javni pristup

Privatnost protiv sigurnostiDeklariranjem metoda i podataka privatnima omogućeno je kompajleru da nađe programerske greške prije nego one postanu bugovi. Svaki programer može lagano izbjeći privatnost ako to želi. Stroustrup, izumitelj jezikaC++, je rekao "C++ mehanizmi kontrole pristupa pružaju zaštitu od nezgoda—ne od prevara."

class ključna riječSintaksa za class ključnu riječ je slijedeća.

class class_name

{// access control keywords here// class variables and methods declared here};

Primjer 1

class Cat

{public:unsigned int Age;unsigned int Weight;void Meow();};

Cat Frisky;Frisky.Age = 8;Frisky.Weight = 18;Frisky.Meow();

Primjer 2

class Car

{public: // the next five are public

void Start();void Accelerate();void Brake();void SetYear(int year);int GetYear();

private: // the rest is private

int Year;Char Model [255];}; // end of class declaration

Car OldFaithful; // make an instance of carint bought; // a local variable of type intOldFaithful.SetYear(84) ; // assign 84 to the yearbought = OldFaithful.GetYear(); // set bought to 84OldFaithful.Start(); // call the start method

Page 60: C++ PRIRUCNIK

60 C++ programer

Implementacija metoda klaseKao što ste vidjeli, pristupna funkcija pruža javno sučelje za privatne podatkovne članove neke klase. Svaka pristupna funkcija, zajedno sa bilo kojom drugom metodom koju deklarirate mora imati svoju implementaciju. Imlementacijom nazivamo definicju funkcije.

Definicija funkcijskog člana počinje s imenom klase, kojeg slijede dvije dvotočke, ime funkcije, te njezini parametri. Listing 6.3 pokazuje kompletnu deklaraciju jednostavne Cat klase i implementacije pristupne funkcije i jednog općeg funkcijskog člana.

Listing 6.3. Implementacija metoda jednostavne klase.

1: // Demonstrates declaration of a class and2: // definition of class methods,3:4: #include <iostream.h> // for cout5:6: class Cat // begin declaration of the class7: {8: public: // begin public section9: int GetAge(); // accessor function10: void SetAge (int age); // accessor function11: void Meow(); // general function12: private: // begin private section13: int itsAge; // member variable14: };15:16: // GetAge, Public accessor function17: // returns value of itsAge member18: int Cat::GetAge()19: {20: return itsAge;21: }22:23: // definition of SetAge, public24: // accessor function25: // returns sets itsAge member26: void Cat::SetAge(int age)27: {28: // set member variable its age to29: // value passed in by parameter age30: itsAge = age;31: }32:33: // definition of Meow method34: // returns: void35: // parameters: None36: // action: Prints "meow" to screen37: void Cat::Meow()38: {39: cout << "Meow.\n";40: }41:42: // create a cat, set its age, have it43: // meow, tell us its age, then meow again.

Page 61: C++ PRIRUCNIK

6144: int main()45: {46: Cat Frisky;47: Frisky.SetAge(5);48: Frisky.Meow();49: cout << "Frisky is a cat who is " ;50: cout << Frisky.GetAge() << " years old.\n";51: Frisky.Meow();52; return 0;53: }Output: Meow.Frisky is a cat who is 5 years old.Meow.

Konstruktori i destruktoriPostoje dva načina za deklariranje cjelobrojne varijable. Možete deklarirati varijablu i poslije joj tjekom izviđenja program dodijeliti vrijednost. Na primjer,

int Weight; // definicija varijable... // drugi kod ide ovdjeWeight = 7; // pridruživanje vrijednosti varijabli

Ili možete definirati cijeli broj i odmah ga inicijalizirati. Na primjer,

int Weight = 7; // definiraj i inicijaliziraj na 7

Inicijalizacija kombinira definiciju u svom prvom pridruživanju. Ništa vas ne zaustavlja da joj kasnije promjenite vrijednost. Inicijalizacija osigurava da vaša varijabla nikad nema besmislenu vrijednost.

Kako inicijalizirati podatkovne članove klase? Klase imaju specijalan funkcijski član zvan konstruktor. Konstruktor može uzeti potrebne parametre, ali nema nikakvu povratnu vrijednost—čak niti void. Konstruktor je metoda klase s istim imenom kao i sama klasa.

Kad god deklarirate konstruktor, trebati će vam i destruktor. Kao što konstruktor kreira i inicijalizira objekte vaše klase, destruktori "čiste teren" nakon vašeg objekta i oslobađaju alociranu memoriju. Destruktor uvijek ima ime klase predvođeno tildom (~). Destruktor ne preuzima nikakve argumente i nema povratnu vrijednost. Prema tome deklaracija Cat uključuje

~Cat();

Page 62: C++ PRIRUCNIK

62 C++ programer

Podrazumijevani konstruktori i destruktoriAko ne deklarirate konstruktor i destruktor, kompajler će ih sam napraviti za vas. Podrazumijevani konstruktor i destruktor ne preuzimaju argumente i ne čine ništa.

Kakva je korist iz konstruktora koji ne čini ništa? To je, u stvari, više stvar forme, odn. formalnosti. Svi objekti moraju biti konstruirani i destruirani, pa se ove "niš korist" funkcije pozivaju u pravom trenutku. Kako bilo, deklariranje objekta bez prosljeđivanja parametara, kao u

Cat Rags; // Rags ne dobija nikakve parametre

uzrokuje postojanje konstruktora u obliku

Cat();

Kad definirate objekt neke klase, konstruktor se poziva. Da Cat konstruktor uzima dva parametra, mogli ste definirati Cat objekt pišući

Cat Frisky (5,7);

Da konstruktor uzima jedan parametar, napisali biste

Cat Frisky (3);

U slučaju da konstruktor ne uzima parametre uopće, izostavili biste zagrade i napisali

Cat Frisky ;

Ovo je iznimka pravilu koji kaže da sve funkcije moraju imati zagrade, čak i one bez parametara. No zbog toga ste u stanju napisati

Cat Frisky;

što je u stvari poziv podrazumijevanom (default) konstruktoru. Budući da on ne prima parametre, zagrade su izostavljene. No vi ne morate koristiti podrazumijevani konstruktor kojeg kompajler kreira. Slobodni ste napisati vlastiti konstruktor sa ili bez parametara. Čak i konstruktori bez parametara mogu imati tijelo funkcije u kojoj inicijaliziraju svoje objekte ili čine neku drugu aktivnost.

Kao stvar lijepog ponašanja, ako deklarirate konstruktor, uvijek deklarirajte i destruktor, čak i ako on ništa ne radi. Istina je da će i podrazumijevani destruktor pravilno raditi, ne boli deklarirati vlastiti. To čini vaš kod čitljivijim.

Listing 6.4. Upotreba konstruktora i destruktora.

1: // Demonstrates declaration of a constructors and2: // destructor for the Cat class3:4: #include <iostream.h> // for cout5:6: class Cat // begin declaration of the class7: {8: public: // begin public section9: Cat(int initialAge); // constructor10: ~Cat(); // destructor11: int GetAge(); // accessor function12: void SetAge(int age); // accessor function13: void Meow();14: private: // begin private section15: int itsAge; // member variable16: };

Page 63: C++ PRIRUCNIK

6317:18: // constructor of Cat,19: Cat::Cat(int initialAge)20: {21: itsAge = initialAge;22: }23:24: Cat::~Cat() // destructor, takes no action25: {26: }27:28: // GetAge, Public accessor function29: // returns value of itsAge member30: int Cat::GetAge()31: {32: return itsAge;33: }34:35: // Definition of SetAge, public36: // accessor function37:38: void Cat::SetAge(int age)39: {40: // set member variable its age to41: // value passed in by parameter age42: itsAge = age;43: }44:45: // definition of Meow method46: // returns: void47: // parameters: None48: // action: Prints "meow" to screen49: void Cat::Meow()50: {51: cout << "Meow.\n";52: }53:54: // create a cat, set its age, have it55 // meow, tell us its age, then meow again.56: int main()57: {58: Cat Frisky(5);59: Frisky.Meow();60: cout << "Frisky is a cat who is " ;61: cout << Frisky.GetAge() << " years old.\n";62: Frisky.Meow();63: Frisky.SetAge(7);64: cout << "Now Frisky is " ;65: cout << Frisky.GetAge() << " years old.\n";66; return 0;67: }

Output: Meow.Frisky is a cat who is 5 years old.

Page 64: C++ PRIRUCNIK

64 C++ programer

Meow.Now Frisky is 7 years old.

Konstantni funkcijski članoviAko deklarirate metodu klase kao const, vi obećajete da ta metoda neće promijeniti vrijednost bilo kojeg člana klase. Za deklarirane metode konstantom samo stavite ključnu riječ const nakon zagrade, a prije točke-zareza. Ova deklaracija konstantnog funkcijskog člana NekaFunkcija() ne uzima nikakve argumente i vraća void. Izgleda ovako:

void SomeFunction() const;

Pristupne funkcije često deklariramo konstantnima koristeći const modifikator. Cat klasa ima dve pristupne funkcije:

void SetAge(int anAge);int GetAge();

SetAge() ne može biti const budući da mijenja padatkovni član, varijablu itsAge. GetAge() može, i trebao bi, biti const budući da uopće ne mijenja klasu. GetAge() jednostavno vraća trenutnu vrijednost podatkovnog člana itsAge. Prema tome deklaracija tih dviju funkcija bi trebala glasiti:

void SetAge(int anAge);int GetAge() const;

Ako deklarirate funkciju konstantnom, a njezina implementacija utječe na objekt mjenjajući vrijednost jednog ili više njegovih članova, kompajler će javiti grešku. Na primjer, da ste napisali GetAge() na takav način da broji koliko smo puta upitali Cat klasu za godine, generirali bi kompajlersku pogrešku. To je stoga jer bi mijenjali Cat objekt pozivanjem određene metode.

Dobra je programerska praksa za deklariranje metoda kao const kad god je to moguće. Svaki put kad to učinite, omogućujete kompajleru da hvata vaše pogreške, umjesto da dozvolite njihovo pretvaranje u bugove koji će se pojavljivati tek prilikom pokretanja vašeg programa.

Sučelje protiv implementacijeKao što ste naučili, klijenti su ddijelovi programa koji kreiraju i koriste objekte vaše klase. Možete razmišljati o sučelju prema vašoj klasi—deklaraciji klase—kao o ugovoru između tih klijenata. Ugovor kazuje koje podatke vaša klasa ima dostupne i kako će se ponašati.

Na primjer, u deklaraciji Cat klase, vi stvarate ugovor da će svaka mačka imati podatkovni član itsAge koji može biti inivcijaliziran u svom konstruktoru, dodjeljen preko vlastite SetAge() pristupne funkcije, i pročitan preko GetAge() akcesora. Također obećajete da će svaka mačka znati kako da mjauče, odn. imati metodu Meow().

Ako proglasite GetAge() konstantnom funkcijom—kao što biste trebali—ugovor također obećaje da GetAge() neće promjeniti mačku za koju je pozvan.

C++ kompajler javiti će vam grešku svaki put kad prekršite ugovor. Listing 6.5 demonstrira program koji se neda kompajlirati zbog kršenja pravila definiranih ugovorom.

Page 65: C++ PRIRUCNIK

65UPOZORENJE: Listing 6.5 se ne može kompajlirati!

Listing 6.5.Demonstracija kršenja sučelja (engl. interface)

1: // Demonstrates compiler errors2:3:4: #include <iostream.h> // for cout5:6: class Cat7: {8: public:9: Cat(int initialAge);10: ~Cat();11: int GetAge() const; // const accessor function12: void SetAge (int age);13: void Meow();14: private:15: int itsAge;16: };17:18: // constructor of Cat,19: Cat::Cat(int initialAge)20: {21: itsAge = initialAge;21: cout << "Cat Constructor\n";22: }23:

24: Cat::~Cat() // destructor, takes no action25: {26: cout << "Cat Destructor\n";27: }28: // GetAge, const function29: // but we violate const!30: int Cat::GetAge() const31: {32: return (itsAge++); // violates const!33: }34:

35: // definition of SetAge, public36: // accessor function37:

38: void Cat::SetAge(int age)39: {40: // set member variable its age to41: // value passed in by parameter age42: itsAge = age;43: }44:45: // definition of Meow method46: // returns: void47: // parameters: None48: // action: Prints "meow" to screen

Page 66: C++ PRIRUCNIK

66 C++ programer

49: void Cat::Meow()50: {51: cout << "Meow.\n";52: }53:54: // demonstrate various violations of the55 // interface, and resulting compiler errors56: int main()57: {58: Cat Frisky; // doesn't match declaration59: Frisky.Meow();60: Frisky.Bark(); // No, silly, cat's can't bark.61: Frisky.itsAge = 7; // itsAge is private62: return 0;63: }

Zašto koristiti kompajler za hvatanje pogrešaka?Iako bi bilo prekrasno kreirati 100% pravilan kod, malo tko je to i stvarno sposoban. Stoga su programeri razvili sistem koji olakšava otkrivanje i ispravljanje bugova rano u procesu stvaranja programa. Iako su kompajlerske pogreške često frustrirajuće, one su mnogo bolje nego njhova alternativa. C++ je prilično krut jezik, što se sintakse tiče, i dok vam neki jezici (poput Visual Basica, na primjer) omogućuju kršenje vlastitih pravila bez prijavljivanja grešaka kod kompajliranja, rezultirajuća izvršna datoteka će se srušiti baš kad je to najkritičnije.

Gdje staviti deklaracije klasa i definicije metodaSvaka funkcija koju deklarirate za vašu klasu mora imati i definiciju. Definicijom također nazivamo i funkcijsku implementaciju. Kao i sve druge funkcije, definicija metode unutar klase ima zaglavlje i tijelo funkcije.

Definicija se mora nalaziti u datoteci koju kompajler može naći. Većina C++ kompajlera očekuje da datoteka završava s .c ili .cpp. Mi ćemo koristiti .cpp, ali provjerite u dokumentaciji kompajlera na kojem radite što mu više odgovara.

Slobodni ste stavljati i deklaracije u takove datoteke, ali to nije dobra programerska praksa. Konvencija koju najviše programera prihvaća je stavljanje deklaracija u ono što nazivamo datotekom zaglavlja (engl. header file), obično sa istim imenom ali ekstenzijom .h, .hp, ili .hpp. Mi ćemo ih obično nazivati .hpp, ali i ovdje trebate provjeriti kompajlerove postavke.

Na primjer, stavit ćete deklaraciju Cat klase u datoteku naziva cat.hpp, a definiciju metoda klase u datoteku zvanu cat.cpp. Tada ćete spojiti datoteku zaglavlja sa .cpp datotekom stavljajući slijedeći kod na vrh cat.cpp:

#include Cat.hpp

Ovime kažemo kompajleru da pročita Cat.hpp u datoteku, kao da ste u tom trenutku utipkali njegov sadržaj. Zašto se gnjaviti s odvojenim datotekama kad ćete ih ionako ponovno spojiti? Većinom vremena, klijenti vaše klase ne mare o specifičnostima implementacije. Čitanje datoteke zaglavlja govori im sve što trebaju znati, te mogu ignorirati implementacijske datoteke.

Page 67: C++ PRIRUCNIK

67ZAPAMTI: Deklaracija klase govori kompajleru što klasa je, koje podatke sadrži, te koje funkcije ima. Deklaracija klase se naziva i njenim sučeljem (engl. interface) stoga što govori korisniku kako komunicirati s objektom. Sučelje se obično sprema u .hpp datoteku, što je skračenica od engl. header C++ file. Definicija funkcije kazuje kompajleru kako funkcija radi. Definiciju funkcije zovemo implementacijom metoda klase, i držimo ju u .cpp datoteci. Detalji vezani uz implementaciju se tiču isključivo autora klase. Klijenti te klase—odn. dijelovi programa koji ju koriste—ne moraju znati, niti mariti, kako je funkcija implementirana.

Inline implementacijaBaš kao što možete tražiti od kompajlera da i običnu funkciju umetne inline, možete i metodu klase načiniti inline. Ključna riječ inline se umeće ispred povratnog tipa. Inline implementacija GetWeight() funkcije, na primjer, izgleda ovako:

inline int Cat::GetWeight()

{return itsWeight; // return the Weight data member

}

Također možete staviti definiciju funkcije u deklaraciju same klase, što automatski čini funkciju inline. Na primjer,

class Cat

{public:int GetWeight() { return itsWeight; } // inlinevoid SetWeight(int aWeight);};

Obratite pozornost na sintaksu GetWeight() definicije. Tijelo inline funkcije počinje odmah nakon deklaracije metode klase; ne postoji točka-zarez nakon zagrade. Kao i kod svake druge funkcije, definicija počinje s otvorenom vitičastom zagradom i završava zatvorenom vitičastom zagradom. Mogli ste, naravno napisati deklaraciju i ovako

class Cat

{public:

int GetWeight()

{return itsWeight;} // inlinevoid SetWeight(int aWeight);};

Listing 6.6 i 6.7 ponovno kreiraju Cat klasu, ali ovaj put deklaraciju drže u cat.hpp, a implementaciju funkcija u cat.cpp. Listing 6.7 također mijenja pristupnu funkciju Meow() u inline.

Page 68: C++ PRIRUCNIK

68 C++ programer

Listing 6.6. Cat class deklaracija u cat.hpp

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

Listing 6.7. Cat implementacija u cat.cpp.

1: // Demonstrates inline functions2: // and inclusion of header files3:4: #include "cat.hpp" // be sure to include the header files!5:6:7: Cat::Cat(int initialAge) //constructor8: {9: itsAge = initialAge;10: }11:12: Cat::~Cat() //destructor, takes no action13: {14: }15:16: // Create a cat, set its age, have it17: // meow, tell us its age, then meow again.18: int main()19: {20: Cat Frisky(5);21: Frisky.Meow();22: cout << "Frisky is a cat who is " ;23: cout << Frisky.GetAge() << " years old.\n";24: Frisky.Meow();25: Frisky.SetAge(7);26: cout << "Now Frisky is " ;27: cout << Frisky.GetAge() << " years old.\n";28: return 0;29: }

Output: Meow.Frisky is a cat who is 5 years old.Meow.Now Frisky is 7 years old.

Page 69: C++ PRIRUCNIK

69Klase s drugim klasama kao podatkovnim članovimaNije neuobičajeno stvarati kompleksne klase deklarirajući jednostavne klase i umetajući ih u deklaraciju komplicanijh klasa. Na primjer mđete deklarirati klasu Kotač, Motor, i Transmisija, itd., te ih kombinirati unutar klase Automobil. Ta deklaracija ima svoj odnos. Auto ima motor, kotače i transmisiju.

Proučite slijedeći primjer. Pravokutnik se sastoji od linija. Linija je definirana sa dvije točke. Točka je definirana s x i y koordinatama. Listing 6.8 pokazuje kompletnu deklaraciju Rectangle klase, kako bi se mogla pojaviti u rectangle.hpp. Budući da je pravokutnik definiran sa četiri linije koje spajaju četiri točke, a svaka točka označava položaj na x i y osi, prvo deklariramo Point klasu, koja sadrži x i y koordinate za svaku točku. Listing 6.9 pokazuje potpunu deklaraciju obje klase.

Listing 6.8. Deklariranje kompleksne klase.

1: // Begin Rect.hpp2: #include <iostream.h>3: class Point // holds x,y coordinates4: {5: // no constructor, use default6: public:7: void SetX(int x) { itsX = x; }8: void SetY(int y) { itsY = y; }9: int GetX()const { return itsX;}10: int GetY()const { return itsY;}11: private:12: int itsX;13: int itsY;14: }; // end of Point class declaration15:16:17: class Rectangle18: {19: public:20: Rectangle (int top, int left, int bottom, int right);21: ~Rectangle () {}22:

23: int GetTop() const { return itsTop; }24: int GetLeft() const { return itsLeft; }25: int GetBottom() const { return itsBottom; }26: int GetRight() const { return itsRight; }27:

28: Point GetUpperLeft() const { return itsUpperLeft; }29: Point GetLowerLeft() const { return itsLowerLeft; }30: Point GetUpperRight() const { return itsUpperRight; }31: Point GetLowerRight() const { return itsLowerRight; }32:

33: void SetUpperLeft(Point Location) {itsUpperLeft = Location;}34: void SetLowerLeft(Point Location) {itsLowerLeft = Location;}35: void SetUpperRight(Point Location) {itsUpperRight = Location;}36: void SetLowerRight(Point Location) {itsLowerRight = Location;}37:

38: void SetTop(int top) { itsTop = top; }

Page 70: C++ PRIRUCNIK

70 C++ programer

39: void SetLeft (int left) { itsLeft = left; }40: void SetBottom (int bottom) { itsBottom = bottom; }41: void SetRight (int right) { itsRight = right; }42:43: int GetArea() const;44:45: private:46: Point itsUpperLeft;47: Point itsUpperRight;48: Point itsLowerLeft;49: Point itsLowerRight;50: int itsTop;51: int itsLeft;52: int itsBottom;53: int itsRight;54: };55: // end Rect.hpp

Listing 6.9. RECT.CPP.

1: // Begin rect.cpp2: #include "rect.hpp"3: Rectangle::Rectangle(int top, int left, int bottom, int right)4: {5: itsTop = top;6: itsLeft = left;7: itsBottom = bottom;8: itsRight = right;9:

10: itsUpperLeft.SetX(left);11: itsUpperLeft.SetY(top);12:

13: itsUpperRight.SetX(right);14: itsUpperRight.SetY(top);15:

16: itsLowerLeft.SetX(left);17: itsLowerLeft.SetY(bottom);18:

19: itsLowerRight.SetX(right);20: itsLowerRight.SetY(bottom);21: }22:23:24: // compute area of the rectangle by finding corners,25: // establish width and height and then multiply26: int Rectangle::GetArea() const27: {28: int Width = itsRight-itsLeft;29: int Height = itsTop - itsBottom;30: return (Width * Height);31: }32:

Page 71: C++ PRIRUCNIK

7133: int main()34: {35: //initialize a local Rectangle variable36: Rectangle MyRectangle (100, 20, 50, 80 );37:38: int Area = MyRectangle.GetArea();39:40: cout << "Area: " << Area << "\n";41: cout << "Upper Left X Coordinate: ";42: cout << MyRectangle.GetUpperLeft().GetX();43: return 0;44: }Output: Area: 3000Upper Left X Coordinate: 20

RECT.HPP je prikazan na listingu 6.8. Samo gledajući u datoteku zaglavlja, koja sadrži deklaraciju Rectangle klase, programer zna da funkcija GetArea() vraća int. Kojim čudom GetArea() to radi nije nas briga (kao korisnika te klase). U stvari, autor Rectangle klase može promjeniti GetArea() bez utjecaja na programe koji se služe Rectangle klasom.

StruktureVrlo blizak rod class ključne riječi je ključna riječ struct, koja se koristi za deklaraciju strukture. U C++, struktura je isto što i klasa, osim što su članovi strukture javni po defaultu. Možete deklarirati strukturu u potpunosti jednako kao i klasu, te joj možete dati ista imena i članove. U stvari, slijedite li pravila lijepoga ponašanja uvijek ekplicitno navodeći privatne i javne dijelove vaše klase, neće biti nikakve razlike.

Zašto dvije ključne riječi rade istu stvarVjerojatno se pitate zašto dvije ključne riječi rade istu stvar. To je nešto što se može okarakterizirati povijesnom pogreškom. Kad je C++ razvijan, napravljen je kao nadgradnja na C jezik. C ima strukture, iako one ne mogu sadržavati metode, odn. funkcije. Bjarne Stroustrup, kreator jezika C++, je nadgradio strukture, ali je promjenio ime u class kako bi naglasio novu proširenu funkcionalnost.

Kviz1. Što je to dot operator i zašto ga koristimo?2. Što rezervira memoriju—deklaracija ili definicija?3. Je li deklaracija klase njezino sučeljeee ili njezina implementacija? 4. Koja je razlika između privatnih i javnih podatkovnih članova? 5. Mogu li funkcijski članovi biti privatni? 6. Mogu li podatkovni članovi biti javni? 7. Ako deklarirate dva Cat objekta, mogu li oni imati različite vrijednosti unutar itsAge

podatkovnog člana?8. Da li deklaracija klase završava sa ";" ? A definicija metode ? 9. Kako bi zaglavlje za Cat funkciju imena Meow izgledalo, pod pretpostavkom da ne

uzima nikakve parametre i vraća void? 10.Koju funkciju pozivamo za inicijalizaciju klase?

Page 72: C++ PRIRUCNIK

72 C++ programer

Vježbe1. Napišite kod koji deklarira klasu zvanu Employee sa slijedećim podatkovnim

članovima: age, yearsOfService, and Salary.2. Promjenite Employee klasu tako da joj podatkovni članovi budu privatni, a pružite

javne pristupne metode za dobivanje i postavljanje svakog od podatkovnih članova.3. Napišite program koji na osnovi gornje klase stvaradva zaposlena, postavlja njihove

godine, godine rada i plaću, te ispisuje njihove vrijednosti.4. Nastavljajući vježbu 3, pružite metodu za Employee koja izvještava koliko tisuća

dolara zarađuje, zaokruženo na najbližu 1000.5. Promjenite Emplyee klasu tako da možete inicijalizirati godine, godine službe i plaću

kad kreirate uposlenika.6. BUG BUSTERS: Šta ne valja sa slijedećom deklaracijom?

class Square

{public:

int Side;

}

7. BUG BUSTERS: Zašto je slijedeća deklaracija klase beskorisna? class Cat

{int GetAge()const;

private:

int itsAge;

};

8. BUG BUSTERS: Koja će tri buga u ovome kodu kompajler pronaći? class TV

{public:

void SetStation(int Station);int GetStation() const;

private:

int itsStation;

};main()

{TV myTV;myTV.itsStation = 9;TV.SetStation(10);TV myOtherTv(2);

}

Page 73: C++ PRIRUCNIK

73

Lekcija 7 Još o programskom toku

Programi obavljaju većinu svog posla s grananjem i petljama (engl. branch & loop). U lekciji 4 smo naučili kako granati program koristeći if naredbu. Danas ćemo naučiti

Što su petlje i kako se koriste Kako napraviti različite petlje Alternativu duboko ugnježdenim if/else naredbama

PetljeMnogi programerski problemi se rješavaju neprekidnim djelovanjem nad istim podacima. Postoje dva načina da se ovo postigne: rekurzija (rasprava u prošloj lekciji) i iteracija. Iteracija označava stalno ponavljanje jedne te iste stvari. Osnovna metoda iteracije je petlja.

Korijeni petlji: goto naredbaU primitivnim danima rane računalne znanosti programi su bili vrlo "prljavo" pisani. Petlje su se sastojale od labele, nekoliko naredbi i skoka.

U C++, labele je samo ime koje prati dvotočka (:). Labelu postavljamo lijevo od legalne C++ naredbe i skok postižemo stavljajući goto kojeg prati ime labele. Listing 7.1 nam ilustrira takvu petlju.

Listing 7.1. Petlja uz pomoć ključne riječi goto.

1: // Listing 7.12: // Looping with goto3:4: #include <iostream.h>5:6: int main()7: {8: int counter = 0; // initialize counter9: loop: counter ++; // top of the loop10: cout << "counter: " << counter << "\n";11: if (counter < 5) // test the value12: goto loop; // jump to the top13:

14: cout << "Complete. Counter: " << counter << ".\n";15: return 0;16: }Output: counter: 1counter: 2counter: 3counter: 4counter: 5Complete. Counter: 5.

Page 74: C++ PRIRUCNIK

74 C++ programer

Zašto je goto prognanGoto je primio neke zbilja poražavajuče kritike, i to prilično zasluženo. goto naredba može uzrokovati skok na bilo koju lokaciju vašeg programa, unaprijed ili unazad. Neprimjerena upotreba goto naredbe je uzrokovala patetično nečitke programe, znane i pod nzivom "špageti kod". Zbog toga profesori informatike provode zadnjih 20 godina uporno govoreći učenicima "Nikad, ikad, a niti tada ne koristite goto! To je Zlo!"

Kako bi se izbjegla upotreba naredbe goto, sofisticiraniji i bolje kontrolirani načini za upotrebu petlji su pronađeni: for, while, i do...while. Upotreba tih naredbi čini programe razumljivijima, te uistinu čini goto suvišnim. Ali, uz sve štetne nuspojave, goto je vrlo moćna naredba, ponekad i strahovito korisna kad nemamo drugog rješenja pa ju je ANSI komitet odlučio zadržati u jeziku. Ali kako se kaže, "djeco, ne pokušavajte to kod kuće".

goto naredba

Dakle, za korištenje goto naredbe utipkati ćete goto i ime labele. To uzrokuje neuvjetovan skok na labelu. Primjer

if (value > 10) goto end;if (value < 10) goto end;cout << "value is Â10!";end:cout << "done";

UPOZORENJE: Upotreba goto naredbe je gotovo uvijek znak lošega dizajna. Najbolji savjet je da ju potpuno izbjegavate.

while petljewhile petlja uzrokuje da vaš program ponavlja slijed naredbi dok je god početni uvjet istinit. U listingu 7.1, brojač je inkrementiran dok nije postao jednak 5. Listing 7.2 pokazuje isti program prepisan da koristi prednosti while petlje.

Listing 7.2. while petlja.

1: // Listing 7.22: // Looping with while3:4: #include <iostream.h>5:6: int main()7: {8: int counter = 0; // initialize the condition9:

10: while(counter < 5) // test condition still true11: {12: counter++; // body of the loop13: cout << "counter: " << counter << "\n";14: }15:

16: cout << "Complete. Counter: " << counter << ".\n";17: return 0;18: }

Page 75: C++ PRIRUCNIK

75while naredba

Sintaksa while naredbe je slijedeća:

while (uvjet)naredba;

uvjet je bilo koji C++ izraz, a naredba je bilo koja legalna C++ naredba ili blok naredbi. Kad god je uvjet istinit, naredba se izvrši, te se uvjet ponovno provjerava. To se ponavlja dok na uvjet ne vrati laž. U tom trenutku petlja završava i izvšenje se nastavlja na prvoj slijedećoj naredbi.

Primjer

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

Još kompliciranije while naredbeTestirani uvjet unutar while petlje može biti kompleksan kao bilo kakav C++ izraz. To može uključivati izraze proizvedene uporabom logičkih && (I), || (ILI), i ! (NE) operatora. Listing 7.3 je malo kompleksniji primjer while naredbe.

Listing 7.3. Složene while petlje.

1: // Listing 7.32: // Complex while statements3:4: #include <iostream.h>5:6: int main()7: {8: unsigned short small;9: unsigned long large;10: const unsigned short MAXSMALL=65535;11:

12: cout << "Enter a small number: ";13: cin >> small;14: cout << "Enter a large number: ";15: cin >> large;16:17: cout << "small: " << small << "...";18:19: // for each iteration, test three conditions20: while (small < large && large > 0 && small < MAXSMALL)21:

22: {23: if (small % 5000 == 0) // write a dot every 5k lines24: cout << ".";25:26: small++;27:28: large-=2;29: }

Page 76: C++ PRIRUCNIK

76 C++ programer

30:

31: cout << "\nSmall: " << small << " Large: " << large << endl;32: return 0;33: }Output: Enter a small number: 2Enter a large number: 100000small: 2.........Small: 33335 Large: 33334

continue i breakPonekad ćete željeti skočiti na početak while petlje prije nego li se izvrše sve naredbe unutar nje. continue naredba čini upravo to, ona skače na početak petlje.

A ponekad ćete željeti i izaći iz petlje prije nego se svi uvjeti zadovolje. break naredba trenutno izlazi iz while petlje, i izvršenje programa se nastavlja od zatvarajuće vitičaste zagrade.

Listing 7.4 demonstrira upotrebu ovih naredbi. Ovaj put igra je postala kompliciranija. Korisnik unosi mali i veliki broj, broj skoka i ciljani broj. Mali broj će se povećavati za 1, veliki će se smanjivati za 2. Međutim, dekrementiranje će biti preskočenio svaki put kad je mali broj višekratnik skok broja i ciljanog broja. Igra završava kad mali broj postane veći od velikog. Ako veliki broj postane ciljani direktno, ipisuje se poruka i igra završava.Cilj korisnika je da pogodi ciljani broj velikog broja koji će zaustaviti igru.

Listing 7.4. break i continue.

1: // Listing 7.42: // Demonstrates break and continue3:4: #include <iostream.h>5:6: int main()7: {8: unsigned short small;9: unsigned long large;10: unsigned long skip;11: unsigned long target;12: const unsigned short MAXSMALL=65535;13:

14: cout << "Enter a small number: ";15: cin >> small;16: cout << "Enter a large number: ";17: cin >> large;18: cout << "Enter a skip number: ";19: cin >> skip;20: cout << "Enter a target number: ";21: cin >> target;22:23: cout << "\n";24:25: // set up 3 stop conditions for the loop26: while (small < large && large > 0 && small < 65535)27:28: {

Page 77: C++ PRIRUCNIK

7729:30: small++;31:32: if (small % skip == 0) // skip the decrement?33: {34: cout << "skipping on " << small << endl;35: continue;36: }37:

38: if (large == target) // exact match for the target?39: {40: cout << "Target reached!";41: break;42: }43:

44: large-=2;45: } // end of while loop46:

47: cout << "\nSmall: " << small << " Large: " << large << endl;48: return 0;49: }Output: Enter a small number: 2Enter a large number: 20Enter a skip number: 4Enter a target number: 6skipping on 4skipping on 8

Small: 10 Large: 8

PAŽNJA: I continue i brak trebaju bit upotrebljavani s pažnjom. Oni su najopasnije naredbe uz goto, iz istog razloga. Programe koji nenadano mijenjaju smjer je teže pratiti.

while (1) petljaTestirani uvjet u while petlji, rekli smo, mora biti pravilan C++ izraz. Dok god je uvjet istinit, while petlja će se vrtiti. Možete kreirati petlju koja nikad ne završava, tzv. "mrtvu" petlju stavljajući broj 1 kao testirani uvjet. Budući da je 1 uvijek istina ta se petlja neće prekinuti ukoliko unutar nje ne stavimo brak naredbu. Listing 7.5 demonstrira brojanje do 10 koristeći takav oblik petlje.

Listing 7.5. while (1)petlja.

1: // Listing 7.52: // Demonstrates a while true loop3:4: #include <iostream.h>5:6: int main()7: {8: int counter = 0;9:

Page 78: C++ PRIRUCNIK

78 C++ programer

10: while (1)11: {12: counter ++;13: if (counter > 10)14: break;15: }16: cout << "Counter: " << counter << "\n";17: return 0;18: }

Output: Counter: 11

do...while petljaMože se dogoditi da se tijelo while petlje nikad ne izvede. While naredba provjerava uvjet prije izvršavanja bilo koje od naredbi, i ako se uvijet pokaže neistinitim, cijelo tijelo while petlje se preskače. Listing 7.6 ilustrira ovo.

Listing 7.6. Preskakanje tijela while petlje

1: // Listing 7.62: // Demonstrates skipping the body of3: // the while loop when the condition is false.4:5: #include <iostream.h>6:7: int main()8: {9: int counter;10: cout << "How many hellos?: ";11: cin >> counter;12: while (counter > 0)13: {14: cout << "Hello!\n";15: counter--;16: }17: cout << "Counter is OutPut: " << counter;18: return 0;19: }Output: How many hellos?: 2Hello!Hello!Counter is OutPut: 0

How many hellos?: 0Counter is OutPut: 0

Što ako biste željeli osigurati da se poruka Hello ispiše barem jedanput? While petlja to ne može postići sama. Zato si možemo pripomoći sa if naredbom prije ulaska u while petlju:

if (counter < 1) // force a minimum valuecounter = 1;ali ovo je očito "naštimavanje", ružno i neelegantno rješenje.

Page 79: C++ PRIRUCNIK

79do...whiledo...while petlja izvrši tijelo petlje prije testiranja uvjeta i osigurava nam da će se tijelo barem jedanput izvršiti. Listing 7.7 je prepisani listing 7.6, ali koji koristi do...while petlju.

Listing 7.7. Demonstracija do...while petlje.

1: // Listing 7.72: // Demonstrates do while3:4: #include <iostream.h>5:6: int main()7: {8: int counter;9: cout << "How many hellos? ";10: cin >> counter;11: do12: {13: cout << "Hello\n";14: counter--;15: } while (counter >0 );16: cout << "Counter is: " << counter << endl;17: return 0;18: }

Output: How many hellos? 2HelloHelloCounter is: 0

Page 80: C++ PRIRUCNIK

80 C++ programer

do...while nareba

Sintaksa za do...while naredbu je slijedeća:

donaredbawhile (uvjet);

Primjer 1

// count to 10int x = 0;docout << "X: " << x++;while (x < 10)

Primjer 2

// print lowercase alphabet.char ch = `a';do{cout << ch << ` `;ch++;} while ( ch <= `z' );

for petljaČesto u while petljama ispitujemo početni uvjet, te ako je istinit inkrementiramo određenu varijablu ili ju na neki drugi način mijenjamo. Listing 7.8 nam to demonstrira.

Listing 7.8.

1: // Listing 7.82: // Looping with while3:4: #include <iostream.h>5:6: int main()7: {8: int counter = 0;9:

10: while(counter < 5)11: {12: counter++;13: cout << "Looping! ";14: }15:

16: cout << "\nCounter: " << counter << ".\n";17: return 0;18: }Output: Looping! Looping! Looping! Looping! Looping!Counter: 5.

Page 81: C++ PRIRUCNIK

81Često je takve slučajeve elegantnije riješiti for petljom, kao u listingu 7.9

Listing 7.9. demonstriranje for petlje.

1: // Listing 7.92: // Looping with for3:4: #include <iostream.h>5:6: int main()7: {8: int counter;9: for (counter = 0; counter < 5; counter++)10: cout << "Looping! ";11:

12: cout << "\nCounter: " << counter << ".\n";13: return 0;14: }Output: Looping! Looping! Looping! Looping! Looping!Counter: 5.

for naredba

Sintaksa for naredbe glasi:

for (inicijalizacija; test; akcija )naredba;

Inicijalizacija nam služi za postavljanje brojača na početnu vrijednost. Test je bilo koji C++ izraz i on se provjerava prilikom svakog prolaska kroz petlju. Ako je on istinit, akcija u zaglavlju se izvršava (obično inkrementiramo brojač) i nakon toga se izvršava naredba unutar for petlje.

Primjer 1

// ispisuje Hello deset putafor (int i = 0; i<10; i++)cout << "Hello! ";

Primjer 2

for (int i = 0; i < 10; i++)

{cout << "Hello!" << endl;cout << "the value of i is: " << i << endl;

}

Page 82: C++ PRIRUCNIK

82 C++ programer

Napredne for petljefor naredbe su moćne i fleksibilne. Tri neovisne naredbe (inicijalizacija, test, akcija) omogućuju nam bezbroj varijacija i otvaraju neograničene mogućnosti.

For petlja radi slijedećim redosljedom:

1. Izvodi inicijalizacijsku operaciju. 2. Provjerava uvjet. 3. Ako uvjet vraća vrijednost TRUE, izvršava akciju navedenu u petlji. Moguće je i višestruke inicijalizacije i inkrementacije navoditi unutar for naredbe, kao u slijedećem primjeru.

Listing 7.10. Demonstracija višestrukih naredbi u for petlji.

1: //listing 7.102: // demonstrates multiple statements in3: // for loops4:5: #include <iostream.h>6:7: int main()8: {9: for (int i=0, j=0; i<3; i++, j++)10: cout << "i: " << i << " j: " << j << endl;11: return 0;12: }

Output: i: 0 j: 0i: 1 j: 1i: 2 j: 2

Za kreiranje for petlje koja se ponaša istovjetno lwhile petlji, izostaviti ćemo prvu i treću naredbu. Listing 7.11 ilustrira tu ideju.

Listing 7.11. Null naredbe u for petlji.

1: // Listing 7.112: // For loops with null statements3:4: #include <iostream.h>5:6: int main()7: {8: int counter = 0;9:

10: for( ; counter < 5; )11: {12: counter++;13: cout << "Looping! ";14: }15:

16: cout << "\nCounter: " << counter << ".\n";17: return 0;18: }

output: Looping! Looping! Looping! Looping! Looping!

Page 83: C++ PRIRUCNIK

83Counter: 5.

Još jednom, C++ nam pruža čitav niz načina da postignemo istu stvar.. Niti jedan iskusan C++ programer ne bi na ovaj način (zlo)upotrebljavao for petlju, ali nam je to ilustracija fleksibilnosti same for naredbe. U stvari, moguće je kreirati for petlju s nijednom od tri naredbe. Listing 7.12 ilustrira nam kako.

Listing 7.12. Ilustracija prazne for petlje.

1: //Listing 7.12 illustrating2: //empty for loop statement3:4: #include <iostream.h>5:6: int main()7: {8: int counter=0; // initialization9: int max;10: cout << "How many hellos?";11: cin >> max;12: for (;;) // a for loop that doesn't end13: {14: if (counter < max) // test15: {16: cout << "Hello!\n";17: counter++; // increment18: }19: else20: break;21: }22: return 0;23: }

Output: How many hellos?3Hello!Hello!Hello!

Iako je ovaj konkretan primjer pomalo apsurdan, postoje trenutci kada su for(;;) i while(1) petlje upravo ono što trebate. Vidjet ćete jednu razumnu upotrebu takve petlje kad budemo diskutirali o switch naredbi malo kasnije.

Prazno tijelo for petljeToliko se može napraviti u zaglavlju for naredbe, da nam ponekad čak neće biti potrebno tijelo s naredbama. U tom slučaju, ne zaboravite staviti nul naredbu (;) u tijelo petlje. Točkazarez može biti u istom retku kao i zaglavlje, ali to se lako zaboravlja. Listing 7.13 ilustrira nam nul tijelo unutar for petlje.

Page 84: C++ PRIRUCNIK

84 C++ programer

Listing 7.13. Ilustracija nul naredbe u for petlji

1: //Listing 7.132: //Demonstrates null statement3: // as body of for loop4:

5: #include <iostream.h>6: int main()7: {8: for (int i = 0; i<5; cout << "i: " << i++ << endl)9: ;10: return 0;11: }

Output: i: 0i: 1i: 2i: 3i: 4

Primjetite da ovo nije lijepo dizajnirana for petlja: akcijska naredba je pretrpana i nerazumljiva. Bolje bi bilo to napisati kao

8: for (int i = 0; i<5; i++)9: cout << "i: " << i << endl;

Dok tijelo čini u potpunosti istu stvar, ovaj primjer je lakše razumljiv.

Ugnježdene petljePetlje mogu biti ugnježdene, s jednom petljom koja stoji u tijelu druge. Unutašnja petlja će se u potpunosti izvrtiti prilikom svakog izvršavanja vanjske petlje. Listing 7.14 ilustrira umetanje oznaka u matricu uporabom ugnježdenih petlji.

Listing 7.14. Ilustracija ugnježdenih petlji.

1: //Listing 7.142: //Illustrates nested for loops3:4: #include <iostream.h>5:6: int main()7: {8: int rows, columns;9: char theChar;10: cout << "How many rows? ";11: cin >> rows;12: cout << "How many columns? ";13: cin >> columns;14: cout << "What character? ";15: cin >> theChar;16: for (int i = 0; i<rows; i++)17: {18: for (int j = 0; j<columns; j++)19: cout << theChar;20: cout << "\n";

Page 85: C++ PRIRUCNIK

8521: }22: return 0;23: }

Output: How many rows? 4How many columns? 12What character? xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Sažetak: petljeU lekciji t, "Funkcije", naučili ste rješiti problem Fibonaccijeva niza korištenjem rekurzije. Kao kratak sažetak, prisjetimo se da takav niz počinje brojevima 1, 1, 2, 3, i slijedećim brojevima koji su sama prethodna dva broja:

1,1,2,3,5,8,13,21,34...

n-ti Fibonaccijev broj je suma n-1 i n-2 Fibonaccijeva broja. Zadatak vam je da napišete isti program ali ovaj put ne koristeći rekurzivnu metodu nego metodu iteracije.

switch naredbeU lekciji 4 smo naučili koristiti if i if/else naredbe. One mogu postati prilično konfuzne kad su preduboko ugnježdene, a C++ nam nudi i alternativu. Za razliku od if, koji pruža jednu vrijednost, switch naredbe nam omogućuju grananje ovisno o više različitih vrijednosti. Opći oblik switch naredbe je:

switch (izraz)

{case vrijednostJedan: naredba;

break;

case vrijednostDva: naredbat;

break;

....case vrijednostN: naredba;

break;

default: naredba;

}

Page 86: C++ PRIRUCNIK

86 C++ programer

Ako jedna od case vrijednosti daje iti rezultat kao i izraz, izvršava se navedeni blok naredbi i iskače se iz petlje. Ako se niti jedna vrijednost ne poklapa, izvršava se default blok naredbi, ako je naveden. U slučaju da default blok nije naveden cijela petlja se preskače.

Listing 7.16. Demonstracija switch naredbe

1: //Listing 7.162: // Demonstrates switch statement3:4: #include <iostream.h>5:6: int main()7: {8: unsigned short int number;9: cout << "Enter a number between 1 and 5: ";10: cin >> number;11: switch (number)12: {13: case 0: cout << "Too small, sorry!";14: break;15: case 5: cout << "Good job!\n"; // fall through16: case 4: cout << "Nice Pick!\n"; // fall through17: case 3: cout << "Excellent!\n"; // fall through18: case 2: cout << "Masterful!\n"; // fall through19: case 1: cout << "Incredible!\n";20: break;21: default: cout << "Too large!\n";22: break;23: }24: cout << "\n\n";25: return 0;26: }

Output: Enter a number between 1 and 5: 3Excellent!Masterful!Incredible!

Enter a number between 1 and 5: 8Too large!

Page 87: C++ PRIRUCNIK

87

Uporaba switch naredbe za menijeListing 7.17 vraća for(;;) petlju u fokus. To su takozvane vječne petlje, koje se izvršavaju dok ne nalete na break naredbu. Vječnu petlju koristimo za kreiranje menija, gdje korisnik ima mogućnost odabira određene akcije koja se izvrši a potom se vraćamo na meni i tako sve dok ne odlućijo izaći iz programa.

Listing 7.17. Demonstracija vječne petlje

1: //Listing 7.172: //Using a forever loop to manage3: //user interaction4: #include <iostream.h>5:6: // types & defines7: enum BOOL { FALSE, TRUE };8: typedef unsigned short int USHORT;9:10: // prototypes11: USHORT menu();12: void DoTaskOne();13: void DoTaskMany(USHORT);14:

15: int main()16: {17:

18: BOOL exit = FALSE;19: for (;;)20: {21: USHORT choice = menu();22: switch(choice)23: {24: case (1):25: DoTaskOne();26: break;27: case (2):28: DoTaskMany(2);29: break;30: case (3):31: DoTaskMany(3);32: break;33: case (4):34: continue; // redundant!35: break;36: case (5):37: exit=TRUE;38: break;39: default:40: cout << "Please select again!\n";41: break;42: } // end switch43:

44: if (exit)

Page 88: C++ PRIRUCNIK

88 C++ programer

45: break;46: } // end forever47: return 0;48: } // end main()49:

50: USHORT menu()51: {52: USHORT choice;53:

54: cout << " **** Menu ****\n\n";55: cout << "(1) Choice one.\n";56: cout << "(2) Choice two.\n";57: cout << "(3) Choice three.\n";58: cout << "(4) Redisplay menu.\n";59: cout << "(5) Quit.\n\n";60: cout << ": ";61: cin >> choice;62: return choice;63: }64:

65: void DoTaskOne()66: {67: cout << "Task One!\n";68: }69:

70: void DoTaskMany(USHORT which)71: {72: if (which == 2)73: cout << "Task Two!\n";74: else75: cout << "Task Three!\n";76: }

Output: **** Menu ****

(1)Choice one.(2)Choice two.(3)Choice three.(4)Redisplay menu.(5)Quit.

: 1Task One!**** Menu ****

(1)Choice one.(2)Choice two.(3)Choice three.(4)Redisplay menu.(5)Quit.

: 3Task Three!**** Menu ****

Page 89: C++ PRIRUCNIK

89(1)Choice one.(2)Choice two.(3)Choice three.(4)Redisplay menu.(5)Quit.

: 5

Kviz1. Kako inicijaliziramo više od jedne varijeble u for petlji?2. Zašto izbjegavamo goto naredbu? 3. Je li moguće napisati for petlju s tijelom koje se nikad ne izvršava?4. Je li moguće umetniti for petlju unutar druge for petlje? 5. Je li moguće napraviti petlju koja nikad ne završava? Dajte primjer.6. Što se događa kad kreirate petlju koja nikad ne završava?

Vježbe1. Što je vrijednost od x naredbe kad petlja dođe do kraja?

for (int x = 0; x < 100; x++)

2. Napišite ugnježdenu for petlju koja ispisuje uzorak 0 na površini 10*10 znakova.3. Napišite for naredbu koja će brojati od 100 do 200 za 2. 4. Napišitewhile petlju koja će brojati od 100 do 200 za 2.5. Napišite do...while petlju koja bi brojala od 100 to 200 za 2.6. BUG BUSTERS: Što ne valja s ovim kodom?

int counter = 0while (counter < 10)

{cout << "counter: " << counter;

}

7. BUG BUSTERS: Što ne valja s ovim kodom? for (int counter = 0; counter < 10; counter++);

cout << counter << " ";

8. BUG BUSTERS: Što ne valja s ovim kodom? int counter = 100;while (counter < 10)

{cout << "counter now: " << counter;counter--;

}

9. BUG BUSTERS: Što ne valja s ovim kodom? cout << "Enter a number between 0 and 5: ";cin >> theNumber;switch (theNumber)

{case 0:

doZero();

case 1: // fall through

Page 90: C++ PRIRUCNIK

90 C++ programer

case 2: // fall through case 3: // fall through case 4: // fall through

case 5:

doOneToFive();break;default:

doDefault();break;

}

Page 91: C++ PRIRUCNIK

91

Lekcija 8 Pokazivači

Jedno od najmoćnijih svojstava C++ jezika je sposobnost direktnog manipuliranja memorijom korištenjem pokazivača (engl. pointer). Danas ćete naučiti

Što su pokazivači. Kako deklarirati i koristiti pokazivače. Što je to slobodno pohranjivanje i kako manipulirati memorijom.

Pokazivači su za početnika vrlo konfuzni, posebice stoga što nije odmah očito zašto su nam uopće potrebni. Iako ova lekcija detaljno objašnjava njihovu ulogu i djelovanje, u potpunosti ćete razumjeti potrebu za pokazivačima tek tjekom nadolazećih lekcija.

Što je pokazivač?

Za razumijevanje pokazivača potrebno nam je osnovno poznavanje računalne memorije. Računalna memorija je podijeljena u sekvencijalno pobrojane memorijske lokacije. Svaka varijabla je pohranjena na njoj jedinstvenu lokaciju u memoriji, znanoj kao adresa varijable.

Različita računala koriste različite sheme adresiranja memorije. Obično programeri ne trebaju znati tičnu adresu bilo koje varijable, zato što se kompajler brine o takvim detaljima. Ako želite svejedno znati tu informaciju, možete koristiti, address of operator (&), kako je ilustrirano na listingu 8.1.

Listing 8.1. Demonstracija adrese varijabli.1: // Listing 8.1 Demonstrates address of operator2: // and addresses of local variables3:4: #include <iostream.h>5:6: int main()7: {8: unsigned short shortVar=5;9: unsigned long longVar=65535;10: long sVar = -65535;11:12: cout << "shortVar:\t" << shortVar;13: cout << " Address of shortVar:\t";14: cout << &shortVar _<< "\n";15:16: cout << "longVar:\t" << longVar;17: cout << " Address of longVar:\t" ;18: cout << &longVar _<< "\n";19:20: cout << "sVar:\t" << sVar;21: cout << " Address of sVar:\t" ;22: cout << &sVar _<< "\n";23:24: return 0;25: }

Page 92: C++ PRIRUCNIK

92 C++ programer

Output: shortVar: 5 Address of shortVar: 0x8fc9:fff4longVar: 65535 Address of longVar: 0x8fc9:fff2sVar: -65535 Address of sVar: 0x8fc9:ffee(Vaš ispis može izgledati drugačije.)

Ne postoji razlog za praćenje prave adrese svih varijabli koje koristite. Ono što je vama bitno jest to da svaka od njih ima svoju adrsu, te da rezervira određenu količinu memorije, ovisno o tipu podatka kojeg čuva.

Spremanje adrese u pokazivač

Svaka varijabla ima adresu. Čak i bez poznavanja specifične adrese zadane varijable, vi možete pohraniti tu vrijednost u pokazivač.Na primjer, pretpostavimo da je howOld integer varijabla. Za deklariranje pokazivača zvanog pAge za čuvanje njezine adrese, napisali biste

int *pAge = 0;

Ovime deklariramo pAge kao pokazivač na int. Odnsno, pAge je deklariran da drži adresu od int.Primjetite da je pAge varijabla kao i svaka druga. Kad deklarirate integer varijablu, ona je postavljena da čuva cijeli broj. Kad deklarirate pokazivač na varijablu pAge, on je spreman za čuvanje adrese. U ovom primjeru pAge je inicijaliziran na 0. Pokazivač čija vrijednost je nula, zove se null pointer. Svi pokazivači, prilikom kreiranja, trebaju biti inicijalizirani na nešto. Ako ne znate što bi trebali dodjeliti pokazivaču, dodijelite mu 0. Neinicijalizirani pokazivač nazivamo divljim pokazivačem (engl. wild pointer). Takvi pokazivači su veoma opasni. Ako inicijalizirate pokazivač na 0, morate specificirati pridruživanje adrese od howOld u pAge. Evo primjera kako to postići:

unsigned short int howOld = 50; // make a variableunsigned short int * pAge = 0; // make a pointerpAge = &howOld; // put howOld's address in pAge

U ovom trenutku, pAge ima kao svoju vrijednost ima adresu od howOld. howOld, i dalje ima vrijednost 50. Ovo smo mogli i kraće napisati

unsigned short int howOld = 50; // make a variableunsigned short int * pAge = &howOld; // make pointer to howOld

pAge je sada pokazivač na adresu howOld varijable. Uporabom pAge, mi možemo ustvari odrediti vrijednost od howOld, što je u ovom slučaju 50. Pristupanje howOld varijabli uporabom pokazivača pAge zovemo indirekcija stoga što indirektno pristupamo varijabli howOld koristeći pAge. Kasnije ćete vidjeti kako koristiti indirekciju za pristupanje vrijednosti varijable.

Page 93: C++ PRIRUCNIK

93

Imena pokazivača

Pravila za imenovanje pokazivača su ista kao i za sve ostale varijable u C++ jeziku. Mi ćemo kroz lekcije slijediti konvenciju imenovanja pokazivača s početinim slvom p, kao i pAge ili pNumber.

Operator indirekcije

Operator indirekcije (*) se također zove i operator dereferenciranja (engl. dereferencing, indirection operator). Kada pokazivač dereferenciramo, zapravo dobijamo vrijednost spremljenu na adresi pokazivača, a ne samu adresu.Normalne varijable pružaju nam direktan pristup vlastitim vrijednostima. Ako kreirate novu varijablu tipa unsigned short int imena yourAge, a potom zaželite pridružiti vrijednost iz howOld toj novoj varijabli, napisat ćete

unsigned short int yourAge;yourAge = howOld;

Pokazivač nam omogućuje indirektan pristup varijabli čiju adresu pohranjuje. Za pridruživanje vrijednosti iz howOld novoj varijabli yourAge preko pokazivača pAge, napisati ćete

unsigned short int yourAge;yourAge = *pAge;

Indirekcijski operator (*) ispred varijable pAge znači "vrijednost pohranjena na". Ovo pridruživanje nam kaže, "Uzmi vrijednost pohranjenu na adresi na koju pokazuje pAge i pridruži ju u yourAge."

Pažnja: Indirekcijski operator (*) se upotrebljava na dva različita načina s pokazivačima: deklaracijom i dereferenciranjem. Kada pokazivač deklariramo, zvjezdica indicira da je to pokazivać, a ne normalna varijabla. Na primjer,

unsigned short * pAge = 0; // make a pointer to an unsigned short

Kada pokazivač dereferenciramo, indirekcijski operator indicira da vrijednost pri memorijskoj lokaciji pohranjenoj u pokazivaču treba biti pročitana, a ne sama adresa.

*pAge = 5; // assign 5 to the value at pAge

Također primjetite da isti znak (*) koristimo i kao operator množenja. Kompajler zna kojeg operatora pozvati, ovisno o kontekstu.

Pokazivači, adrese i varijable

Važno je razlikovati sam pokazivač, adresu koju on sadrži, i vrijednost na adresi koju sadrži pokazivač. Ovo je uzrok mnogobrojnih zabuna o pokazivačima.Pogledajte slijedeći odlomak koda:

int theVariable = 5;int * pPointer = &theVariable ;

Page 94: C++ PRIRUCNIK

94 C++ programer

theVariable je deklariran kao cjelobrojna varijabla inicijalizirana vrijednošću 5. pPointer je deklariran kao pokazivač na cjeli broj, inicijaliziran je s adresom od theVariable. pPointer je pokazivač. Adresa koju pPointer čuva je adresa od theVariable. Vrijednost na adresi koju pokazivač čuva je 5.

Manipuliranje podacima upotrebom pokazivača

Kada jednom pokazivač pridružimo adresi varijable, možemo ga koristiti za pristupanje podacima te varijable. Listing 8.2 demonstrira kako je adresa lokalne varijable dodjeljena pokazivaču i kako pokazivač manipulira vrijednostima te varijable.

Listing 8.2. Manipulirajne podacima uporabom pokazivača.

1: // Listing 8.2 Using pointers2:3: #include <iostream.h>4:5: typedef unsigned short int USHORT;6: int main()7: {8: USHORT myAge; // a variable9: USHORT * pAge = 0; // a pointer10: myAge = 5;11: cout << "myAge: " << myAge << "\n";12:13: pAge = &myAge; // assign address of myAge to pAge14:15: cout << "*pAge: " << *pAge << "\n\n";16:17: cout << "*pAge = 7\n";18:19: *pAge = 7; // sets myAge to 720:21: cout << "*pAge: " << *pAge << "\n";22: cout << "myAge: " << myAge << "\n\n";23:24:25: cout << "myAge = 9\n";26:27: myAge = 9;28:29: cout << "myAge: " << myAge << "\n";30: cout << "*pAge: " << *pAge << "\n";31:32: return 0;33: }Output: myAge: 5*pAge: 5*pAge = 7*pAge: 7myAge: 7myAge = 9myAge: 9

Page 95: C++ PRIRUCNIK

95*pAge: 9

Ispitivanje adresa

Pokazivači vam omogućuju manipuliranje adresama čak i kad ne znate njihovu stvarnu vrijednost. Nakon ovog eksperimenta uzimat ćete zdravo za gotovo da dodjeljivanje adrese varijable pokazivaču uistinu sadrži adresu varijable kao svoju vrijednost. Ali samo ovaj put ćemo to i u praksi potvrditi.

Listing 8.3. Otkrivanje što je spremljeno u pokazivaču.

1: // Listing 8.3 What is stored in a pointer.2:3: #include <iostream.h>4:5: typedef unsigned short int USHORT;6: int main()7: {8: unsigned short int myAge = 5, yourAge = 10;9: unsigned short int * pAge = &myAge; // a pointer10:11: cout << "myAge:\t" << myAge << "\tyourAge:\t" << yourAge << "\n";12: cout << "&myAge:\t" << &myAge << "\t&yourAge:\t" << &yourAge <<"\n";13:14: cout << "pAge:\t" << pAge << "\n";15: cout << "*pAge:\t" << *pAge << "\n";16:17: pAge = &yourAge; // reassign the pointer18:19: cout << "myAge:\t" << myAge << "\tyourAge:\t" << yourAge << "\n";20: cout << "&myAge:\t" << &myAge << "\t&yourAge:\t" << &yourAge <<"\n";21:22: cout << "pAge:\t" << pAge << "\n";23: cout << "*pAge:\t" << *pAge << "\n";24:25: cout << "&pAge:\t" << &pAge << "\n";26: return 0;27: }Output: myAge: 5 yourAge: 10&myAge: 0x355C &yourAge: 0x355EpAge: 0x355C*pAge: 5myAge: 5 yourAge: 10&myAge: 0x355C &yourAge: 0x355EpAge: 0x355E*pAge: 10&pAge: 0x355A

Page 96: C++ PRIRUCNIK

96 C++ programer

Pokazivači

Za deklariranje pokazivača, napišite tip varijable, napišite tip varijable ili objekta čija adresa će biti pohranjena u pokazivaču, pa zatim pointer operator (*) i ime pokazivača. Na primjer,

unsigned short int * pPointer = 0;

Za pridruživanje ili inicijaliziranje pokazivača, pridružite mu ime varijable sa address of operatorom (&). Na primjer,

unsigned short int theVariable = 5;unsigned short int * pPointer = & theVariable;

Za dereferenciranje pokazivača, pridružite ime pokazivača sa operatorom dereferenciranja. NA primjer,

unsigned short int theValue = *pPointer

Zašto koristiti pokazivače?

Do sada ste naučili kako dodijeliti adresu varijable nekom pokazivaču. U praksi, zapravo, to nikada nećete raditi. Nakon svega, zašto se gnjaviti s pokazivačima kad već imate varijablu sa pristupom željenoj vrijednosti? Jedini razlog za prikazanu vrstu manipulacije pokazivačima je demonstracija funkcioniranja pokazivača. Sada kad ste upoznati sa samom sintaksom, možete ih dobro iskoristiti. Pokazivači se najčešće koriste za slijedeće tri zadaće:

Upravljanje podacima u slobodnom dijelu memorije (engl. free store). Pristupanje podatkovnim članovima i funkcijama neke klase. Prijenos varijabli po vrijednosti u funkciju.

Ostatak lekcije fokusira se na upravljanje podacima u slobdnom dijelu memorije. U narednoj lekciji naučiti ćemo prenositi varijable po referenci.

Stog i slobodna memorija.

Memoriju u grubo možemo podijeliti na 5 kategorija (naravno, ovo se odnosi na dostupnu memoriju za naš program tjekom programiranja):

Prostor za globalna imena Slobodni dio memorije Registri Prostor za kod Stog

Lokalne varijable se smještaju na stog, zajedno sa funkcijskim parametrima. Kod je u prostoru za kod, a globalne varijable, naravno, zauzimaju prostor za globalne varijable. Registri se koriste za internu obradu podataka, kao npr., vođenje brige o vrhu stoga i pokazivaču na instrukcije. Gotovo sva preostala memorija se dodjeljuje slobodnom dijelu memorije, kojeg često nazivamo engleskom riječi heap (gomila, hrpa).Problem sa lokalnim varijablama je u tome što one ne traju. Kada funkcija dođe do returna, sve lokalne varijable se odbacuju. Globalne varijable rješavaju taj problem s cijenom neometanog pristupa iz bilo kojeg dijela programa, što vodi u stvaranje nečitkog i teškog za održavanje koda. Stavljanje podataka u slobodni prostor rješava oba problema.Možete misliti o slobodnom prostoru kao o masivnom bloku memorije u kojem tisuće sekvencijalno adresiranih kockica čekaju vaše podatke. Međutim ove kockice ne

Page 97: C++ PRIRUCNIK

97možete imenovati za razliku od onih koje smještamo u stogu. Morate pitati adresu kockice koju rezervirate, te potom u pravom trenutku proslijediti adresu pokazivaču.Jedan način da ovo sagledate je analogija. Recimo da vam je prijatelj dao svoj telefonski broj. Vi dođete doma, ubacite telefonski broj u memoriju telefona i bacite papirić. Ako pritisnete gumb, njegov telefon će zazvoniti i vaš će se prijatelj javiti. Vi više ne znate telefonski broj, te možda ne znate ni gdje je telefon lociran, ali vam taj gumb daje pristup do vašeg prijatelja.. Vaš prijatelj su u stvari podaci u slobodnom dijelu memorije. Vi ne znate gdje su oni, ali znate doći do njih. Do njih dolazite koristeći njihovu adresu—u našem slučaju telefonski broj. Vi ne morate znati broj, dovoljno je da ga stavite u pokazivač (gumb). Pokazivač vam daje pristup vašim podacima ne gnjaveći vas detaljima.Stog se automatski čisti kad funkcija vrati vrijednost. Sve lokalne varijable odlaze izvan doesga i uklanjaju se sa stoga. Slobodni prostor se ne čisti dok program ne završi, pa je vaša dužnost osloboditi memoriju koju ste rezervirali kad vam više ne treba.Prednost slobodnog prostora je u tome što memorija koju ste rezervirali ostaje dostupna dok ju ekplicitno ne oslobodite. Ako rezervirate memorijuu u slobodnom dijelu dok ste u funkciji, memorija će biti dostupna i kada funkcija završi.Prednost pristupa memoriji na ovaj način u odnosu na globalne varijable je u tome što jedino funkcije s pristpom pokazivaču imaju pristup podacima. To nam omogućuje čvrstu kontrolu sučelja nad tim podacima, te eliminira problem funkcije koja mijenja podatke na neočekivan i nepredviđen način.Kako biste to mogli postići, morate biti sposobni kreirati pokazivač na područje slobodne memorije, te da predajete taj pokazivač među funkcijama, Slijedeći odjeljak nam opisuje kako se to radi.

new

Memoriju alocirate na slobodnom prostoru koristeći ključnu riječ new. new slijedi tip objekta kojeg želite alocrati, kako bi kompajler znao koliko mjesta treba zauzeti. Tako na primjer, new unsigned short int alocira dva bytea na slobodnom prostoru, a new long alocira četiri.Povratna vrijednost iz new je memorijska adresa. Ona mora biti dodijeljena pokazivaču. Za kreiranje unsigned short u slobodnom spremištu, možete napisati

unsigned short int * pPointer;pPointer = new unsigned short int;

Vi naravno možete inicijalizirati pokazivač prilikom kreiranja sa

unsigned short int * pPointer = new unsigned short int;

U svakom slučaju, pPointer sada pokazuje na unsigned short int na slobodnom dijelu memorije. Možete ga koristiti kao i svaki drugi pokazivač na varijablu i dodjeliti vrijednost na taj dio memorije pišući

*pPointer = 72;

Ovo znači, "Stavi 72 kao vrijednost u pPointer," ili "Pridruži vrijednost 72 u područje na slobodnom dijelu memorije na koje pokazuje pokazivač pPointer."Ako new ne može kreirati memoriju na slobodnom spremištu (memorija je, na kraju krajeva, limitirani resurs), vratiti će null pointer. cannot create memory on the free store (memory is, after all, a limited resource) it returns the null pointer. Vi morate provjeriti vaš pokazivač svaki put kad zatražite novu memoriju.

Page 98: C++ PRIRUCNIK

98 C++ programer

UPOZORENJE: Svaki put kad kad alocirate memoriju koristeći ključnu riječ new, morate provjeriti kako bi bili sigurni da pokazivač nije nula.

delete

Kada završite sa upotrebom memorije, morate pozvati naredbu delete na taj pointer. delete vraća memoriju slobodnom spremniku. Zapamtite da je sam pokazivač—za razliku od memorije na koju pokazuje—lokalna varijabla. Kada funkcija u kojoj je deklariran obavi svoje, taj pokazivač odlazi iz dosega i biva izgubljen. Memorija alocirana sa new se svejedno ne oslobađa automatski.Ta memorija postaje nedostupna—situacija koju nazivamo "curenjem memorije" (engl. memory leak). Tako ju zovemo zato što je ta memorija nepovratno izgubljena sve dok program ne završi. To je kao da je memorija iscurila iz vašeg računala. Za vraćanje memorije slobodnom spremniku, koristite ključnu riječ delete. Na primjer,

delete pPointer;Kad obrišete pokazivač, vi ustvari oslobađate memoriju čija adresa je spremljena u pokazivaču. Vi kažete, "Vrati slobodnom spremniku memoriju na koju pokazivač pokazuje." Pokazivač je i daljee pokazivač, i može mu biti dodijeljena nova vrijednost. Listing 8.4 demonstrira alociranje varijable u slobodnom spremniku, korištenjeme te varijable, te zatim njezinim brisanjem.

UPOZORENJE: Kad pozovete delete za pokazivač, memorija na koju on pokazuje je oslobođena. Ponovni poziv delete na isti pokazivač uzrokuje rušenje programa! Kad obrišete pokazivač, postavite ga na nulu (null). Pozivanje delete za null pointer je garantirano sigurno. Na primjer:

Animal *pDog = new Animal; delete pDog; //oslobađa memoriju pDog = 0; //postavlja pointer na nulu //... delete pDog; //bezopasno

Listing 8.4. Alociranje, upotreba i brisanje pokazivača.

1: // Listing 8.42: // Allocating and deleting a pointer3:4: #include <iostream.h>5: int main()6: {7: int localVariable = 5;8: int * pLocal= &localVariable;9: int * pHeap = new int;10: if (pHeap == NULL)11: {12: cout << "Error! No memory for pHeap!!";13: return 0;14: }15: *pHeap = 7;16: cout << "localVariable: " << localVariable << "\n";

Page 99: C++ PRIRUCNIK

9917: cout << "*pLocal: " << *pLocal << "\n";18: cout << "*pHeap: " << *pHeap << "\n";19: delete pHeap;20: pHeap = new int;21: if (pHeap == NULL)22: {23: cout << "Error! No memory for pHeap!!";24: return 0;25: }26: *pHeap = 9;27: cout << "*pHeap: " << *pHeap << "\n";28: delete pHeap;29: return 0;30: }Output: localVariable: 5*pLocal: 5*pHeap: 7*pHeap: 9

Curenje memorije

Drugi način na koji biste mogli nenamjerno uzrokovati curenje memorije je repridruživajući vaš pokazivač prije nego ste obrisali memoriju na koju pokazuje. Pogledajte slijedeći fragmet koda:

1: unsigned short int * pPointer = new unsigned short int;2: *pPointer = 72;3: pPointer = new unsigned short int;4: *pPointer = 84;

Linija 1 kreira pPointer i pridružujue ga adresi u području unutar slobodnog spremnika. Linija 2 stavlja 72 u to memorijsko područje. Linija 3 repridružuje pPointer na drugo memorijsko pdruje, a linija 4 stavlja vrijednost 84 u to novo područje. Originalno područje—ono u kojem je vrijednost 72—je needostupno budući da pokazivač sada pokazuje na drugo memorijsko područje. Ne postoji način da ponovo pristupimo orginalnoj memoriji, niti postoji način da ju oslobodimo prije nego program završi.Kod je trebao biti ovako napisan:

1: unsigned short int * pPointer = new unsigned short int;2: *pPointer = 72;3: delete pPointer;4: pPointer = new unsigned short int;5: *pPointer = 84;

Sad je memorija na koju originalno pokazuje pokazivač pPointer obrisana, pa prema tome i slobodna, u liniji 3.

Pažnja: Za svaki put kad u programu pozovete new, trebao bi biti postojati i jedan poziv za delete. Važno je pratiti koji pokazivač posjeduje memorijsko područje i osigurati da se memorija oslobodi kad nam više nije potrebna.

Page 100: C++ PRIRUCNIK

100 C++ programer

Stvaranje objekata u slobodnom spremištu

Baš kao što možete kreirati pokazivač na cijeli broj, možete kreirati i pokazivač na bilo koji objekt. Ako ste deklarirali objekt tipa Cat, možete deklarirati pokazivač na tu klasu te time napraviti instancu Cat objekta u slobodnom spremniku, isto kao što se dosad kreirali na stogu. Sintaksa je ista kao i za cijele brojeve:

Cat *pCat = new Cat;

Ovime pozivam podrazumijevani konstruktor—onaj bez parametara. Konstruktor se poziva svaki put kad kreiramo novi objekt (kako na stogu tako i na slobodnom spremištu).

Brisanje objekata

Kad pozovete delete za pokazivač na objekt spremljen u slobodnom spremištu, poziva se destrzktor objekta prije nego oslobodimo memoriju. To pruža priliku vašoj klasi da počisti za sobom, baš kao što čini uništavajući objekte na stogu. Listing 8.5 prikazuje stvaranje i brisanje objekta na slobodnom spremištu.

Listing 8.5. Stvaranje i brisanje objekata na slobodnom spremištu.

1: // Listing 8.52: // Creating objects on the free store3:4: #include <iostream.h>5:6: class SimpleCat7: {8: public:9: SimpleCat();10: ~SimpleCat();11 private:12 int itsAge;13 };1415 SimpleCat::SimpleCat()16 {17 cout << "Constructor called.\n";18 itsAge = 1;19 }2021 SimpleCat::~SimpleCat()22 {23 cout << "Destructor called.\n";24 }2526 int main()27 {28 cout << "SimpleCat Frisky...\n";29 SimpleCat Frisky;30 cout << "SimpleCat *pRags = new SimpleCat...\n";31 SimpleCat * pRags = new SimpleCat;

Page 101: C++ PRIRUCNIK

10132 cout << "delete pRags...\n";33 delete pRags;34 cout << "Exiting, watch Frisky go...\n";35 return 0;36 }Output: SimpleCat Frisky...Constructor called.SimpleCat *pRags = new SimpleCat..Constructor called.delete pRags...Destructor called.Exiting, watch Frisky go...Destructor called.

Pristupanje podatkovnim članovima

Podatkovnim i funkcijskim članovima pristupali ste koristeći dot (.) operator za Cat objekte kreirane lokalno. Za pristup Cat objektu na slobodnom prostoru, prvo morate dereferencirati pokazivač i pozvati dot operator na objekt na kojeg pokazuje pokazivač. Prema tome, za pristup GetAge funkcijskom članu napisali biste

(*pRags).GetAge();

Zagrade nam osguravaju kako bi bili sigurni da će pRags biti dereferenciran prije nego li pristupi GetAge() funkciji.Budući da je ovo nepraktično, C++ nam pruža skraćeni operator za indirektan pristup: tzv. points-to operator (->), koji nastaje utipkavanjem minusa (-) kojeg slijedi veće-od simbol (>). C++ tretira ovo kao jedan simbol, a listing 8.6 demonstrira pristupanje funkcijskim i podatkovnim članovima objekta kreiranog u slobodnom spremniku.

Listing 8.6.

1: // Listing 8.62: // Accessing data members of objects on the heap3:4: #include <iostream.h>5:6: class SimpleCat7: {8: public:9: SimpleCat() {itsAge = 2; }10: ~SimpleCat() {}11: int GetAge() const { return itsAge; }12: void SetAge(int age) { itsAge = age; }13: private:14: int itsAge;15: };16:17: int main()18: {19: SimpleCat * Frisky = new SimpleCat;20: cout << "Frisky is " << Frisky->GetAge() << " years old\n";21: Frisky->SetAge(5);

Page 102: C++ PRIRUCNIK

102 C++ programer

22: cout << "Frisky is " << Frisky->GetAge() << " years old\n";23: delete Frisky;24: return 0;25: }Output: Frisky is 2 years oldFrisky is 5 years old

Podatkovni članovi na slobodnom spremniku

Jedan ili više podatkovnih članova klase može biti pokazivač na objekt u slobodnom spremištu. Memorija može biti alocirana u konstruktoru klase ili jednoj od metoda, te može biti obrisan sa svojim destruktorom, kako listing 8.7 prikazuje.

Listing 8.7. Pokazivači kao podatkovni članovi.

1: // Listing 8.72: // Pointers as data members3:4: #include <iostream.h>5:6: class SimpleCat7: {8: public:9: SimpleCat();10: ~SimpleCat();11: int GetAge() const { return *itsAge; }12: void SetAge(int age) { *itsAge = age; }13:14: int GetWeight() const { return *itsWeight; }15: void setWeight (int weight) { *itsWeight = weight; }16:17: private:18: int * itsAge;19: int * itsWeight;20: };21:22: SimpleCat::SimpleCat()23: {24: itsAge = new int(2);25: itsWeight = new int(5);26: }27:28: SimpleCat::~SimpleCat()29: {30: delete itsAge;31: delete itsWeight;32: }33:34: int main()35: {36: SimpleCat *Frisky = new SimpleCat;37: cout << "Frisky is " << Frisky->GetAge() << " years old\n";38: Frisky->SetAge(5);39: cout << "Frisky is " << Frisky->GetAge() << " years old\n";

Page 103: C++ PRIRUCNIK

10340: delete Frisky;41: return 0;42: }Output: Frisky is 2 years oldFrisky is 5 years old

Analiza: Klasa SimpleCat je deklarirana da ima dve varijable—obadve su pokazivači na cijele brojeve—u linijama 14 i 15. Konstruktor (linije 22-26) inicijalizira pokazivače na memoriju u slobodnom spremniku i na njihove default vrijednosti. Destruktor (linije 28-32) čisti alociranu memoriju. Budući da je riječ o destruktoru, nema potrebe za postavljanjem tih pokazivača na null, jer nam ionako neće više biti dostupni. Ovo je jedno od sigurnih mjesta za kršenje pravila da obrisani pokazivač moramo postaviti na null, iako pridržavanje pravila ne bi škodilo.Pozivna funkcija (u ovom slučaju, main()) je nesvjesna da su itsAge i itsWeight ustvari pokazivači na memoriju slobodnog spremišta. main() nastavlja zvati GetAge() i SetAge(), pa su detalji oko upravljanja memorijom skriveni u implementaciji klase—onako kako to i treba biti.Kad je Frisky obrisan u liniji 40, njegov destruktor je pozvan. Destruktor briše svakog od pokazivačkih članova. Ako ovi pokazuju na objekt druge korisnički definirane klase, i njihovi se destruktori pozivaju.

this pokazivač

Svaki funkcijski član klase ima skriveni parametar: this pointer. this pokazuje na individualni objekt. Prema tome, svaki poziv GetAge() ili SetAge()funkciji, uzrokuje uključivanje this pokazivača kao skrivenog parametra. Moguće je koristiti i this pointer explicitno, kao što to pokazuje listing 8.8.

Listing 8.8. Upotreba this pokazivača.

1: // Listing 8.82: // Using the this pointer3:4: #include <iostream.h>5:6: class Rectangle7: {8: public:9: Rectangle();10: ~Rectangle();11: void SetLength(int length) { this->itsLength = length; }12: int GetLength() const { return this->itsLength; }13:14: void SetWidth(int width) { itsWidth = width; }15: int GetWidth() const { return itsWidth; }16:17: private:18: int itsLength;19: int itsWidth;20: };21:22: Rectangle::Rectangle()23: {24: itsWidth = 5;

Page 104: C++ PRIRUCNIK

104 C++ programer

25: itsLength = 10;26: }27: Rectangle::~Rectangle()28: {}29:30: int main()31: {32: Rectangle theRect;33: cout << "theRect is " << theRect.GetLength() << " feet long.\n";34: cout << "theRect is " << theRect.GetWidth() << " feet wide.\n";35: theRect.SetLength(20);36: theRect.SetWidth(10);37: cout << "theRect is " << theRect.GetLength()<< " feet long.\n";38: cout << "theRect is " << theRect.GetWidth()<< " feet wide.\n";39: return 0;40: }Output: theRect is 10 feet long.theRect is 5 feet long.theRect is 20 feet long.theRect is 10 feet long.

Analiza: SetLength() i GetLength() pristupne funkcije eksplicitno koriste this pointer kako bi pristupile podatkovnim članovima Rectangle objekta. SetWidth i GetWidth pristupnici ne koriste. Nema nikakve razlike u njihovu ponašanju, iako je njihova sintaksa razumljivija. Da je to sve što nam donosi this pointer, nebi bilo smisla gnjaviti Vas sa tim. Međutim, this pointer, ima sva svojstva pokazivača, dakle, sprema memorijsku adresu objekta. Kao takav, on može biti moćno oruđe.Vidjeti ćete praktičnu vrijednost this pokazivača u lekciji 10, "Napredne funkcije", kad budemo diskutirali o preopterećenju operatora. Za sada, vaš cilj je saznanje da this pointer postoji i da shvatite što je on: pokazivač na sam objekt. Ne morate brinuti oko kreiranja ili brisanja this poikazivača. Kompajler se brine o tome.

"Viseći" pokazivači (engl. stray or dangling pointers)

Gadan i teško otkriv izvor bugova su viseći pokazivači. Veiseći pokazivač se stvara kad pozovete delete na pokazivač—time oslobađajući memoriju na koju pokazuje—a kasnije ga pokušate upotrijebiti a da mu prethodno niste pridružili novu vrijednost.To je kao da je onaj vaš promjenio telefonski broj (recimo da je prešao sa CRONETA na VIP mrežu ), a vi idalje pritišćete gumb s programiranim brojem u vašem telefonu. Ukratko, pazite da ne koristite pokazivač nakon što ste pozvali naredbu delete za njega. Pokazivač još uvijek pokazuje na stari dio memorije, ali kompajler je slobodan staviti druge podatke tamo; to može uzrokovati rušenje vašeg programa. Što je još gore, vaš program se može lijepo ponašati prilikom toga poziva, ali se srušiti nekoliko minuta kasnije. To se zove vremenska bomba (engl. time bomb), i uopće nije zabavno. Kako biste bili sigurni, nakon što obrišete pokazivač, postavite ga na null (0). Time ćete ga potpuno razoružati.

const pokazivači

Možete koristiti ključnu riječ const za pokazivače stavljajući je prije tipa, nakon tipa, ili na oba mjesta. Na primjer, sve ovo su legalne deklaracije:

const int * pOne;

Page 105: C++ PRIRUCNIK

105int * const pTwo;const int * const pThree;

pOne je pokazivač na konstantan integer. Vrijednost na koju pokazuje ne može biti promijenjena.pTwo je konstantan pokazivač na cijeli broj. Sam broj može biti promijenjen, ali pTwo ne može pokazivati na neki drugi objekt ili varijablu.pThree je konstantan pokazivač na konstantni cijeli broj. Vrijednost na koju pokazuje ne može biti promijenjena, a pThree ne može pokazivati ni na što drugo.Trik je u tope da uvijek gledamo desno od riječi const kako bi vidjeli što proglašavamo konstantom. Ako je desno od const, tip varijable, vrijednost će morati biti konstantna. Ako je varijabla s desne strane ključne riječi const, pokazivač je ono što proglašavamo konstantom.

const int * p1; // the int pointed to is constantint * const p2; // p2 is constant, it can't point to anything else

const pokazivači i konst funkcijski članovi

U lekciji 6, "Osnovne klase", naučili ste da možete riječ const primjeniti i na funkcijski član. Kada je funkcija deklarirana kao const, kompajler javlja greškom svaki pokušaj mijenjanja podataka unutar objekta iz te funkcije.Ako deklarirate pokazivač na const objekt, jedine metode koje možete pozvati s tim pokazivačem su const metode.

Listing 8.10. Upotreba pokazivača na const objektima.

1: // Listing 8.102: // Using pointers with const methods3:4: #include <iostream.h>5:6: class Rectangle7: {8: public:9: Rectangle();10: ~Rectangle();11: void SetLength(int length) { itsLength = length; }12: int GetLength() const { return itsLength; }13:14: void SetWidth(int width) { itsWidth = width; }15: int GetWidth() const { return itsWidth; }16:17: private:18: int itsLength;19: int itsWidth;20: };21:22: Rectangle::Rectangle():23: itsWidth(5),24: itsLength(10)25: {}26:27: Rectangle::~Rectangle()

Page 106: C++ PRIRUCNIK

106 C++ programer

28: {}29:30: int main()31: {32: Rectangle* pRect = new Rectangle;33: const Rectangle * pConstRect = new Rectangle;34: Rectangle * const pConstPtr = new Rectangle;35:36: cout << "pRect width: " << pRect->GetWidth() << " feet\n";37: cout << "pConstRect width: " << pConstRect->GetWidth() << " feet\n";38: cout << "pConstPtr width: " << pConstPtr->GetWidth() << " feet\n";39:40: pRect->SetWidth(10);41: // pConstRect->SetWidth(10);42: pConstPtr->SetWidth(10);43:44: cout << "pRect width: " << pRect->GetWidth() << " feet\n";45: cout << "pConstRect width: " << pConstRect->GetWidth() << " feet\n";46: cout << "pConstPtr width: " << pConstPtr->GetWidth() << " feet\n";47: return 0;48: }Output: pRect width: 5 feetpConstRect width: 5 feetpConstPtr width: 5 feetpRect width: 10 feetpConstRect width: 5 feetpConstPtr width: 10 feet

Analiza: Linije 6-20 deklariraju Rectangle. Linija 15 deklarira GetWidth() člansku metodu kao const. Linija 32 deklarira pokazivač na Rectangle. Linija 33 deklarira pConstRect, što je pokazivač na konstantu Rectangle. Linija 34 deklarira pConstPtr, što je konstantan pokazivač na Rectangle. Linije 36-38 ispisuju njihove vrijednosti. U liniji 40, pRect koristimo za postavljanje širine pravokutnika na 10. U linij 41, pConstRect bi bio upotrebljen, ali je deklariran da pokazuje na konstantu Rectangle. Prema tome, on ne može legalno pozvati nekonstantan funkcijsku član; zao je umetnut u komentar. U liniji 38, pConstPtr zove SetAge(). pConstPtr je deklariran kao konstantan pokazivač na pravokutnik. Drugim riječima pokazivač je konstanta i ne može pokazivati ni na što drugo osim pravokutnika., ali pravokutnik nije konstanta.

const this pokazivači

Kad deklarirate objekt kao const, vi efektivno deklarirate this pointer kao pokazivač na const objekt. const this pokazivač može se koristiti samo sa const funkcijskim članovima. Konstantni objekti i konstantni pokazivači bit će i naša sutrašnja tema, kad budemo raspravljali o referencama na konstantne objekte.

Kviz

1. Koji operator koristimo za određivanje adrese od varijable?2. Koji operator koristimo za pronalaženje vrijednosti pohranjene na adresi na koju pokazuje pokazivač? 3. Što je pokazivač? 4. Koja je razlika između adres pohranjene u pokazivaču i vrijednosti na toj adresi?

Page 107: C++ PRIRUCNIK

1075. Koja je razlika između operatora indirekcije i adress of operatora?6. Koja je razlika između const int * ptrOne i int * const ptrTwo?

Vježbe

1. Što ove deklaracije rade?

a. int * pOne;

b. int vTwo;

c. int * pThree = &vTwo;

2. Ako imate unsigned short varijablu imena yourAge, kako biste deklarirali pokazivač za manipuliranje sa yourAge?

3. Pridružite vrijednost 50 varijabli e yourAge koristeći pokazivač koji ste deklarirali u vježbi 2.

4. BUG BUSTERS: Što ne valja s slijedećim kodom?

#include <iostream.h>int main(){ int *pInt; *pInt = 9; cout << "The value at pInt: " << *pInt; return 0;}

6. BUG BUSTERS: Što ne valja sa slijedećim kodom?

int main(){ int SomeVariable = 5; cout << "SomeVariable: " << SomeVariable << "\n"; int *pVar = & SomeVariable; pVar = 9; cout << "SomeVariable: " << *pVar << "\n";return 0;}

Page 108: C++ PRIRUCNIK

108 C++ programer

Lekcija 9 Reference

Jučer ste naučili kako koristiti pokazivače za manipuliranje objektima u slobodnom spremniku i kako ih pozivati preko njihove adrese. Reference, naša današnja teme, daju vam gotovo svu moć kao i pokazivači, ali s mnogo jednostavnijom sintaksom. Danas ćemo naučiti slijedeće

Što su reference. Kako se referenca razlikuje od pokazivača. Kako kreirati i koristiti reference. Koja su njihova ograničenja. Kako prosljeđivati vrijednosti i objekte u i iz funkcija preko referenci.

Što je referenca?

Referenca je zapravo alias; kad kreirate referencu, inicijalizirate ju s imenom drugog, ciljanog objekta. Od tog trenutka, referenca nam služi kao alternativno ime za ciljani objekt, te sve što radite na referenci ima svoje posljedice na ciljanom objektu. Referencu kreirate pišući tip ciljanog objekta, kojeg slijedi operator reference (&), kojeg slijedi ime reference. Referanca može imati bilo koje ime legalno za varijablu, ali u našim lekcijama ćemo sve reference označavati prefiksom "r". Tako, ako imate integer varijablu imena someInt, možete napraviti referencu na tu varijablu pišući slijedeće:

int &rSomeRef = someInt;

Ovo čitamo kao "rSomeRef je referenca na integer koja je inicijalizirana da se odnosi na someInt." Listing 9.1 nam pokazuje kako se reference kreiraju i koriste.

PAŽNJA: Primjetite da je operator reference (&) isti simbol kao i operator adrese. To nisu isti operatori, iako su, jasno je, na određeni način povezani.

Listing 9.1. Kreiranje i upotreba referenci.

1: //Listing 9.12: // Demonstrating the use of References3:4: #include <iostream.h>5:6: int main()7: {8: int intOne;9: int &rSomeRef = intOne;10:11: intOne = 5;12: cout << "intOne: " << intOne << endl;13: cout << "rSomeRef: " << rSomeRef << endl;14:15: rSomeRef = 7;16: cout << "intOne: " << intOne << endl;

Page 109: C++ PRIRUCNIK

10917: cout << "rSomeRef: " << rSomeRef << endl;18: return 0;19: }Output: intOne: 5rSomeRef: 5intOne: 7rSomeRef: 7

Analiza: U liniji 8, lokalna int varijabla, intOne, je deklarirana. U liniji 9, referenca na int, rSomeRef, je deklarirana i inicijalizirana da se odnosi na intOne. Ako deklarirate referencu, ali ju ne inicijalizirate, dobit ćete greku prilikom kompajliranja. Reference moraju biti inicijalizirane.U liniji 11, intOne varijabli je pridružena vrijednost 5. Na linijama 12 i 13, vrijednosti u intOne i rSomeRef se ispisuju, i naravno, one su iste.U liniji 15, 7 je pridružen u rSomeRef. Budući da je riječ o referenci, drugm imenu za varijablu intOne, i time je vrijednost 7 u stvari dodijeljena u intOne, kao što se vidi na ispisu u redovima 16 i 17.

Upotreba operatora adrese & na referencama

Ako pitate referencu za njezinu adresu, ona vraća adresu varijable na koju je povezana. To je stoga što je, rekli smo, sama referenca samo alias, drugo ime za varijablu. Listing 9.2 nam to i demonstrira.

Listing 9.2. Adresa reference.

1: //Listing 9.22: // Demonstrating the use of References3:4: #include <iostream.h>5:6: int main()7: {8: int intOne;9: int &rSomeRef = intOne;10:11: intOne = 5;12: cout << "intOne: " << intOne << endl;13: cout << "rSomeRef: " << rSomeRef << endl;14:15: cout << "&intOne: " << &intOne << endl;16: cout << "&rSomeRef: " << &rSomeRef << endl;17:18: return 0;19: }Output: intOne: 5rSomeRef: 5&intOne: 0x3500&rSomeRef: 0x3500

Analiza: Još jednom je rSomeRef inicijaliziran i referenciran na intOne. Ovaj put ispisujemo adrese dvaju varijabli, i one su identične. C++ nam ne pruža način pristupa

Page 110: C++ PRIRUCNIK

110 C++ programer

adresi same reference budući da to nema nikakvoga značenja, kao što bi imalo da koristite pokazivač ili neku drugu varijablu. Reference se inicijaliziraju prilikom kreiranja, i uvijek služe kao sinonim za njihov cilj, čak i kad primjenjujemo address of operator. Na primjer, ako imate klasu zvanu President, mođda želite deklarirati instancu te klase kako slijedi:

President William_Jefferson_Clinton;Onda bi mogli deklarirati referencu na President i inicijalizirati ju na taj objekt:

President &Bill_Clinton = William_Jefferson_Clinton;Postoji samo jedan President; oba identifikatora se odnose na isti objekt iste klase. Svaku akciju koju pokrenete preko Bill_Clinton imat će efekt i na William_Jefferson_Clinton također.Uočite razliku između & simbola u liniji 9 listinga 9.2, koja deklarirareferencu na int nazvanu rSomeRef, a & simbole u linijama 15 i 16, koji vraćaju adresu od integer varijable intOne i reference rSomeRef.Normalno, prilikom realne upotrebe reference nećete koristiti address of operator. Jednostavno ćete koristiti referencu kao da je riječ o ciljanoj varijabli. To se vidi u liniji 13.

Čak i iskusni C++ programeri, koji znaju pravilo da reference ne mogu biti promijenjene i uvijek su alijasi na istu varijablu, mogu biti zbunjeni s pitanjem "Što se događa kad pokušavamo "repridružiti" referencu. Ono što izgleda kao ponovno pridruživanje, zapravo je pridruživanje nove vrijednosti ciljanoj varijabli. Listing 9.3 nam to ilustrira.

Listing 9.3. Pridruživanje reference.

1: //Listing 9.32: //Reassigning a reference3:4: #include <iostream.h>5:6: int main()7: {8: int intOne;9: int &rSomeRef = intOne;10:11: intOne = 5;12: cout << "intOne:\t" << intOne << endl;13: cout << "rSomeRef:\t" << rSomeRef << endl;14: cout << "&intOne:\t" << &intOne << endl;15: cout << "&rSomeRef:\t" << &rSomeRef << endl;16:17: int intTwo = 8;18: rSomeRef = intTwo; // not what you think!19: cout << "\nintOne:\t" << intOne << endl;20: cout << "intTwo:\t" << intTwo << endl;21: cout << "rSomeRef:\t" << rSomeRef << endl;22: cout << "&intOne:\t" << &intOne << endl;23: cout << "&intTwo:\t" << &intTwo << endl;24: cout << "&rSomeRef:\t" << &rSomeRef << endl;25: return 0;26: }Output: intOne: 5rSomeRef: 5&intOne: 0x213e

Page 111: C++ PRIRUCNIK

111&rSomeRef: 0x213eintOne: 8intTwo: 8rSomeRef: 8&intOne: 0x213e&intTwo: 0x2130&rSomeRef: 0x213e

Analiza: Još jednom, cjelobrojna varijabla i referenca na cijeli broj su deklarirani, u linijama 8 i 9. Integeru je pridružena vrijednost 5 u liniji 11, i vrijednosti i njihove adrese se ispisuju u linijama 12-15.U liniji 17, nova varijabla, intTwo, je kreirana i inicijalizirana s vrijednošću 8. U liniji 18, programer pokušava pridružiti rSomeRef da sada bude alias na vaijablu intTwo, ali to se ne događa. Što se zapravo događa? U stvari rSomeRef ostaje alias na intOne, pa je zapravo to pridruživanje jednako ovom:

intOne = intTwo;Sada nam je jasno da vrijednosti intOne i rSomeRef prilikom ispisa (linije 19-21) imaju istu vrijednost kao i intTwo. U stvari, kad im ispisujemo adrese na linijama 22-24, primjetite da rSomeRef nastavlja pokazivati na intOne a ne na intTwo.

Što može biti referencirano?

Bilo koji objekt može biti referenciran, uključujući i korisnički definirane objekte. Primjetite da kreirate referencu na objekt, a ne na klasu. Vi nećete napisati:

int & rIntRef = int; // wrongVi morate inicijalizirati rIntRef na određeni cijeli broj, kao npr.:

int howBig = 200;int & rIntRef = howBig;

Isto tako, ne inicijalizirate referencu na CAT:CAT & rCatRef = CAT; // wrong

Vi morate inicijalizirati rCatRef na određeni CAT objekt:CAT frisky;CAT & rCatRef = frisky;

Reference na objekte se koriste baš kao i sami objekti. Podatkovnim i funkcijskim članovima pristupamo koristeći ormalni pristupni operator (.), I baš isto kao i sa ugrađenim tipovima, reference služe kao alias na objekt. Listing 9.4 ilustrira taj slučaj.

Listing 9.4. Reference na objekte.

1: // Listing 9.42: // References to class objects3:4: #include <iostream.h>5:6: class SimpleCat7: {8: public:9: SimpleCat (int age, int weight);10: ~SimpleCat() {}11: int GetAge() { return itsAge; }12: int GetWeight() { return itsWeight; }13: private:14: int itsAge;15: int itsWeight;

Page 112: C++ PRIRUCNIK

112 C++ programer

16: };17:18: SimpleCat::SimpleCat(int age, int weight)19: {20: itsAge = age;21: itsWeight = weight;22: }23:24: int main()25: {26: SimpleCat Frisky(5,8);27: SimpleCat & rCat = Frisky;28:29: cout << "Frisky is: ";30: cout << Frisky.GetAge() << " years old. \n";31: cout << "And Frisky weighs: ";32: cout << rCat.GetWeight() << " pounds. \n";33: return 0;34: }Output: Frisky is: 5 years old.And Frisky weighs 8 pounds.

Analiza: U liniji 26, Frisky je deklariran kao objekt SimpleCat klase. U liniji 27, SimpleCat referenca, rCat, je deklarirana i inicijalizirana da se odnosi na Friskija. U linijama 30 i 32, SimpleCat pristupnim metodama pristupamo prvo preko SimpleCat objekta, a potom preko njegove reference. Primjetite da je pristup identičan. Ponovno, referenca na alias je stvaran objekt.

Reference

Referencu deklariramo pišući njen tip, kojeg slijedi operator reference (&), kojeg slijedi ime reference. Reference moraju biti inicijalizirane prilikom kreiranja. Primjer 1

int hisAge;int &rAge = hisAge;

Primjer 2CAT boots;CAT &rCatRef = boots;

Nul pokazivači i nul reference

Kada pokazivači nisu inicijalizirani, odn. kada su obrisani, trebala biti im pridružena nula (0). Ovo ne vrijedi za reference. U stvari, referenca ne može biti nula, a program s referencom na nul objektom se smatra nepravilnim programom. Kad je program nepravilan, nepredviđene stvari se mogu dogoditi.Većina kompajlera će podržavati nul objekte bez mnogo prigovaranja, rušeći se samo ako nakon toga pokušate upotrijebiti objekt na neki način. Iskorištavanje ove osobine svejedno ne zvuči kao naročito dobra ideja. Kad prebacite svoj program na drugi stroj ili kompajler, misteriozni bugovi se mogu razviti ako koristite nul objekte.

Page 113: C++ PRIRUCNIK

113Prosljeđivanje argumenata funkcije po referenci

U lekciji 5, "Funkcije" naučili ste da funkcije imaju dva ograničenja: argumenti se prosljeđuju po vrijednosti (engl. passing by value), i return izjava može vraćati samo jednu vrijednost.Proslijeđivanje vrijednosti u funkciju po referenci može izbjeći oba ograničenja. U C++ jeziku, proslijeđivanje po referenci (engl. passing by reference) postižemo na dva načina: upotrebom pokazivača i upotrebom referenci. Sintaksa je različita ali je konačni rezultat jednak. Umjesto kopije vrijednosti koja se kreira unutar dosega same funkcije, pravi originalni objekt se prosljeđuje funkciji.

PAŽNJA: Ako ste pažljivo čitali dosadašnje lekcije, naučili ste da funkcije svoje parametre postavljaju u dio memorije kojeg zovemo stog. Kad je funkciji proslijeđena vrijednost preko reference (bilo korištenjem pokazivača, bilo referenci), samo adresa objekta se stavlja na stog, a ne cijeli objekt. U stvari, na nekim računalima se adresa čuva u registru i ništa ne ide na stog. U svakom slučaju, kompajler zna kako doći do originalnog objekta i promjene se vrše tamo a ne na njegovoj kopiji.

Proslijeđivanje objekata po referenci omogućuje funkcijama da mijenjaju objekt na kojeg se odnose. Pogledajte listing 5.5 u petoj lekciji i prisjetite se kako swap() funkcija nije utjecala na vrijednosti pozivajuće funkcije. Listing 5.5 je ponovno napisan kao listing 9.5 kako ne biste morali kopati po papirima.

Listing 9.5. Demonstracija prosljeđivanja po vrijednosti.

1: //Listing 9.5 Demonstrates passing by value2:3: #include <iostream.h>4:5: void swap(int x, int y);6:7: int main()8: {9: int x = 5, y = 10;10:11: cout << "Main. Before swap, x: " << x << " y: " << y << "\n";12: swap(x,y);13: cout << "Main. After swap, x: " << x << " y: " << y << "\n";14: return 0;15: }16:17: void swap (int x, int y)18: {19: int temp;20:21: cout << "Swap. Before swap, x: " << x << " y: " << y << "\n";22:23: temp = x;24: x = y;25: y = temp;26:27: cout << "Swap. After swap, x: " << x << " y: " << y << "\n";

Page 114: C++ PRIRUCNIK

114 C++ programer

28:29: }Output: Main. Before swap, x: 5 y: 10Swap. Before swap, x: 5 y: 10Swap. After swap, x: 10 y: 5Main. After swap, x: 5 y: 10

Problem kod ovoga primjera je u tome što se x i y prenose po vrijednosti u swap() funkciju. To znači da su lokalne kopije tih varijabli napravljene unutar same funkcije. Ono što želite je prijenos x i y po referenci.Postoje dva načina za rješavanje tog problema u C++ jeziku. Možete parametre swap() funkcije proglasiti pokazivačima na orginalne vrijednosti, ili možete proslijediti reference na orginalne vrijednosti.

Pravljenje swap() funkcije s pokazivačima

Kada prenesete pokazivač, vi zapravo prenosite adresu objekta, pa time funkcija "zna" manipulirati vrijednošću na toj adresi. kako biste swap() promijenili, morate ju deklarirati da prima dva int pokazivača. Tada, dereferenciranjem pokazivača, vrijednosti od x i y će zapravo biti zamijenjene. Listing 9.6 je demonstracija te ideje.

Listing 9.6. Prijenos po referenci korištenjem pokazivača.

1: //Listing 9.6 Demonstrates passing by reference2:3: #include <iostream.h>4:5: void swap(int *x, int *y);6:7: int main()8: {9: int x = 5, y = 10;10:11: cout << "Main. Before swap, x: " << x << " y: " << y << "\n";12: swap(&x,&y);13: cout << "Main. After swap, x: " << x << " y: " << y << "\n";14: return 0;15: }1617: void swap (int *px, int *py)18: {19: int temp;20:21: cout << "Swap. Before swap, *px: " << *px << " *py: " << *py << "\n";22:23: temp = *px;24: *px = *py;25: *py = temp;26:27: cout << "Swap. After swap, *px: " << *px << " *py: " << *py << "\n";28:29: }Output: Main. Before swap, x: 5 y: 10Swap. Before swap, *px: 5 *py: 10

Page 115: C++ PRIRUCNIK

115Swap. After swap, *px: 10 *py: 5Main. After swap, x: 10 y: 5

Analiza: Bingo! Uspjeli smo! U liniji 5, prototip swap() funkcije je promijenjen kako bi ukazao da su ulazni parametri pokazivači na int umjesto samih int varijabli. Kada pozovemo swap() izu linije 12, adrese od x i y su proslijeđene kao argumenti. U liniji 19, lokalna varijabla, temp, je deklarirana u swap() funkciji. Temp ne mora biti pokazivač, on će samo zadržati vrijednost od *px (odnosno, vrijednost x iz pozivne funkcije) za vrijeme trajanja funkcije. Nakon što funkcija završi, temp nam više neće biti potreban.U liniji 23, temp varijabli je pridružena vrijednost na px. U liniji 24, vrijednost na px je pridružena vrijednosti na py. U liniji 25, vrijednost spremjena u temp (odnosno originalna vrijednost na px) se stavlja na py.Krajnji rezultat je da se vrijednosti u pozivnoj funkciji, koja je samo poslala adrese varijabli, zamijene.

Implementiranje swap() sa referencama

Prethodni program radi, ali je sintaksa swap() funkcije nezgrapna zbog dva razloga. Prvo, ponovljena potreba za dereferenciranjem pokazivača unutar swap() funkcije čini ju podložnom greškama i nečitkom. Drugo, potreba za proslijeđivanjem adrese varijabli iz pozivne funkcije čini unutrašnje funkcioniranje previše očiglednim za korisnika funkcije. Cilj C++ jezika je omogućavanje korisniku da ne vodi brigu kako neka funkcija radi. Stoga je elegantnije napisati funkciju tako da se koristi referencama.

Listing 9.7. swap() preuređen za reference.

1: //Listing 9.7 Demonstrates passing by reference2: // using references!3:4: #include <iostream.h>5:6: void swap(int &x, int &y);7:8: int main()9: {10: int x = 5, y = 10;11:12: cout << "Main. Before swap, x: " << x << " y: " << y << "\n";13: swap(x,y);14: cout << "Main. After swap, x: " << x << " y: " << y << "\n";15: return 0;16: }17:18: void swap (int &rx, int &ry)19: {20: int temp;21:22: cout << "Swap. Before swap, rx: " << rx << " ry: " << ry << "\n";23:24: temp = rx;25: rx = ry;26: ry = temp;

Page 116: C++ PRIRUCNIK

116 C++ programer

27:28: cout << "Swap. After swap, rx: " << rx << " ry: " << ry << "\n";29:30: }

Output: Main. Before swap, x:5 y: 10Swap. Before swap, rx:5 ry:10Swap. After swap, rx:10 ry:5Main. After swap, x:10, y:5

Razumijevanje funkcijskih zaglavlja i prototipova

Listing 9.6 pokazuje swap() koji koristi pokazivače, dok listing 9.7 koristi reference. Upotreba funkcije koja koristi reference je jednostavnija, i kod se lakše čita, ali kako funkcija koja poziva zna kada se parametri proslijeđuju po vrijednosti a kada po referenci? Kao korisnik swap() funkcije, programer mora biti siguran da će swap() uistinu promjeniti parametre. Tu dolazimo do još jedne uloge funkcijskih prototipova, koji su obično u datoteci zaglavlja. Promatrajući datoteku zaglavlja programer zna da su vrijednosti funkciji swap() pridruženi po referenci, i samim time pravilno zamijenjeni.Ako je swap() kojim slučajem bio funkcijski član deklaracije neke klase, to bismo također vidjeli u zaglavlju datoteke prilikom deklaracije klase.U C++ jeziku, korisnici klasa i funkcija se oslanjaju na header datoteku kako bi vidjeli što je sve potrebno; ona služi kao sučelje (engl. interface) prema klasi ili funkciji. Stvarna implementacija je skrivena samome klijentu. To omogućuje programeru da se usredotoči na problem i koristi klasu ili funkciju bez brige o tome kako ona radi.

Vraćanje višestrukih vrijednosti

Kao što smo razmatrali, funkcije mogu vraćati samo jednu vrijednost. Što ako trebamo vratiti dvije vrijednosti iz funkcije? Jedno od rješenja je proslijeđivanje dvaju objekata funkciji, po referenci. Funkcija tada može dodijeliti objektima željene vrijednosti. Budući da proslijeđivanje po referenci omogućuje funkciji mijenjanje originalnih objekata, to je kao i da smo imali dvije povratne vrijednosti. Taj pristup zaobilazi povratnu vrijednost funkcije, koja tada može biti rezervirana za izvještaje o potencijalnim greškama.I još jednom, ovo možemo postići sa referencama ili pokazivačima. Listing 9.8 demonstrira funkciju koja vraća tri vrijednosti: dve kao parametri pokazivača ijednu kao povratnu vrijednost funkcije.

Listing 9.8. Vraćanje vrijednosti sa pokazivačima.

1: //Listing 9.82: // Returning multiple values from a function3:4: #include <iostream.h>5:6: typedef unsigned short USHORT;7:8: short Factor(USHORT, USHORT*, USHORT*);9:10: int main()

Page 117: C++ PRIRUCNIK

11711: {12: USHORT number, squared, cubed;13: short error;14:15: cout << "Enter a number (0 - 20): ";16: cin >> number;17:18: error = Factor(number, &squared, &cubed);19:20: if (!error)21: {22: cout << "number: " << number << "\n";23: cout << "square: " << squared << "\n";24: cout << "cubed: " << cubed << "\n";25: }26: else27: cout << "Error encountered!!\n";28: return 0;29: }30:31: short Factor(USHORT n, USHORT *pSquared, USHORT *pCubed)32: {33: short Value = 0;34: if (n > 20)35: Value = 1;36: else37: {38: *pSquared = n*n;39: *pCubed = n*n*n;40: Value = 0;41: }42: return Value;43: }Output: Enter a number (0-20): 3number: 3square: 9cubed: 27

Vraćanje vrijednosti po referenci

Iako listing 9.8 radi, možemo ga pojednostaviti uporabom referenci umjesto pokazivača. Listing 9.9 pokazuje isti program ponovno napisan kako bi koristio reference i uključio bolje određivanje error varijable preko enumeriranih tipova.

Listing 9.9.Listing 9.8 sa referencama.

1: //Listing 9.92: // Returning multiple values from a function3: // using references4:5: #include <iostream.h>6:7: typedef unsigned short USHORT;

Page 118: C++ PRIRUCNIK

118 C++ programer

8: enum ERR_CODE { SUCCESS, ERROR };9:10: ERR_CODE Factor(USHORT, USHORT&, USHORT&);11:12: int main()13: {14: USHORT number, squared, cubed;15: ERR_CODE result;16:17: cout << "Enter a number (0 - 20): ";18: cin >> number;19:20: result = Factor(number, squared, cubed);21:22: if (result == SUCCESS)23: {24: cout << "number: " << number << "\n";25: cout << "square: " << squared << "\n";26: cout << "cubed: " << cubed << "\n";27: }28: else29: cout << "Error encountered!!\n";30: return 0;31: }32:33: ERR_CODE Factor(USHORT n, USHORT &rSquared, USHORT &rCubed)34: {35: if (n > 20)36: return ERROR; // simple error code37: else38: {39: rSquared = n*n;40: rCubed = n*n*n;41: return SUCCESS;42: }43: }Output: Enter a number (0 - 20): 3number: 3square: 9cubed: 27

Proslijeđivanje po referencama za efikasnost

Svaki put kad proslijedimo objekt u funkciju po njegovoj vrijednosti, stvara se kopija objekta. Svaki put kad vratite objekt iz funkcije (po vrijednosti), još jedna kopija je kreirana.Do sada smo već naučili da se ti objekti u stvari kopiraju u dijelu memorije koju zovemo stog. Za male objekte, kao što su cijeli brojevi, to je malena cijena.Ali sa velikim korisnički definiranim objektima, zauzeće memorije je znatno veća. Veličina takvog objekta je jednaka sumi svih varijabli—članova objekta. Taj postupak kopiranja objekata na stog i sa stoga za velike objekte može biti prilično zahtjevno i po pitanju performansi, po pitanju zauzeća memorije.

Page 119: C++ PRIRUCNIK

119Postoji još jedan trošak. Sa klasama koje kreirate svaka od tih privremenih kopija se kopira tako da kompajler poziva jedan specijalni konstruktor, tzv. konstruktor kopiranja. Ti stalni pozivi konstruktora (sjetite se, svaki objekt je na kraju potrebno i uništiti destruktorom) su također veliko opterećenje za procesor i memoriju. Kako bi vam to predočio, listing 9.10 stvara ogoljenu verziju objekta: SimpleCat. Pravi objekti su naravno još glomazniji, ali ovo je dovoljno za demonstraciju kako često se konstruktor i destruktor pozivaju.Listing 9.10 kreira SimpleCat objekt i tada poziva dvije funkcije. Prva i prima i vraća Cat po vrijednosti. Druga prihvaća pokazivač na objekt umjesto samog objekta, i vraća pokazivač na objekt.

Listing 9.10. Proslijeđivanje objekata po referenci.

1: //Listing 9.102: // Passing pointers to objects3:4: #include <iostream.h>5:6: class SimpleCat7: {8: public:9: SimpleCat (); // constructor10: SimpleCat(SimpleCat&); // copy constructor11: ~SimpleCat(); // destructor12: };13:14: SimpleCat::SimpleCat()15: {16: cout << "Simple Cat Constructor...\n";17: }18:19: SimpleCat::SimpleCat(SimpleCat&)20: {21: cout << "Simple Cat Copy Constructor...\n";22: }23:24: SimpleCat::~SimpleCat()25: {26: cout << "Simple Cat Destructor...\n";27: }28:29: SimpleCat FunctionOne (SimpleCat theCat);30: SimpleCat* FunctionTwo (SimpleCat *theCat);31:32: int main()33: {34: cout << "Making a cat...\n";35: SimpleCat Frisky;36: cout << "Calling FunctionOne...\n";37: FunctionOne(Frisky);38: cout << "Calling FunctionTwo...\n";39: FunctionTwo(&Frisky);40: return 0;41: }

Page 120: C++ PRIRUCNIK

120 C++ programer

42:43: // FunctionOne, passes by value44: SimpleCat FunctionOne(SimpleCat theCat)45: {46: cout << "Function One. Returning...\n";47: return theCat;48: }49:50: // functionTwo, passes by reference51: SimpleCat* FunctionTwo (SimpleCat *theCat)52: {53: cout << "Function Two. Returning...\n";54: return theCat;55: } Output: 1: Making a cat... Simple Cat Constructor... Calling FunctionOne... Simple Cat Copy Constructor... Function One. Returning... Simple Cat Copy Constructor... Simple Cat Destructor... Simple Cat Destructor... Calling FunctionTwo... Function Two. Returning... Simple Cat Destructor...

Proslijeđivanje const pokazivača

Iako proslijeđivanje pokazivača na FunctionTwo() definitovno zrokuje efikasniji kod, isto tako je i opasno. FunctionTwo() nema dozvolu za izmjene proslijeđenog SimpleCat objekta, iako jj je predana adresa samog objekta. To izlaže naš objekt većoj mogućnosti nesmotrenog mijenjanja i uklanja zaštite ponuđene prilikom proslijeđivanja po vrijednosti.Proslijeđivanje po vrijednosti je poput davanja reprodukcije vrhunske slike muzeju. Ako ju vandali oštete, nikakva šteta neće nastati na orginalu. Proslijeđivanje po referenci je poput slanja vlastite adrese muzeju i pozivanja gostiju da pogledaju pravu stvar.Izlaz je u proslijeđivanju const pokazivača u SimpleCat. Čineći to onemogućavamo pozivanje nekonstantnih metoda klase SimpleCat, i time štitimo objekt od promjena. Listing 9.11 demonstrira ideju.

Listing 9.11. Proslijeđivanje const pokazivača.

1: //Listing 9.112: // Passing pointers to objects3:4: #include <iostream.h>5:6: class SimpleCat7: {8: public:9: SimpleCat();10: SimpleCat(SimpleCat&);11: ~SimpleCat();12:

Page 121: C++ PRIRUCNIK

12113: int GetAge() const { return itsAge; }14: void SetAge(int age) { itsAge = age; }15:16: private:17: int itsAge;18: };19:20: SimpleCat::SimpleCat()21: {22: cout << "Simple Cat Constructor...\n";23: itsAge = 1;24: }25:26: SimpleCat::SimpleCat(SimpleCat&)27: {28: cout << "Simple Cat Copy Constructor...\n";29: }30:31: SimpleCat::~SimpleCat()32: {33: cout << "Simple Cat Destructor...\n";34: }35:36:const SimpleCat * const FunctionTwo (const SimpleCat * const theCat);37:38: int main()39: {40: cout << "Making a cat...\n";41: SimpleCat Frisky;42: cout << "Frisky is " ;43 cout << Frisky.GetAge();44: cout << " years _old\n";45: int age = 5;46: Frisky.SetAge(age);47: cout << "Frisky is " ;48 cout << Frisky.GetAge();49: cout << " years _old\n";50: cout << "Calling FunctionTwo...\n";51: FunctionTwo(&Frisky);52: cout << "Frisky is " ;53 cout << Frisky.GetAge();54: cout << " years _old\n";55: return 0;56: }57:58: // functionTwo, passes a const pointer59: const SimpleCat * const FunctionTwo (const SimpleCat * const theCat)60: {61: cout << "Function Two. Returning...\n";62: cout << "Frisky is now " << theCat->GetAge();63: cout << " years old \n";64: // theCat->SetAge(8); const!65: return theCat;66: }

Page 122: C++ PRIRUCNIK

122 C++ programer

Output: Making a cat...Simple Cat constructor...Frisky is 1 years oldFrisky is 5 years oldCalling FunctionTwo...FunctionTwo. Returning...Frisky is now 5 years oldFrisky is 5 years oldSimple Cat Destructor...

Reference kao alternativa

Listing 9.11 rješava problem pravljenja više kopija i time štedi vrijeme i prostor koje gubimo prilikom poziva konstruktora kopiranja i destruktora. On koristi konstantne pokazivače na konstantne objekte i time rješava problem funkcije koja mijenja objekt. I dalje je pomalo nezgodno to što objekte proslijeđujemo preko pokazivača.Kako znate da objekt nikada neće biti nul, bilo bi lakše raditi unutar funkcije ako umjesto pokazivača proslijedimo referencu.

Listing 9.12. Proslijeđivanje referenci objektu.

1: //Listing 9.122: // Passing references to objects3:4: #include <iostream.h>5:6: class SimpleCat7: {8: public:9: SimpleCat();10: SimpleCat(SimpleCat&);11: ~SimpleCat();12:13: int GetAge() const { return itsAge; }14: void SetAge(int age) { itsAge = age; }15:16: private:17: int itsAge;18: };19:20: SimpleCat::SimpleCat()21: {22: cout << "Simple Cat Constructor...\n";23: itsAge = 1;24: }25:26: SimpleCat::SimpleCat(SimpleCat&)27: {28: cout << "Simple Cat Copy Constructor...\n";29: }30:31: SimpleCat::~SimpleCat()32: {

Page 123: C++ PRIRUCNIK

12333: cout << "Simple Cat Destructor...\n";34: }35:36: const SimpleCat & FunctionTwo (const SimpleCat & theCat);37:38: int main()39: {40: cout << "Making a cat...\n";41: SimpleCat Frisky;42: cout << "Frisky is " << Frisky.GetAge() << " years old\n";43: int age = 5;44: Frisky.SetAge(age);45: cout << "Frisky is " << Frisky.GetAge() << " years old\n";46: cout << "Calling FunctionTwo...\n";47: FunctionTwo(Frisky);48: cout << "Frisky is " << Frisky.GetAge() << " years old\n";49: return 0;50: }51:52: // functionTwo, passes a ref to a const object53: const SimpleCat & FunctionTwo (const SimpleCat & theCat)54: {55: cout << "Function Two. Returning...\n";56: cout << "Frisky is now " << theCat.GetAge();57: cout << " years old \n";58: // theCat.SetAge(8); const!59: return theCat;60: }Output: Making a cat...Simple Cat constructor...Frisky is 1 years oldFrisky is 5 years oldCalling FunctionTwo...FunctionTwo. Returning...Frisky is now 5 years oldFrisky is 5 years oldSimple Cat Destructor...

const reference

C++ programeri obično ne razlikuju "konstantnu referencu na SimpleCat objekt" i "referencu na konstantni SimpleCat objekt." Same reference i onako ne mogu biti ponovno pridružene na drugi objekt, pa su ionako uvijek konstantne. Ako ključnu riječ const primjenimo na referencu, to je kao da smo objekt proglasili konstantom.

Kada koristiti reference a kada pokazivače

C++ programeri uvijek preferiraju reference nad pokazivačima. One su jasnije i jednostavnije za upotrebu, te bolje skrivaju informacije, kako smo vidjeli u prethodnim primjerima.Bilo kako bilo, one nemogu biti repridružene. Ako u programu trebate pokazivati prvo na jedan objekt a potom na drugi, morate koristiti pokazivač. Reference ne mogu biti

Page 124: C++ PRIRUCNIK

124 C++ programer

nule, pa ako postoji ikakva šansa da vam objekt bude nula, ne smijezte koristiti referencu. Primjer dodatne brige je operator new. Ako new ne može alocirati memoriju na slobodnom spremniku, on vraća nul pokazivač. Budući da referenca ne može biti nul, ne smijete ju inicijalizirati na toj memoriji dok ne provjerite da li je možda nula. Slijedeći primjer nam ukazuje na moguće rješenje toga problema:

int *pInt = new int;if (pInt != NULL)int &rInt = *pInt;

U ovom primjeru okazivač na int, pInt, je deklariran i inicijaliziran s memorijskom adresom koju nam vrati operator new. Adresa u pInt se testira i ako nije nula, pInt je dereferenciran. Rezultat dereferenciranja int varijable je int objekt, i rInt je inicijaliziran kao referenca na taj objekt. Time rInt postaje alias na int kojeg je vratio operator new.

Miješanje referenci i pokazivača

Potpuno je legalno deklarirati i pokazivača i refeence u istoj listi parametara, skupa sa objektima proslijeđenim po vrijednosti.Evo primjera:

CAT * SomeFunction (Person &theOwner, House *theHouse, int age);Ova deklaracija kaže da SomeFunction uzima tri parametra. Prvi je referenca na Person objekt, drugi je pokazivač na house objekt, a treći je cijeli broj. Funkcija vraća pokazivač na CAT objekt.

Ne vraćajte referencu na objekt koji nije u dosegu!

Jednom kad programeri nauče proslijeđivati po referenci, imaju tendeciju ka potpunom divljanju. Ipa neumjerenost nikada nije dobra osobina. Zapamtite da je referenca uvijek alias na neki drugi objekt. Ako proslijedite referencu u ili iz funkcije, uvijek se zapitajte, "Što je objekt na kojeg se referenciram, i hoće li postojati svaki put kad kad ga koristim?"

Listing 9.13 ilustrira opasnost vraćanja reference na objekt koji više ne postoji.

1: // Listing 9.132: // Returning a reference to an object3: // which no longer exists4:5: #include <iostream.h>6:7: class SimpleCat8: {9: public:10: SimpleCat (int age, int weight);11: ~SimpleCat() {}12: int GetAge() { return itsAge; }13: int GetWeight() { return itsWeight; }14: private:15: int itsAge;16: int itsWeight;17: };18:

Page 125: C++ PRIRUCNIK

12519: SimpleCat::SimpleCat(int age, int weight):20: itsAge(age), itsWeight(weight) {}21:22: SimpleCat &TheFunction();23:24: int main()25: {26: SimpleCat &rCat = TheFunction();27: int age = rCat.GetAge();28: cout << "rCat is " << age << " years old!\n";29: return 0;30: }31:32: SimpleCat &TheFunction()33: {34: SimpleCat Frisky(5,9);35: return Frisky;36: }Output: Compile error: Attempting to return a reference to a local object!

UPOZORENJE: Ovaj se program neće kompajlirati na Borlandovom prevodiocu. Iako će ga Microsoftov C prevesti, treba zapamtiti da je ovo primjer lošeg programiranja, sa ponekad nepredvidljivim rezultatima.

Analiza: U linijima 7-17, SimpleCat je deklariran. U liniji 26, referenca na SimpleCat je inicijalizirana s rezultatima pozivanja TheFunction(), koja je deklarirana u liniji 22 da vraća referencu na SimpleCat.Tijelo funkcije TheFunction() deklarira lokalni objekt tipa SimpleCat i inicijalizira njegove godine i težinu. Tada vraća lokalni objekt preko reference. Neki kompajleri su dovoljno pametni da uhvate ovu grešku i ne daju vam da pokrenete program. Drugi će vam omogućiti kreiranje programa s nepredvidljivim ponašanjem.Kada TheFunction() završi, lokalni objekt, Frisky, će biti uništen (bezbolno, uvjeravam vas). Referenca koju funkcija vraća će biti alias na nepostojeći objekt, a to je ono što ne smijemo raditi.

Vraćanje reference na objekt u slobodnom spremištu

Možda dođete u iskušenje da riješite problem sa listinga 9.13 tjerajući TheFunction() da kreira Frisky u slobodnom spremniku. Na taj način će Frisky postojati čak i kad izađemo iz funkcije.Problem sa tim pristupom je slijedeći: Što učiniti s memorijom alociranom za Frisky objekt kada nam više nije potreban? Listing 9.14 ilustrira problem.

Listing 9.14. Curenje memorije.

1: // Listing 9.142: // Resolving memory leaks3: #include <iostream.h>4:5: class SimpleCat6: {7: public:

Page 126: C++ PRIRUCNIK

126 C++ programer

8: SimpleCat (int age, int weight);9: ~SimpleCat() {}10: int GetAge() { return itsAge; }11: int GetWeight() { return itsWeight; }12:13 private:14: int itsAge;15: int itsWeight;16: };17:18: SimpleCat::SimpleCat(int age, int weight):19: itsAge(age), itsWeight(weight) {}20:21: SimpleCat & TheFunction();22:23: int main()24: {25: SimpleCat & rCat = TheFunction();26: int age = rCat.GetAge();27: cout << "rCat is " << age << " years old!\n";28: cout << "&rCat: " << &rCat << endl;29: // How do you get rid of that memory?30: SimpleCat * pCat = &rCat;31: delete pCat;32: // Uh oh, rCat now refers to ??33: return 0;34: }35:36: SimpleCat &TheFunction()37: {38: SimpleCat * pFrisky = new SimpleCat(5,9);39: cout << "pFrisky: " << pFrisky << endl;40: return *pFrisky;41: }Output: pFrisky: 0x2bf4rCat is 5 years old!&rCat: 0x2bf4

UPOZORENJE: Ovaj se primjer kompajlira, linka i izgleda kao da radi. U stvari, to je vremenska bomba koja samo čeka da eksplodira.

Analiza: TheFunction() je promijenjen kako ne bi vraćao referencu na lokalnu varijablu. Memorija je alocirana u slobodnom spremištu i pridružena pokazivaču u liniji 38. Adresa koju pokazivač sadrži je ispisana, a potom pokazivač dereferenciran i SimpleCat objekt je vraćen po referenci.U liniji 25, povrat iz TheFunction() je pridružen referenci SimpleCat, i taj objekt nam služi za dobivanje starosti mačke, što ispisujemo u liniji 27.Kako bismo dokazali da referenca deklarirana u main() se odnosi na objekt u slob. spremniku iz TheFunction(), operator adrese je primjenjen na rCat. Sigurno će to biti ista adresa.Za sada, sve OK. Ali kako ćemo osloboditi memoriju? Ne možemo pozvati delete na referencu. Jedno pametno rješenje je kreiranje drugog pokazivača i njegovo

Page 127: C++ PRIRUCNIK

127inicijaliziranje na adresu dobivenu iz rCat. Time brišemo memoriju, i sprečavamo curenje memorije. Ima samo jedan mali problem: Na šta se rCat odnosi nakon linije 31? Referenca uvijek mora pokazivati na stvaran objekt. Ako je ona 0, program je nepravilan.U praksi postoje tri rješenja za ovaj problem. Prvo je deklariranje SimpleCat objektom u liniji 25, i zatim povrat cat iz TheFunction po vrijednosti. Drugo je da deklariramo SimpleCat u slobodnom spremniku unutar TheFunction(), ali da TheFunction() vraća pokazivač na tu memoriju. Nakon toga pozivajuća funkcija može obrisati pokazivač kada više nije potreban.Treće, i vjerojatno najelegantnije, rješenje je deklariranje objekta u pozivajućoj funkciji i njegovo proslijeđivanje u TheFunction() po referenci.

Kviz

1. U čemu je razlika između pokazivača i reference?2. Kada koristimo pokazivač, a ne referencu? 3. Što vraća new ako nema dovoljno memorije za pravljenje novog objekta?4. Kakva je to konstantna referenca?

Vježbe

1. Napišite program koji deklarira int, referencu na int, i pokazivač na int. Koristeći pokazivač i referencu, promijenite vrijednost u int. 2. Napišite program koji deklarira konstantan pokazivač na konstantan integer. Inicijalizirajte pokazivač na integer varijablu, varOne. Dodijelite 6 u varOne. Preko pokazivača, dodijelite 7 u varOne. Kreirajte novu varijablu, varTwo. Pridružite pokazivač novoj varijabli.3. BUG BUSTERS: Što ne valja u ovom programu?

1: #include <iostream.h>2:3: class CAT4: {5: public:6: CAT(int age) { itsAge = age; }7: ~CAT(){}8: int GetAge() const { return itsAge;}9: private:10: int itsAge;11: };12:13: CAT & MakeCat(int age);14: int main()15: {16: int age = 7;17: CAT Boots = MakeCat(age);18: cout << "Boots is " << Boots.GetAge() << " years old\n";19: }20:21: CAT & MakeCat(int age)22: {23: CAT * pCat = new CAT(age);24: return *pCat;25: }

Page 128: C++ PRIRUCNIK

128 C++ programer

4. Popravite gornji program.

Lekcija 10 Napredne funkcije

U petoj lekciji naučili smo osnove rada sa funkcijama. Sada, kada znate kako pokazivači i reference rade, možemo naučiti još bolje koristiti funkcije. Danas ćete naučiti

Kako preopteretiti funkcijske članove. Kako preopteretiti operatore. Kako napisati funkcije koje podržavaju klase s dinamički alociranim varijablama.

Preopterećeni funkcijski članovi

U petoj lekciji naučili smo kako implementirati funkcijski polimorfizam ili preopterećenje funkcija, pišući dvije ili više funkcija istog imena ali sa različitim parametrim. I funkcijski članovi mogu biti preopterećeni na približno sti način. Rectangle klasa, demonstrirana na listingu 10.1, ima dvije DrawShape() funkcije. Jedna, koja ne uzima parametre, crta pravokutnik ovisno o trenutnim vrijednostima klase. Druga uzima dvije vrijednosti, width i length, te crta pravokutnik baziran na tim vrijednostima, ignorirajući trenutne vrijednosti unutar klase.

Listing 10.1. Preopterećenje funkcijskih članova.

1: //Listing 10.1 Overloading class member functions2: #include <iostream.h>3:4: typedef unsigned short int USHORT;5: enum BOOL { FALSE, TRUE};6:7: // Rectangle class declaration8: class Rectangle9: {10: public:11: // constructors12: Rectangle(USHORT width, USHORT height);13: ~Rectangle(){}14:15: // overloaded class function DrawShape16: void DrawShape() const;17: void DrawShape(USHORT aWidth, USHORT aHeight) const;18:19: private:20: USHORT itsWidth;21: USHORT itsHeight;22: };23:24: //Constructor implementation25: Rectangle::Rectangle(USHORT width, USHORT height)26: {

Page 129: C++ PRIRUCNIK

12927: itsWidth = width;28: itsHeight = height;29: }30:31:32: // Overloaded DrawShape - takes no values33: // Draws based on current class member values34: void Rectangle::DrawShape() const35: {36: DrawShape( itsWidth, itsHeight);37: }38:39:40: // overloaded DrawShape - takes two values41: // draws shape based on the parameters42: void Rectangle::DrawShape(USHORT width, USHORT height) const43: {44: for (USHORT i = 0; i<height; i++)45: {46: for (USHORT j = 0; j< width; j++)47: {48: cout << "*";49: }50: cout << "\n";51: }52: }53:54: // Driver program to demonstrate overloaded functions55: int main()56: {57: // initialize a rectangle to 30,558: Rectangle theRect(30,5);59: cout << "DrawShape(): \n";60: theRect.DrawShape();61: cout << "\nDrawShape(40,2): \n";62: theRect.DrawShape(40,2);63: return 0;64: }

PAŽNJA: Ovaj listing proslijeđuje width i height vrijednosti u nekoliko funkcija. Primjetite da ponekad width proslijeđujemo prvog, a ponekad je i height proslijeđen prvi.

Output: DrawShape():******************************************************************************************************************************************************

DrawShape(40,2):************************************************************

Page 130: C++ PRIRUCNIK

130 C++ programer

************************************************************

Analiza: Listing 10.1 predstavlja ogljenu verziju jednog dijela vašeg prvog seminarskog rada. Važan dio koda se nalazi u linijama 16 i 17, gdje je DrawShape() preopterećen. Implementacija za te preopterećene funkcijskee članove je u linijama 32-52. Primjetite da verzija DrawShape() koja ne uzima nikakve parametre jednostavno poziva verziju koja uzima dva parametra, proslijeđujući trenutne vrijednosti podtkovnih članova. Uvijek pokušavajte izbjeći dupliranje koda u dvijema funkcijama. Inače ćete u slučaju izmjena morati paziti da ih sprovedete u obje funkcije, što je često potencijalni izvor pogreške.

Upotreba podrazumijevanih vrijednosti

Baš kao što i funkcije koje ne pripadaju klasi mogu imati jednu ili više podrazumijevanih vrijednosti, tako ih može imati i funkcijski član neke klase. To nam prikazuje listing 10.2.

Listing 10.2. Upotreba podrazumijevanih vrijednosti.

1: //Listing 10.2 Default values in member functions2: #include <iostream.h>3:4: typedef unsigned short int USHORT;5: enum BOOL { FALSE, TRUE};6:7: // Rectangle class declaration8: class Rectangle9: {10: public:11: // constructors12: Rectangle(USHORT width, USHORT height);13: ~Rectangle(){}14: void DrawShape(USHORT aWidth, USHORT aHeight, BOOL UseCurrentVals = Â FALSE) const;15:16: private:17: USHORT itsWidth;18: USHORT itsHeight;19: };20:21: //Constructor implementation22: Rectangle::Rectangle(USHORT width, USHORT height):23: itsWidth(width), // initializations24: itsHeight(height)25: {} // empty body26:27:28: // default values used for third parameter29: void Rectangle::DrawShape(30: USHORT width,31: USHORT height,32: BOOL UseCurrentValue33: ) const34: {

Page 131: C++ PRIRUCNIK

13135: int printWidth;36: int printHeight;37:38: if (UseCurrentValue == TRUE)39: {40: printWidth = itsWidth; // use current class values41: printHeight = itsHeight;42: }43: else44: {45: printWidth = width; // use parameter values46: printHeight = height;47: }48:49:50: for (int i = 0; i<printHeight; i++)51: {52: for (int j = 0; j< printWidth; j++)53: {54: cout << "*";55: }56: cout << "\n";57: }58: }59:60: // Driver program to demonstrate overloaded functions61: int main()62: {63: // initialize a rectangle to 10,2064: Rectangle theRect(30,5);65: cout << "DrawShape(0,0,TRUE)...\n";66: theRect.DrawShape(0,0,TRUE);67: cout <<"DrawShape(40,2)...\n";68: theRect.DrawShape(40,2);69: return 0;70: }Output: DrawShape(0,0,TRUE)...******************************************************************************************************************************************************DrawShape(40,2)...************************************************************************************************************************

Analiza: Listing 10.2 zamjenjuje preopterećenu DrawShape() funkciju s jednom funkcijom koja ima podrazumijevane parametre. Funkcija je deklariran u liniji 14, i prima tri parametra. Prva dva, aWidth i aHeight, su USHORT, a treći, UseCurrentVals, je BOOL (istina ili laž) kojemu je podrazumijevana vrijednost FALSE.

PAŽNJA: Boolean vrijednosti su one koje sadrže ili TRUE ili FALSE. C++ smatra 0 kao laž (false), a sve druge vrijednosti istinom (true).

Page 132: C++ PRIRUCNIK

132 C++ programer

Implementacija ove pomalo neobične funkcije počinje u liniji 29. Treći parametar, UseCurrentValue, je ispitan. Ako je istina, podatkovni članovi itsWidth i itsHeight se koriste za postavljanje lokalnih varijabli printWidth i printHeight.Ako je UseCurrentValue laž, bilo po podrazumijevanoj vrijednosti, bilo postavljen na laž od strane korisnika, prva dva parametra nam služe za postavljanje printWidth i printHeight.Primjetite da u slučaju UseCurrentValue je istina, vrijednosti druga dva parametra funkcija u potpunosti ignorira.

Odabir između podrazumijevanih vrijednosti i preopterećenih funkcija

Iako programi sa listinga 10.1 i 10.2 postižu istu stvar, preopterećene funkcije sa listinga 10,1 su jednostavnije za razumijevanje i, nekako, prirodniji izbor. Također, ako je potrebna i treća varijacija—na primjer korisnik poželi unesti samo širinu ili sam visinu—lakše je proširiti preopterećene funkcije. Funkcija iz 10.2 će ubrzo postati neupotrebljivo kompleksna kako budemo dodavali nove varijacije.Kako odlučiti kada koristiti preopterećenje funkcija, a kada podrazumijevane vrijednosti? Evo nekoliko pravila:Koristite preopterećenje funkcija kada

Ne postoji razumna podrazumijevana vrijednost. Trebate različite algoritme. Trebate podršku za različite tupove parametara.

Podrazumijevani konstruktor

Kao što smo diskutirali u šestoj lekciji ("Osnovne klase"), ako ekplicitno ne deklarirate podrazumijevani konstruktor za vašu klasu, podrazumijevani konstruktor se kreira koji ne prima nikakve parametre i ne čini ništa. Vi ste naravno slobodni kreirati i vlastiti konstruktor koji ne uzima nikakve argumente, ali "postavlja" vaš objekt na željenee vrijednosti.Samoinicijalizirani konstruktor se zove i podrazumijevani, "default" konstruktor, ali po konvenciji to je svaki konstruktor koji ne prima nikakve parametre.

Preopterećenje konstruktora

Ideja konstruktora je da napravi objekt. Na primjer, Rectangle konstruktor pravi pravokutnik u memoriji. Prije nego li je konstruktor pokrenut, postoji samo područje memorije. Kad konstruktor završi, memorija se pretvara u kompletan rectangle objekt, spreman za uporabu.Konstruktori, kao i svi funkcijski članovi, mogu biti preopterećeni. Sposobnost preopterećenja konstruktora je vrlo moćna i fleksibilna.Na primjer, naš rectangle objekt bi mogao imati dva konstruktora: prvi koji uzima dužinu i širinu, te pravi pravokutnik željene veličine. Drugi ne uzima nikakve vrijednosti i pravi pravokutnik podrazumijevane veličine. Listing 10.3 je implementacija te ideje.

Listing 10.3. Preopterećenje konstruktora.

1: // Listing 10.32: // Overloading constructors3:4: #include <iostream.h>5:6: class Rectangle7: {

Page 133: C++ PRIRUCNIK

1338: public:9: Rectangle();10: Rectangle(int width, int length);11: ~Rectangle() {}12: int GetWidth() const { return itsWidth; }13: int GetLength() const { return itsLength; }14: private:15: int itsWidth;16: int itsLength;17: };18:19: Rectangle::Rectangle()20: {21: itsWidth = 5;22: itsLength = 10;23: }24:25: Rectangle::Rectangle (int width, int length)26: {27: itsWidth = width;28: itsLength = length;29: }30:31: int main()32: {33: Rectangle Rect1;34: cout << "Rect1 width: " << Rect1.GetWidth() << endl;35: cout << "Rect1 length: " << Rect1.GetLength() << endl;36:37: int aWidth, aLength;38: cout << "Enter a width: ";39: cin >> aWidth;40: cout << "\nEnter a length: ";41: cin >> aLength;42:43: Rectangle Rect2(aWidth, aLength);44: cout << "\nRect2 width: " << Rect2.GetWidth() << endl;45: cout << "Rect2 length: " << Rect2.GetLength() << endl;46: return 0;47: }Output: Rect1 width: 5Rect1 length: 10Enter a width: 20

Enter a length: 50

Rect2 width: 20Rect2 length: 50

Analiza: Rectangle klasa je deklarirana u linijama 6-17. Dva konstruktora su deklarirana: "default konstruktor" u liniji 9 i konstruktor koji uzima dve cjelobrojne varijable. U liniji33, pravokutnik se kreira koristeći podrazumijevani konstruktor, te su njegove vrijednosti ispisane u linijama 34-35. U linijama 37-41, korisnik unosi visinu i širinu, te

Page 134: C++ PRIRUCNIK

134 C++ programer

pozivamo konstruktor koji uzima dva parametra u liniji 43. Konačno, širina i visina pravokutnika se ispisuje u linijama 44-45.Baš kao prilikom preopterećenja funkcija, prevodioc odabire pravi konstruktor, ovisno o broju i tipu parametara.

Inicijaliziranje objekta

Do sada ste postavljali podatkovne članove objekta u tijelu konstruktora. Konstruktori se sprovode u dvije faze. Prilikom inicijalizacije i samoga tijela.Većina varijabli može biti postavljena u bilo kojoj od te dvije faze. "Čišće" je i često efikasnije inicijalizirati podatkovne članove u inicijalizacijskoj fazi. Slijedeći primjer nam pokazuje kako se iniijaliziraju podatkovni članovi:

CAT(): // constructor name and parametersitsAge(5), // initialization listitsWeight(8){ } // body of constructor

Nakon zatvorene zagrade u listi parametara samog konstruktora, stavljamo dvotočku. Nakon toga stavljamo ime podatkovnog člana i par zagrada. Unutar zagrada, pišemo izraz koji se koristi za inicijaliziranje podatkovnog člana. Ako je više od jedne inicijalizacije, nakon zatvaranja zagrade stavljamo zarez, te navodimo novi član. Listing 10.4 pokazuje definiciju konstruktora sa listinga 10.3, inicijaliziranjem podatkovnih članova umjesto pridruživanja.

Listing 10.4. Djelić koda koji pokazuje inicijaliziranje podatkovnih članova.

1: Rectangle::Rectangle():2: itsWidth(5),3: itsLength(10)4: {5: };6:7: Rectangle::Rectangle (int width, int length)8: itsWidth(width),9: itsLength(length)10: 11: };

Konstruktor kopiranja

Uz dodatak koji omogućuje podrazumijevani konstruktor i destruktor, kompajler nam pruža i podrazumijevani konstruktor kopiranja. On se poziva svaki put kad se stvara kopija objekta.Kada proslijeđujete objekt po vrijednosti, bilo u funkciju, bilo kao povratnu vrijednost funkcije, njegova privremena kopija se stvara. Ako je riječ o korisnički definiranom objektu, konstruktor kopiranja klase se poziva, kao što ste vidjeli u prethodnoj lekciji na listingu 9.6.Svi konstruktori kopiranja uzimaju jedan parametar, referencu na objekt iste klase. Dobra ideja je proglasiti ga konstantom, budući da konstruktor neće mijenjati proslijeđeni objekt. Na primjer:

CAT(const CAT & theCat);Ovdje CAT konstruktor prima konstantnu referencu na postojeći CAT objekt. Cilj konstruktora kopiranja je da napravi kopiju od CAT.Podrazumijevani konstruktor kopiranja jednostavno kopira svaki podatkovni član iz proslijeđenog objekta u podatkovne članove novostvorenog objekta. Iako ovo

Page 135: C++ PRIRUCNIK

135funkcionira za podatkovne članove, brzo se "slomi" za podatkovne članove koji su pokazivači na objekte u slobodnom spremniku.Rješenje za taj problem je kreiranje vlastitog konstruktora kopiranja i alociranja potrebne memorije. Jednom kad je memorija rezervirana, stare vrijednosti mogu biti kopirane u novu memoriju. To se zove "duboka" (engl. deep) kopija, za razliku od podrazumijevane, "plitke" (engl. shallow) kopije. Listing 10.5 ilustrira kako to postići.

Listing 10.5. Konstruktori kopiranja.

1: // Listing 10.52: // Copy constructors3:4: #include <iostream.h>5:6: class CAT7: {8: public:9: CAT(); // default constructor10: CAT (const CAT &); // copy constructor11: ~CAT(); // destructor12: int GetAge() const { return *itsAge; }13: int GetWeight() const { return *itsWeight; }14: void SetAge(int age) { *itsAge = age; }15:16: private:17: int *itsAge;18: int *itsWeight;19: };20:21: CAT::CAT()22: {23: itsAge = new int;24: itsWeight = new int;25: *itsAge = 5;26: *itsWeight = 9;27: }28:29: CAT::CAT(const CAT & rhs)30: {31: itsAge = new int;32: itsWeight = new int;33: *itsAge = rhs.GetAge();34: *itsWeight = rhs.GetWeight();35: }36:37: CAT::~CAT()38: {39: delete itsAge;40: itsAge = 0;41: delete itsWeight;42: itsWeight = 0;43: }44:45: int main()

Page 136: C++ PRIRUCNIK

136 C++ programer

46: {47: CAT frisky;48: cout << "frisky's age: " << frisky.GetAge() << endl;49: cout << "Setting frisky to 6...\n";50: frisky.SetAge(6);51: cout << "Creating boots from frisky\n";52: CAT boots(frisky);53: cout << "frisky's age: " << frisky.GetAge() << endl;54: cout << "boots' age: " << boots.GetAge() << endl;55: cout << "setting frisky to 7...\n";56: frisky.SetAge(7);57: cout << "frisky's age: " << frisky.GetAge() << endl;58: cout << "boot's age: " << boots.GetAge() << endl;59: return 0;60: }Output: frisky's age: 5Setting frisky to 6...Creating boots from friskyfrisky's age: 6boots' age: 6setting frisky to 7...frisky's age: 7boots' age: 6

Analiza: U linijama 6-19, CAT klasa je deklarirana. Primjetite da je u liniji 9 podrazumijevani konstruktor deklariran, a u liniji 10 je deklaracija konstruktora kopiranja.U linijama 17 i 18, dva podatkovna člana su deklarirana, svaki kao pokazivač na cijeli broj. U stvarnosti se rijetko ukazuje potreba za tim da klasa pohranjuje članove kao pokazivače, ali ovo smo napravili da demonstriramo kako upravljati podatkovnim članovima u slobodnom spremniku. Pdrazumijevani konstruktor, u linijama 21-27, alocira prostor u slobodnom spremniku za dve int varijable i potom im pridružuje vrijednost.Konstruktor kopiranja počinje u liniji 29. Primjetite da je parametar rhs. Često se parametru konstruktora kopiranja odnosimo kao rhs, što je skraćenica od engl. right-hand side. Kad proučite pridruživanja u linijama 33 i 34, vidjet ćete da su objekti proslijeđeni kao parametar s desne strane. Evo kako to radi. U linijama 31 i 32, memoriju alociramo u slobodnmo spremniku. Tada, u linijama 33 i 34, vrijednost nove memorijske lokacije je pridružena vrijednostima u postojećem CAT.Parametar rhs je CAT koji je proslijeđen u konstruktor kopiranja kao konstantna referenca. Funkcijski član rhs.GetAge() vraća vrijednost pohranjenu u memoriji na koju pokazuje podatkovni član itsAge. Kao CAT objekt, rhs sadrži sve podatkovne članove od bilo kojeg drugog objekta klase CAT.Kad je konstruktor kopiranja pozvan da kreira novi CAT, postojeći CAT je proslijeđen kao parametar. Novi CAT može raditi s vlastitim podatkovnim članovima direktno; ipak, on mora pristupati podatkovnim članovima rhs-a korištenjem javnih pristupnih metoda.Slika 10.1 demonstrira što se dogodilo ovdje. Vrijednosti na koje pokazuje postojeći CAT su iskopirane u memoriju zauzetu za novi CAT

Slika 10.1 Ilustracija duboke kopije.

Page 137: C++ PRIRUCNIK

137

U liniji 47, CAT zvan frisky je stvoren. frisky-jeve godine su ispisane i potom njegove godine postavljene na 6 u liniji 50. U liniji 52, novi CAT boots je kreiran korištenjem konstruktora kopiranja i pridruživanjem frisky-ja.U linijama 53 i 54, godine obje mačke su ispisane. Očito, boots ima frisky-jeve godine, 6, a ne podrazumijavanu vrijednost 5. U liniji 56, frisky-jeve godine su postavljene na 7, i potom ponovno ispisane. Ovaj put frisky-jeve godine su 7, ali boots je još uvijek na 6, time demonstrirajući da su njihovi podaci spremljeni u različitim dijelovima memorije.Kad CAT objekti izađu iz dosega, njihovi destruktori se automatski pokreću. Implementacija destruktora klase CAT je prikazana u linijama 37-43. delete je pozvan na oba pokazivača, itsAge i itsWeight, vraćajući rezerviranu memoriju u slobodni spremnik. Također, sigurnosti radi, pokazivači su postavljeni na NULL.

Preopterećenje operatora

C++ ima mnoštvo ugrađenih tipova, uključujući i int, real, char, itd. Svaki od njih ima mnoštvo predefiniranih operatora, poput zbrajanja (+) i množenja (*). C++ omogućuje dodavanje tih operatora i našoj klasi također. Kako biste u potpunosti istražili preopterećenje operatora, listing 10.6 stvara novu klasu, Counter. Counter objekt će biti korišten prilikom brojanja u petlji i ostalim aplikacijama gje broj mora biti inkrementiran, dekrementiran ili na neki drugi način praćen.

Listing 10.6. Counter klasa.

1: // Listing 10.62: // The Counter class3:4: typedef unsigned short USHORT;5: #include <iostream.h>6:7: class Counter8: {9: public:10: Counter();11: ~Counter(){}12: USHORT GetItsVal()const { return itsVal; }13: void SetItsVal(USHORT x) {itsVal = x; }14:15: private:16: USHORT itsVal;17:

Page 138: C++ PRIRUCNIK

138 C++ programer

18: };19:20: Counter::Counter():21: itsVal(0)22: {};23:24: int main()25: {26: Counter i;27: cout << "The value of i is " << i.GetItsVal() << endl;28: return 0;29: }Output: The value of i is 0

Analiza: Kao što vidite, ovo je prilični beskorisna klasa. Ona je definirana u linijama 7-18. Njezina jedina podatkovna varijabla je USHORT. Podrazumijevani konstruktor, koji je deklariran u liniji 10, a čija je implementacija u liniji 20, inicijalizira jedan podatkovni član, itsVal, na nulu.

Pisanje funkcije inkrementiranjaPreopterećenje operatora vraća nam mnogo od funkcionalnosti koja je bila iščupana iz ove klase. Na primjer, postoje dva načina za dodavanje svojstva inkrementiranja našoj klasi. Prvi je da napišemo metodu inkrementiranja, kako je prikazano na listingu 10.7.

Listing 10.7. Dodavanje operatora inkrementiranja.

1: // Listing 10.72: // The Counter class3:4: typedef unsigned short USHORT;5: #include <iostream.h>6:7: class Counter8: {9: public:10: Counter();11: ~Counter(){}12: USHORT GetItsVal()const { return itsVal; }13: void SetItsVal(USHORT x) {itsVal = x; }14: void Increment() { ++itsVal; }15:16: private:17: USHORT itsVal;18:19: };20:21: Counter::Counter():22: itsVal(0)23: {};24:25: int main()26: {27: Counter i;28: cout << "The value of i is " << i.GetItsVal() << endl;

Page 139: C++ PRIRUCNIK

13929: i.Increment();30: cout << "The value of i is " << i.GetItsVal() << endl;31: return 0;32: }Output: The value of i is 0The value of i is 1

Analiza: Listing 10.7 dodaje funkciju Increment, definiranu u liniji 14. Iako ovo radi, prilično je nezgodno za upotrebu. Ovaj program upravo "plače" za svojstvom dodavanja ++ operatora, i to se, naravno, može napraviti.

Preopterećeni prefix operator

Prefiks operatori mogu biti preopterećeni deklariranjem funkcije u obliku:returnType Operator op (parameters)

Ovdje je, op operator kojeg preopterećujemo. Prema tome, ++ operator može biti preopterećen slijedećom sintaksom:

void operator++ ()Listing 10.8 demonstrira nam ovu alternativu.

Listing 10.8. Preopterećeni operator++.

1: // Listing 10.82: // The Counter class3:4: typedef unsigned short USHORT;5: #include <iostream.h>6:7: class Counter8: {9: public:10: Counter();11: ~Counter(){}12: USHORT GetItsVal()const { return itsVal; }13: void SetItsVal(USHORT x) {itsVal = x; }14: void Increment() { ++itsVal; }15: void operator++ () { ++itsVal; }16:17: private:18: USHORT itsVal;19:20: };21:22: Counter::Counter():23: itsVal(0)24: {};25:26: int main()27: {28: Counter i;29: cout << "The value of i is " << i.GetItsVal() << endl;30: i.Increment();

Page 140: C++ PRIRUCNIK

140 C++ programer

31: cout << "The value of i is " << i.GetItsVal() << endl;32: ++i;33: cout << "The value of i is " << i.GetItsVal() << endl;34: return 0;35: }Output: The value of i is 0The value of i is 1The value of i is 2

Analiza: U liniji 15, operator++ je preopterećen i upotrebljen u liniji32. Ovo je mnogo bliže sintaksi koju bismo očekivali uz Counter objekt. Prilikom ovoga možete razmisliti o stavljanju dodatnih sposobnosti zbog kojih je objekt i kreiran, kao na primjer detekcija kad Counter prevrši svoju maksimalnu veličinu.Ipak, postoji značajan defekt u načinu na koji je operator inkrementiranja napisan. Ako želite staviti Counter na lijevu stranu pridruživanja, neće vam uspjeti. Na primjer:

Counter a = ++i;Ovaj kod pokušava napraviti novi Counter, a, i potom u njega pridružiti vrijednost od i nakon što je i inkrementiran. Ugrađeni konstruktor kopije će obraditi pridruživanje, ali trenutni operator inkrementa ne vraća Counter objekt. On nam vraća void. Vi naravno ne možete pridružiti void objekt u Counter objekt. (Odnosno, ne možete iz Ničega dobiti Nešto!)

Povratni tipovi u preopterećenim operatorskim funkcijama

Očito, vi želite vratiti Counter objekt kako biste ga pridružili drugome Counter objektu. Koji bi objekt trebao biti vraćen? Jedan bi pristubp bio kreiranje trenutnog objekta i vraćanje istog. Listing 10.9 ilustrira taj pristup.

Listing 10.9. Vraćanje privremenog objekta.

1: // Listing 10.92: // operator++ returns a temporary object3:4: typedef unsigned short USHORT;5: #include <iostream.h>6:7: class Counter8: {9: public:10: Counter();11: ~Counter(){}12: USHORT GetItsVal()const { return itsVal; }13: void SetItsVal(USHORT x) {itsVal = x; }14: void Increment() { ++itsVal; }15: Counter operator++ ();16:17: private:18: USHORT itsVal;19:20: };21:22: Counter::Counter():23: itsVal(0)24: {};

Page 141: C++ PRIRUCNIK

14125:26: Counter Counter::operator++()27: {28: ++itsVal;29: Counter temp;30: temp.SetItsVal(itsVal);31: return temp;32: }33:34: int main()35: {36: Counter i;37: cout << "The value of i is " << i.GetItsVal() << endl;38: i.Increment();39: cout << "The value of i is " << i.GetItsVal() << endl;40: ++i;41: cout << "The value of i is " << i.GetItsVal() << endl;42: Counter a = ++i;43: cout << "The value of a: " << a.GetItsVal();44: cout << " and i: " << i.GetItsVal() << endl;45: return 0;46: }Output: The value of i is 0The value of i is 1The value of i is 2The value of a: 3 and i: 3

Analiza: U ovoj verziji, operator++ je deklariran u liniji 15 da vraća Counter objekt. U linioji 29, trenutna varijabla, temp, je napravljena i njezina vrijednost postavljena da odgovara trenutnom objektu. Ta privremena varijabla je vraćena i trenutno pridržena u liniji 42.

Vraćanje bezimenih privremenih

Uopće nema potrebe za imenovanjem privremenog objekta kreiranog u liniji 29. Ako je Counter imao konstruktor koji uzima vrijednost, jednostavno možete vratiti rezultat toga konstruktora kao povratnu vrijednost operatora inkrementiranja. Listing 10.10 ilustrira tu ideju.

Listing 10.10. Vraćanje neimenovanog privremenog objekta.

1: // Listing 10.102: // operator++ returns a nameless temporary object3:4: typedef unsigned short USHORT;5: #include <iostream.h>6:7: class Counter8: {9: public:10: Counter();11: Counter(USHORT val);12: ~Counter(){}13: USHORT GetItsVal()const { return itsVal; }

Page 142: C++ PRIRUCNIK

142 C++ programer

14: void SetItsVal(USHORT x) {itsVal = x; }15: void Increment() { ++itsVal; }16: Counter operator++ ();17:18: private:19: USHORT itsVal;20:21: };22:23: Counter::Counter():24: itsVal(0)25: {}26:27: Counter::Counter(USHORT val):28: itsVal(val)29: {}30:31: Counter Counter::operator++()32: {33: ++itsVal;34: return Counter (itsVal);35: }36:37: int main()38: {39: Counter i;40: cout << "The value of i is " << i.GetItsVal() << endl;41: i.Increment();42: cout << "The value of i is " << i.GetItsVal() << endl;43: ++i;44: cout << "The value of i is " << i.GetItsVal() << endl;45: Counter a = ++i;46: cout << "The value of a: " << a.GetItsVal();47: cout << " and i: " << i.GetItsVal() << endl;48: return 0;49: }Output: The value of i is 0The value of i is 1The value of i is 2The value of a: 3 and i: 3

Analiza: U liniji 11, novi konstruktor je deklariran koji prima USHORT. Implementacija je u linijama 27-29. On inicijalizira itsVal s proslijeđenom mu vrijednošću. Implementacija operator++ je sad pojednostavljena. U liniji 33, itsVal je inkrementiran. Potom, u liniji 34 kreiramo privremeni Counter objekt, te inicijaliziran na vrijednost iz itsVal, i potom vraćen kao rezultat od operator++.Ovo je u svakom slučaju elegantnije, ali povlači pitanje "Zašto uopće kreirati privremeni objekt?" Zapamtite da svaki privremeni objekt mora biti konstruiran i kasnije i uništen—dvije potiencijalno rastrošne operacije. Također, objekt i već postoji i ima željenu vrijednost, pa zašto ne vratiti njega? Riješiti ćemo taj problem uporabom this pokazivača.

Page 143: C++ PRIRUCNIK

143Upotreba this pokazivača

this pokazivač, o kojem smo diskutirali u prethodnoj lekciji, je proslijeđen u funkcijski član operator++ kao u sve funkcijske članove. this pokazivač pokazuje na i, i ako je dereferenciran, vratit će objekt i, koji već ima pravu vrijednost u svojom podatkovnom članu itsVal. Listing 10.11 ilustrira vraćanje dereferenciranog this pokazivača i izbjegavanja stvaranja nepotrebnog privremenog objekta.

Listing 10.11. Vraćanje this pokazivača.

1: // Listing 10.112: // Returning the dereferenced this pointer3:4: typedef unsigned short USHORT;5: #include <iostream.h>6:7: class Counter8: {9: public:10: Counter();11: ~Counter(){}12: USHORT GetItsVal()const { return itsVal; }13: void SetItsVal(USHORT x) {itsVal = x; }14: void Increment() { ++itsVal; }15: const Counter& operator++ ();16:17: private:18: USHORT itsVal;19:20: };21:22: Counter::Counter():23: itsVal(0)24: {};25:26: const Counter& Counter::operator++()27: {28: ++itsVal;29: return *this;30: }31:32: int main()33: {34: Counter i;35: cout << "The value of i is " << i.GetItsVal() << endl;36: i.Increment();37: cout << "The value of i is " << i.GetItsVal() << endl;38: ++i;39: cout << "The value of i is " << i.GetItsVal() << endl;40: Counter a = ++i;41: cout << "The value of a: " << a.GetItsVal();42: cout << " and i: " << i.GetItsVal() << endl;48: return 0;49: }

Page 144: C++ PRIRUCNIK

144 C++ programer

Output: The value of i is 0The value of i is 1The value of i is 2The value of a: 3 and i: 3

Analiza: Implementacija operator++, u linijama 26-30, je promijenjena kako bi dereferencirala this pokazivač i vratila trenutni objekt. To omogućuje pridruživanje Counter objekta u a. Kao što smo prethodno diskutirali, da je Counter objekt alocirao memoriju, bilo bi neophodno zaobići konstruktor kopiranja. U ovom slučaju podrazumjevani konstruktor kopije radi u redu.Primjetite da je povratna vrijednost referenca na Counter, te je time izbjegnuto kreiranje dodatnog privremenog objekta. To je const referenca stoga što vrijednost ne bi smjela biti promijenjena u funkciji koja koristi ovaj Counter.

Preopterećenje postfiks operatora

Do sada ste propteretili prefiks operator. Što ako želita preopteretiti postfiks inkrement operator? Ovdje kompajler nailazi na problem: Kako razlikovati prefiks i postfiks? Po konvenciji, cjelobrojna varijabla je pružena kao parametar u deklaraciji operatora. Vrijednost parametra se ignrira; to je samo signal da je riječ o postfiks operatoru.

Razlika između prefiksa i postfiksa

Prije nego li napišemo postfiks operator, moramo razumjeti na koji se on način razlikuje od prefiks operatora. O tome smo detaljno raspravljali u Lekciji 4 (pogledajte listing 4.3).Za podsjećanje, prefiks kaže "inkrementiraj i pridruži" dok postfiks kaže "pridruži i inkrementiraj".Tako, dok prefiks operator može jednostavno inkrementirati vrijednost i potom vratiti sami objekt, postfiks mora vratiti vrijednost koja je bila prije inkrementiranja. Kako biste to napravili, morate kreirati privremeni objekt koji će zadržati originalnu vrijednost, potom inkrementirati vrijednost orginalnog objekta, a potom vratiti privremeni. Prođimo kroz to još jedanput. Proučite slijedeću liniju koda:

a = x++;Ako je x bio 5, nakon ove naredbe varijabla a ima vrijednost 5, ali x je 6. Dakle, mi smo vratili vrijednost od x i pridružili ju u a, i potom povećali vrijednost x. Da je x objekt, njegov postfiks inkremnt operator mora proslijediti originalnu vrijednost (5) u privremeni objekt, povećati vrijednost x na 6, i potom vratiti privremeni objekt pridruživši njegovu vrijednost u a.Primjetite da budući je riječ o vraćanju privremenog objekta, moramo ga vratiti po vrijednosti a ne po referenci, jer će privremeni objekt izaći iz dosega čim izađemo iz funkcije.Listing 10.12 demonstrira korištenje obadva i prefiks i postfiks operatora.

Listing 10.12. Prefiks i postfiks operatori.

1: // Listing 10.122: // Returning the dereferenced this pointer3:4: typedef unsigned short USHORT;5: #include <iostream.h>

Page 145: C++ PRIRUCNIK

1456:7: class Counter8: {9: public:10: Counter();11: ~Counter(){}12: USHORT GetItsVal()const { return itsVal; }13: void SetItsVal(USHORT x) {itsVal = x; }14: const Counter& operator++ (); // prefix15: const Counter operator++ (int); // postfix16:17: private:18: USHORT itsVal;19: };20:21: Counter::Counter():22: itsVal(0)23: {}24:25: const Counter& Counter::operator++()26: {27: ++itsVal;28: return *this;29: }30:31: const Counter Counter::operator++(int)32: {33: Counter temp(*this);34: ++itsVal;35: return temp;36: }37:38: int main()39: {40: Counter i;41: cout << "The value of i is " << i.GetItsVal() << endl;42: i++;43: cout << "The value of i is " << i.GetItsVal() << endl;44: ++i;45: cout << "The value of i is " << i.GetItsVal() << endl;46: Counter a = ++i;47: cout << "The value of a: " << a.GetItsVal();48: cout << " and i: " << i.GetItsVal() << endl;49: a = i++;50: cout << "The value of a: " << a.GetItsVal();51: cout << " and i: " << i.GetItsVal() << endl;52: return 0;53: }Output: The value of i is 0The value of i is 1The value of i is 2The value of a: 3 and i: 3The value of a: 3 and i: 4

Page 146: C++ PRIRUCNIK

146 C++ programer

Analiza: Postfiks operator je deklariran u liniji 15 i implementiran u linijama 31-36. Primjetite da poziv prefiks operatora u liniji 14 ne uključuje signalni cijeli broj (x), ali se koristi svojom normalnom sintaksom. Postfiks operator koristi signalnu vrijednost (x) kako bi dao do znanja da je riječ o postfiksu a ne o prefiksu. Ta se vrijednost inače nikada ne koristi.

Operator zbrajanja

Operator inkrementiranja je unarni operator. To znači da djeluje samo na jedan objekt. Operator zbrajanja (+) je binarni operator, u kojem su uključena dva objekta. Kako implementirati preopterećenje + operatora za Count?Cilj je da budemo u stanju deklarirati dvije Counter varjiable i potom ih zbrojiti, kao u slijedećem primjeru:

Counter varOne, varTwo, varThree;VarThree = VarOne + VarTwo;

Još jednom, mogli bismo početi pisanjem funkcije Add(), koja bi uzimala Counter kao svoj argument, zbrojila vrijednosti, te potom vratila Counter s rezultatom. Listing 10.13 ilustrira taj pristup.

Listing 10.13. Add() funkcija.

1: // Listing 10.132: // Add function3:4: typedef unsigned short USHORT;5: #include <iostream.h>6:7: class Counter8: {9: public:10: Counter();11: Counter(USHORT initialValue);12: ~Counter(){}13: USHORT GetItsVal()const { return itsVal; }14: void SetItsVal(USHORT x) {itsVal = x; }15: Counter Add(const Counter &);16:17: private:18: USHORT itsVal;19:20: };21:22: Counter::Counter(USHORT initialValue):23: itsVal(initialValue)24: {}25:26: Counter::Counter():27: itsVal(0)28: {}29:30: Counter Counter::Add(const Counter & rhs)31: {32: return Counter(itsVal+ rhs.GetItsVal());33: }

Page 147: C++ PRIRUCNIK

14734:35: int main()36: {37: Counter varOne(2), varTwo(4), varThree;38: varThree = varOne.Add(varTwo);39: cout << "varOne: " << varOne.GetItsVal()<< endl;40: cout << "varTwo: " << varTwo.GetItsVal() << endl;41: cout << "varThree: " << varThree.GetItsVal() << endl;42:43: return 0;44: }Output: varOne: 2varTwo: 4varThree: 6

Analiza: Add()funkcija je deklarirana u liniji 15. Ona uzima konstantnu Counter referencu, što je u stvari broj kojeg pribrajamo trenutnom objektu. Ona veaća Counter objekt, što je rezultat koji se pridružuje lijevoj strani naredbe, kako je vidljivo u liniji 38. U tome slučaju, VarOne je objekt, varTwo je parametar u Add() funkciji, a rezultat se pridružuje u VarThree.Kako bismo kreirali varThree bez potrebe za incijaliziranjem njegove vrijednosti, potreban je podrazumijevani konstruktor. Podrazumijevani konstruktor inicijalizira itsVal na 0, kako je prikazano u linijama 26-28. Budući da varOne i varTwo moraju biti inicijalizirani na vrijednost različitu od nule, još jedan konstrzktor je kreiran, u linijama 22-24. Drugo rješenje za ovaj problem bilo bi stavljanje podrazumijevane vrijednosti 0 u konstrukto deklariran u liniji 11.

Preopterećeni operator +

Sama Add() funckcija je prikazana u linijama 30-33. On radi, ali njezina upotreba je neprirodna. Preopterećenje + operatora bi omogućilo prirodniju upotrebu Counter klase. Listing 10.14 ilustrira ovo.

Listing 10.14. Operator +.

1: // Listing 10.142: //Overload operator plus (+)3:4: typedef unsigned short USHORT;5: #include <iostream.h>6:7: class Counter8: {9: public:10: Counter();11: Counter(USHORT initialValue);12: ~Counter(){}13: USHORT GetItsVal()const { return itsVal; }14: void SetItsVal(USHORT x) {itsVal = x; }15: Counter operator+ (const Counter &);16: private:17: USHORT itsVal;

Page 148: C++ PRIRUCNIK

148 C++ programer

18: };19:20: Counter::Counter(USHORT initialValue):21: itsVal(initialValue)22: {}23:24: Counter::Counter():25: itsVal(0)26: {}27:28: Counter Counter::operator+ (const Counter & rhs)29: {30: return Counter(itsVal + rhs.GetItsVal());31: }32:33: int main()34: {35: Counter varOne(2), varTwo(4), varThree;36: varThree = varOne + varTwo;37: cout << "varOne: " << varOne.GetItsVal()<< endl;38: cout << "varTwo: " << varTwo.GetItsVal() << endl;39: cout << "varThree: " << varThree.GetItsVal() << endl;40:41: return 0;42: }Output: varOne: 2varTwo: 4varThree: 6

Analiza: operator+ je deklariran u liniji 15 i definiran u linijama 28-31. Usporedite ih s deklaracijom i definicijom Add() funkcije u prethodnom listingu; oni su gotovo identični. Sintaksa za njihovu upotrebu je bitno drugačija. Puno je prirodnije napisati

varThree = varOne + varTwo;nego napisati

varThree = varOne.Add(varTwo);Nije velika promjena, ali dovoljna za pravljenje programa lakšim za upotrebu i razumijevanje.

PAŽNJA: Tehnike opisane za preopterećeni operator++ mogu biti primjenjene i na druge unarne operatore, poput operator-.

Preopterećeni binarni operatori

Binarni operatori su kreirani kao i unarni, osim što oni primaju parametar. Parametar je konstantna referenca na objekt istoga tipa.Primjer 1

Counter Counter::operator+ (const Counter & rhs);Primjer 2

Counter Counter::operator-(const Counter & rhs);

Page 149: C++ PRIRUCNIK

149

Operator pridruživanja

Četvrta i konačna funkcija koja dolazi s prevoditeljem, ako je ne specificirate je operator pridruživanja (operator=()). Ovaj operator se poziva kad god nešto pridružite objektu. Na primjer:

CAT catOne(5,7);CAT catTwo(3,4);// ... other code herecatTwo = catOne;

Ovdje, catOne je kreiran i inicijaliziran sa itsAge jednako 5 i itsWeight jednako 7. catTwo je tada kreiran i pridruženi su mu vrijednosti 3 i 4.Nakon nekog vremena, catTwo je pridružena vrijednost iz catOne. Ovdje se povlače dva pitanja: Što se događa ako je itsAge pokazivač, te što se događa sa originalnim vrijednostima iz catTwo?Upravljanje podatkovnim članovima kako bi spremali podatke u slobodnom spremniku je tema obrađena prilikom diskusije o konstruktoru kopije. Isto se pitanje povlači i ovdje.C++ programeri razlikuju shallow (plitku) kopiju s jedne strane i deep (duboku) kopiju s druge. Plitka kopija kopira samo članove, te na kraju oba objekta pokazuju na isto područje slobodnog spremnika. Duboka kopija alocira neophodnu memoriju.Dodatna komplikacija kod operatora pridruživanja je u tome što objekt catTwo već postoji i ima alociranu memoriju. Ta memorija mora biti obrisana ako ne želimo prouzročiti curenje memorije. Ali što se događa ako pridružimo catTwo samome sebi?

catTwo = catTwo;Nitko ovo neće napraviti namjerno, ali program mora znati baratati i s takvim izrazom. Što je još važnije, moguće je da se ovo dogodi pogreškom kada referencirani i dereferencirani pokazivači sakriju činjenicu da pridružujemo objekt samom sebi.Ako ne pripazite na tu moguću situaciju, catTwo će izbrisati svoju memorijsku alokaciju. Tada će, kad je spreman kopirati u memoriju desnu stranu izraza, imati velik problem: memorija će biti izgubljena.Kako bi se zaštitili od toga, vaš operator pridruživanja mora provjeriti da li je desna strana operatora pridružvanja sam objekt. To radi ispitujući this pokazivač. Listing 10.15 pokazuje nam klasu s operatorom pridruživanja.

Listing 10.15. Operator pridruživanja.

1: // Listing 10.152: // Copy constructors3:4: #include <iostream.h>5:6: class CAT7: {8: public:9: CAT(); // default constructor10: // copy constructor and destructor elided!11: int GetAge() const { return *itsAge; }12: int GetWeight() const { return *itsWeight; }13: void SetAge(int age) { *itsAge = age; }14: CAT operator=(const CAT &);15:16: private:17: int *itsAge;

Page 150: C++ PRIRUCNIK

150 C++ programer

18: int *itsWeight;19: };20:21: CAT::CAT()22: {23: itsAge = new int;24: itsWeight = new int;25: *itsAge = 5;26: *itsWeight = 9;27: }28:29:30: CAT CAT::operator=(const CAT & rhs)31: {32: if (this == &rhs)33: return *this;34: delete itsAge;35: delete itsWeight;36: itsAge = new int;37: itsWeight = new int;38: *itsAge = rhs.GetAge();39: *itsWeight = rhs.GetWeight();40: return *this;41: }42:43:44: int main()45: {46: CAT frisky;47: cout << "frisky's age: " << frisky.GetAge() << endl;48: cout << "Setting frisky to 6...\n";49: frisky.SetAge(6);50: CAT whiskers;51: cout << "whiskers' age: " << whiskers.GetAge() << endl;52: cout << "copying frisky to whiskers...\n";53: whiskers = frisky;54: cout << "whiskers' age: " << whiskers.GetAge() << endl;55: return 0;56: }frisky's age: 5Setting frisky to 6...whiskers' age: 5copying frisky to whiskers...whiskers' age: 6

Izlaz: Listing 10.15 nam vraća CAT klasu, i izostavlja konstruktor kopije i destuktor kako bi uštedjeli prostor. U liniji 14, deklariran je operator pridruživanja, te je definiran u linijama 30-41.

Analiza: U liniji 32, trenutni objekt (CAT kojemu pridružujemo) se testira kako bi provjerili da li je jednak onome objektu kojega pridružujemo. To postižemo provjerom adrese rhs i usporešivanjem s adresom pohranjenom u this pokazivaču.

Page 151: C++ PRIRUCNIK

151

Operatori konverzije

Što se događa kad pokušamo pridružiti varijablu ugrađenog tipa, poput int ili unsigned short, u objekt korisnički definirane klase? Listing 10.16 nam vraća Counter klasu, i pokušava pridružiti varijablu tipa USHORT to u Counter objekt.

UPOZORENJE: Listing 10.16 se neće prevesti!

Listing 10.16. Pokušaj pridruživanja Counter objekta u USHORT

1: // Listing 10.162: // This code won't compile!3:4: typedef unsigned short USHORT;5: #include <iostream.h>6:7: class Counter8: {9: public:10: Counter();11: ~Counter(){}12: USHORT GetItsVal()const { return itsVal; }13: void SetItsVal(USHORT x) {itsVal = x; }14: private:15: USHORT itsVal;16:17: };18:19: Counter::Counter():20: itsVal(0)21: {}22:23: int main()24: {25: USHORT theShort = 5;26: Counter theCtr = theShort;27: cout << "theCtr: " << theCtr.GetItsVal() << endl;28: return ;029: }Output: Compiler error! Unable to convert USHORT to Counter

Analiza: Counter klasa deklarirana u linijama 7-17 ima samo podrazumijevani konstruktor. Ne deklarira posebnu metodu za pretvaranje USHORT u Counter objekt, pa linija 26 uzrokuje pogrešku prilikom prevođenja. Prevoditelj ne zna "skužiti" ukoliko mu ne kažete, da ako mu pridružite USHORT, treba pridružiti vrijednost podatkovnom članu itsVal.

Listing 10.17 popravlja ovo kreirajući operator konverzije: konstruktor koji uzima USHORT i proizvodi Counter objekt.

Listing 10.17. Pretvaranje USHORT u Counter.

Page 152: C++ PRIRUCNIK

152 C++ programer

1: // Listing 10.172: // Constructor as conversion operator3:4: typedef unsigned short USHORT;5: #include <iostream.h>6:7: class Counter8: {9: public:10: Counter();11: Counter(USHORT val);12: ~Counter(){}13: USHORT GetItsVal()const { return itsVal; }14: void SetItsVal(USHORT x) {itsVal = x; }15: private:16: USHORT itsVal;17:18: };19:20: Counter::Counter():21: itsVal(0)22: {}23:24: Counter::Counter(USHORT val):25: itsVal(val)26: {}27:28:29: int main()30: {31: USHORT theShort = 5;32: Counter theCtr = theShort;33: cout << "theCtr: " << theCtr.GetItsVal() << endl;34: return 0;35: Output: theCtr: 5

Analiza: Važna promjena je u liniji 11, gdje je konstruktor preopterećen kako bi primio USHORT, i u linijama 24-26, gdje je konstruktor implementiran. Efekt ovog konstruktora je kreiranje Counter iz USHORT.Dajući ovo, kompajler je sposoban pozvati konstruktor koji uzima USHORT kao svoj argument. Što se događa, kad pokušamo izokrenuti pridruživanje pišući slijedeći kod?

1: Counter theCtr(5);2: USHORT theShort = theCtr;3: cout << "theShort : " << theShort << endl;

Još jednom, generirati ćemo grešku kod prevođenja. Iako sada prevoditelj zna generirati Counter iz USHORT, on nezna izokrenuti proces.

Operatori konverzije

Kako bi riješili taj i njemu slične probleme, C++ nam pruža operatore konverzije koji mogu biti umetnuti u klasu. To omogućuje specificiranje kako implicirati konverziju u

Page 153: C++ PRIRUCNIK

153ugrađene tipove. Listing 10.18 ilustrira ovo. Jedna napomena: Operatori konverzije ne specificiraju povratnu vrijednost, iako oni efektivno vraćaju zamijenjenu vrijednost.

Listing 10.18. Pretvaranje iz Counter u unsigned short().

1: // Listing 10.182: // conversion operator3:4: typedef unsigned short USHORT;5: #include <iostream.h>6:7: class Counter8: {9: public:10: Counter();11: Counter(USHORT val);12: ~Counter(){}13: USHORT GetItsVal()const { return itsVal; }14: void SetItsVal(USHORT x) {itsVal = x; }15: operator unsigned short();16: private:17: USHORT itsVal;18:19: };20:21: Counter::Counter():22: itsVal(0)23: {}24:25: Counter::Counter(USHORT val):26: itsVal(val)27: {}28:29: Counter::operator unsigned short ()30: {31: return ( USHORT (itsVal) );32: }33:34: int main()35: {36: Counter ctr(5);37: USHORT theShort = ctr;38: cout << "theShort: " << theShort << endl;39: return 0;40: Output: theShort: 5

Analiza: U liniji 15, operator konverzije je deklariran. Primjetite da on nema povratnu vrijednost. Implementacija te funkcije je u linijama 29-32. Linija 31 vraća vrijednost iz itsVal, pretvorenu u USHORT.Sada prevoditelj zna kako prebaciti USHORT u Counter objekte i ibrnuto, te oni mogu međusobno biti pridruživani kako želimo.

Page 154: C++ PRIRUCNIK

154 C++ programer

Kviz

1. Kad preopterećujete funkcijske članove, kako se oni trebaju razlikovati? 2. Koja je razlika između deklaracije i definicije? 3. Kada se poziva konstruktor kopije? 4. Kada se poziva destruktor? 5. Koja je razlika između konstruktora kopije i operatora pridruživanja (=)?6. Što je this pokazivač? 7. Kako razlikujemo između preopterećenja prefiks i postfiks inkrement operatora?8. Možete li preopteretiti operator+ za short integer vrijednosti?9. Da li je legalno U C++ preopteretiti operator++ tako da on dekrementira vrijednost u vašoj klasi? 10. Kakvu povratnu vrijednost moraju imati operatorri konverzije u svojim deklaracijama?

Vježbe

1. Napišite SimpleCircle deklaraciju klase s jednim podatkovnim članom: itsRadius. Uključite podrazumijevani konstruktor, destruktor, te pristupne metode za polumjer.

2. Koristeći klasu kreiranu u vježbi 1, napišite implementaciju podrazumijevanog konstruktora, inicijalizirajući itsRadius s vrijednošću 5.

3. Koristeći istu klasu dodajte i drugi konstruktor koji prima vrijednost kao svoj parametar i pridružuje ju u itsRadius.

4. Kreirajte prefiks i postfiks inkrement operator za vašu SimpleCircle klasu koji inkrementira itsRadius.

5. Promjenite SimpleCircle da pohrani itsRadius u slobodnom spremniku, te popravite postojeće metode.

6. Napravite konstruktor kopije za SimpleCircle.

7. Napravite operator pridruživanja za SimpleCircle.

8. Napišite program koji kreira dva SimpleCircle objeka. Koristeći podrazumijevani konstruktor na jednom i inicijaliziranjem drugog na vrijednost 9. Pozovite operator inkrementiranja za svaki i potom ispišite njihove vrijednosti. Konačno, pridružite drugi prvome i ispišite njihove vrijednosti.

9. BUG BUSTERS: Što ne valja s ovom implementacijom operatora pridruživanja?

SQUARE SQUARE ::operator=(const SQUARE & rhs){ itsSide = new int; *itsSide = rhs.GetSide(); return *this;}

10. BUG BUSTERS: Što ne valja s implementacijom operatora zbrajanja?

Page 155: C++ PRIRUCNIK

155VeryShort VeryShort::operator+ (const VeryShort& rhs){ itsVal += rhs.GetItsVal(); return *this;}

Page 156: C++ PRIRUCNIK

156 C++ programer

Lekcija 11 Polja

U prethodnim lekcijama deklarirali smo jedinstvene int, char, ili nek druge objekte. Često je potrebno deklarirati grupu objekata, kao na primjer, 20 cijelih brojeva ili 12 objekata klase Machak. Danas ćete naučiti

Što su polja i kako ih deklarirati. Što su stringovi i kako napraviti polja znakova da bi ih koristili. Odnos među poljima i pokazivačima. Kako koristiti aritmetiku pokazivača sa poljima.

Što je polje?

Polje je kolekcija lokacija za pohranu podataka, od kojih svaka drži isti tip podatka. Svaka lokacija za pohranu se zove element polja.Polje deklarirate pišući tip, kojeg slijedi ime polja i subskript. Subskript je broj članova polja okružen uglatim zagradama. Na primjer,

long LongArray[25];deklarira polje od 25 long integera nazvanio LongArray. Kad kompajler vidi ovu deklaraciju, rezervira dovoljno memorije za pohranu svih 25 elemenata. Budući da svaki long integer zahtijeva 4 bytea, ta deklaracija rezervira 100 byteova memorije u nizu, kao što je ilustrirano na slici 11.1

Slika 11.1: Deklaracija polja.

Elementi polja

Pojedinim elementima polja pristupamo navodeći ofset (redni broj) tog elementa. Elementi se broje od nule. Prema tome, prvi element gore spomenutog polja bio bi LongArray[0], drugi je LongArray[1] itd.Ovo zna biti pomalo zbunjujuće. Polje SomeArray[3] ima tri elementa. To su SomeArray[0], SomeArray[1], i SomeArray[2]. Uopćeno, SomeArray[n] ima n elemenata koji su pobrojani SomeArray[0] do SomeArray[n-1].Prema tome, LongArray[25] ide od LongArray[0] do LongArray[24]. Listing 11.1 pokazuje kako deklarirati polje pet cijelih brojeva i ispuniti ih vrijednošću.

Listing 11.1. Uotreba cjelobrojnog polja.

1: //Listing 11.1 - Arrays2: #include <iostream.h>3:4: int main()5: {6: int myArray[5];

Page 157: C++ PRIRUCNIK

1577: int i;8: for ( i=0; i<5; i++) // 0-49: {10: cout << "Value for myArray[" << i << "]: ";11: cin >> myArray[i];12: }13: for (i = 0; i<5; i++)14: cout << i << ": " << myArray[i] << "\n";15: return 0;16: }Output: Value for myArray[0]: 3Value for myArray[1]: 6Value for myArray[2]: 9Value for myArray[3]: 12Value for myArray[4]: 150: 31: 62: 93: 124: 15

Analiza: Linija 6 deklarira polje zvano myArray, koje drži 5 cjelobrojnih varijabli. Linija 8 stvaa petlju koja broji od 0 do 4, što je pravilan slijed ofseta za polje od 5 elemenata. Korisnik je upitan za vrijednost, i ona se pohranjuje u pravilnom ofsetu u polje. Prva vrijednost je pohranjena u myArray[0], druga u myArray[1], i tako dalje. Druga for petlja ispisuje sve vrijednosti na ekran.

PAŽNJA: Polja broje od 0, a ne od 1. To uzrokuje mnoge bugove u programima koji su pisali C++ početnici. Kad god koristite polje, zapamtite da polje sa 10 elemenata broji od ArrayName[0] do ArrayName[9]. Ne postoji ArrayName[10].

Pisanje nakon kraja polja

Kada upisujete vrijednost u element polja, kompajler računa gdje spremiti vrijednosti bazirane na veličini pojedinog elementa i indeksu polja. Recimo da zatražite upisivanje vrijednosti u LongArray[5], što je zapravo šesti element. Kompajler množi ofset (5) s veličinom svakog elementa—u ovom slučaju, 4. Tada skoči za toliko mjesta (20 byteova) od početka polja i upisuje vrijednost u tu lokaciju.Ako zahtjevate upisivanje na LongArray[50], kompajler ignorira činjenicu da ne postoji takav element. On računa koliko daleko od prvog elementa treba gledati (200 byteova) i zatim piše preko vrijednosti koja je pohranjena na toj lokaciji. To u praksi može biti bilo koji podatak, i upisivanje nove vrrijednosti može dati nepredvidljive rezultate. Ako imate sreće, vaš program će se trenutno srušiti. Ako nemate, dobiti ćete neobične rezultate mnogo kasnije u vašem programu, i teško ćete otkriti što je pošlo po zlu.Kompajler je poput slijepca koji mjeri razdaljinu od svoje kuće. On počne od prve kuće, MainStreet[0]. Ako mu kažete da dođe do šeste kuće, on će si reći, "Moram proći još pet kuća. Svaka kuća je četiri velika koraka. Znači, trebam dodatnih 20 koraka." Ako mu kažete da ide u MainStreet[100], a Main Street je samo 25 kuća dugačka, on će napraviti 400 koraka. Puno prije nego što dođe tamo, vjerojatno će izletiti pred autobus. Zato budite pažljivi kamo ga šaljete.

Page 158: C++ PRIRUCNIK

158 C++ programer

Listing 11.2 pokazuje što se dogodi kad upisujemo "iza" zavšetka polja.

UPOZORENJE: Ne pokrećite ovaj program; može vam srušiti računalo!

Listing 11.2. Upisivanje iza završetka polja.

1: //Listing 11.22: // Demonstrates what happens when you write past the end3: // of an array4:5: #include <iostream.h>6: int main()7: {8: // sentinels9: long sentinelOne[3];10: long TargetArray[25]; // array to fill11: long sentinelTwo[3];12: int i;13: for (i=0; i<3; i++)14: sentinelOne[i] = sentinelTwo[i] = 0;15:16: for (i=0; i<25; i++)17: TargetArray[i] = 0;18:19: cout << "Test 1: \n"; // test current values (should be 0)20: cout << "TargetArray[0]: " << TargetArray[0] << "\n";21: cout << "TargetArray[24]: " << TargetArray[24] << "\n\n";22:23: for (i = 0; i<3; i++)24: {25: cout << "sentinelOne[" << i << "]: ";26: cout << sentinelOne[i] << "\n";27: cout << "sentinelTwo[" << i << "]: ";28: cout << sentinelTwo[i]<< "\n";29: }30:31: cout << "\nAssigning...";32: for (i = 0; i<=25; i++)33: TargetArray[i] = 20;34:35: cout << "\nTest 2: \n";36: cout << "TargetArray[0]: " << TargetArray[0] << "\n";37: cout << "TargetArray[24]: " << TargetArray[24] << "\n";38: cout << "TargetArray[25]: " << TargetArray[25] << "\n\n";39: for (i = 0; i<3; i++)40: {41: cout << "sentinelOne[" << i << "]: ";42: cout << sentinelOne[i]<< "\n";43: cout << "sentinelTwo[" << i << "]: ";44: cout << sentinelTwo[i]<< "\n";45: }46:

Page 159: C++ PRIRUCNIK

15947: return 0;48: }

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

SentinelOne[0]: 0SentinelTwo[0]: 0SentinelOne[1]: 0SentinelTwo[1]: 0SentinelOne[2]: 0SentinelTwo[2]: 0

Assigning...Test 2:TargetArray[0]: 20TargetArray[24]: 20TargetArray[25]: 20

SentinelOne[0]: 20SentinelTwo[0]: 0SentinelOne[1]: 0SentinelTwo[1]: 0SentinelOne[2]: 0SentinelTwo[2]: 0

"Fence Post" pogreške

Toliko je često upisivanje jednog člana iza završetka polja da ta pogreška ima i svoje ime. Zove se "fence post error". To se odnosi na broj stupića od ograde potrebnih za 10-metara ograde, ako zabijate stupiće svaki metar. Većina ljudi odgovara 10, ali vi ih u stvari trebate 11. Slika 11.2 to pojašnjava.

Slika 11.2. Fence post error.

Inicijaliziranje polja

Jednostavno polje ugrađenih tipova možete inicijalizirati prilikom deklaracije. Nakon imena polja, stavljamo znak jednakosti (=) i listu zarezom odvojenih vrijednosti u vitičastim zagradama. Na primjer,

int IntegerArray[5] = { 10, 20, 30, 40, 50 };deklarira IntegerArray kao polje 5 cijelih brojeva. U IntegerArray[0] pridružuje vrijednost 10, IntegerArray[1] je 20, i tako dalje.Ako ne navedete veličinu polja, polje dovoljno veliko za pohranjivanje inicijalizacije se kreira. Tako, ako napišete

int IntegerArray[] = { 10, 20, 30, 40, 50 };kreirati ćete potpuno isto polje kao ono iz prethodnog primjera.

Page 160: C++ PRIRUCNIK

160 C++ programer

Ako trebate znati veličinu polja, možete tražiti od kompajlera da vam ju izračuna. Na primjer,

const USHORT IntegerArrayLength;IntegerArrayLength = sizeof(IntegerArray)/sizeof(IntegerArray[0]);

Računa ukupno zaueće memorije za cijelo polje i dijeli ga s zauzećem memorije jednog elementa polja, što nam daje broj članova polja.Ne možete inicijalizirati više elemenata nego što ste deklarirali polje. Prema tome,

int IntegerArray[5] = { 10, 20, 30, 40, 50, 60};generira grešku prilikom prevođenja. Međutim, legalno je napisati

int IntegerArray[5] = { 10, 20};Iako neinicijalizirani članovi polja nemaju garantiranu vrijednost, u stvari, će oni biti postavljeni na 0.

Deklaracija polja

Polja mogu imati bilo koje legalno ime varijable, ali ne mogu imati isto ime kao druga varijabla ili poljee unutar njihova dosega. Prema tome ne možete imati polje myCats[5] i varijablu myCats u isto vrijeme.Možete deklarirati dimenzije polja i s konstantom ili enumeracijom. Listing 11.3 ilustrira ovo.

Listing 11.3. Upotreba const i enum u polju.

1: // Listing 11.32: // Dimensioning arrays with consts and enumerations3:4: #include <iostream.h>5: int main()6: {7: enum WeekDays { Sun, Mon, Tue, 8: Wed, Thu, Fri, Sat, DaysInWeek };9: int ArrayWeek[DaysInWeek] = { 10, 20, 30, 40, 50, 60, 70 };10:11: cout << "The value at Tuesday is: " << ArrayWeek[Tue];12: return 0;13: }Output: The value at Tuesday is: 30

Analiza: Linija 7 stvara enumeraciju nazvanu WeekDays. Ona ima osam brojeva. Sunday je jednak 0, a DaysInWeek je jednak 7.Linija 11 koristi enumeriranu konstantu Tue kao indeks elementa polja. budući da je Tue jednak 2, treći element polja, DaysInWeek[2], je vraćen i ispisan u liniji 11.

Polja

Za deklariranje niza pišemo tip pohranjenog objekta, kojeg slijedi ime niza i uglata zagrada s brojem elemenata polja. Primjeri:

int MyIntegerArray[90];long * ArrayOfPointersToLongs[100];

Za pristupanje pojedinim članovima polja koristimo njihove indekse. Primjeri:int theNinethInteger = MyIntegerArray[8];long * pLong = ArrayOfPointersToLongs[8]

Page 161: C++ PRIRUCNIK

161Sva polja broje od 0. Polje od n elemenata je označeno od 0 do n-1.

Polja objekata

Svaki objekt, bilo ugrađeni, bilo korisnički definiran, može biti pohranjen u polje. Prilikom deklaracije polja kažemo kompajleru tip objekta za pohranjivanje i broj objekata za koje treba rezervirati prostor. Kompajler zna koliko mjesta svaki objekt zahtjeva po njegovoj deklaraciji. Klasa kojoj pripada mora imati podrazumijevani konstruktor koji ne prima nikakve argumente kako bi objekti mogli biti kreirani prilikom definicije polja.Pristupanje podatkovnim članovima u polju objekata je dvostupanjski proces. Identificiramo element polja koristeći se indeks operatorom ([ ]), a tada pozivamo operator člana (member operator) (.) za pristup pojedinom podatkovnom članu. Listing 11.4 demonstrira kako biste kreirali polje od pet objekata klase CAT.

Listing 11.4. Kreiranje niza objekata.

1: // Listing 11.4 - An array of objects2:3: #include <iostream.h>4:5: class CAT6: {7: public:8: CAT() { itsAge = 1; itsWeight=5; } 9: ~CAT() {} 10: int GetAge() const { return itsAge; }11: int GetWeight() const { return itsWeight; }12: void SetAge(int age) { itsAge = age; }13:14: private:15: int itsAge;16: int itsWeight;17: };18:19: int main()20: {21: CAT Litter[5];22: int i;23: for (i = 0; i < 5; i++)24: Litter[i].SetAge(2*i +1);25:26: for (i = 0; i < 5; i++)27: {28: cout << "Cat #" << i+1<< ": ";29: cout << Litter[i].GetAge() << endl;30: }31: return 0;32: }Output: cat #1: 1cat #2: 3cat #3: 5cat #4: 7cat #5: 9

Page 162: C++ PRIRUCNIK

162 C++ programer

Višedimenzionalna polja

Moguće je imati i polja koja sadržavaju više dimenzija. Svaka dimenzija je reprezentirana u svojoj uglatoj zagradi prilikom deklaracije polja. Broj dimenzija nije ograničen, ali je malo vjerojatno da ćete koristiti nešto drugo osim jedno i dvodimenzionalnih polja. Dobar primjer dvodimenzionalnog polja je šahovska ploča. Jedna dimenzija predstavlja osam redaka; druga dimenzija predstavlja osam stupaca. Slika 11.3 ilustrira ideju.Pretpostavimo da imate klasu nazvanu SQUARE. Deklaracija polja imena Board koji ju predstavlja glasila bi

SQUARE Board[8][8];Iste podatke možete predstaviti i jednodimenzionalnim poljem od 64 elementa, na primjer,

SQUARE Board[64]Međutim, prva deklaracija je puno bliža stvarnom svijetu. Kad igra počinje, kralj je lociran na četvrtu poziciju prvoga reda, Brojeći od nule, pozicija odfgovara

Board[0][3];Pretpostavljajući da prva zagrada predstavlja retke, a druga stupce, prikaz za cijelu ploču bio bi ilustriran na slici 11.3.

Slika 11.3. Šahovska ploča i dvodimenzionalno polje.

Inicijaliziranje višedimenzionalnog polja

Inicijalizirati možete i višedimenzionalno polje. Dodjeljujete listu vrijednosti niza u redosljedu, s promjenjivim zadnjim indeksom polja, dok drugi miruju. Tako na primjer, ako imate polje

int theArray[5][3]prva tri elementa idu u theArray[0]; slijedeća tri idu u theArray[1]; i tako dalje. To polje inicijalizirate pišući

int theArray[5][3] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 }Kako bi pojednostavili čitljivost, možete grupirati inicijalizacije u vitičaste zagrade. Na primjer,

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

Svaka vrijednost mora biti odvojena zarezom, bez obzira na zagrade, jer ih kompajler ignorira.

Page 163: C++ PRIRUCNIK

163Listing 11.5 stvara dvodimenzionalno polje. Prva dimenzija je slijed brojeva od 0 do 5. Druga dimenzija se sastoji od duplirane svake vrijednosti u prvoj dimenziji.

Listing 11.5. Stvaranje višedimenzionalnog polja.

1: #include <iostream.h>2: int main()3: {4: int SomeArray[5][2] = { {0,0}, {1,2}, {2,4}, {3,6}, {4,8}};5: for (int i = 0; i<5; i++)6: for (int j=0; j<2; j++)7: {8: cout << "SomeArray[" << i << "][" << j << "]: ";9: cout << SomeArray[i][j]<< endl;10: }11:12: return 0;13: }Output: SomeArray[0][0]: 0SomeArray[0][1]: 0SomeArray[1][0]: 1SomeArray[1][1]: 2SomeArray[2][0]: 2SomeArray[2][1]: 4SomeArray[3][0]: 3SomeArray[3][1]: 6SomeArray[4][0]: 4SomeArray[4][1]: 8

Analiza: Linija 4 deklarira SomeArray kao dvodimenzionalno polje. Prva dimenzija se sastoji od pet integera; druga se sastoji od dva integera. Ovo kreira mrežu 5x2, kao što prikazuje slika 11.4

Slika 11.4. polje 5x2.

Vrijednosti inicijaliziramo u parovima, iako bi mogle biti i izračunate. Linije 5 i 6 kreiraju ugnježdenu for petlju. Vanjska for petlja se mijenja za svakog člana prve dimenzije. Za svakog člana u toj dimenziji, unutrašnja for petlja se izvrti kroz sve članove druge dimenzije. Tako će SomeArray[0][0] biti prvi element, nakon njega ići će SomeArray[0][1]. Kad se izvrte svi članovi druge dimenzije, prva će se povećati za jedan i ponovo će unutrašnja petlja krenuti od 0.

Riječ o memoriji

Prilikom deklaracije polja vi kažete kompajleru točno koliko očekujete pohranjenih objekata u njemu. Kompajler rezervira memoriju za sve objekte, čak i za one koje nikad ne koristite. To nam je neprihvatljivo kod velikih polja unaprijed nepoznate veličine, jer bi time morali rezervirati ogromne količine memorije. U tom slučaju maramo koristiti napredne strukture podataka.

Page 164: C++ PRIRUCNIK

164 C++ programer

Mi ćemo se pozabaviti nizovima pokazivača, nizovima kreiranim u slobodnom spremniku i raznim drugim metodama.

Nizovi pokazivača

Nizovi o kojima smo do sada diskutirali smještaju sve svoje članove u stog. Obično je memorijski prostor stoga ograničen, dok je slobodni spremnik i veći i predviđen upravo za smještanje velike količine podataka. Moguće je deklarirati svaki objekt u slobodnom spremniku a potom pohraniti samo pokazivače na objekte unutar polja. Time dramatično smanjujemo količinu zauzetog stoga. Listing 11.6 je prepisano polje sa listinga 11.4, ali sprema sve objekte u slobodnom spremniku. Kao pokazatelj koliko više memorije smo si time priskrbili, polje je prošireno sa 5 na 500 i promijenjeno je ime polja.

Listing 11.6. Spremanje polja u slobodnom spremniku.

1: // Listing 11.6 - An array of pointers to objects2:3: #include <iostream.h>4:5: class CAT6: {7: public:8: CAT() { itsAge = 1; itsWeight=5; } 9: ~CAT() {} // destructor10: int GetAge() const { return itsAge; }11: int GetWeight() const { return itsWeight; }12: void SetAge(int age) { itsAge = age; }13:14: private:15: int itsAge;16: int itsWeight;17: };18:19: int main()20: {21: CAT * Family[500];22: int i;23: CAT * pCat;24: for (i = 0; i < 500; i++)25: {26: pCat = new CAT;27: pCat->SetAge(2*i +1);28: Family[i] = pCat;29: }30:31: for (i = 0; i < 500; i++)32: {33: cout << "Cat #" << i+1 << ": ";34: cout << Family[i]->GetAge() << endl;35: }36: return 0;37: }

Page 165: C++ PRIRUCNIK

165

Output: Cat #1: 1Cat #2: 3Cat #3: 5...Cat #499: 997Cat #500: 999

Analiza: CAT objekt deklariran u linijama 5-17 je potpuno jednak CAT objektu deklariranom u listingu 11.4. Ovaj put je, polje u liniji 27 nazvano Family, i deklarirano je da drži 500 pokazivača na CAT objekte.U inicijalnoj petlji (linije 24-29) novi CAT objekti se stvaraju u slobodnom spremniku, i svakome je godina postavljena na dvostruku vrijednost indeksa plus 1. Tako će prvi CAT imati vrijednost 1, drugi – 3, treći – 5 itd. Konačno, i pokazivač je dodan polju.Budući da polje deklarirano da sadrži pokazivače, sam pokazivač—za razliku od dereferencirane vrijednosti pokazivača—je dodan u polje.Druga petlja (linije 31 i 32) ispisuje svaku od vrijednosti. Pokazivaču se pristupa koristeći indeks, Family[i]. Ta adresa se koristi za pristup GetAge() metodi.U ovom primjeru, polje Family i svi njezini pokazivači su pohranjeni na stogu, ali svih 500 CAT objekata je spremljeno u slobodnom spremniku.

Deklariranje polja u slobodnom spremniku

Moguće je staviti i cijelo polje u slobodni spremnik, također poznat i pod nazivom "heap". To činimo pozivom new i korištenjem subskript operatora. Rezultat je pokazivač na područje slobodnog spremnika koji sadrži polje. Na primjer,

CAT *Family = new CAT[500];deklarira Family kao pokazivač ma prvi u nizu od 500 CAT objekata. Drugim rječima, , Family pokazuje na—odnosno ima adresu od--Family[0].Prednost ovog načina upotrebe je mogućnost korištenja aritmetike pokazivača za pristupanje pojedinim članovima od Family. Na primjer, ako napišete

CAT *Family = new CAT[500];CAT *pCat = Family; //pCat points to Family[0]pCat->SetAge(10); // set Family[0] to 10pCat++; // advance to Family[1]pCat->SetAge(20); // set Family[1] to 20

Time deklarirate novo polje od 500 CAT objekata i pokazivač koji pokazuje na početak toga polja. Koristeći taj pokzivač, prva CAT SetAge() funkcija je pozvana s vrijednosti 10. Pokazivač se tada inkrementira na mjesto idućeg objekta, i druga Cat SetAge() metoda je tada pozvana.

Pokazivač na polje protiv polja pokazivača

Proučite slijedeće deklaracije:1: Cat FamilyOne[500]2: CAT * FamilyTwo[500];3: CAT * FamilyThree = new CAT[500];

FamilyOne je polje od 500 CAT objekata. FamilyTwo je polje od 500 pokazivača na objekte klase CAT. FamilyThree je pokazivač na polje od 500 objekata.Razlike među ovim linijama dramatično utječu na to kakao polja funkcioniraju. Što je još čudnije, FamilyThree je varijanta od FamilyOne, ali je vrlo različit od FamilyTwo.Ovo povlači (bolno) pitanje u kakvom su odnosu pokazivači i polja. U trećem slučaju, FamilyThree je pokazivač na polje. Što znači da je adresa u FamilyThree iadresa prvog člana polja. Isti je slučaj i sa FamilyOne.

Page 166: C++ PRIRUCNIK

166 C++ programer

Pokazivači i imena polja

U C++ jeziku, naziv polja je konstantan pokazivač na prvi element toga polja. Prema tome, prilikom deklaracije

CAT Family[50];Family je pokazivač na &Family[0], što je adresa prvog elementa polja Family.Legelno je koristiti imena polja kao konstantne pokazivače i obrnuto, Na primjer, Family + 4 je legalan način za pristup podacima u Family[4].Kompajler obavlja svu aritmetiku kada dodajete, inkrementirate i dekrementirate pokazivače. Adresa kojoj pristupate kad napišete Family + 4 nije 4 bytea iza adrese od Family—već je četiri objekta. Ako je svaki objekt dug 4 bytea, Family + 4 je 16 bytea. Ako je svaki objekt CAT koji sadrži četiri long podatkovna člana od 4 bytea svaki i dva short podatkovna člana od 2 bytea svaki, objekt klase CAT zauzima 20 byteova, a Family + 4 ide 80 byteova iza početka polja.Listing 11.7 ilustrira deklariranje i upotrebu polja u slobodnom spremniku.

Listing 11.7. Stvaranje polja korištenjem new.

1: // Listing 11.7 - An array on the free store2:3: #include <iostream.h>4:5: class CAT6: {7: public:8: CAT() { itsAge = 1; itsWeight=5; } 9: ~CAT(); 10: int GetAge() const { return itsAge; }11: int GetWeight() const { return itsWeight; }12: void SetAge(int age) { itsAge = age; }13:14: private:15: int itsAge;16: int itsWeight;17: };18:19: CAT :: ~CAT()20: {21: // cout << "Destructor called!\n";22: }23:24: int main()25: {26: CAT * Family = new CAT[500];27: int i;28: CAT * pCat;29: for (i = 0; i < 500; i++)30: {31: pCat = new CAT;32: pCat->SetAge(2*i +1);33: Family[i] = *pCat;34: delete pCat;35: }36:37: for (i = 0; i < 500; i++)

Page 167: C++ PRIRUCNIK

16738: {38: cout << "Cat #" << i+1 << ": ";39: cout << Family[i].GetAge() << endl;40: }41:42: delete [] Family;43:44: return 0;45: }Output: Cat #1: 1Cat #2: 3Cat #3: 5...Cat #499: 997Cat #500: 999

Analiza: Linija 26 deklarira polje Family, koje sadrži 500 CAT objekata. Cijelo polje je kreirano u slobodnom spremniku s pozivom new CAT[500].Svaki CAT objekt umetnut u polje je također napravljen u slobodnom spremniku (linij 31). Primjetite, da sada ne dodajemo pokazivač u polje nego sam objekt. Ovo nije polje pokazivača na objekte, već je to polje objekata.

Brisanje polja u slobodnom spremniku

Family je pokazivač na polje u slobodnom spremniku. Kada u liniji 33 dereferenciramo pokazivač pCat, CAT objekt je spremljen u polju. Ali pCat ponovno koristimo u slijedećoj iteraciji petlje. Nije li opasno sada ostaviti prethodni objekt bez pokazivača i nećemo l itako uzrokovati curenje memorije?To bi bio povelik problem, kada brisanje Family ne bi oslobodilo svu zauzetu memoriju. Kompajler je dovoljno pametan za to.Kako biste to provjerili , promjeite veličinu polja sa 500 na 10 u linijama 26, 29, i 37. Potom odkomentirajte cout naredbu u liniji 21. Kada dosegnemo liniju 40, i dođe do uništavanja polja, svaki CAT objekt destruktor je pozvan.Kada god stvaramo objekt u slobodnom spremniku koristeći new, moramo ga i obristai sa delete. Slično tome, kad god stvorimo niz koristeći new <class>[size], brišete to polje i oslobađate memoriju sa delete[]. Uglate zagrade signaliziraju kompajleru da se briše cijelo polje.Izostavite li ih, samo će prvi element polja biti obrisan. To si možete dokazati mičući zagrade u liniji 40. U slučaju dekomentirane linije 21, kako bi se destruktor ispisao, vidjet ćete da je samo jedan CAT objekt uništen. Čestitam! Upravo ste prouzročili curenje memorije.

polja znakova

String je niz znakova. Jedini stringovi koje ste doasad koristili bili su oni neimenovani, upotrebljeni u cout naredbama, poput

cout << "hello world.\n";U C++ string je niz char elemenata koji završava null karakterom. Možete deklarirati i inicijalizirati string baš kao što bi inicijalizirali bilo koje drugo polje. Na primjer,

char Greeting[] = { `H', `e', `l', `l', `o', ` `, `W','o','r','l','d', `\0' };Posljednji znak, `\0', je null znak, kojeg mnoge C++ funkcije prepoznaju kao terminator stringa. Zbog male nezgrapnosti ovog načina pisanja, C++ nam omogućuje i skraćivanje prethodnog koda, npr:

Page 168: C++ PRIRUCNIK

168 C++ programer

char Greeting[] = "Hello World";Trebate primjetiti dvije stvari o sintaksi:

Umjesto jednostrukih navodnika, koristimo dvostruke, te su nam nepotrebni zarezi i vitičaste zagrade.

Ne morate dodavati null znak jer ga kompajler doda umjesto vas. String Hello World je dug 12 byteova. Hello je 5 byteova, razmak je 1, World 5, i nul znak je 1.Također možete kreirati i neinicijalizirane nizove znakova. Kao i uvijek sa poljima, treba paziti da ne unesemo više znakova u spremnik nego li ima mjesta.Listing 11.8 demonstrira upotrebu neinicijaliziranog spremnika.

Listing 11.8. Popunjavanje niza.

1: //Listing 11.8 char array buffers2:3: #include <iostream.h>4:5: int main()6: {7: char buffer[80];8: cout << "Enter the string: ";9: cin >> buffer;10: cout << "Here's the buffer: " << buffer << endl;11: return 0;12: }Output: Enter the string: Hello WorldHere's the buffer: Hello

Analiza: U liniji 7, buffer je deklariran da drži 80 znakova. To je dovoljno veliko za držanje 79 znakova i završnog null znaka.U liniji 8, Korisnika tražimo da unese string, koji se sprema u buffer u liniji 9. Sam cin se brine o upisivanju null karaktera kad završimo s tekstom.Postoje dva problema s tim programom. Prvo, ne može spremiti više od 79 znakova. Drugo, ako korisnik utipka razmak, cin misli da je došlo do kraja stringa, te prestaje zapisivati u buffer.Kako bismo riješili te probleme, morate pozvati specijalnu metodu na cin: get(). cin.get() uzima tri parametra:

Bafer u za popunjavanje

Maksimalan broj znakova

Delimiter koji terminira upis

Podrazumijevani delimiter je newline. Listing 11.9 ilustrira njegovu upotrebu.

Listing 11.9. Ispunjavanje polja.

1: //Listing 11.9 using cin.get()2:3: #include <iostream.h>4:5: int main()6: {7: char buffer[80];8: cout << "Enter the string: ";

Page 169: C++ PRIRUCNIK

1699: cin.get(buffer, 79); // get up to 79 or newline10: cout << "Here's the buffer: " << buffer << endl;11: return 0;12: }Output: Enter the string: Hello WorldHere's the buffer: Hello World

Analiza: Linija 9 poziva metodu get() od cin. Bafer deklariran u liniji 7 se prosljeđuje kao prvi argument. Drugi argument je maksimalan broj znakova. U našem slučaju to mora biti 79 kako bi ostalo mjesta za terminirajući null znak. Ne postoji potreba za pružanjem terminirajućeg znaka budući da je podrazumijevana vrijednost newline zadovoljavajuća.

cin i sve njegove varijacije će biti još detaljnije objašnjeni prilikom diskutiranja o tokovima.

strcpy() i strncpy()

C++ nasljeđuje iz C bibilioteke funkcije za baratanje stringovima. Među mnoštva funkcija su i dvije za kopiranje jednog stringa u drugi: strcpy() i strncpy(). strcpy() kopira cijeli sadržaj nekog stringa u redviđeni bafer. Listing 11.10 demonstrira upotrebu strcpy().

Listing 11.10. Upotreba strcpy().

1: #include <iostream.h>2: #include <string.h>3: int main()4: {5: char String1[] = "No man is an island";6: char String2[80];7:8: strcpy(String2,String1);9:10: cout << "String1: " << String1 << endl;11: cout << "String2: " << String2 << endl;12: return 0;13: }Output: String1: No man is an islandString2: No man is an island

Analiza: Datoteka zaglavlja, string.h je uključena u liniji 2. Ta datoteka sadrži prototip strcpy() funkcije. strcpy() uzima dva niza znakova, odredišni i početni, te kopira početni u odredišni. Ako je početni veći od odredišnog, strcpy() bi nastavio pisati iza kraj bafera.

Kako bi se zaštitili od toga, standardna biblioteka također sadrži i strncpy(). Ova varijanta uzima i maksimalan broj znakova za kopiranje u obzir. Kopiranje će ići ili do prvog null znaka na kojeg naleti ili do maksimalnog broja znakova deklariranog za odredišni bafer.Listing 11.11 ilustrira upotrebu strncpy().

Page 170: C++ PRIRUCNIK

170 C++ programer

Listing 11.11. Upotreba strncpy().

1: #include <iostream.h>2: #include <string.h>3: int main()4: {5: const int MaxLength = 80;6: char String1[] = "No man is an island";7: char String2[MaxLength+1];8:9:10: strncpy(String2,String1,MaxLength);11:12: cout << "String1: " << String1 << endl;13: cout << "String2: " << String2 << endl;14: return 0;15: }Output: String1: No man is an islandString2: No man is an island

String klase

Većina C++ kompajlera dolazi s bibliotekom klasa koja uključuje i velik skup klasa za manipulaciju podacima. Standardna komponenta biblioteke klasa je i String klasa.C++ je naslijedio null-terminirani string i biblioteku funkcija koja uključuje i strcpy() iz C-a, ali te funkcije nisu integrirane u objektno-orjentirani sustav. String klasa nam pruža enkapsulirani set podataka i funkcija za njihovo manipuliranje, kao i pristupne funkcije kako bi sami podaci bili skriveni od klijenata, korisnika String klase.Ako vaš kompajler ne dolazi s napravljenom String klasom—a možda i ako dolazi—možemo ju napisati sami. Ostatak ovog poglavlja diskutira o dizajnu i djelomičnoj implementaciji String klase.Kao minimum, String klasa bi trebala nadilaziti osnovna ograničenja polja znakova. Kao i sva polja, polja znakova su statična. Vi definirate koliko su velika. Oni uvijek zauzimaju deklariranu količinu memorije, čak i ako ju ne koristite. Pisanje iza kraja polja uzrokuje katastrofalne bagove.Dobra String klasa alocira samo onoliko memorije koliko joj je stvarno potrebno, i uvijek dovoljno da drži što god joj damo. Ako nije u stanju alocirati dovoljnu količinu memorije, treba nam to dojaviti.Listing 11.12 pruža brzu aproksimaciju String klase.

Listing 11.12. Upotreba string klase.

1: //Listing 11.122:3: #include <iostream.h>4: #include <string.h>5:6: // Rudimentary string class7: class String8: {9: public:10: // constructors11: String();

Page 171: C++ PRIRUCNIK

17112: String(const char *const);13: String(const String &);14: ~String();15:16: // overloaded operators17: char & operator[](unsigned short offset);18: char operator[](unsigned short offset) const;19: String operator+(const String&);20: void operator+=(const String&);21: String & operator= (const String &);22:23: // General accessors24: unsigned short GetLen()const { return itsLen; }25: const char * GetString() const { return itsString; }26:27: private:28: String (unsigned short); // private constructor29: char * itsString;30: unsigned short itsLen;31: };32:33: // default constructor creates string of 0 bytes34: String::String()35: {36: itsString = new char[1];37: itsString[0] = `\0';38: itsLen=0;39: }40:41: // private (helper) constructor, used only by42: // class methods for creating a new string of43: // required size. Null filled.44: String::String(unsigned short len)45: {46: itsString = new char[len+1];47: for (unsigned short i = 0; i<=len; i++)48: itsString[i] = `\0';49: itsLen=len;50: }51:52: // Converts a character array to a String53: String::String(const char * const cString)54: {55: itsLen = strlen(cString);56: itsString = new char[itsLen+1];57: for (unsigned short i = 0; i<itsLen; i++)58: itsString[i] = cString[i];59: itsString[itsLen]='\0';60: }61:62: // copy constructor63: String::String (const String & rhs)64: {65: itsLen=rhs.GetLen();

Page 172: C++ PRIRUCNIK

172 C++ programer

66: itsString = new char[itsLen+1];67: for (unsigned short i = 0; i<itsLen;i++)68: itsString[i] = rhs[i];69: itsString[itsLen] = `\0';70: }71:72: // destructor, frees allocated memory73: String::~String ()74: {75: delete [] itsString;76: itsLen = 0;77: }78:79: // operator equals, frees existing memory80: // then copies string and size81: String& String::operator=(const String & rhs)82: {83: if (this == &rhs)84: return *this;85: delete [] itsString;86: itsLen=rhs.GetLen();87: itsString = new char[itsLen+1];88: for (unsigned short i = 0; i<itsLen;i++)89: itsString[i] = rhs[i];90: itsString[itsLen] = `\0';91: return *this;92: }93:94: //nonconstant offset operator, returns95: // reference to character so it can be96: // changed!97: char & String::operator[](unsigned short offset)98: {99: if (offset > itsLen)100: return itsString[itsLen-1];101: else102: return itsString[offset];103: }104:105: // constant offset operator for use106: // on const objects (see copy constructor!)107: char String::operator[](unsigned short offset) const108: {109: if (offset > itsLen)110: return itsString[itsLen-1];111: else112: return itsString[offset];113: }114:115: // creates a new string by adding current116: // string to rhs117: String String::operator+(const String& rhs)118: {119: unsigned short totalLen = itsLen + rhs.GetLen();

Page 173: C++ PRIRUCNIK

173120: String temp(totalLen);121: for (unsigned short i = 0; i<itsLen; i++)122: temp[i] = itsString[i];123: for (unsigned short j = 0; j<rhs.GetLen(); j++, i++)124: temp[i] = rhs[j];125: temp[totalLen]='\0';126: return temp;127: }128:129: // changes current string, returns nothing130: void String::operator+=(const String& rhs)131: {132: unsigned short rhsLen = rhs.GetLen();133: unsigned short totalLen = itsLen + rhsLen;134: String temp(totalLen);135: for (unsigned short i = 0; i<itsLen; i++)136: temp[i] = itsString[i];137: for (unsigned short j = 0; j<rhs.GetLen(); j++, i++)138: temp[i] = rhs[i-itsLen];139: temp[totalLen]='\0';140: *this = temp;141: }142:143: int main()144: {145: String s1("initial test");146: cout << "S1:\t" << s1.GetString() << endl;147:148: char * temp = "Hello World";149: s1 = temp;150: cout << "S1:\t" << s1.GetString() << endl;151:152: char tempTwo[20];153: strcpy(tempTwo,"; nice to be here!");154: s1 += tempTwo;155: cout << "tempTwo:\t" << tempTwo << endl;156: cout << "S1:\t" << s1.GetString() << endl;157:158: cout << "S1[4]:\t" << s1[4] << endl;159: s1[4]='x';160: cout << "S1:\t" << s1.GetString() << endl;161:162: cout << "S1[999]:\t" << s1[999] << endl;163:164: String s2(" Another string");165: String s3;166: s3 = s1+s2;167: cout << "S3:\t" << s3.GetString() << endl;168:169: String s4;170: s4 = "Why does this work?";171: cout << "S4:\t" << s4.GetString() << endl;172: return 0;173: }

Page 174: C++ PRIRUCNIK

174 C++ programer

Output: S1: initial testS1: Hello worldtempTwo: ; nice to be here!S1: Hello world; nice to be here!S1[4]: oS1: Hellx World; nice to be here!S1[999]: !S3: Hellx World; nice to be here! Another stringS4: Why does this work?

Analiza: Linije 7-31 su deklaracije jednostavne String klase. Linije 11-13 sadrže tri konstruktora: pdrazumijevani konstruktor, konstruktor kopije, i konstruktor koji prima postojeći nul-terminirani string.Ova String klasa preopterećuje offset operator ([ ]), operator plus (+), i operator plus-jednako (+=). Ofset operator je propterećen dvaputa: jednom kao konstantna funkcija koja vraća char i ponovo kao nekonstantna funkcija koja vraća referencu na char.Nekonstantna verzija se koristi u naredbama poput

SomeString[4]='x';u liniji 159. To nam omogućuje direktan pristup svakog znakovnog karaktera u nizu. Referenca na znak se vraća kako bi ju pozivajuća funkcija mogla manipulirati.Konstantna verzija se koristi kada pristupamo konstantnom String objektu, kao što je implementacija konstruktora kopijre (linija 63). Primjetite da se pristupa rhs[i], a ipak je rhs is deklariran kao const String &. U slučaju da je objekt kojeg vraćamo velik, možda bismo željeli deklarirati konstantnu referencu kao povratnu vrijednost. Budući da je char samo jedan byte, to u ovom slučaju ne bi imalo naročitog smisla.Podrazumijevani konstruktor je implementiran u linijama 33-39. On kreira string čija je dužina 0. Konvencija je String klase da vraća svoju veličinu ne računajući terminirajuću nulu. konstruktor kopije je implementiran u linijama 63-70. On postavlja novu veličinu stringa na onu postojećeg stringa—plus1 za treminirajuću nulu. On kopira svaki znak iz postojećeg stringa u novi string i null-terminira ga na kraju.Linije 53-60 implementiraju konstruktor koji uzima postojeći string C-stila. Ovaj konstruktor je sličan konstruktoru kopije. Dužina postojećeg stringa je postignuta pozivanjem standardne String bibliotečne funkcije strlen().U liniji 28, još jedan konstruktor, String(unsigned short), je deklarairan kao privatni funkcijsk član. Cilj je dizajnera ove klase da niti jedna korisnička klasa nikad ne kreira String proizvoljne duljine. Ovaj konstruktor postoji samo kako bi pomogao u internom kreiranju String objekata po potrebi, na primjer s operator+=, u liniji 130. String(unsigned short) koonstruktor ispunjava svaki član svog polja sa NULL. Zbog toga, for petlja provjerava do i<=len umjesto i<len.Destruktor, implementiran u linijama 73-77, briše niz znakova klase. Budite sigurni da su uglate zagrade uz poziv delete operatora, da se svaki pojedini član toga polja obriše, umjesto samo prvog člana.Operator pridruživanja prvo provjerava da li je desna strana jednadžbe jednaka lijevoj. Ako nije, trenutni string se briše, a novi kreira i kopira na mjesto.Linije 117-127 implementiraju operator plus (+) kao operator združivanja. Zgodno je kada možemo napisati

String3 = String1 + String2;te da nakon toga String3 bude skup dva stringa. Kako bismo to postigli, funkcija operator plus računa združenu duljinu dva navedena stringa i kreira privremeni string temp. U novi string se prv kopira string naveden lijevo (*this), za kojim slijedi desni string (rhs).main()funkcija (linije 143-173) služi kao test driver program za ovu klasu.

Page 175: C++ PRIRUCNIK

175Vezane liste i ostale strukture

Polja su poput kontejnera. Odlična su spremišta, ali fiksne veličine. Ako odaberete prevelik kontejner, gubite prostor u vašoj ostavi. Ako odaberete premali, sve će vam se prosipati i imat ćete veliki nered.Jedan način za rješenje tog problema su vezane liste.Vezana lista je takva struktura podataka koja se sastoji od malih kontejnera koji se po potrebi međusobno mogu povezivati. Ideja je da se napiše klasa koja sadrži jedan objekt vaših podataka—poput jednog CAT ili jednog Rectangle—i da pokazuje na slijedeći kontejner. Vi kreirate jedan konteiner za svaki objekt koji trebate pohraniti, i ulančavate ih po potrebi.Kontejnere nazivamo nodovima (engl. nodes). Prvi nod u listi se zove glava, a zadnji rep.Liste dolaze u tri osnovna oblika. Od najjednostavnijih do najkompleksnijih, to su

Jednostruko povezane Dvostruko povezane Stabla

U jednostruko povezanoj listi, svaki nod pokazuje naprijed na slijedeći, ali ne i nazad. Za traženje određenog noda, krećemo od vrha i idemo od noda do noda. Duplo vezana lista omogućuje nam kretanje unaprijed i unazad kroz lanac. Stablo je kompleksna struktura izgrađena iz nodova, od kojih svaki može pokazivati na dva ili tri mjesta. Listing 11.13 pokazuje nam kako kreirati i koristiti jednostavne vezane liste.

Listing 11.13. Implementiranje vezane liste.

1: // Listing 11.132: // Linked list simple implementation3:4: #include <iostream.h>5:6: // object to add to list7: class CAT8: {9: public:10: CAT() { itsAge = 1;}11: CAT(int age):itsAge(age){}12: ~CAT(){};13: int GetAge() const { return itsAge; }14: private:15: int itsAge;16: };17:18: // manages list, orders by cat's age!19: class Node20: {21: public:22: Node (CAT*);23: ~Node();24: void SetNext(Node * node) { itsNext = node; }25: Node * GetNext() const { return itsNext; }26: CAT * GetCat() const { return itsCat; }27: void Insert(Node *);28: void Display();29: private:30: CAT *itsCat;

Page 176: C++ PRIRUCNIK

176 C++ programer

31: Node * itsNext;32: };33:34:35: Node::Node(CAT* pCat):36: itsCat(pCat),37: itsNext(0)38: {}39:40: Node::~Node()41: {42: cout << "Deleting node...\n";43: delete itsCat;44: itsCat = 0;45: delete itsNext;46: itsNext = 0;47: }48:49: // ************************************50: // Insert51: // Orders cats based on their ages52: // Algorithim: If you are last in line, add the cat53: // Otherwise, if the new cat is older than you54: // and also younger than next in line, insert it after55: // this one. Otherwise call insert on the next in line56: // ************************************57: void Node::Insert(Node* newNode)58: {59: if (!itsNext)60: itsNext = newNode;61: else62: {63: int NextCatsAge = itsNext->GetCat()->GetAge();64: int NewAge = newNode->GetCat()->GetAge();65: int ThisNodeAge = itsCat->GetAge();66:67: if ( NewAge >= ThisNodeAge && NewAge < NextCatsAge )68: {69: newNode->SetNext(itsNext);70: itsNext = newNode;71: }72: else73: itsNext->Insert(newNode);74: }75: }76:77: void Node::Display()78: {79: if (itsCat->GetAge() > 0)80: {81: cout << "My cat is ";82: cout << itsCat->GetAge() << " years old\n";83: }84: if (itsNext)

Page 177: C++ PRIRUCNIK

17785: itsNext->Display();86: }87:88: int main()89: {90:91: Node *pNode = 0;92: CAT * pCat = new CAT(0);93: int age;94:95: Node *pHead = new Node(pCat);96:97: while (1)98: {99: cout << "New Cat's age? (0 to quit): ";100: cin >> age;101: if (!age)102: break;103: pCat = new CAT(age);104: pNode = new Node(pCat);105: pHead->Insert(pNode);106: }107: pHead->Display();108: delete pHead;109: cout << "Exiting...\n\n";110: return 0;111: }Output: New Cat's age? (0 to quit): 1New Cat's age? (0 to quit): 9New Cat's age? (0 to quit): 3New Cat's age? (0 to quit): 7New Cat's age? (0 to quit): 2New Cat's age? (0 to quit): 5New Cat's age? (0 to quit): 0My cat is 1 years oldMy cat is 2 years oldMy cat is 3 years oldMy cat is 5 years oldMy cat is 7 years oldMy cat is 9 years oldDeleting node...Deleting node...Deleting node...Deleting node...Deleting node...Deleting node...Deleting node...Exiting...

Page 178: C++ PRIRUCNIK

178 C++ programer

Kviz

1. Koji su prvi i zadnji elementi u SomeArray[25]?2. Kako deklarirati neko višedimenzionalno polje? 3. Inicijalizirajte članove polja iz drugog pitanja. 4. Koliko elemenata se nalazi u SomeArray[10][5][20]?5. Koji je najveći broj elemenata koje možete dodati u vezane liste? 6. Koji je zadnji znak stringa "Josip je dobar momak "?

Vježbe

1. Deklarirajte dvodimenzionalno polje koje predstavlja križić-kružić poligon za igru. 2. Napišite kod koji inicijalizira sve elemente u kreiranom polju iz vj.1 na vrijednost 0. 3. Napišite deklaraciju Node klase koja drži unsigned short integere.

class Node { public: Node (); Node (int); ~Node(); void SetNext(Node * node) { itsNext = node; } Node * GetNext() const { return itsNext; } int GetVal() const { return itsVal; } void Insert(Node *); void Display(); private: int itsVal; Node * itsNext; };

4. BUG BUSTERS: Što ne valja u slijedećem kodu?

unsigned short SomeArray[5][4];for (int i = 0; i<4; i++) for (int j = 0; j<5; j++) SomeArray[i][j] = i+j;

5. BUG BUSTERS: Što ne valja u slijedećem kodu?

unsigned short SomeArray[5][4];for (int i = 0; i<=5; i++) for (int j = 0; j<=4; j++) SomeArray[i][j] = 0;

Page 179: C++ PRIRUCNIK

179

Lekcija 12 Naslijeđivanje

Danas ćete naučiti Što je naslijeđivanje Kako izvesti jednu klasu iz druge. Što je zaštićeni pristup i kako ga koristiti. Što su virtualne funkcije.

Što je naslijeđivanje?

Toyota je marka automobila, koji je prevozno sredstvo. Tiramisu je vrsta deserta koji spada u širu nazivnu skupinu hrana. Što time mislimo reći? Mi mislimo reći da je to oblikk specijalizacije. Npr., automobil je specijalna vrsta prevoznih sredstava.

Naslijeđivanje i derivacija

Pas automatski nasljeđuje svojstva sisavaca. Budući da je sisavac, znamo da se miče i da diše zrak—svi sisavci to rade po definiciji. Ali u definiciju psa ide i pojam lajanja, mahanja repom, itd. Dalje možemo podijeliti pse na lovačke pse i terijere, a terijere dijelimo na Jorkširske terijere, Dendi terijere, Bulterijere, itd. Hijerarija je predstavljena na slici 12.1.

Slika 12.1.Hijerarhija životinja

C++ pokušava predstaviti ove relacije omogućavajući nam definiranje klase koja se izvodi iz druge klase. Izvođenje (derivacija) je način izražavanja odnosa među klasama. Vi derivirate novu klasu, Pas, iz klase Sisavac. Ne morate ekplicitno navesti da se pas miče, jer je to svojstvo naslijedio od sisavaca.

Životinja

Sisavac Reptil

Konj Pas

Ovčar Terijer

Jorkšir Pit Bull

Page 180: C++ PRIRUCNIK

180 C++ programer

Životinjsko carstvo

Zamislite sa ste bili upitani da dizajnirate dječiju igru—simulaciju farme.S vremenom ćete razviti čitav niz životinja na farmi, uključujući konje, krave, pse, mačke, ovce, itd. Kreirat ćete metode za ove klase tako da one djeluju na očekivani način, ali zasad ćemo se zadovoljiti s jednostavnom print naredbom.Osjećajte se slobodnim da proširite minimalni kod kado bi omogučili životinjama realističnije ponašanje.

Sintaksa derivacije

Kad deklarirate klasu, možete indicirati iz koje je klase izvedena tipkajući dvotočku iza njenog imena, tip derivacije (public ili neki drugi) i klasu iz koje deriviramo. Slijedi primjer:

class Dog : public MammalTip derivacije će biti tema nešto kasnije diskusije. Za sada, uvijek koristite public. Klasa iz koje derivirate morala je biti ranije deklarirana, ili će van kompajler javiti grešku. Listing 12.1 ilustrira kako deklarirati kalsu Dog koja je izvedena iz Mammal klase.

Listing 12.1. Jednostavno naslijeđivanje.

1: //Listing 12.1 Simple inheritance2:3: #include <iostream.h>4: enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };5:6: class Mammal7: {8: public:9: // constructors10: Mammal();11: ~Mammal();12:13: //accessors14: int GetAge()const;15: void SetAge(int);16: int GetWeight() const;17: void SetWeight();18:19: //Other methods20: void Speak();21: void Sleep();22:23:24: protected:25: int itsAge;26: int itsWeight;27: };28:

Page 181: C++ PRIRUCNIK

18129: class Dog : public Mammal30: {31: public:32:33: // Constructors34: Dog();35: ~Dog();36:37: // Accessors38: BREED GetBreed() const;39: void SetBreed(BREED);40:41: // Other methods42: // WagTail();43: // BegForFood();44:45: protected:46: BREED itsBreed;47: };

Ovaj program nema nikakav output budući da je riječ samo odeklaraciji klase bez njihove implementacije. Ipak, imamo dosta za pogledati ovdje.

Privatne protiv zaštićenih

Možda ste primjetili da je nova ključna riječ, protected, upotrebljena u linijama 24 i 25 listinga 12.1. Do nedavn su podaci u klasi uvijek bili deklarirani kao private. Ali private članovi nisu dostupni izvedenoj, odn. deriviranoj klasi. Mogli bi ih naravno proglasiti i javnima, ali to nije preporučljivo. Ne želite da druge klase pristupaju ovim članovima direktno.Listing 12.2 demonstrira kako kreirati objekte tipa Dog i pristupati podacima i funkcijama toga tipa.

Listing 12.2. Upotreba deriviranog objekta.

1: //Listing 12.2 Using a derived object2:3: #include <iostream.h>4: enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };5:6: class Mammal7: {8: public:9: // constructors10: Mammal():itsAge(2), itsWeight(5){}11: ~Mammal(){}12:13: //accessors14: int GetAge()const { return itsAge; }15: void SetAge(int age) { itsAge = age; }16: int GetWeight() const { return itsWeight; }17: void SetWeight(int weight) { itsWeight = weight; }18:

Page 182: C++ PRIRUCNIK

182 C++ programer

19: //Other methods20: void Speak()const { cout << "Mammal sound!\n"; }21: void Sleep()const { cout << "shhh. I'm sleeping.\n"; }22:23:24: protected:25: int itsAge;26: int itsWeight;27: };28:29: class Dog : public Mammal30: {31: public:32:33: // Constructors34: Dog():itsBreed(YORKIE){}35: ~Dog(){}36:37: // Accessors38: BREED GetBreed() const { return itsBreed; }39: void SetBreed(BREED breed) { itsBreed = breed; }40:41: // Other methods42: void WagTail() { cout << "Tail wagging...\n"; }43: void BegForFood() { cout << "Begging for food...\n"; }44:45: private:46: BREED itsBreed;47: };48:49: int main()50: {51: Dog fido;52: fido.Speak();53: fido.WagTail();54: cout << "Fido is " << fido.GetAge() << " years old\n";55: return 0;56: }Output: Mammal sound!Tail wagging...Fido is 2 years old

Konstruktori i destruktori

Dog objekti su Mammal objekti. Kada se stvara Fido, njegov glavni konstruktor se prvo poziva, kreirajući Mammal. Potom se poziva Dog konstruktor, kompletirajući stvaranje Dog objekta. Budući da u Fido nismo proslijedili nikakve parametre, podrazumijevani konstruktor se poziva u svakom slučaju. Fido ne postoji dok nije kompletno konstruiran, što znači da i njegov Mammal dio i njegov Dog dio moraju biti konstruirani. Samime time moraju biti pozvana i oba konstruktora.Kada je Fido uništen, prvo se poziva Dog destruktor, a potom i destruktor za Mammal dio od Fida. Svaki destruktor počisti za svojim dijelom Fida. Upamtite da morate čistiti za Vašim Psom! Listing 12.3 demonstrira ideju.

Page 183: C++ PRIRUCNIK

183

Listing 12.3. Pozivi konstruktora i destruktora.

1: //Listing 12.3 Constructors and destructors called.2:3: #include <iostream.h>4: enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };5:6: class Mammal7: {8: public:9: // constructors10: Mammal();11: ~Mammal();12:13: //accessors14: int GetAge() const { return itsAge; }15: void SetAge(int age) { itsAge = age; }16: int GetWeight() const { return itsWeight; }17: void SetWeight(int weight) { itsWeight = weight; }18:19: //Other methods20: void Speak() const { cout << "Mammal sound!\n"; }21: void Sleep() const { cout << "shhh. I'm sleeping.\n"; }22:23:24: protected:25: int itsAge;26: int itsWeight;27: };28:29: class Dog : public Mammal30: {31: public:32:33: // Constructors34: Dog();35: ~Dog();36:37: // Accessors38: BREED GetBreed() const { return itsBreed; }39: void SetBreed(BREED breed) { itsBreed = breed; }40:41: // Other methods42: void WagTail() { cout << "Tail wagging...\n"; }43: void BegForFood() { cout << "Begging for food...\n"; }44:45: private:46: BREED itsBreed;47: };48:49: Mammal::Mammal():50: itsAge(1),

Page 184: C++ PRIRUCNIK

184 C++ programer

51: itsWeight(5)52: {53: cout << "Mammal constructor...\n";54: }55:56: Mammal::~Mammal()57: {58: cout << "Mammal destructor...\n";59: }60:61: Dog::Dog():62: itsBreed(YORKIE)63: {64: cout << "Dog constructor...\n";65: }66:67: Dog::~Dog()68: {69: cout << "Dog destructor...\n";70: }71: int main()72: {73: Dog fido;74: fido.Speak();75: fido.WagTail();76: cout << "Fido is " << fido.GetAge() << " years old\n";77: return 0;78: }Output: Mammal constructor...Dog constructor...Mammal sound!Tail wagging...Fido is 1 years oldDog destructor...Mammal destructor...

Analiza: Listing 12.3 je poput Listinga 12.2, osim što konstruktori i destruktori sada ispisuju na ekran kada su pozvani. Mammal konstruktor se prvo poziva, a tek zatim Dog. U tom trenutku Dog postoji u cjelosti, i njegove metode možemo koristiti. Kad fido izađe iz dosega, Dog destruktor je pozvan, iza kojeg slijedi i poziv Mammal destruktora.

Proslijeđivanje argumenata baznim konstruktorima

Moguće je da čete željeti preopteretiti konstruktor od Mammal da prima određenu starost, a da preopterećeni Dog construktor prima pasminu. Kako postižemo da se ti parametri pravilo proslijede u prikladne konstruktore u Mammal? Što ako Dog želi inicijalizirati težinu ali Mammals ne želi?Osnovna inicijalizacija klase može biti izvedena za vrijeme inicijalizacije klase upisivanjem imena osnovne klase i pripadajućim parametrima. Listing 12.4 demonstrira ovo.

Listing 12.4. Preopterećenje konstruktora u izvedenim klasama.

Page 185: C++ PRIRUCNIK

185

1: //Listing 12.4 Overloading constructors in derived classes2:3: #include <iostream.h>4: enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };5:6: class Mammal7: {8: public:9: // constructors10: Mammal();11: Mammal(int age);12: ~Mammal();13:14: //accessors15: int GetAge() const { return itsAge; }16: void SetAge(int age) { itsAge = age; }17: int GetWeight() const { return itsWeight; }18: void SetWeight(int weight) { itsWeight = weight; }19:20: //Other methods21: void Speak() const { cout << "Mammal sound!\n"; }22: void Sleep() const { cout << "shhh. I'm sleeping.\n"; }23:24:25: protected:26: int itsAge;27: int itsWeight;28: };29:30: class Dog : public Mammal31: {32: public:33:34: // Constructors35: Dog();36: Dog(int age);37: Dog(int age, int weight);38: Dog(int age, BREED breed);39: Dog(int age, int weight, BREED breed);40: ~Dog();41:42: // Accessors43: BREED GetBreed() const { return itsBreed; }44: void SetBreed(BREED breed) { itsBreed = breed; }45:46: // Other methods47: void WagTail() { cout << "Tail wagging...\n"; }48: void BegForFood() { cout << "Begging for food...\n"; }49:50: private:51: BREED itsBreed;52: };

Page 186: C++ PRIRUCNIK

186 C++ programer

53:54: Mammal::Mammal():55: itsAge(1),56: itsWeight(5)57: {58: cout << "Mammal constructor...\n";59: }60:61: Mammal::Mammal(int age):62: itsAge(age),63: itsWeight(5)64: {65: cout << "Mammal(int) constructor...\n";66: }67:68: Mammal::~Mammal()69: {70: cout << "Mammal destructor...\n";71: }72:73: Dog::Dog():74: Mammal(),75: itsBreed(YORKIE)76: {77: cout << "Dog constructor...\n";78: }79:80: Dog::Dog(int age):81: Mammal(age),82: itsBreed(YORKIE)83: {84: cout << "Dog(int) constructor...\n";85: }86:87: Dog::Dog(int age, int weight):88: Mammal(age),89: itsBreed(YORKIE)90: {91: itsWeight = weight;92: cout << "Dog(int, int) constructor...\n";93: }94:95: Dog::Dog(int age, int weight, BREED breed):96: Mammal(age),97: itsBreed(breed)98: {99: itsWeight = weight;100: cout << "Dog(int, int, BREED) constructor...\n";101: }102:103: Dog::Dog(int age, BREED breed):104: Mammal(age),105: itsBreed(breed)106: {

Page 187: C++ PRIRUCNIK

187107: cout << "Dog(int, BREED) constructor...\n";108: }109:110: Dog::~Dog()111: {112: cout << "Dog destructor...\n";113: }114: int main()115: {116: Dog fido;117: Dog rover(5);118: Dog buster(6,8);119: Dog yorkie (3,YORKIE);120: Dog dobbie (4,20,DOBERMAN);121: fido.Speak();122: rover.WagTail();123: cout << "Yorkie is " << yorkie.GetAge() << " years old\n";124: cout << "Dobbie weighs ";125: cout << dobbie.GetWeight() << " pounds\n";126: return 0;127: }

Pažnja: Ispis je pobrojan kako bi se mogli referencirati na pojedine linije tjekom analize.

Output: 1: Mammal constructor...2: Dog constructor...3: Mammal(int) constructor...4: Dog(int) constructor...5: Mammal(int) constructor...6: Dog(int, int) constructor...7: Mammal(int) constructor...8: Dog(int, BREED) constructor....9: Mammal(int) constructor...10: Dog(int, int, BREED) constructor...11: Mammal sound!12: Tail wagging...13: Yorkie is 3 years old.14: Dobbie weighs 20 pounds.15: Dog destructor. . . 16: Mammal destructor...17: Dog destructor...18: Mammal destructor...19: Dog destructor...20: Mammal destructor...21: Dog destructor...22: Mammal destructor...23: Dog destructor...24: Mammal destructor...

Analiza: U Listingu 12.4, Mammal konstruktor je preopterećen u liniji 11 kako bi uzimao cijeli broj, Mammal age. Implementacija u linijama 61-66 inicijalizira itsAge s vrijednošću proslijeđenoj u konstruktor i inicijalizira itsWeight s vrijednošću 5.

Page 188: C++ PRIRUCNIK

188 C++ programer

Dog ima pet preopterećenih konstruktora, u linijama 35-39. Prvi je podrazumijevani konstruktor. Drugi prima godine, što je isti parametar koji uzima i Mammal konstruktor. Treći konstruktor prima i godine i težinu, četvrti godine i pasminu, a peti godine, težinu i pasminu.Primjetite da u liniji 74 podrazumijevani konstruktor od Dog poziva Mammal podrazumijevani konstruktor. Iako nije striktno neophodno to učiniti, služi nam kao dokumentacija da smo željeli pozvati bazni konstruktor bez parametara. Bazni konstruktor bi bio svakako pozvan, ali čineći to naše namjere su razumljivije.Sama implementacija Dog konstruktora, koji prima cijeli broj, je u linijama 80-85. U svojoj inicijalizacijskoj fazi (linije 81-82), Dog inicijalizira svoju baznu klasu, proslijeđujući parametar, a potom inicijalizira svoju pasminu, its breed.Drugi Dog konstruktor je u linijama 87-93. Obvaj prima dva parametra. Još jednom on inicijalizira svoju baznu klasu s pozivom odgovarajućeg konstruktora, ali ovaj put također pridružuje weight u varijablu bazne klase itsWeight. Primjetite da ne možete pridruživati u baznu klasu u inicijalizacijskoj fazi. Budući da Mammal nema konstruktor koji prima taj parametar, to morate učiniti unutar tijela Dog konstruktora.

Zaobilaženje funkcija

Dog objekt ima pristup do svih funkcijskih članova klase Mammal, kao i do bilo koje vlastite funkcije, poput WagTail(). On također može i zaobilaziti funkcije bazne klase. Zaobilaženje funkcije znači mijenjanje implementacije funkcije iz bazne klase u deriviranoj klasi. Kada napravite objekt derivirane klase, prava funkcija je pozvana.

Novi izraz: Kada derivirana klasa stvori funkciju istog povratnog tipa i potpisa kao i funkcijski član bazne klase, ali sa novom implementacijom, kažemo da ona zaobilazi tu metodu.

Kad zaobilazite funkciju. moraju se podudarati povratni tipovi i potpisi samih funkcija. Potpiss je funkcijski prototip osim povratnog tipa, odnosno :ime funkcije, lista parametara, te riječ const ako je upotrebljena.

Listing 12.5 ilustrira što se događa sa Dog klasom koja zaobilazi Speak() metodu u Mammal. Za uštedu prostora, pristupne funkcije su izbačene iz ovih klasa.

1: //Listing 12.5 Overriding a base class method in a derived class2:3: #include <iostream.h>4: enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };5:6: class Mammal7: {8: public:9: // constructors10: Mammal() { cout << "Mammal constructor...\n"; }11: ~Mammal() { cout << "Mammal destructor...\n"; }12:13: //Other methods14: void Speak()const { cout << "Mammal sound!\n"; }15: void Sleep()const { cout << "shhh. I'm sleeping.\n"; }16:17:

Page 189: C++ PRIRUCNIK

18918: protected:19: int itsAge;20: int itsWeight;21: };22:23: class Dog : public Mammal24: {25: public:26:27: // Constructors28: Dog(){ cout << "Dog constructor...\n"; }29: ~Dog(){ cout << "Dog destructor...\n"; }30:31: // Other methods32: void WagTail() { cout << "Tail wagging...\n"; }33: void BegForFood() { cout << "Begging for food...\n"; }34: void Speak()const { cout << "Woof!\n"; }35:36: private:37: BREED itsBreed;38: };39:40: int main()41: {42: Mammal bigAnimal;43: Dog fido;44: bigAnimal.Speak();45: fido.Speak();46: return 0;47: }Output: Mammal constructor...Mammal constructor...Dog constructor...Mammal sound!Woof!Dog destructor...Mammal destructor...Mammal destructor...

Skrivanje metoda osnovne klase

U prethodnom listingu, Speak() metoda Dog klase sakriva metodu bazne klase. To je upravo ono što smo i željeli, ali može imati neočekivane rezultate. Ako npr., Mammal ima preopterećenu metodu, Move(),a Dog zaobilazi tu metodu, Dog metoda će sakriti sve Mammal metode sa tim imenom.Ako Mammal preopterećuje Move() sa tri metode—jednom bez parametara, jednom koja prima integer, te jednom koja prima integer i smjer—a Dog zaobilazi samo Move() metodu koja ne prima parametre, neće biti jednostavno pristupiti preostalim dvjema metodama koristeći Dog objekt. Listing 12.6 ilustrira taj problem.

Listing 12.6. Skrivanje metoda.

1: //Listing 12.6 Hiding methods

Page 190: C++ PRIRUCNIK

190 C++ programer

2:3: #include <iostream.h>4:5: class Mammal6: {7: public:8: void Move() const { cout << "Mammal move one step\n"; }9: void Move(int distance) const 10: { 11: cout << "Mammal move ";12: cout << distance <<" _steps.\n"; 13: }14: protected:15: int itsAge;16: int itsWeight;17: };18:19: class Dog : public Mammal20: {21: public:22: // You may receive a warning that you are hiding a function!23: void Move() const { cout << "Dog move 5 steps.\n"; }24: }; 25:26: int main()27: {28: Mammal bigAnimal;29: Dog fido;30: bigAnimal.Move();31: bigAnimal.Move(2);32: fido.Move();33: // fido.Move(10);34: return 0;35: }Output: Mammal move one stepMammal move 2 steps.Dog move 5 steps.

Zaobilaženje protiv skrivanja

U slijedećem odlomku bit će opisane virtualne metode. Zaobilaženje virtualnih metoda podržava polimorfizam—njihovo skrivanje ga "potkopava". Uskoro ćete naučiti više o tome.

Pozivanje bazne metode

Ako ste zaobišli baznu metodu, i dalje ju je moguće pozvati potpunim navođenjem imena metode. To postižete pisanjem imena baze, dvije dvotočke i potom imena metode. Na primjer, Mammal::Move().Moguće je prepraviti liniju 28 u listingu 12.6 kako bi bio kompletan, pišući

28: fido.Mammal::Move(10);Time ekplicitno pozivamo metodu klase Mammal. Listing 12.7 ilustrira tu ideju.

Page 191: C++ PRIRUCNIK

191

Page 192: C++ PRIRUCNIK

192 C++ programer

Listing 12.7. Pozivanje bazne metode iz zaobiđene metode.

1: //Listing 12.7 Calling base method from overridden method.2:3: #include <iostream.h>4:5: class Mammal6: {7: public:8: void Move() const { cout << "Mammal move one step\n"; }9: void Move(int distance) const 10: { 11: cout << "Mammal move " << distance;12: cout << " steps.\n"; 13: }14:15: protected:16: int itsAge;17: int itsWeight;18: };19:20: class Dog : public Mammal21: {22: public:23: void Move()const;24:25: };26:27: void Dog::Move() const28: {29: cout << "In dog move...\n";30: Mammal::Move(3);31: }32:33: int main()34: {35: Mammal bigAnimal;36: Dog fido;37: bigAnimal.Move(2);38: fido.Mammal::Move(6);39: return 0;40: }Output: Mammal move 2 steps.Mammal move 6 steps.

Analiza: U liniji 35, Mammal, bigAnimal, je stvoren, a u linijii 36 i Dog, fido. Poziv metode u liniji 37 poziva Move() metodu od Mammal, koja uzima int.Programer je želio pozvati Move(int) za Dog objekt, ali ima problem. Dog zaobilazi Move() metodu, ali ju ne preopterećuje i ne pruža verziju koja prima int. To je riješeno eksplicitnom pozivom metode iz bazne klase Move(int) u liniji 33.

Page 193: C++ PRIRUCNIK

193Virtualne metode

Ova lekcija je naglasila činjenicu da je Dog objekt ujedno i Mammal objekt. Do sada je to značilo da je Dog objekt naslijedio sve atribute (podatke) i sposobnosti (metode) bazne klase. U C++ jezuku njihova je relacija i mnogo dublja od toga.C++ proširuje svoj polimorfizam kako bi omogućio pokazivačima na bazne klase da budu pridruženi izvedenim objektima. Tako možete pisati

Mammal* pMammal = new Dog;Ovime kreiramo novi Dog objekt u slobodnom spremniku i vraćamo pokazivač na taj objekt, kojeg potom pridružujemo pokazivaču na Mammal. To je u redu, budući da je pas i sisavac.

PAŽNJA: Ovo je bit polimorfizma. Na primjer, možete kreirati mnoštvo različitih tipova prozora, uključujući i dijaloške okvire, srolajuće prozore, i liste, te im svima dodjeliti virtualnu draw() metodu. Kreirajući pokazivač na prozor i pridružujući dijaloške okvire i ostale derivirane tipove na taj pokazivač, vi možete zvati draw() bez obzira na trenutni tip objekta na kojeg pokazujemo. Ispravna draw() funkcija će biti pozvana.

Potom možete koristiti taj pokazivač za pokretanje bilo koje Mammal metode. Ono što biste željeli je da one metode koje su zaobiđene u Dog() pozovu pravilnu funkciju. Virtualne funkcije omogućuju nam upravo to. Listing 12.8 ilustrira kako to radi, te što se događa sa ne-virtualnim metodama.

Listing 12.8. Upotreba virtualnih metoda.

1: //Listing 12.8 Using virtual methods2:3: #include <iostream.h>4:5: class Mammal6: {7: public:8: Mammal():itsAge(1) { cout << "Mammal constructor...\n"; }9: ~Mammal() { cout << "Mammal destructor...\n"; }10: void Move() const { cout << "Mammal move one step\n"; }11: virtual void Speak() const { cout << "Mammal speak!\n"; }12: protected:13: int itsAge;14:15: };16:17: class Dog : public Mammal18: {19: public:20: Dog() { cout << "Dog Constructor...\n"; }21: ~Dog() { cout << "Dog destructor...\n"; }22: void WagTail() { cout << "Wagging Tail...\n"; }23: void Speak()const { cout << "Woof!\n"; }24: void Move()const { cout << "Dog moves 5 steps...\n"; }25: };26:27: int main()28: {

Page 194: C++ PRIRUCNIK

194 C++ programer

29:30: Mammal *pDog = new Dog;31: pDog->Move();32: pDog->Speak();33:34: return 0;35: }Output: Mammal constructor...Dog Constructor...Mammal move one stepWoof!

Analiza: U liniji 11, Mammal ima virtualnu metodu--speak(). Programer ove klase time signalizira da očekuje kako će ova klasa eventualno postati bazna klasa neke druge klase. Izvedena klasa će vjerojatno željeti zaobići ovu funkciju.U liniji 30, pokazivač na Mammal je kreiran (pDog), ali mu je pridružena adresa novoga Dog objekta. Budući da je pas sisavac, ovo je legalno pridruživanje. Pointer nam potom služi za pozivanje Move() funkcije. Budući da kompajler zna samo da je pDog Mammal, on u Mammal objektu traži Move() metodu.U liniji 32, pokazivač potom poziva Speak() metodu. Budući da je Speak() virtualan, zaobiđena Speak() metoda u Dog se pokreće.Ovo je gotovo magično. Koliko je pozivna funkcija znala, imala je Mammal pokazivač. ali je ipak pozvana metoda iz Dog klase. U stvari, da ste imali poloje pokazivača na Mammal, od kojih svaki pokazuje na podklasu od Mammal, mogli bi pozivati svakog od njih i prava funkcija bi bila pozvana. Listing 12.9 ilustrira tu ideju.

Listing 12.9. Višestruke virtualne funkcije naizmjenično pozivane.

1: //Listing 12.9 Multiple virtual functions called in turn2:3: #include <iostream.h>4:5: class Mammal6: {7: public:8: Mammal():itsAge(1) { }9: ~Mammal() { }10: virtual void Speak() const { cout << "Mammal speak!\n"; }11: protected:12: int itsAge;13: };14:15: class Dog : public Mammal16: {17: public:18: void Speak()const { cout << "Woof!\n"; }19: };20:21:22: class Cat : public Mammal23: {24: public:25: void Speak()const { cout << "Meow!\n"; }26: };

Page 195: C++ PRIRUCNIK

19527:28:29: class Horse : public Mammal30: {31: public:32: void Speak()const { cout << "Winnie!\n"; }33: };34:35: class Pig : public Mammal36: {37: public:38: void Speak()const { cout << "Oink!\n"; }39: };40:41: int main()42: {43: Mammal* theArray[5];44: Mammal* ptr;45: int choice, i;46: for ( i = 0; i<5; i++)47: {48: cout << "(1)dog (2)cat (3)horse (4)pig: ";49: cin >> choice;50: switch (choice)51: {52: case 1: ptr = new Dog;53: break;54: case 2: ptr = new Cat;55: break;56: case 3: ptr = new Horse;57: break;58: case 4: ptr = new Pig;59: break;60: default: ptr = new Mammal;61: break;62: }63: theArray[i] = ptr;64: }65: for (i=0;i<5;i++)66: theArray[i]->Speak();67: return 0;68: }Output: (1)dog (2)cat (3)horse (4)pig: 1(1)dog (2)cat (3)horse (4)pig: 2(1)dog (2)cat (3)horse (4)pig: 3(1)dog (2)cat (3)horse (4)pig: 4(1)dog (2)cat (3)horse (4)pig: 5Woof!Meow!Winnie!Oink!Mammal speak!

Page 196: C++ PRIRUCNIK

196 C++ programer

Kako virtualne funkcije rade

Kada se izvedeni objekt, poput Dog, kreira, prvo se poziva konstruktor bazne klase a potom konstruktor derivirane klase. Slika 12.2 pokazuje kako Dog objekt izgleda nakon to je kreiran. Primjetite da je Mammal dio objekta u memorijskom bloku zajedno s Dog dijelom.

Slika 12.2. Dog objekt nakon kreiranja.

Kad je virtualna funkcija kreirana u objektu, objekt mora biti svjestan njenog postojanja. Mnogi kompajleri grade tablicu virtualnih funkcija, zvanu v-table. Jedna tablica se kreira za svaki tip, i svaki objekt tog tipa sadrži pokazivač na virtualnu tablicu (zvan vptr ili v-pointer), koji pokazuje na tu tablicu.Dok implementacije variraju, svi kompajleri moraju postići istu stvar, pa ova definicija nije "previše pogrešna". Slika 12.3. v-table od Mammal.

vptr svakog objekta pokazuje na v-tablicu koja, zauzvrat, ima pokazivač na svaku od virtualnih funkcija.Kada je Mammal dio od Dog kreiran, vptr je inicijaliziran da pokazuje na korektan dio v-tablice, kako je prikazano na slici 12.3.

Slika 12.4. v-table od Dog.

Kada pozovemo Dog konstruktor, i Dog dio tog objekta se dodaje, vptr se prilagođava kako bi pokazivao na zaobiđene virtualne funkcije (ako ih ima) u Dog objektu (vidi sl. 12.4) .Kada koristimo pokazivač na Mammal, vptr nastavlja pokazivati na pravu funkciju, ovisno o tipu objekta, Prema tome, kada pozovemo Speak(), prava funkcija se pokreće.

Ne možete stići tamo odavde

Da je Dog objekt imao metodu, WagTail(), koja se ne nalazi u Mammal, ne biste mogli koristiti pokazivač na Mammal kako biste pristupili toj metodi (osim ako ju proglasite pokazivačem na Dog). Budući da WagTail() nije virtualna funkcija, i budući da nije Mammal objekt, ne možete joj pristupiti bez bilo Dog objekta bilo Dog pokazivača.

Page 197: C++ PRIRUCNIK

197Iako možete transformirati Mammal pokazivač u Dog pokazivač, obično postoje mnogo bolji i sigurniji načini za poziv WagTail() metode.

Rezanje

Primjetite da magija virtualnih funkcija djeluje samo na pokazivače i reference. Proslijeđivanje objekta po vrijednosti neće omogućiti pokretanje virtualne funkcije. Listing 12.10 ilustrira taj problem.

Listing 12.10. Rezanje podataka kad se proslijeđuju po vrijednosti.

1: //Listing 12.10 Data slicing with passing by value2:3: #include <iostream.h>4:5: enum BOOL { FALSE, TRUE };6: class Mammal7: {8: public:9: Mammal():itsAge(1) { }10: ~Mammal() { }11: virtual void Speak() const { cout << "Mammal speak!\n"; }12: protected:13: int itsAge;14: };15:16: class Dog : public Mammal17: {18: public:19: void Speak()const { cout << "Woof!\n"; }20: };21:22: class Cat : public Mammal23: {24: public:25: void Speak()const { cout << "Meow!\n"; }26: };27:28 void ValueFunction (Mammal);29: void PtrFunction (Mammal*);30: void RefFunction (Mammal&);31: int main()32: {33: Mammal* ptr=0;34: int choice;35: while (1)36: {37: BOOL fQuit = FALSE;38: cout << "(1)dog (2)cat (0)Quit: ";39: cin >> choice;40: switch (choice)41: {42: case 0: fQuit = TRUE;43: break;44: case 1: ptr = new Dog;

Page 198: C++ PRIRUCNIK

198 C++ programer

45: break;46: case 2: ptr = new Cat;47: break;48: default: ptr = new Mammal;49: break;50: }51: if (fQuit)52: break;53: PtrFunction(ptr);54: RefFunction(*ptr);55: ValueFunction(*ptr);56: }57: return 0;58: }59:60: void ValueFunction (Mammal MammalValue)61: {62: MammalValue.Speak();63: }64:65: void PtrFunction (Mammal * pMammal)66: {67: pMammal->Speak();68: }69:70: void RefFunction (Mammal & rMammal)71: {72: rMammal.Speak();73: }Output: (1)dog (2)cat (0)Quit: 1WoofWoofMammal Speak!(1)dog (2)cat (0)Quit: 2Meow!Meow!Mammal Speak!(1)dog (2)cat (0)Quit: 0

Virtualni destruktori

Legalno je i prilično često proslijediti pokazivač na izvedeni objekt kada očekujemo pokazivač na bazni objekt. Što se događa kada obrišemo takav pokazivač na izvedeni objekt? Ako je destruktor virtualan, kako bi i trebao biti, dogodit će se "prava stvar"—bit će pozvan destruktor derivirane klase. Budući da će destruktor izvedene klase automatski pozvati i bazni destruktor, cijeli objekt će biti pravilno izbrisan. Pravilo glasi: Ako je bilo koja funkcija u klasi virtualna, trebao bi biti i destruktor.

Virtualni konstruktori kopije

Kao što smo prethodno rekli, niti jedan konstruktor ne može biti virtualan. Ipak, ponekad nam je neophodna mogućnost proslijeđivanja pokazivača na bazni objekt i držanja kopije pravilno izvedenog objekta. Često rješenje toga problema je kreiranje

Page 199: C++ PRIRUCNIK

199Clone() metode u baznoj klasi i proglašavanje je virtualnom. Clone() metoda kreira kopiju novog objekta trenutne klase, te vraća taj objekt. Budući da svaka derivirana klasa zaobilazi Clone() metodu, kopija derivirane klase se kreira. Listing 12.11 ilustrira kako se to koristi.

Listing 12.11. Virtualni konstruktor kopije.

1: //Listing 12.11 Virtual copy constructor2:3: #include <iostream.h>4:5: class Mammal6: {7: public:8: Mammal():itsAge(1) { cout << "Mammal constructor...\n"; }9: ~Mammal() { cout << "Mammal destructor...\n"; }10: Mammal (const Mammal & rhs);11: virtual void Speak() const { cout << "Mammal speak!\n"; }12: virtual Mammal* Clone() { return new Mammal(*this); } 13: int GetAge()const { return itsAge; }14: protected:15: int itsAge;16: };17:18: Mammal::Mammal (const Mammal & rhs):itsAge(rhs.GetAge())19: {20: cout << "Mammal Copy Constructor...\n";21: }22:23: class Dog : public Mammal24: {25: public:26: Dog() { cout << "Dog constructor...\n"; }27: ~Dog() { cout << "Dog destructor...\n"; }28: Dog (const Dog & rhs);29: void Speak()const { cout << "Woof!\n"; }30: virtual Mammal* Clone() { return new Dog(*this); }31: };32:33: Dog::Dog(const Dog & rhs):34: Mammal(rhs)35: {36: cout << "Dog copy constructor...\n";37: }38:39: class Cat : public Mammal40: {41: public:42: Cat() { cout << "Cat constructor...\n"; }43: ~Cat() { cout << "Cat destructor...\n"; }44: Cat (const Cat &);45: void Speak()const { cout << "Meow!\n"; }46: virtual Mammal* Clone() { return new Cat(*this); }47: };

Page 200: C++ PRIRUCNIK

200 C++ programer

48:49: Cat::Cat(const Cat & rhs):50: Mammal(rhs)51: {52: cout << "Cat copy constructor...\n";53: }54:55: enum ANIMALS { MAMMAL, DOG, CAT};56: const int NumAnimalTypes = 3;57: int main()58: {59: Mammal *theArray[NumAnimalTypes];60: Mammal* ptr;61: int choice, i;62: for ( i = 0; i<NumAnimalTypes; i++)63: {64: cout << "(1)dog (2)cat (3)Mammal: ";65: cin >> choice;66: switch (choice)67: {68: case DOG: ptr = new Dog;69: break;70: case CAT: ptr = new Cat;71: break;72: default: ptr = new Mammal;73: break;74: }75: theArray[i] = ptr;76: }77: Mammal *OtherArray[NumAnimalTypes];78: for (i=0;i<NumAnimalTypes;i++)79: {80: theArray[i]->Speak();81: OtherArray[i] = theArray[i]->Clone();82: }83: for (i=0;i<NumAnimalTypes;i++)84: OtherArray[i]->Speak();25: return 0;86: }1: (1)dog (2)cat (3)Mammal: 12: Mammal constructor...3: Dog constructor...4: (1)dog (2)cat (3)Mammal: 25: Mammal constructor...6: Cat constructor...7: (1)dog (2)cat (3)Mammal: 38: Mammal constructor...9: Woof!10: Mammal Copy Constructor...11: Dog copy constructor...12: Meow!13: Mammal Copy Constructor...14: Cat copy constructor...15: Mammal speak!

Page 201: C++ PRIRUCNIK

20116: Mammal Copy Constructor...17: Woof!18: Meow!19: Mammal speak!

Kviz

1. Što je v-table?2. Što je virtualni destruktor?3. Kako pokazujemo deklaraciju virtualnog konstruktora? 4. Kako kreiramo virtualni konstruktor kopije? 5. Kako pozivamo funkciju bazne klase iz derivirane klase u kojoj ste zaobišli tu funkciju? 6. Kako pozivamo baznu funkciju iz izvedene klase u kojoj nismo zaobilazili funkciju?7. Ako bazna klasa deklarira funkciju virtualnom, a izvedena klasa ne koristi izraz virtual kad zaobilazi klasu, da li je ona još uvije kvirtualna ako ju naslijedi treća gemneracija klase? 8. Kada koristimo protected ključnu riječ?

Vježbe

1. Pokaži deklaraciju virtualne funkcije koja uzima cjelobrojni parametar i vraća void.2. Pokažite deklaraciju klase Square, koja se izvodi iz Rectangle, kojii se izvodi iz Shape.3. Ako, u vj.2, Shape ne uzima parametre, Rectangle uzima dva (length i width), ali Square uzima samo jedan (length), pokažite inicijalizaciju konstruktora za Square.4. Napišite virtualni konstruktor kopije za klasu Square (vj. 3).5. BUG BUSTERS: Što ne valja?

void SomeFunction (Shape);Shape * pRect = new Rectangle;SomeFunction(*pRect);

6. BUG BUSTERS: Što ne valja?

class Shape(){public: Shape(); virtual ~Shape(); virtual Shape(const Shape&);};

Page 202: C++ PRIRUCNIK

202 C++ programer

Lekcija 13 Polimorfizam

U prošloj ste lekciji naučili kako pisati virtualne funkcije u izvedenim klasama. To je osnovni građevni blok polimorfizma: sposobnost vezanja specifičnih izvedenih objekata na pokazivače bazne klase. Danas ćemo vidjeti što je to višestruko naslijeđivanje i kako se koristi.

Što je virtualno naslijeđivanje. Što su apstraktni tipovi podataka (ADT engl. Abstract Data Types). Što su čiste virtualne funkcije.

Problemi s jednostrukim naslijeđivanjem

Pretpostavimo da ste radili sa vašim klasama životinja neko vrijeme, te ste podijelili hijerarhiju klasa na Birds i Mammals. Bird klasa sadrži funkcijski član Fly(). Mammal klasa je podijeljena na čitav niz podtipova od Mammal, uključujući Horse. Horse klasa sadrži funkcijske članove Whinny() i Gallop().Iznenada shvatite da vam je potreban Pegasus objekt: mješanac između konja i ptice. Pegasus zna Fly(), ali i Whinny(), i zna Gallop(). S jednostrukim naslijeđivanjem sada ste u priličnoj "gabuli".Možete proglasiti Pegasus kao Bird, ali onda neće znati ni Whinny() ni Gallop(). Možete ga načiniti kao Horse, ali tada neće znati Fly().Prvo rješenje bilo bi kopiranje Fly() metode u Pegasus klasu i izvođenje Pegasus iz Horse. Ovo radi, ali pod cijenu imanja dvije istovjetene Fly() metode na dva mjesta (Bird i Pegasus). Ako promjenite jednu, morate se sjetiti promjeniti i drugu. Naravno, programer koji dođe raditi mjesecima nakon vas mora također znati da se promjene vrše na dva mjesta.Uskoro dolazimo i do novog problema. Vi želite napraviti listu svih Horse objekata i listu svih Bird objekata. Željeli biste staviti svoj Pegasus objekt na obje liste, ali ako je Pegasus horse, ne možete ga staviti na listu od birds.Postoji nekoliko potencijalnih rješenja. Možete preimenovati Horse metodu Gallop() u Move(), i potom zaobići Move() u svom Pegasus objektu da radi posao od Fly(). Potom biste zaobišli Move() u drugim konjima da radi posao od Gallop(). Možda bi čak Pegasus mogao biti dovoljno pametan da galopira na kraćim relacijama, a leti na dužim.

Pegasus::Move(long distance){if (distance > veryFar)fly(distance);elsegallop(distance);}

Ovo nas pomalo ograničava. Možda će jednoga dana naš Pegasus zaželjeti letjeti i na kraćoj relaciji ili galopirati na duljoj. Slijedeće rješenje bilo bi da pomaknemo Fly() u Horse, kao na listingu 13.1. Problem je što većina konja ipak ne leti, pa morate natjerati tu metodu da ništa ne radi osim ako je riječ o Pegasus.

Listing 13.1. Kad bi konji letjeli...

1: // Listing 13.1. If horses could fly...2: // Percolating Fly() up into Horse3:

Page 203: C++ PRIRUCNIK

2034: #include <iostream.h>5:6: class Horse7: {8: public:9: void Gallop(){ cout << "Galloping...\n"; }10: virtual void Fly() { cout << "Horses can't fly.\n" ; }11: private:12: int itsAge;13: };14:15: class Pegasus : public Horse16: {17: public:18: virtual void Fly() { cout << "I can fly! I can fly! I can fly!\n"; }19: };20:21: const int NumberHorses = 5;22: int main()23: {24: Horse* Ranch[NumberHorses];25: Horse* pHorse;26: int choice,i;27: for (i=0; i<NumberHorses; i++)28: {29: cout << "(1)Horse (2)Pegasus: ";30: cin >> choice;31: if (choice == 2)32: pHorse = new Pegasus;33: else34: pHorse = new Horse;35: Ranch[i] = pHorse;36: }37: cout << "\n";38: for (i=0; i<NumberHorses; i++)39: {40: Ranch[i]->Fly();41: delete Ranch[i];42: }43: return 0;44: }Output: (1)Horse (2)Pegasus: 1(1)Horse (2)Pegasus: 2(1)Horse (2)Pegasus: 1(1)Horse (2)Pegasus: 2(1)Horse (2)Pegasus: 1Horses can't fly.I can fly! I can fly! I can fly!Horses can't fly.I can fly! I can fly! I can fly!Horses can't fly.

Page 204: C++ PRIRUCNIK

204 C++ programer

Višestruko naslijeđivanje

Moguće je izvesti novu klasu iz više od jedne bazne klase. Ovo se zove višestruko naslijeđivanje. Kako bi izveli iz više baznih klasa, svaku baznu klasu navodimo u opsiu klase odvojenu zarezom. Listing 13.3 ilustrira kako deklarirati Pegasus tako da se izvodi i iz Horses i Birds. Program potom dodaje Pegasus objekte na liste oba tipa.

Listing 13.3. Višestruko naslijeđivanje.

1: // Listing 13.3. Multiple inheritance.2: // Multiple Inheritance3:4: #include <iostream.h>5:6: class Horse7: {8: public:9: Horse() { cout << "Horse constructor... "; }10: virtual ~Horse() { cout << "Horse destructor... "; }11: virtual void Whinny() const { cout << "Whinny!... "; }12: private:13: int itsAge;14: };15:16: class Bird17: {18: public:19: Bird() { cout << "Bird constructor... "; }20: virtual ~Bird() { cout << "Bird destructor... "; }21: virtual void Chirp() const { cout << "Chirp... "; }22: virtual void Fly() const 23: { 24: cout << "I can fly! I can fly! I can fly! "; 25: }26: private:27: int itsWeight;28: };29:30: class Pegasus : public Horse, public Bird31: {32: public:33: void Chirp() const { Whinny(); }34: Pegasus() { cout << "Pegasus constructor... "; }35: ~Pegasus() { cout << "Pegasus destructor... "; }36: };37:38: const int MagicNumber = 2;39: int main()40: {41: Horse* Ranch[MagicNumber];42: Bird* Aviary[MagicNumber];43: Horse * pHorse;44: Bird * pBird;

Page 205: C++ PRIRUCNIK

20545: int choice,i;46: for (i=0; i<MagicNumber; i++)47: {48: cout << "\n(1)Horse (2)Pegasus: ";49: cin >> choice;50: if (choice == 2)51: pHorse = new Pegasus;52: else53: pHorse = new Horse;54: Ranch[i] = pHorse;55: }56: for (i=0; i<MagicNumber; i++)57: {58: cout << "\n(1)Bird (2)Pegasus: ";59: cin >> choice;60: if (choice == 2)61: pBird = new Pegasus; 62: else63: pBird = new Bird;64: Aviary[i] = pBird;65: }66:67: cout << "\n";68: for (i=0; i<MagicNumber; i++)69: {70: cout << "\nRanch[" << i << "]: " ;71: Ranch[i]->Whinny();72: delete Ranch[i];73: }74:75: for (i=0; i<MagicNumber; i++)76: {77: cout << "\nAviary[" << i << "]: " ;78: Aviary[i]->Chirp();79: Aviary[i]->Fly();80: delete Aviary[i];81: }82: return 0;83: }Output: (1)Horse (2)Pegasus: 1Horse constructor...(1)Horse (2)Pegasus: 2Horse constructor... Bird constructor... Pegasus constructor...(1)Bird (2)Pegasus: 1Bird constructor...(1)Bird (2)Pegasus: 2Horse constructor... Bird constructor... Pegasus constructor...Ranch[0]: Whinny!... Horse destructor...Ranch[1]: Whinny!... Pegasus destructor... Bird destructor... Horse destructor...Aviary[0]: Chirp... I can fly! I can fly! I can fly! Bird destructor...Aviary[1]: Whinny!... I can fly! I can fly! I can fly! Pegasus destructor... Bird destructor... Horse destructor...Aviary[0]: Chirp... I can fly! I can fly! I can fly! Bird destructor...

Page 206: C++ PRIRUCNIK

206 C++ programer

Aviary[1]: Whinny!... I can fly! I can fly! I can fly! Pegasus destructor.. Bird destructor... Horse destructor...

Analiza: U linijama 6-14, Horse klasa je deklarirana. Konstruktor i destruktor ispisuju poruku, a Whinny() metoda ispisuje poruku Whinny!U linijama 16-25, Bird klasa je deklarirana. Uz dodatak konstruktoru i destruktoru, ova klasa ima dvije metode: Chirp() i Fly().Konačno, u linijama 30-36, klasa Pegasus je deklarirana. Ona izvodi i iz Horse i Bird. Pegasus klasa zaobilazi Chirp() metodu pozivanjem Whinny() metode, koju nasljeđuje iz Horse.Dvije liste su kreirane, Ranch s pokazivačima na Horse u liniji 41, te Aviary s pokazivačima na Bird u liniji 42. U linijama 46-55, Horse i Pegasus objekti su dodani u Ranch. U linijama 56-65, Bird i Pegasus objekti su dodani u Aviary.Pozivanje virtualnih metoda i na Bird pokazivače i na Horse pokazivače čini prave stvari za Pegasus objekte. Na primjer, u liniji 78 članovi Aviary niza pozivaju Chirp() na pokazane objekte. Bird klasa deklarira ovo virtualnom metodom, pa se prava funkcija poziva za svaki objekt.Primjetite da za svaki kreirani Pegasus objekt, izlaz reflektira da se i Bird dio i Horse dio Pegasus objekta također kreira. Kada je Pegasus objekt uništen, Bird i Horse dijelovi se također uništavaju, zahvaljujući virtualnim destruktorima.

Dijelovi višestruko naslijeđenog objekta

Kada je Pegasus objekt kreiran u memoriji, obje osnovne klase formiraju dio Pegasus objekta, kao što je prikazano na slici 13.1.

Slika 13.1. Višestruko naslijeđeni objekti.

Mnogo se pitanja postavlja kod objekata izvedenih iz više baznih klasa. Na primjer, što se događa ako dvije bazne klase imaju isto ime neke virtualne funkcije ili nekog podatka? Kako se inicijaliziraju konstruktori za ovako izvedene klase? Što ako višestruke bazne klase potječu iz iste klase?

Konstruktori u višestruko naslijeđenim objektima

Ako Pegasus derivira i iz Horse i Bird, a svaka od baznih klasa ima konstruktore koji primaju parametre, Pegasus klasa inicijalizira te konstruktore naizmjenično. Listing 13.4 ilustrira kako se to radi.

Listing 13.4. Pozivi višestrukih konstruktora.

1: // Listing 13.42: // Calling multiple constructors3: #include <iostream.h>4: typedef int HANDS;

Page 207: C++ PRIRUCNIK

2075: enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown } ;6: enum BOOL { FALSE, TRUE };7:8: class Horse9: {10: public:11: Horse(COLOR color, HANDS height);12: virtual ~Horse() { cout << "Horse destructor...\n"; }13: virtual void Whinny()const { cout << "Whinny!... "; }14: virtual HANDS GetHeight() const { return itsHeight; }15: virtual COLOR GetColor() const { return itsColor; }16: private:17: HANDS itsHeight;18: COLOR itsColor;19: };20:21: Horse::Horse(COLOR color, HANDS height):22: itsColor(color),itsHeight(height)23: {24: cout << "Horse constructor...\n";25: }26:27: class Bird28: {29: public:30: Bird(COLOR color, BOOL migrates);31: virtual ~Bird() {cout << "Bird destructor...\n"; }32: virtual void Chirp()const { cout << "Chirp... "; }33: virtual void Fly()const 34: { 35: cout << "I can fly! I can fly! I can fly! "; 36: }37: virtual COLOR GetColor()const { return itsColor; }38: virtual BOOL GetMigration() const { return itsMigration; }39: 40: private:41: COLOR itsColor;42: BOOL itsMigration;43: };44:45: Bird::Bird(COLOR color, BOOL migrates):46: itsColor(color), itsMigration(migrates)47: {48: cout << "Bird constructor...\n";49: }50:51: class Pegasus : public Horse, public Bird52: {53: public:54: void Chirp()const { Whinny(); }55: Pegasus(COLOR, HANDS, BOOL,long);56: ~Pegasus() {cout << "Pegasus destructor...\n";}57: virtual long GetNumberBelievers() const 58: {

Page 208: C++ PRIRUCNIK

208 C++ programer

59: return itsNumberBelievers; 60: }61:62: private:63: long itsNumberBelievers;64: };65: 66: Pegasus::Pegasus(67: COLOR aColor, 68: HANDS height, 69: BOOL migrates, 70: long NumBelieve):71: Horse(aColor, height),72: Bird(aColor, migrates),73: itsNumberBelievers(NumBelieve)74: {75: cout << "Pegasus constructor...\n";76: }77:78: int main()79: {80: Pegasus *pPeg = new Pegasus(Red, 5, TRUE, 10);81: pPeg->Fly();82: pPeg->Whinny();83: cout << "\nYour Pegasus is " << pPeg->GetHeight();84: cout << " hands tall and ";85: if (pPeg->GetMigration())86: cout << "it does migrate.";87: else88: cout << "it does not migrate.";89: cout << "\nA total of " << pPeg->GetNumberBelievers();90: cout << " people believe it exists.\n";91: delete pPeg;92: return 0;93: }Output: Horse constructor...Bird constructor...Pegasus constructor...I can fly! I can fly! I can fly! Whinny!...Your Pegasus is 5 hands tall and it does migrate.A total of 10 people believe it exists.Pegasus destructor...Bird destructor...Horse destructor...

Problem dvosmislenosti

U listingu 13.4, i Horse klasa i Bird klasa imaju metodu GetColor(). Vi, naravno, možete te metode koristiti i a Pegasus objektu, ali tu nastupa problem: Pegasus klasa naslijeđuje i iz Bird i Horse. Obe klase imaju color, i njihove metode za dobivanje boje imaju ista imena i potpis. To stvara problem dvosmislenosti kojeg morate rješiti.Ako napišete

COLOR currentColor = pPeg->GetColor();dobiti ćete grešku prilikom prevođenja:

Page 209: C++ PRIRUCNIK

209Member is ambiguous: `Horse::GetColor' and `Bird::GetColor'

Tu dvosmislenost (engl. ambiguity) rješavamo ekplicitnim navođenjem funkcije koju želimo pozvati:

COLOR currentColor = pPeg->Horse::GetColor();Primjetite da ako Pegasus zaobilazi tu funkciju, problem bi bio izbjegnut:

virtual COLOR GetColor()const { return Horse::itsColor; }Korisnik bi i dalje mogao forsirati poziv pišući:

COLOR currentColor = pPeg->Bird::GetColor();

Naslijeđivanje iz dijeljene bazne klase

Što se događa ako i Bird i Horse potječu iz zajedničke bazne klase, kao npr. Animal? Slika 13.2 ilustrira kako to izgleda.Kao što možete vidjeti na slici 13.2, dva bazna objekta postoje i potvo dolazi do problema dvosmislenosti, ilustriranog u Listingu 13.5.

Slika 13.2.

Listing 13.5. Zajedničke bazne klase.

1: // Listing 13.52: // Common base classes3: #include <iostream.h>4:5: typedef int HANDS;6: enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown } ;7: enum BOOL { FALSE, TRUE };8:9: class Animal // common base to both horse and bird10: {11: public:12: Animal(int);13: virtual ~Animal() { cout << "Animal destructor...\n"; }14: virtual int GetAge() const { return itsAge; }15: virtual void SetAge(int age) { itsAge = age; }16: private:17: int itsAge;18: };19:20: Animal::Animal(int age):21: itsAge(age)

Page 210: C++ PRIRUCNIK

210 C++ programer

22: {23: cout << "Animal constructor...\n";24: }25:26: class Horse : public Animal27: {28: public:29: Horse(COLOR color, HANDS height, int age);30: virtual ~Horse() { cout << "Horse destructor...\n"; }31: virtual void Whinny()const { cout << "Whinny!... "; }32: virtual HANDS GetHeight() const { return itsHeight; }33: virtual COLOR GetColor() const { return itsColor; }34: protected:35: HANDS itsHeight;36: COLOR itsColor;37: };38: 39: Horse::Horse(COLOR color, HANDS height, int age):40: Animal(age),41: itsColor(color),itsHeight(height)42: {43: cout << "Horse constructor...\n";44: }45:46: class Bird : public Animal47: {48: public:49: Bird(COLOR color, BOOL migrates, int age);50: virtual ~Bird() {cout << "Bird destructor...\n"; }51: virtual void Chirp()const { cout << "Chirp... "; }52: virtual void Fly()const 53: { cout << "I can fly! I can fly! I can fly! "; }54: virtual COLOR GetColor()const { return itsColor; }55: virtual BOOL GetMigration() const { return itsMigration; }56: protected:57: COLOR itsColor;58: BOOL itsMigration;59: };60:61: Bird::Bird(COLOR color, BOOL migrates, int age):62: Animal(age),63: itsColor(color), itsMigration(migrates)64: {65: cout << "Bird constructor...\n";66: }67:68: class Pegasus : public Horse, public Bird69: {70: public:71: void Chirp()const { Whinny(); }72: Pegasus(COLOR, HANDS, BOOL, long, int);73: ~Pegasus() {cout << "Pegasus destructor...\n";}74: virtual long GetNumberBelievers() const 75: { return itsNumberBelievers; }

Page 211: C++ PRIRUCNIK

21176: virtual COLOR GetColor()const { return Horse::itsColor; }77: virtual int GetAge() const { return Horse::GetAge(); }78: private:79: long itsNumberBelievers;80: };81: 82: Pegasus::Pegasus(83: COLOR aColor,84: HANDS height,85: BOOL migrates,86: long NumBelieve,87: int age):88: Horse(aColor, height,age),89: Bird(aColor, migrates,age),90: itsNumberBelievers(NumBelieve)91: {92: cout << "Pegasus constructor...\n";93: }94:95: int main()96: {97: Pegasus *pPeg = new Pegasus(Red, 5, TRUE, 10, 2);98: int age = pPeg->GetAge();99: cout << "This pegasus is " << age << " years old.\n";100: delete pPeg;101: return 0;102: }Output: Animal constructor...Horse constructor...Animal constructor...Bird constructor...Pegasus constructor...This pegasus is 2 years old.Pegasus destructor...Bird destructor...Animal destructor...Horse destructor...Animal destructor...

Analiza: Čitav je niz zanimljivosti u ovom listingu. Animal klasa je deklarirana u linijama 9-18. Animal dodaje jedan podatkovni član, itsAge i pristupnu funkciju SetAge().U liniji 26, Horse klasa je deklarirana kao izvedenica iz Animal. Horse konstruktor sad ima i treći parametar, age, kojeg proslijeđuje u baznu klasu, Animal. Primjetite da Horse klasa ne zaobilazi GetAge(), već ga jednostavno naslijeđuje.U liniji 46, Bird klasa je deklarirana kao izvedenica iz Animal. Pegasus naslijeđuje i Bird i Horse, i time ima dvije Animal klase u svom lancu naslijeđivanja. Ako pozovete GetAge() na Pegasus objekt, morali bi prvo razrješiti čiju metodu koristimo da Pegasus ne zaobilazi tu metodu.To je razrješeno u liniji 77 kad Pegasus objekt zaobilazi GetAge() i to običnim ulančavanjem—odnosno pozivanjem iste metode iz bazne klase.Pegasus konstruktor prima 5 parametara: boju, visinu, da li migrira, koliko ih vjeruje u njega, i svoje godine.

Page 212: C++ PRIRUCNIK

212 C++ programer

Virtualno naslijeđivanje

U listingu 13.5, Pegasus klasa prolazi kroz dosta muka da se izbjegne dvosmislenost poziva baznih klasa. Neke stvari možemo poboljšati upotrebom virtualnog naslijeđivanja.

Slika 13.3. "Dijamantno" naslijeđivanje

Listing 13.6. Ilustracija upotrbe virtualnog naslijeđivanja.

1: // Listing 13.62: // Virtual inheritance3: #include <iostream.h>4:5: typedef int HANDS;6: enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown } ;7: enum BOOL { FALSE, TRUE };8:9: class Animal // common base to both horse and bird10: {11: public:12: Animal(int);13: virtual ~Animal() { cout << "Animal destructor...\n"; }14: virtual int GetAge() const { return itsAge; }15: virtual void SetAge(int age) { itsAge = age; }16: private:17: int itsAge;18: };19:20: Animal::Animal(int age):21: itsAge(age)22: {23: cout << "Animal constructor...\n";24: }25:26: class Horse : virtual public Animal27: {28: public:29: Horse(COLOR color, HANDS height, int age);30: virtual ~Horse() { cout << "Horse destructor...\n"; }31: virtual void Whinny()const { cout << "Whinny!... "; }

Page 213: C++ PRIRUCNIK

21332: virtual HANDS GetHeight() const { return itsHeight; }33: virtual COLOR GetColor() const { return itsColor; }34: protected:35: HANDS itsHeight;36: COLOR itsColor;37: };38: 39: Horse::Horse(COLOR color, HANDS height, int age):40: Animal(age),41: itsColor(color),itsHeight(height)42: {43: cout << "Horse constructor...\n";44: }45:46: class Bird : virtual public Animal47: {48: public:49: Bird(COLOR color, BOOL migrates, int age);50: virtual ~Bird() {cout << "Bird destructor...\n"; }51: virtual void Chirp()const { cout << "Chirp... "; }52: virtual void Fly()const 53: { cout << "I can fly! I can fly! I can fly! "; }54: virtual COLOR GetColor()const { return itsColor; }55: virtual BOOL GetMigration() const { return itsMigration; }56: protected:57: COLOR itsColor;58: BOOL itsMigration;59: };60:61: Bird::Bird(COLOR color, BOOL migrates, int age):62: Animal(age),63: itsColor(color), itsMigration(migrates)64: {65: cout << "Bird constructor...\n";66: }67:68: class Pegasus : public Horse, public Bird69: {70: public:71: void Chirp()const { Whinny(); }72: Pegasus(COLOR, HANDS, BOOL, long, int);73: ~Pegasus() {cout << "Pegasus destructor...\n";}74: virtual long GetNumberBelievers() const 75: { return itsNumberBelievers; }76: virtual COLOR GetColor()const { return Horse::itsColor; }77: private:78: long itsNumberBelievers;79: };80: 81: Pegasus::Pegasus(82: COLOR aColor,83: HANDS height,84: BOOL migrates,85: long NumBelieve,

Page 214: C++ PRIRUCNIK

214 C++ programer

86: int age):87: Horse(aColor, height,age),88: Bird(aColor, migrates,age),89: Animal(age*2),90: itsNumberBelievers(NumBelieve)91: {92: cout << "Pegasus constructor...\n";93: }94:95: int main()96: {97: Pegasus *pPeg = new Pegasus(Red, 5, TRUE, 10, 2);98: int age = pPeg->GetAge();99: cout << "This pegasus is " << age << " years old.\n";100: delete pPeg;101: return 0;102: }Output: Animal constructor...Horse constructor...Bird constructor...Pegasus constructor...This pegasus is 4 years old.Pegasus destructor...Bird destructor...Horse destructor...Animal destructor...

Deklariranje klasa za virtualno naslijeđivanje

Kako biste osigurali da derivirane klase imaju samo jednu instancu osnovnih baznih klasa, deklariramo međuklase koje virtualno nasljeđuju iz osnovne klase. Primjer 1:

class Horse : virtual public Animalclass Bird : virtual public Animalclass Pegasus : public Horse, public Bird

Primjer 2:class Schnauzer : virtual public Dogclass Poodle : virtual public Dogclass Schnoodle : public Schnauzer, public Poodle

Abstraktni tipovi podataka (Abstract Data Types-ADT)

Često ćete kreirati zajeedničku hijerarhiju klasa. Na primjer, možete kreirati Shape klasu i iz nje izvesti klase Rectangle i Circle. Iz Rectangle, potom možete izvesti Square, kao poseban slučaj od Rectangle.Svaka od izvedenih klasa će zaobilaziti Draw() metodu, GetArea() metodu, i tako dalje. Listing 13.7 ilustrira kostur implementacije Shape klase i njeoj izvedenih Circle i Rectangle klasa.

Listing 13.7. "Shape" klase.

1: //Listing 13.7. Shape classes.2:3: #include <iostream.h>

Page 215: C++ PRIRUCNIK

2154:5: enum BOOL { FALSE, TRUE };6:7: class Shape8: {9: public:10: Shape(){}11: ~Shape(){}12: virtual long GetArea() { return -1; } // error13: virtual long GetPerim() { return -1; }14: virtual void Draw() {}15: private:16: };17:18: class Circle : public Shape19: {20: public:21: Circle(int radius):itsRadius(radius){}22: ~Circle(){}23: long GetArea() { return 3 * itsRadius * itsRadius; }24: long GetPerim() { return 9 * itsRadius; }25: void Draw();26: private:27: int itsRadius;28: int itsCircumference;29: };30: 31: void Circle::Draw()32: {33: cout << "Circle drawing routine here!\n";34: }35:36:37: class Rectangle : public Shape38: {39: public:40: Rectangle(int len, int width):41: itsLength(len), itsWidth(width){}42: ~Rectangle(){}43: virtual long GetArea() { return itsLength * itsWidth; }44: virtual long GetPerim() {return 2*itsLength + 2*itsWidth; }45: virtual int GetLength() { return itsLength; }46: virtual int GetWidth() { return itsWidth; }47: virtual void Draw();48: private:49: int itsWidth;50: int itsLength;51: };52:53: void Rectangle::Draw()54: {55: for (int i = 0; i<itsLength; i++)56: {57: for (int j = 0; j<itsWidth; j++)

Page 216: C++ PRIRUCNIK

216 C++ programer

58: cout << "x ";59:60: cout << "\n";61: }62: }63:64: class Square : public Rectangle65: {66: public:67: Square(int len);68: Square(int len, int width);69: ~Square(){}70: long GetPerim() {return 4 * GetLength();}71: };72:73: Square::Square(int len):74: Rectangle(len,len)75: {}76:77: Square::Square(int len, int width):78: Rectangle(len,width) 79:80: {81: if (GetLength() != GetWidth())82: cout << "Error, not a square... a Rectangle??\n";83: }84:85: int main()86: {87: int choice;88: BOOL fQuit = FALSE;89: Shape * sp;90:91: while (1)92: {93: cout << "(1)Circle (2)Rectangle (3)Square (0)Quit: ";94: cin >> choice;95:96: switch (choice)97: {98: case 1: sp = new Circle(5);99: break;100: case 2: sp = new Rectangle(4,6);101: break;102: case 3: sp = new Square(5);103: break;104: default: fQuit = TRUE;105: break;106: }107: if (fQuit)108: break;109:110: sp->Draw();111: cout << "\n";

Page 217: C++ PRIRUCNIK

217112: }113: return 0;114: }

Output: (1)Circle (2)Rectangle (3)Square (0)Quit: 2x x x x x xx x x x x xx x x x x xx x x x x x(1)Circle (2)Rectangle (3)Square (0)Quit:3x x x x xx x x x xx x x x xx x x x xx x x x x(1)Circle (2)Rectangle (3)Square (0)Quit:0

Analiza: U linijama 7-16, Shape klasa je deklarirana. GetArea() i GetPerim() methode vraćaju poruku o greški, a Draw() ne radi ništa. Na kraju krajeva, što to znači "Nacrtaj oblik"? Samo tiopvi oblika (kružnice, pravokutnici, itd.) mogu biti nacrtani, oblici kao apstraktan pojam ne mogu.Circle izvodi iz Shape i zaobilazi tri virtualne metode. Primjetite da nije potrebno dodoavati riječ "virtual", jer je riječ o njihovom naslijeđu. Ali niti ne smeta ako dodamo, kao što vidimo u linijama 43, 44 i 47, kod Rectangle klase. Dobra je ideja uključiti izraz virtual, kao podsjetnik za kasnije lakše čitanje programa.Square izvodi iz Rectangle, i on također zaobilazi GetPerim() metodu, naslijeđujući ostale metode definirane u Rectangle.Potencijalni je problem ako iz glavnog programa kreiramo Shape objekt, i poželjno je da to onemogućimo. Shape klasa postoji samo za omogućavanje sučelja za njoj izveden klase; kao takvu, nazivamo ju apstraktnim tipom podatka, engl. Abstract Data Type, ili ADT.

Novi izraz: Abstract Data Type predstavlja koncept (kao oblik, shape klasa u prethodnom primjeru) za razliku od objekta (krug). U C++, ADT he uvijek bazna klasa za druge klase, i nije moguće napraviti instancu od ADT-a.

Čiste virtualne funkcije

C++ omogućuje kreiranje abstraktnih tipova podataka s čistim virtualnim funkcijama. Virtualna funkcija je proglašena čistom kad ju inicijaliziramo s nulom, kao u

virtual void Draw() = 0;Svaka klasa s jednom ili više čistih virtualnih funkcija je ADT, i ilegalno je instancirati objekt klase koja je ADT. Pokušavanje toga će dati grešku prilikom prevođenja. Umetanjem takve funkcije u klasu signaliziramo dvije stvari korisniku klase:

Ne pravite objekt takve klase, izvedite iz nje. Ne zaboravite zaobići čiste virtualne funkcije.

Svaka klas koja se izvodi iz ADT-a. naslijeđuje i čiste virtualne funkcije kao takve, te ih mora zaobići želi li instancirati objekt. Prema tome, ako Rectangle naslijeđuje od Shape, a Shape ima tri čiste virtualne funkcije, Rectangle mora zaobići sve tri inače će i on biti ADT. Listing 13.8 mijenja Shape klasu kako bi bila abstraktni tip podatka.

Page 218: C++ PRIRUCNIK

218 C++ programer

Listing 13.8. Apstraktni tipovi podataka.

1: class Shape2: {3: public:4: Shape(){}5: ~Shape(){}6: virtual long GetArea() = 0; // error7: virtual long GetPerim()= 0;8: virtual void Draw() = 0;9: private:10: };Output: (1)Circle (2)Rectangle (3)Square (0)Quit: 2x x x x x xx x x x x xx x x x x xx x x x x x(1)Circle (2)Rectangle (3)Square (0)Quit: 3x x x x xx x x x xx x x x xx x x x xx x x x x(1)Circle (2)Rectangle (3)Square (0)Quit: 0

Implementiranje čistih virtualnih funkcija

Obično se čiste virtualne funkcije ne implementiraju, budući da ionako iz ADT-a ne možemo instancirati objekte.Ipak je moguće i pružiti implementaciju čiste virtualne funkcije. Funkcija tada može biti pozvana iz objekata deriviranih iz ADT-a, možda za pružanje funkcionalnosti svim zaobiđenim funkcijama. Listing 13.9 je prepravljeni listing 13.7, ovaj put sa Shape kao ADT i s implementacijom čiste virtualne funkcije Draw().Circle klasa zaobilazi Draw(), kako i treba, ali se potom ulančava s funkcijom bazne klase za dodatnu funkcionalnost.

Listing 13.9. Implementiranje čistih virtualnih funkcija.

1: //Implementing pure virtual functions2:3: #include <iostream.h>4:5: enum BOOL { FALSE, TRUE };6:7: class Shape8: {9: public:10: Shape(){}11: ~Shape(){}12: virtual long GetArea() = 0; // error13: virtual long GetPerim()= 0;14: virtual void Draw() = 0;15: private:16: };

Page 219: C++ PRIRUCNIK

21917:18: void Shape::Draw()19: {20: cout << "Abstract drawing mechanism!\n";21: }22:23: class Circle : public Shape24: {25: public:26: Circle(int radius):itsRadius(radius){}27: ~Circle(){}28: long GetArea() { return 3 * itsRadius * itsRadius; }29: long GetPerim() { return 9 * itsRadius; }30: void Draw();31: private:32: int itsRadius;33: int itsCircumference;34: };35:36: void Circle::Draw()37: {38: cout << "Circle drawing routine here!\n";39: Shape::Draw();40: }41:42: 43: class Rectangle : public Shape44: {45: public:46: Rectangle(int len, int width):47: itsLength(len), itsWidth(width){}48: ~Rectangle(){}49: long GetArea() { return itsLength * itsWidth; }50: long GetPerim() {return 2*itsLength + 2*itsWidth; }51: virtual int GetLength() { return itsLength; }52: virtual int GetWidth() { return itsWidth; }53: void Draw();54: private:55: int itsWidth;56: int itsLength;57: };58:59: void Rectangle::Draw()60: {61: for (int i = 0; i<itsLength; i++)62: {63: for (int j = 0; j<itsWidth; j++)64: cout << "x ";65:66: cout << "\n";67: }68: Shape::Draw();69: }70:

Page 220: C++ PRIRUCNIK

220 C++ programer

71:72: class Square : public Rectangle73: {74: public:75: Square(int len);76: Square(int len, int width);77: ~Square(){}78: long GetPerim() {return 4 * GetLength();}79: };80: 81: Square::Square(int len):82: Rectangle(len,len)83: {}84:85: Square::Square(int len, int width):86: Rectangle(len,width)87:88: {89: if (GetLength() != GetWidth())90: cout << "Error, not a square... a Rectangle??\n";91: }92:93: int main()94: {95: int choice;96: BOOL fQuit = FALSE;97: Shape * sp;98: 99: while (1)100: {101: cout << "(1)Circle (2)Rectangle (3)Square (0)Quit: ";102: cin >> choice;103:104: switch (choice)105: {106: case 1: sp = new Circle(5);107: break;108: case 2: sp = new Rectangle(4,6);109: break;110: case 3: sp = new Square (5);111: break;112: default: fQuit = TRUE;113: break;114: }115: if (fQuit)116: break;117:118: sp->Draw();119: cout << "\n";120: }121: return 0;122: }Output: (1)Circle (2)Rectangle (3)Square (0)Quit: 2x x x x x x

Page 221: C++ PRIRUCNIK

221x x x x x xx x x x x xx x x x x xAbstract drawing mechanism!(1)Circle (2)Rectangle (3)Square (0)Quit: 3x x x x xx x x x xx x x x xx x x x xx x x x xAbstract drawing mechanism!(1)Circle (2)Rectangle (3)Square (0)Quit: 0

Kviz

1. Što je v-ptr?2. Ako zaobljeni pravokutnik ima ravne linije i oble rubove, a vaša RoundRect klasa naslijeđuje i iz Rectangle i Circle, a oni su naslijednici iz Shape, koliko Shapes se kreira kad kreirate RoundRect?3. Ako Horse i Bird naslijeđuju iz Animal koristeći javno virtualno naslijeđivanje, da li njihovi konstruktori inicijaliziraju Animal konstruktor? Ako Pegasus naslijeđuje i iz Horse i Bird, kako on inicijalizira Animal konstruktor?4. Deklarirajte klasu vehicle, i proglasite ju ADT-om. 5. Ako je bazna klasa ADT, i ima tri čiste virtualne funkcije, koliko od njih mora biti zaobiđeno u izvedenim klasama?

Vježbe

1. Prikažite deklaraciju klase JetPlane, koja naslijeđuje od Rocket i Airplane.2. Pokažite deklaraciju od 747, koji naslijeđuje iz JetPlane klase prethodne vježbe. 3. Napišite program koji izvodi Car i Bus iz klase Vehicle. Proglasite Vehicle ADT-om s dvije čiste virtualne funkcije. Napravite da Car i Bus nisu ADT-i.

Page 222: C++ PRIRUCNIK

222 C++ programer

Lekcija 14 Specijalne klase i funkcije

C++ nudi nam različite načine određivanja dosega i djelovanja varijabli i pokazivača. Do sada ste naučili kako kreirati globalne varijable, lokalne funkcijeske varijable, pokazivače na varijable, i podatkovne članove klasa. Danas ćete naučiti

Što su statički podatkovni članovi i statički funkcijski članov i kako ih koristiti. Kako kreirati i manipulirati pokazivačima na funkcije i pokazivačima na

funkcijske članove. Kako raditi s poljima pokazivača na funkcije.

Statički podatkovni članovi

Do sada ste vjerojatno smatrali podatke unutar nekog objekta jedinstvenim samo za taj objekt i nedjeljivim među objektima iste kase. Na primjer, ako imate pet Cat objekata, svaki od njih ima vlastitu starost, težinu i ostale podatke. Starost jednoga ne utječe na starost drugoga.Postoje trenuci kad ćete željeti kontrolirati skupom podataka. Npr., možda ćete željeti znati koliko je ukupno objekata neke klase kreirano u vašem programu, te koliko ih još postoji. Statički podatkovni članovi se dijele između svih instanci određene klase. Oni su kompromis između globalnih podataka, koji su dostupni svim dijelovima programa, te podatkovnih članova koji su obično dostupni unutar jednog objekta.Možete misliti o statičkim članovima kao o pripadnicima klase, a ne objekta. Listing 14.1 deklarira Cat objekt sa statičkim članom, HowManyCats. Ova varijabla vodi račun o tome koliko Cat objekata je kreirano. Ovo postižemo inkrementiranjem statičke varijable, HowManyCats, prilikom svakog kreiranja i dekrementiranjem prilikom svakog uništavanja.

Listing 14.1. Statički podatkovni članovi.

1: //Listing 14.1 static data members2:3: #include <iostream.h>4:5: class Cat6: {7: public:8: Cat(int age):itsAge(age){HowManyCats++; }9: virtual ~Cat() { HowManyCats--; }10: virtual int GetAge() { return itsAge; }11: virtual void SetAge(int age) { itsAge = age; }12: static int HowManyCats;13:14: private:15: int itsAge;16:17: };18:19: int Cat::HowManyCats = 0;20:21: int main()

Page 223: C++ PRIRUCNIK

22322: {23: const int MaxCats = 5; int i;24: Cat *CatHouse[MaxCats];25: for (i = 0; i<MaxCats; i++)26: CatHouse[i] = new Cat(i);27:28: for (i = 0; i<MaxCats; i++)29: {30: cout << "There are ";31: cout << Cat::HowManyCats;32: cout << " cats left!\n";33: cout << "Deleting the one which is ";34: cout << CatHouse[i]->GetAge();35: cout << " years old\n";36: delete CatHouse[i];37: CatHouse[i] = 0;38: }39: return 0;40: }Output: There are 5 cats left!Deleting the one which is 0 years oldThere are 4 cats left!Deleting the one which is 1 years oldThere are 3 cats left!Deleting the one which is 2 years oldThere are 2 cats left!Deleting the one which is 3 years oldThere are 1 cats left!Deleting the one which is 4 years old

Analiza: U linijama 5 do 17 je deklarirana pojednostavljena klasa Cat. U liniji 12, HowManyCats je deklariran kao statički podatkovni član tipa int.Deklaracija HowManyCats ne definira nam kolika je vrijednost unutra, niti razervira memoriju za tu varijablu. Stoga je u liniji 19 varijabla definirana i inicijalizirana.Česta je pogreška zaboraviti definirati statičke podatkovne članove. Neka se to ne dogodi i vama! Vi to naravno ne trebate raditi i za itsAge, budući da je to ne-statički podatkovni član, pa je definiran prilikom stvaranja Cat objekta, ovdje to radimo u liniji 26.Konstruktor za Cat inkrementira statičku varijablu u liniji 8. Destruktor dekrementira istu u liniji 9. Stoga varijabla HowManyCats u svakom trenutku ima točan podatak o tome koliko je Cat objekata kreirano, ali još nije uništeno.Program dalje kreira pet Cat objekata i stavlja ih u polje (linije 21-40). Time se poziva pet konstruktora, pa se i HowManyCats inkrementira pet puta sa svoje inicijalne vrijednosti 0.Primjetite da je HowManyCats public i da mu pristupamo direktno iz main(). Ne postoji razlog za takvo izlaganje podatkovnog člana. Preferirano je rješenje proglasiti ju privatnom zajedno s ostalim podatkovnim članovima i omogućiti javnu pristupnu metodu, i time omogućiti pristup preko bilo kojeg objekta. Želite li toj varijabli pristupati direktno, bez uvjetovanog Cat objekta, imate dvije opcije: ostavite ju javnom, kako je prikazano na listingu 14.2, ili omogučite statički funkcijski član, no o tome nešto kasnije.

Page 224: C++ PRIRUCNIK

224 C++ programer

Listing 14.2. Pristupanje statičkim članovima bez objekta.

1: //Listing 14.2 static data members2:3: #include <iostream.h>4:5: class Cat6: {7: public:8: Cat(int age):itsAge(age){HowManyCats++; }9: virtual ~Cat() { HowManyCats--; }10: virtual int GetAge() { return itsAge; }11: virtual void SetAge(int age) { itsAge = age; }12: static int HowManyCats;13:14: private:15: int itsAge;16:17: };18:19: int Cat::HowManyCats = 0;20:21: void TelepathicFunction();22:23: int main()24: {25: const int MaxCats = 5; int i;26: Cat *CatHouse[MaxCats];27: for (i = 0; i<MaxCats; i++)28: {29: CatHouse[i] = new Cat(i);30: TelepathicFunction();31: }32:33: for ( i = 0; i<MaxCats; i++)34: {35: delete CatHouse[i];36: TelepathicFunction();37: }38: return 0;39: }40:41: void TelepathicFunction()42: {43: cout << "There are ";44: cout << Cat::HowManyCats << " cats alive!\n";45: }Output: There are 1 cats alive!There are 2 cats alive!There are 3 cats alive!There are 4 cats alive!There are 5 cats alive!There are 4 cats alive!

Page 225: C++ PRIRUCNIK

225There are 3 cats alive!There are 2 cats alive!There are 1 cats alive!There are 0 cats alive!

Analiza: Listing 14.2 je sličan prethodnom osim dodatka nove funkcije, TelepathicFunction(). Ova funkcija ne kreira Cat objekt, niti uzima Cat objekt kao parametar, pa ipak može pristupiti HowManyCats varijabli. Još jednom vrijedi naglasiti da se ova varijabla ne nalazi niti u jednom objektu, već u klasi, i, akoje javna, može joj se pristupiti iz bilo koje funkcije u programu.Alternativa je proglašavanje varijable privatnom i pristupanje preko pristupne funkcije. U tom slučaju joj možete pristupiti samo ako imate dostupan i objekt. Listing 14.3 predstavlja takav pristup. Alternativa, statički funkcijski članovi slijedi odmah nakon listinga 14.3.

Listing 14.3. Pristupanje statičkim članovima preko ne-statičkih funkcijskih članova.

1: //Listing 14.3 private static data members2:3: #include <iostream.h>4:5: class Cat6: {7: public:8: Cat(int age):itsAge(age){HowManyCats++; }9: virtual ~Cat() { HowManyCats--; }10: virtual int GetAge() { return itsAge; }11: virtual void SetAge(int age) { itsAge = age; }12: virtual int GetHowMany() { return HowManyCats; }13:14:15: private:16: int itsAge;17: static int HowManyCats;18: };19:20: int Cat::HowManyCats = 0;21:22: int main()23: {24: const int MaxCats = 5; int i;25: Cat *CatHouse[MaxCats];26: for (i = 0; i<MaxCats; i++)27: CatHouse[i] = new Cat(i);28:29: for (i = 0; i<MaxCats; i++)30: {31: cout << "There are ";32: cout << CatHouse[i]->GetHowMany();33: cout << " cats left!\n";34: cout << "Deleting the one which is ";35: cout << CatHouse[i]->GetAge()+2;36: cout << " years old\n";37: delete CatHouse[i];

Page 226: C++ PRIRUCNIK

226 C++ programer

38: CatHouse[i] = 0;39: }40: return 0;41: }Output: There are 5 cats left!Deleting the one which is 2 years oldThere are 4 cats left!Deleting the one which is 3 years oldThere are 3 cats left!Deleting the one which is 4 years oldThere are 2 cats left!Deleting the one which is 5 years oldThere are 1 cats left!Deleting the one which is 6 years old

Analiza: U liniji 17, HowManyCats je deklarirana sa privatnim pristupom. Sada više ne bi mogli pristupati varijabli iz ne-članski funkcija, poput TelepathicFunction sa pretodnog listinga.Iako je HowManyCats statička varijabla, još uvijek je u dosegu klase. Svaka funkcija klase, kao GetHowMany(), joj može pristupati, baš kao što i funkcijski članovi mogu pristupati bilo kojim podatkovnim članovima. Ipak, da bismo pozvali GetHowMany(), moramo imati objek na kojem ćemo pozvati tu funkciju.

Statički funkcijski članovi

Statički funkcijski članovi su poput statičkih podatkovnih članova: postoje ne u objektu nego unutar dosegaa klase. Tako mogu biti pozvane bez postojanja objekta te klase, kao što je prikazano u listingu 14.4.

Listing 14.4. Statički funkcijski članovi.

1: //Listing 14.4 static data members2:3: #include <iostream.h>4:5: class Cat6: {7: public:8: Cat(int age):itsAge(age){HowManyCats++; }9: virtual ~Cat() { HowManyCats--; }10: virtual int GetAge() { return itsAge; }11: virtual void SetAge(int age) { itsAge = age; }12: static int GetHowMany() { return HowManyCats; }13: private:14: int itsAge;15: static int HowManyCats;16: };17:18: int Cat::HowManyCats = 0;19:20: void TelepathicFunction();21:22: int main()23: {

Page 227: C++ PRIRUCNIK

22724: const int MaxCats = 5;25: Cat *CatHouse[MaxCats]; int i;26: for (i = 0; i<MaxCats; i++)27: {28: CatHouse[i] = new Cat(i);29: TelepathicFunction();30: }31:32: for ( i = 0; i<MaxCats; i++)33: {34: delete CatHouse[i];35: TelepathicFunction();36: }37: return 0;38: }39:40: void TelepathicFunction()41: {42: cout << "There are " << Cat::GetHowMany() << " cats alive!\n";43: }Output: There are 1 cats alive!There are 2 cats alive!There are 3 cats alive!There are 4 cats alive!There are 5 cats alive!There are 4 cats alive!There are 3 cats alive!There are 2 cats alive!There are 1 cats alive!There are 0 cats alive!

Analiza: Statički podatkovni član HowManyCats je deklariran sa privatnim pristupom u liniji 15. Javna pristupna funkcija, GetHowMany(), je deklarirana i kao public i kao static ou liniji 12.Budući da je GetHowMany() public, može joj se pristupiti iz bilo koje funkcije, a kako je i static nema potrebe za stvaranjem objekta tog tipa kako bismo joj pristupili. Zato je u liniji 42, funkcija TelepathicFunction() sposobna pristupiti javnoj statičnoj pristupnoj funkciji, iako nema pristup da Cat objekta. Naravno, mogli ste pozvati GetHowMany() i na Cat objekte dostupne u main(), baš kao i bilo koju drugu pristupnu funkciju.

PAŽNJA: Statički funkcijski članovi nemaju this pokazivač. Tako ne mogu biti deklarirani kao const. Također, budući da se podatkovnim članovima u funkcijskim članovima pristupa preko this pokazivača, statički funkcijski članovi ne mogu pristupati bilo kojoj ne-statičkoj varijabli!

Statički funkcijski članovi

Možete pristupati statičkim funkcijskim članovima pozivajući ih preko objekta te klase kao i svaki drugi funkcijski član, ili ih možete pozivati i bez objekta navodeći ime klase.Primjer

class Cat{

Page 228: C++ PRIRUCNIK

228 C++ programer

public:static int GetHowMany() { return HowManyCats; }private:static int HowManyCats;};int Cat::HowManyCats = 0;int main(){int howMany;Cat theCat; // define a cathowMany = theCat.GetHowMany(); // access through an objecthowMany = Cat::GetHowMany(); // access without an object}

Pokazivači na funkcije

Baš kao što je ime polja konstantan pokazivač na prvi element polja, ime funkcije je konstantan pokazivač na funkciju. Moguće je deklarirati pokazivačku varijablu koja pokazuje na funkciju, i pozvati funkciju koristeći taj pokazivač. To može biti vrlo korisno; omogućuje nam kreiranje programa koji pozivaju određenu funkciju ovisno o korisnikovom inputu.Jedini "tricky" dio u razumijevanju funkcijskih pokazivača je tip objekta na kojeg pokazujemo. Pokazivač na int pokazuje na cjelobrojnu varijablu, a pokazivač na funkciju mora pokazivati na funkciju odgovarajućeg povratnog tipa i potpisa.U deklaraciji

long (* funcPtr) (int);funcPtr je deklariran kao pokazivač (primjetite * ispred imena) koji pokazuje na funkciju koja prima integer parametar i vraća long. Proučite ove dvije deklaracije:

long * Function (int);long (* funcPtr) (int);

Prva, Function (), je funkcija koja prima integer i vraća pokazivač na varijablu tipa long. Druga, funcPtr, je pokazivač na funkciju koja prima cijeli broj i vraća varijablu tipa long.

Listing 14.5. Pokazivači na funkcije.

1: // Listing 14.5 Using function pointers2:3: #include <iostream.h>4:5: void Square (int&,int&);6: void Cube (int&, int&);7: void Swap (int&, int &);8: void GetVals(int&, int&);9: void PrintVals(int, int);10: enum BOOL { FALSE, TRUE };11:12: int main()13: {14: void (* pFunc) (int &, int &);15: BOOL fQuit = FALSE;16:17: int valOne=1, valTwo=2;

Page 229: C++ PRIRUCNIK

22918: int choice;19: while (fQuit == FALSE)20: {21: cout << "(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: ";22: cin >> choice;23: switch (choice)24: {25: case 1: pFunc = GetVals; break;26: case 2: pFunc = Square; break;27: case 3: pFunc = Cube; break;28: case 4: pFunc = Swap; break;29: default : fQuit = TRUE; break;30: }31:32: if (fQuit)33: break;34:35: PrintVals(valOne, valTwo);36: pFunc(valOne, valTwo);37: PrintVals(valOne, valTwo);38: }39: return 0;40: }41:42: void PrintVals(int x, int y)43: {44: cout << "x: " << x << " y: " << y << endl;45: }46:47: void Square (int & rX, int & rY)48: {49: rX *= rX;50: rY *= rY;51: }52: 53: void Cube (int & rX, int & rY)54: {55: int tmp;56: 57: tmp = rX;58: rX *= rX;59: rX = rX * tmp;60:61: tmp = rY;62: rY *= rY;63: rY = rY * tmp;64: }65:66: void Swap(int & rX, int & rY)67: {68: int temp;69: temp = rX;70: rX = rY;71: rY = temp;

Page 230: C++ PRIRUCNIK

230 C++ programer

72: }73:74: void GetVals (int & rValOne, int & rValTwo)75: {76: cout << "New value for ValOne: ";77: cin >> rValOne;78: cout << "New value for ValTwo: ";79: cin >> rValTwo;80: }Output: (0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 1x: 1 y: 2New value for ValOne: 2New value for ValTwo: 3x: 2 y: 3(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 3x: 2 y: 3x: 8 y: 27(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 2x: 8 y: 27x: 64 y: 729(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 4x: 64 y: 729x: 729 y: 64(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 0

Analiza: U linijama 5-8, četiri funkcije su deklarirane, svaka s istim povratnim tipom i potpisom, sve vraćaju void i primaju dvije reference na cijele brojeve.U liniji 14, pFunc je deklariran kao pokazivač na funkciju koja vraća void i prima dvije reference na cijele brojeve. Svaka od deklariranih funkcija može biti pokazana od pFunc. Korisniku se učestalo nudi odabir poziva funkcije, i prema tome se pridružuje pFunc. U linijama 35-36, trenutna vrijednost dva cijela broja se ispisuje, trenutno pridružena funkcija se poziva, i vrijednosti se ponovno ispisuju.

Zašto koristiti pokazivače na funkcije?

Sigurno biste mogli napisati program sa listinga 14.5 bez funkcijskih pokazivača, ali upotreba pokazivača čini upotrebu i rad programa jasnijim: odaberite funkciju s liste i potom ju pozovite.Listing 14.6 koristi funkcijske prototipe i definicije s Listinga 14.5, ali tijelo programa ne koristi funkcijski pokazivač. Pročite razlike između dva listinga.

PAŽNJA: Za prevođenje ovoga programa, stavite linije 41-80 iz listinga 14.5 odmah nakon linije 56.

Listing 14.6. Ponovljeni listing 14.5 bez pokazivača na funkcije.

1: // Listing 14.6 Without function pointers2:3: #include <iostream.h>4:

Page 231: C++ PRIRUCNIK

2315: void Square (int&,int&);6: void Cube (int&, int&);7: void Swap (int&, int &);8: void GetVals(int&, int&);9: void PrintVals(int, int);10: enum BOOL { FALSE, TRUE };11:12: int main()13: {14: BOOL fQuit = FALSE;15: int valOne=1, valTwo=2;16: int choice;17: while (fQuit == FALSE)18: {19: cout << "(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: ";20: cin >> choice;21: switch (choice)22: {23: case 1:24: PrintVals(valOne, valTwo);25: GetVals(valOne, valTwo);26: PrintVals(valOne, valTwo);27: break;28:29: case 2:30: PrintVals(valOne, valTwo);31: Square(valOne,valTwo);32: PrintVals(valOne, valTwo);33: break;34:35: case 3:36: PrintVals(valOne, valTwo);37: Cube(valOne, valTwo);38: PrintVals(valOne, valTwo);39: break;40:41: case 4:42: PrintVals(valOne, valTwo);43: Swap(valOne, valTwo);44: PrintVals(valOne, valTwo);45: break;46:47: default :48: fQuit = TRUE;49: break;50: }51: 52: if (fQuit)53: break;54: }55: return 0;56: }Output: (0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 1x: 1 y: 2

Page 232: C++ PRIRUCNIK

232 C++ programer

New value for ValOne: 2New value for ValTwo: 3(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 3x: 2 y: 3x: 8 y: 27(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 2x: 8 y: 27x: 64 y: 729(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 4x: 64 y: 729x: 729 y: 64(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 0

Polja pokazivača na funkcije

Baš kao što deklarirate polje pokazivača na integere, možete deklarirati i polja pokazivača na funkcije (koje naravno moraju biti specifičnoga povratnog tipa i potpisa). Listing 14.7 ponovo koristi listing 14.5 ovaj put rabeći polje za pokretanje svih izbora odjedanput.

PAŽNJA: Za prevođenje ovoga programa, umetnite linije 41-80 listinga 14.5 odmah nakon linije 39.

Listing 14.7. Demonstracija upotrebe polja pokazivača na funkcije.

1: // Listing 14.7 demonstrates use of an array of pointers to functions2:3: #include <iostream.h>4:5: void Square (int&,int&);6: void Cube (int&, int&);7: void Swap (int&, int &);8: void GetVals(int&, int&);9: void PrintVals(int, int);10: enum BOOL { FALSE, TRUE };11:12: int main()13: {14: int valOne=1, valTwo=2;15: int choice, i;16: const MaxArray = 5;17: void (*pFuncArray[MaxArray])(int&, int&);18:19: for (i=0;i<MaxArray;i++)20: {21: cout << "(1)Change Values (2)Square (3)Cube (4)Swap: ";22: cin >> choice;23: switch (choice)24: {25: case 1:pFuncArray[i] = GetVals; break;26: case 2:pFuncArray[i] = Square; break;27: case 3:pFuncArray[i] = Cube; break;

Page 233: C++ PRIRUCNIK

23328: case 4:pFuncArray[i] = Swap; break;29: default:pFuncArray[i] = 0;30: }31: }32:33: for (i=0;i<MaxArray; i++)34: {35: pFuncArray[i](valOne,valTwo);36: PrintVals(valOne,valTwo);37: }38: return 0;39: }Output: (1)Change Values (2)Square (3)Cube (4)Swap: 1(1)Change Values (2)Square (3)Cube (4)Swap: 2(1)Change Values (2)Square (3)Cube (4)Swap: 3(1)Change Values (2)Square (3)Cube (4)Swap: 4(1)Change Values (2)Square (3)Cube (4)Swap: 2New Value for ValOne: 2New Value for ValTwo: 3x: 2 y: 3x: 4 y: 9x: 64 y: 729x: 729 y: 64x: 7153 y:4096

Proslijeđivanje pokazivača na funkcije u druge funkcije

Pokazivač na funkciju (kao i polje pokazivača na funkcije) može biti proslijeđeno u druge funkcije, koje mogu potom poduzeti akciju pozivom odgovarajuće funkcije korištenjem pokazivača.Na primjer, možemo poboljšati listing 14.5 proslijeđujući odabrani pokazivač na funkciju u drugu funkciju (izvan main()), koja potom ispisuje vrijednosti, poziva funkciju i ponovno ispisuje vrijednosti. Listing 14.8 ilustrira tu varijaciju.

PAŽNJA: Za prevođenje programa, umetnite linije 46-80 listinga 14.5 odmah nakon linije 45.

Listing 14.8. Slanje pokazivača na funkcije kao funkcijskih argumenata.

1: // Listing 14.8 Without function pointers2:3: #include <iostream.h>4:5: void Square (int&,int&);6: void Cube (int&, int&);7: void Swap (int&, int &);8: void GetVals(int&, int&);9: void PrintVals(void (*)(int&, int&),int&, int&);10: enum BOOL { FALSE, TRUE };11:12: int main()13: {

Page 234: C++ PRIRUCNIK

234 C++ programer

14: int valOne=1, valTwo=2;15: int choice;16: BOOL fQuit = FALSE;17:18: void (*pFunc)(int&, int&);19:20: while (fQuit == FALSE)21: {22: cout << "(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: ";23: cin >> choice;24: switch (choice)25: {26: case 1:pFunc = GetVals; break;27: case 2:pFunc = Square; break;28: case 3:pFunc = Cube; break;29: case 4:pFunc = Swap; break;30: default:fQuit = TRUE; break;31: }32: if (fQuit == TRUE)33: break;34: PrintVals ( pFunc, valOne, valTwo);35: }36:37: return 0;38: }39:40: void PrintVals( void (*pFunc)(int&, int&),int& x, int& y)41: {42: cout << "x: " << x << " y: " << y << endl;43: pFunc(x,y);44: cout << "x: " << x << " y: " << y << endl;45: }Output: (0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 1x: 1 y: 2New value for ValOne: 2New value for ValTwo: 3x: 2 y: 3(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 3x: 2 y: 3x: 8 y: 27(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 2x: 8 y: 27x: 64 y: 729(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 4x: 64 y: 729x: 729 y:64(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 0

Zadatak: Pronađite nekog C++ programera i pitajte ga što znači slijedeća deklaracija:void PrintVals(void (*)(int&, int&),int&, int&);

Ovoje vrlo rijetko korištenje i vjerojatno ćete svaki put kad naletite na takvo što morati gledati u neku C++ knjigu, ali vam može spasiti program u rijetkim situacijama kada vam je takva konstrukcija neophodna.

Page 235: C++ PRIRUCNIK

235

Upotreba typedef s pokazivačima na funkcije

Konstrukcija void (*)(int&, int&) je, blago rečeno, nečitka. Možete koristiti typedef kako biste to pojednostavili deklarirajući tip VPF kao pokazivač na funkciju koja vraća void i prima dve reference na cijele brojeve. Listing 14.9 je prepisani listing 14.8 koji koristi typedef naredbu.

PAŽNJA: Za prevođenje programa, stavite linije 46-80 u listing 14.5 odmah nakon linije 45.

Listing 14.9. Upotreba typedef za pravljenje pokazivača na funkcije čitljivijima.

1: // Listing 14.9. Using typedef to make pointers to functions more _readable2:3: #include <iostream.h>4:5: void Square (int&,int&);6: void Cube (int&, int&);7: void Swap (int&, int &);8: void GetVals(int&, int&);9: typedef void (*VPF) (int&, int&) ;10: void PrintVals(VPF,int&, int&);11: enum BOOL { FALSE, TRUE };12:13: int main()14: {15: int valOne=1, valTwo=2;16: int choice;17: BOOL fQuit = FALSE;18:19: VPF pFunc;20:21: while (fQuit == FALSE)22: {23: cout << "(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: ";24: cin >> choice;25: switch (choice)26: {27: case 1:pFunc = GetVals; break;28: case 2:pFunc = Square; break;29: case 3:pFunc = Cube; break;30: case 4:pFunc = Swap; break;31: default:fQuit = TRUE; break;32: }33: if (fQuit == TRUE)34: break;35: PrintVals ( pFunc, valOne, valTwo);36: }37: return 0;38: }39:40: void PrintVals( VPF pFunc,int& x, int& y)

Page 236: C++ PRIRUCNIK

236 C++ programer

41: {42: cout << "x: " << x << " y: " << y << endl;43: pFunc(x,y);44: cout << "x: " << x << " y: " << y << endl;45: }Output: (0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 1x: 1 y: 2New value for ValOne: 2New value for ValTwo: 3x: 2 y: 3(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 3x: 2 y: 3x: 8 y: 27(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 2x: 8 y: 27x: 64 y: 729(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 4x: 64 y: 729x: 729 y: 64(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 0

Pokazivači na funkcijske članove

Do sada su svi pokazivači na funkcije bili za opće ne-klasne funkcije. Također je moguće kreirati i pokazivače na funkcijske članove neke klase.Za kreiranje pokazivača na funkcijski član, koristite istu sintaksu kao i s pokazivačima na funkciju, ali uključite ime klase i scoping operator (::). Tako, ako pFunc pokazuje na funkcijski član klase Shape, koja prima dva integera i vraća void, deklaracija za pFunc je slijedeća:

void (Shape::*pFunc) (int, int);Pokazivači na funkcijske članove se koriste isto kao i pokazivači na funkcije, osim što zahtijevaju objekt odgovarajuće klase kako bi ih mogli pozvati. Listing 14.10 ilustrira upotrebu pokazivača na funkcijske članove.

Listing 14.10. Pokazivači na funkcijske članove.

1: //Listing 14.10 Pointers to member functions using virtual methods2:3: #include <iostream.h>4:5: enum BOOL {FALSE, TRUE};6: class Mammal7: {8: public:9: Mammal():itsAge(1) { }10: ~Mammal() { }11: virtual void Speak() const = 0;12: virtual void Move() const = 0;13: protected:14: int itsAge;15: };16:17: class Dog : public Mammal18: {

Page 237: C++ PRIRUCNIK

23719: public:20: void Speak()const { cout << "Woof!\n"; }21: void Move() const { cout << "Walking to heel...\n"; }22: };23:24:25: class Cat : public Mammal26: {27: public:28: void Speak()const { cout << "Meow!\n"; }29: void Move() const { cout << "slinking...\n"; }30: };31:32:33: class Horse : public Mammal34: {35: public:36: void Speak()const { cout << "Winnie!\n"; }37: void Move() const { cout << "Galloping...\n"; }38: };39:40: 41: int main()42: {43: void (Mammal::*pFunc)() const =0;44: Mammal* ptr =0;45: int Animal;46: int Method;47: BOOL fQuit = FALSE;48:49: while (fQuit == FALSE)50: {51: cout << "(0)Quit (1)dog (2)cat (3)horse: ";52: cin >> Animal;53: switch (Animal)54: {55: case 1: ptr = new Dog; break;56: case 2: ptr = new Cat; break;57: case 3: ptr = new Horse; break;58: default: fQuit = TRUE; break;59: }60: if (fQuit)61: break;62:63: cout << "(1)Speak (2)Move: ";64: cin >> Method;65: switch (Method)66: {67: case 1: pFunc = Mammal::Speak; break;68: default: pFunc = Mammal::Move; break;69: }70:71: (ptr->*pFunc)();72: delete ptr;

Page 238: C++ PRIRUCNIK

238 C++ programer

73: }74: return 0;75: }Output: (0)Quit (1)dog (2)cat (3)horse: 1(1)Speak (2)Move: 1Woof!(0)Quit (1)dog (2)cat (3)horse: 2(1)Speak (2)Move: 1Meow!(0)Quit (1)dog (2)cat (3)horse: 3(1)Speak (2)Move: 2Galloping(0)Quit (1)dog (2)cat (3)horse: 0

Analiza: U linijama 6-15, abstraktni tip podatka Mammal je deklariran s dvije čiste virtualne metode, Speak() i Move(). Mammal je subklasiran u Dog, Cat, i Horse, od kojih svaki zaobilazi Speak() i Move().Glavni dio programa unutar main() pita korisnika za odabir kojeg tipa životinje kreirati, i potom nova podklasa od Animal se kreira u slobodnom spremniku i dodjeluje u ptr u linijama 55-57.Korisnik je potom upitan koju metodu pozvati, te se potom ta metoda pridružuje u pokazivač pFunc. U liniji 71, odabrana metoda se poziva preko kreiranog objekta, koristeći pokazivač ptr za pristup objektu i pFunc za pristup funkciji.Konačno, u liniji 72, delete je pozvan na pokazivač ptr kako bi oslobodili zauzetu memoriju. Primjetite da nema potrebe zvati delete za pFunc budući da je to pokazivač na kod, a ne na objekt u slobodnom spremniku. U stvari pokušate li napraviti takvo što, izazvat ćete grešku priliko kompajliranja.

Polja pokazivača na funkcijske članove

Kao sa pokazivačima na funkcije, pokazivači na funkcijske članove mogu biti pohranjeni u polju. Polje može biti inicijalizirano s adresama raznih funkcijskih članova, i oni mogu biti pozvani preko indeksa polja. Listing 14.11 ilustrira ovu tehniku.

Listing 14.11. Polja pokazivača na funkcijske članove.

1: //Listing 14.11 Array of pointers to member functions2:3: #include <iostream.h>4:5: enum BOOL {FALSE, TRUE};6:7: class Dog8: {9: public:10: void Speak()const { cout << "Woof!\n"; }11: void Move() const { cout << "Walking to heel...\n"; }12: void Eat() const { cout << "Gobbling food...\n"; }13: void Growl() const { cout << "Grrrrr\n"; }14: void Whimper() const { cout << "Whining noises...\n"; }15: void RollOver() const { cout << "Rolling over...\n"; }16: void PlayDead() const { cout << "Is this the end of Little Caeser?\n"; }17: };

Page 239: C++ PRIRUCNIK

23918:19: typedef void (Dog::*PDF)()const ;20: int main()21: {22: const int MaxFuncs = 7;23: PDF DogFunctions[MaxFuncs] =24: { Dog::Speak,25: Dog::Move,26: Dog::Eat,27: Dog::Growl,28: Dog::Whimper,29: Dog::RollOver,30: Dog::PlayDead };31:32: Dog* pDog =0;33: int Method;34: BOOL fQuit = FALSE;35: 36: while (!fQuit)37: {38: cout << "(0)Quit (1)Speak (2)Move (3)Eat (4)Growl";39: cout << " (5)Whimper (6)Roll Over (7)Play Dead: ";40: cin >> Method;41: if (Method == 0)42: {43: fQuit = TRUE;44: break;45: }46: else47: {48: pDog = new Dog;49: (pDog->*DogFunctions[Method-1])();50: delete pDog;51: }52: }53: return 0;54: } Output: (0)Quit (1)Speak (2)Move (3)Eat (4)Growl (5)Whimper (6)Roll Over (7)Play Dead: 1Woof! (0)Quit (1)Speak (2)Move (3)Eat (4)Growl (5)Whimper (6)Roll Over (7)Play Dead: 4Grrr (0)Quit (1)Speak (2)Move (3)Eat (4)Growl (5)Whimper (6)Roll Over (7)Play Dead: 7Is this the end of Little Caeser? (0)Quit (1)Speak (2)Move (3)Eat (4)Growl (5)Whimper (6)Roll Over (7)Play Dead: 0

Analiza: U linijama 7-17, klasa Dog je krirana, sa 7 funkcijskih članova istog povratnog tipa i potpisa. U liniji 19, typedef deklarira PDF kao pokazivač na funkcijski član od Dog koji ne prima parametre i ne vraća vrijednost, i koji je const: potpis 7 funkcijskih članova od Dog.

Page 240: C++ PRIRUCNIK

240 C++ programer

U linijama 23-30, polje DogFunctions je deklarirano da drži 7 takvih funkcijskih članova, i inicijalizirano s adresama tih funkcija.U linijama 38 i 39, korisnik je upitan da odabere metodu. Ukoliko ne odabere Quit, novi Dog je kreiran u slobodnom spremniku, i potom se korektna metoda poziva iz polja u liniji 49. Evo još jedne dobre linije za testiranje nekog C++ programera:

(pDog->*DogFunctions[Method-1])();Iako pomalo ezoterična, kad vam zatreba tablicu izgrađenu iz funkcijskih članova, može učiniti vaš program mnogo razumljivijim.

Kviz

1. Mogu li statički podatkovni članovi biti privatni?2. Pokažite deklaraciju statičkog podatkovnog člana.3. Pokažite deklaraciju statičkog funkcijskog pokazivača. 4. Pokažite deklariciju za pokazivač na funkciju koji vraća long i prima integer parametar. 5. Promijenite pokazivač iz četvrtog pitanja da pokazuje na funkcijski član klase Car.6. Pokažite deklaraciju za polje 10 pokazivača difiniranih u pitanju 5.

Vježbe

1. Napišite kratak program deklariranjem klase s jednim podatkovnim članom i jednim statičkim podatkovnim članom. Neka konstruktor inicijalizira podatkovni član i inkrementira statični podatkovni član. Neka destruktor dekrementira podatkovni član. 2. Koristeći program sa vježbe 1, napišite kratki program koji kreira tri objekta i potom prikazuje njihove podatkovne članove i statički podatkovni član. Potom uništite svaki objekt i pokažite kako to djeluje na statički podatkovni član. 3. Promijenite program sa vježbe 2 da koristi statički funkcijski član kako bi pristupio statičkom podatkovnom članu. Proglasite statički podatkovni član privatnim. 4. Napišite pokazivač na funkcijski član za pristup ne-statičkim podatkovnim članovima u programu sa vježbe 3, i koristite taj pokazivač za ispis vrijednosti tog podatka. 5. Dodajte još dva podatkovna člana u klasu iz prethodnog pitanja. Dodajte pristupne funkcije za dobijanje vrijednosti tih članova, i dajte svim funkcijskim članovima iste povratne tipove i potpise. Koristeći pokazivač na funkcijske članove pristupite tim funkcijama.

Page 241: C++ PRIRUCNIK

241

Lekcija 15 Tokovi

Do sada ste koristili cout za pisanje na ekran i cin za čitanje s tastature, bez potpunog razumijevanja kako zapravo rade. Danas ćete naučiti

Što su tokovi i kako se koriste Kako upravljati ulazom i izlazom koristeći tokove Kako pisati i čitati iz datoteka koristeći tokove

Osnovno o tokovima

C++ ne definira kao dio jezika, kako se podaci zapisuju na ekran ili u datoteku, niti kako se podaci učitavaju u program. Ovo su očito esencijalni dijelovi svakog ozbiljnijeg C++ programa, te stoga standardna C++ bibilioteka uključuje iostream biblioteku, koja omogućuje ulazno/izlazne funkcije našem programu (engl. input/output -- I/O).Prednost držanja ulaza i izlaza izvan samog jezika i uključenog u biblioteke je u tome što time naši programi postaju neovisni o platformi na kojoj se izvršavaju.

Enkapsulacija

iostream klase promatraju tok podataka iz vašeg programa ka ekranu kao tok podataka, u kojem jedan byte slijedi drugog. Ako je destinacija toka datoteka ili ekran, izvor je obično neki dio našeg programa. Ako je tok obrnut, podaci mogu doći sa tastature ili iz datoteke na disku te se potom "pretočiti" u podatkovne varijable.Osnovni cilj tokova je enkapsulacija problema vezanih uz dobijanje/proslijeđivanje podataka. Jednom kad je tok kreiran, vaš program radi s njim, i sam tok sakriva detalje. Slika 15.1 ilustrira osnovnu ideju.

Slika 15.1 Enkapsulacija kroz tokove.

Buffering

Zapisivanje na disk (i u manjem obimu na ekran) je vrlo "skupo". Potrebo je puno vremena (relativno govoreći) za pisanje i čitanje podataka sa diska, a izvršenje programa je općenito blokirano sa tim pisanjima i čitanjima. Za rješenje tog problema, tokovi nam omogućuju "buffering." Podaci se zapisuju na tok, ali se on ne zapisuje trenutno na disk. Umjesto toga, spremnik (engl. buffer) toka se puni, i jednom kad je pun zapisuje svoj kompletan sadržaj na disk odjednom.

Page 242: C++ PRIRUCNIK

242 C++ programer

Tokovi i bufferi

Kao što se može i očekivati, C++ implmentira tokove i spremnike na objektno-orjentirani način.

streambuf klasa upravlja bufferom, i njoj pripadne funkcije omogućuju punjenje, pražnjenje, te ostala manipuliranja s bufferom.

ios klasa je osnovna klasa za ulazno izlazne tokove. ios klasa ima streambuf objekt kao podatkovni član.

istream i ostream klase izvode se iz ios klase i specijalizirane su za ponašanje toka prilikom ulaza, odnosno izlaza.

iostream klasa je derivirana iz istream i ostream klasa i pruža nam metode ulaza i izlaza za pisanje na ekran.

fstream klase omogućuju input i output iz datoteka.

Standardni I/O objekti

Kada se C++ program koji uključuje iostream klase pokrene, četiri objekta se kreiraju i inicijaliziraju:

cin barata ulazom za standardni input, tastaturom. cout barata outputom za standardni output, ekran. cerr barata nebufferirani output na standardni uređaj za baratanje greškama,

ekran. Budući da je nebuferriran, sve se odmah ispisuje na ekran, bez popunjavanja ili pražnjenja.

clog upravlja bufferiranim porukama o greškama, koje se obično preusmjeravaju u log datoteku.

Input upotrebom cin

Globalni objekt cin je odgovoran za unos i dostpan je našem programu kad uključimo iostream.h. U dosadašnjim primjerima, koristeći preopterećeni operator ekstrakcije (>>) umetali smo podatke u programske varijable. Kako to radi? Sintakse je, kako se zasigurno sjećate, slijedeća:

int someVariable;cout << "Enter a number: ";cin >> someVariable;

O globalnom objektu nešto kasnije; za sada se usredotočimo na treću liniju, cin >> someVariable;. Što zaključujete o cin?Očito je riječ o globalnom objektu, budući da nije definiran u našem kodu. S prijašnjim iskustvom o operatorima također znate da je cin preopteretio operator (>>) i da je efekt naredbe da se bilo koji podaci koji se nalaze u bufferu prenose u lokalnu varijablu, someVariable.Ono što odmah ne upada u oči jest da je cin preopteretio operator ekstrakcije za veliki raspon parametara, među njima int&, short&, long&, double&, float&, char&, char*, i tako dalje. Kada utipkate cin >> someVariable;, tipu od someVariable se pristupa. U gornjem primjeru, someVariable je cijeli broj, pa je slijedeća funkcija pozvana:

istream & operator>> (int &)Primjetite da je stoga što parametar proslijeđujemo po referenci, moguće djelovati na originalnu varijablu. Listing 15.1 ilustrira ideju upotrebe cin.

Listing 15.1. cin barata različitim tipovima podataka.

1: //Listing 15.1 -- character strings and cin2:3: #include <iostream.h>

Page 243: C++ PRIRUCNIK

2434:5: int main()6: {7: int myInt;8: long myLong;9: double myDouble;10: float myFloat;11: unsigned int myUnsigned;12:13: cout << "int: ";14: cin >> myInt;15: cout << "Long: ";16: cin >> myLong;17: cout << "Double: ";18: cin >> myDouble;19: cout << "Float: ";20: cin >> myFloat;21: cout << "Unsigned: ";22: cin >> myUnsigned;23:24: cout << "\n\nInt:\t" << myInt << endl;25: cout << "Long:\t" << myLong << endl;26: cout << "Double:\t" << myDouble << endl;27: cout << "Float:\t" << myFloat << endl;28: cout << "Unsigned:\t" << myUnsigned << endl;29: return 0;30: }Output: int: 2Long: 70000Double: 987654321Float: 3.33Unsigned: 25Int: 2Long: 70000Double: 9.87654e+08Float: 3.33Unsigned: 25

Stringovi

cin također barata i pokazivačima na znakove (char*); tako možete kreirati i spremnik znakova i koristiti cin za njegovo ispunjavanje. Na primjern možete napisati :

char YourName[50]cout << "Enter your name: ";cin >> YourName;

Ako unesete Jesse, varijabla YourName će biti ispunjena znakovima J, e, s, s, e, \0. Zadnji znak je null; cin automatski završava string sa null znakom, i morate imati dovoljno mjesta u bufferu za spremanje cijelog stringa i null znaka. Taj znak signalizira kraj stringa standardnoj biblioteci funkcija.

Page 244: C++ PRIRUCNIK

244 C++ programer

Problemi sa stringovima

cin vjeruje da je i razmak separator. Kad vidi razmak ili novu liniju, pretpostavi da je upis avršen, i u slučaju stringa dodaje null znak upravo na to mjesto. Listing 15.2 ilustrira problem.

Listing 15.2. Pokušaj upisivanja više riječi u cin.

1: //Listing 15.2 -- character strings and cin2:3: #include <iostream.h>4:5: int main()6: {7: char YourName[50];8: cout << "Your first name: ";9: cin >> YourName;10: cout << "Here it is: " << YourName << endl;11: cout << "Your entire name: ";12: cin >> YourName;13: cout << "Here it is: " << YourName << endl;14: return 0;15: }Output: Your first name: JesseHere it is: JesseYour entire name: Jesse LibertyHere it is: Jesse

Za razumijevanje ovoga problema pogledajte listing 15.3, koji pokazuje input više polja.

Listing 15.3. Višestruki input.

1: //Listing 15.3 - character strings and cin2:3: #include <iostream.h>4:5: int main()6: {7: int myInt;8: long myLong;9: double myDouble;10: float myFloat;11: unsigned int myUnsigned;12: char myWord[50];13:14: cout << "int: ";15: cin >> myInt;16: cout << "Long: ";17: cin >> myLong;18: cout << "Double: ";19: cin >> myDouble;20: cout << "Float: ";

Page 245: C++ PRIRUCNIK

24521: cin >> myFloat;22: cout << "Word: ";23: cin >> myWord;24: cout << "Unsigned: ";25: cin >> myUnsigned;26:27: cout << "\n\nInt:\t" << myInt << endl;28: cout << "Long:\t" << myLong << endl;29: cout << "Double:\t" << myDouble << endl;30: cout << "Float:\t" << myFloat << endl;31: cout << "Word: \t" << myWord << endl;32: cout << "Unsigned:\t" << myUnsigned << endl;33:34: cout << "\n\nInt, Long, Double, Float, Word, Unsigned: ";35: cin >> myInt >> myLong >> myDouble;36: cin >> myFloat >> myWord >> myUnsigned;37: cout << "\n\nInt:\t" << myInt << endl;38: cout << "Long:\t" << myLong << endl;39: cout << "Double:\t" << myDouble << endl;40: cout << "Float:\t" << myFloat << endl;41: cout << "Word: \t" << myWord << endl;42: cout << "Unsigned:\t" << myUnsigned << endl;43:44:45: return 0;46: }Output: Int: 2Long: 30303Double: 393939397834Float: 3.33Word: HelloUnsigned: 85

Int: 2Long: 30303Double: 3.93939e+11Float: 3.33Word: HelloUnsigned: 85

Int, Long, Double, Float, Word, Unsigned: 3 304938 393847473 6.66 bye -2

Int: 3Long: 304938Double: 3.93847e+08Float: 6.66Word: bye

Unsigned: 65534

Ostali funkcijski članovi cin

Uz preopterećeni operator >>, cin ima i neke korisne funkcijske članove. Oni se koriste kad je preciznija kontrola inputa potrebna.

Page 246: C++ PRIRUCNIK

246 C++ programer

Jednostuki znakovni input

Funkcijski član get() nam služi za učitavanje samo jednog znaka, i to može raditi na dva načina. get() može biti korišten bez parametara, u tom slučaju se koristi povratna vrijednost, ili može biti upotrebljen s referencom na znak. Listing 15.4. ilustrira upotrebu get() bez parametara.

Listing 15.4. Upotreba get() bez parametara.

1: // Listing 15.4 - Using get() with no parameters2: #include <iostream.h>3:4: int main()5: {6: char ch;7: while ( (ch = cin.get()) != EOF)8: {9: cout << "ch: " << ch << endl;10: }11: cout << "\nDone!\n";12: return 0;13: }

PAŽNJA: Za izlazak iz program morate poslati znak EOF (eng. End Of File) sa tastature. Na DOS računalima to je Ctrl+Z; UNIX koristi Ctrl+D.

Output: Helloch: Hch: ech: lch: lch: och:

Worldch: Wch: och: rch: lch: dch:

(ctrl-z)Done!

Primjetite da svaka implementacija istream ne podržava verziju get() prikazanu na slijedećem listingu.

Listing 15.5 Upotreba get() sa parametrima.

1: // Listing 15.5 - Using get() with parameters

Page 247: C++ PRIRUCNIK

2472: #include <iostream.h>3:4: int main()5: {6: char a, b, c;7:8: cout << "Enter three letters: ";9:10: cin.get(a).get(b).get(c);11:12: cout << "a: " << a << "\nb: " << b << "\nc: " << c << endl;13: return 0;14: }Output: Enter three letters: onea: ob: nc: e

Dobijanje stringova iz standardnog inputa

Operator ekstrakcije (>>) se može koristiti za ispunjavanje polja znakova, ka što mogu i funkcijski članovi get() i getline().Konačni oblik od get() prima tri parametra. Prvi parametar je pokazivač na polje znakova, drugi je maksimalan broj znakova plus jedan, i treći je znak terminacije.Ako unesete 20 za drugi parametar, get() će pročitati 19 znakova i potom null-terminirati string. Treći parametar, znak terminacije ima podrazumijevanu vrijednost "newline" (`\n'). On se koristi ako dođemo do kraja prije ispunjavanja cijelog polja.Listing 15.6 ilustrira ovakvu upotrebu get().

Listing 15.6. Upotreba get() s poljem znakova.

1: // Listing 15.6 - Using get() with a character array2: #include <iostream.h>3:4: int main()5: {6: char stringOne[256];7: char stringTwo[256];8:9: cout << "Enter string one: ";10: cin.get(stringOne,256);11: cout << "stringOne: " << stringOne << endl;12:13: cout << "Enter string two: ";14: cin >> stringTwo;15: cout << "StringTwo: " << stringTwo << endl;16: return 0;17: }Output: Enter string one: Now is the timestringOne: Now is the timeEnter string two: For all goodStringTwo: For

Page 248: C++ PRIRUCNIK

248 C++ programer

Drugi problem za rješenje problema učitavanja stringova je upotreba getline(), kao što je ilustrirano u listingu 15.7.

Listing 15.7. Upotreba getline().

1: // Listing 15.7 - Using getline()2: #include <iostream.h>3:4: int main()5: {6: char stringOne[256];7: char stringTwo[256];8: char stringThree[256];9:10: cout << "Enter string one: ";11: cin.getline(stringOne,256);12: cout << "stringOne: " << stringOne << endl;13:14: cout << "Enter string two: ";15: cin >> stringTwo;16: cout << "stringTwo: " << stringTwo << endl;17:18: cout << "Enter string three: ";19: cin.getline(stringThree,256);20: cout << "stringThree: " << stringThree << endl;21: return 0;22: }Output: Enter string one: one two threestringOne: one two threeEnter string two: four five sixstringTwo: fourEnter string three: stringThree: five six

Upotreba cin.ignore()

Ponekad želite ignorirati preostale znakove u liniji dok ne dođete ili na kraj linije (EOL) ili kraj datoteke (EOF). Funkcijski član ignore() služi toj svrsi . ignore() prima dva parametra, maksimalan broj znakova koje će ignorirati i znak terminacije. Ako utipkate ignore(80,'\n'), do 80 znakova će biti odbačeno dok ne nađemo znak nove linije. Potom se newline odbacuje i ignore() naredba završava. Listing 15.8 ilustrira upotrebu ignore().

Listing 15.8. Upotreba ignore().

1: // Listing 15.8 - Using ignore()2: #include <iostream.h>3:4: int main()5: {6: char stringOne[255];7: char stringTwo[255];8:9: cout << "Enter string one:";10: cin.get(stringOne,255);

Page 249: C++ PRIRUCNIK

24911: cout << "String one" << stringOne << endl;12:13: cout << "Enter string two: ";14: cin.getline(stringTwo,255);15: cout << "String two: " << stringTwo << endl;16:17: cout << "\n\nNow try again...\n";18:19: cout << "Enter string one: ";20: cin.get(stringOne,255);21: cout << "String one: " << stringOne<< endl;22:23: cin.ignore(255,'\n');24:25: cout << "Enter string two: ";26: cin.getline(stringTwo,255);27: cout << "String Two: " << stringTwo<< endl;28: return 0;29: }Output: Enter string one:once upon a timeString oneonce upon a timeEnter string two: String two:Now try again...Enter string one: once upon a timeString one: once upon a timeEnter string two: there was aString Two: there was a

peek() i putback()

Input objekt cin ima dvije dodatne metode koje mogu biti prilično korisne: peek(), koja gleda ali ne izvlači slijedeći znak, te putback(), koji unosi znak u ulazni tok. Listing 15.9 ilustrira kako se oni mogu koristiti.

Listing 15.9. Upotreba peek() i putback().

1: // Listing 15.9 - Using peek() and putback()2: #include <iostream.h>3:4: int main()5: {6: char ch;7: cout << "enter a phrase: ";8: while ( cin.get(ch) )9: {10: if (ch == `!')11: cin.putback(`$');12: else13: cout << ch;14: while (cin.peek() == `#')15: cin.ignore(1,'#');16: }17: return 0;18: }

Page 250: C++ PRIRUCNIK

250 C++ programer

Output: enter a phrase: Now!is#the!time#for!fun#!Now$isthe$timefor$fun$

Pražnjenje izlaza

Već ste vidjeli kako će endl isprazniti izlazni buffer. endl poziva cout funkcijski član flush(), koji ispisuje sve podatke iz buffera. Možete pozivati flush() metodu direktno, ili pozivanjem flush() funkcijskog člana pišući slijedeće:

cout << flushOvo je prikladno kada trebamo osigurati da je izlazni buffer prazan i da je njegov sadržaj ispisan na ekran.

Povezane funkcije

Baš kao što operator ekstrakcije može biti nadopunjen s get() i getline(), operator umetanja može biti nadopunjen sa put() i write().Funkcija put() se koristi za pisanje jednoga znaka na izlazni uređaj. Budući da put() vraća ostream referencu, i budući da je cout također ostream objekt, možete ulančavati put() baš kao i operator umetanja Listing 15.10 ilustrira tu ideju.

Listing 15.10. Upotreba put().

1: // Listing 15.10 - Using put()2: #include <iostream.h>3:4: int main()5: {6: cout.put(`H').put(`e').put(`l').put(`l').put(`o').put(`\n');7: return 0;8: }Output: Hello

Funkcija write() radi kaoi operator umetanja (<<), osim što prima i parametar koji kazuje funkciji maksimalan broj znakova za pisanje. Listing 15.11 pokazuje njezinu upotrebu.

Listing 15.11. Upotreba write().

1: // Listing 15.11 - Using write()2: #include <iostream.h>3: #include <string.h>4:5: int main()6: {7: char One[] = "One if by land";8:9:10:11: int fullLength = strlen(One);12: int tooShort = fullLength -4;13: int tooLong = fullLength + 6;

Page 251: C++ PRIRUCNIK

25114:15: cout.write(One,fullLength) << "\n";16: cout.write(One,tooShort) << "\n";17: cout.write(One,tooLong) << "\n";18: return 0;19: }Output: One if by landOne if byOne if by land i?!

Upotreba cout.width()

Podrazumijevana širina vašega izlaza će biti upravo onolika koliko je potrebno za ispis zadanog znaka, broja, stringa, odn. izlaznog toka. To možete i promijeniti koristeći width(). Budući da je width() funkcijski član, on se mora pozvati preko cout objekta. On samo mijenja širinu slijedećeg izlaznog polja i potom se odmah vraća na default vrijednost. Listing 15.12. ilustrira njegovu upotrebu.

Listing 15.12. Podešavanje širine izlaza.

1: // Listing 15.12 - Adjusting the width of output2: #include <iostream.h>3:4: int main()5: {6: cout << "Start >";7: cout.width(25);8: cout << 123 << "< End\n";9:10: cout << "Start >";11: cout.width(25);12: cout << 123<< "< Next >";13: cout << 456 << "< End\n";14:15: cout << "Start >";16: cout.width(4);17: cout << 123456 << "< End\n";18:19: return 0;20: }Output: Start > 123< EndStart > 123< Next >456< EndStart >123456< End

Postavljanje Fill znakova

Normalno cout spunjava prazna polja nastala pozivanjem width() s razmacima. Ponekad ćete takve praznine željeti ispuniti drugim znakovima, na primjer, asteriksima (*). Za to možemo koristiti fill() i kao parametar proslijediti znak koji želimo koristiti kao fill znak. Listing 15.13 ilustrira ovo.

Listing 15.13. Upotreba fill().

Page 252: C++ PRIRUCNIK

252 C++ programer

1: // Listing 15.3 - fill()2:3: #include <iostream.h>4:5: int main()6: {7: cout << "Start >";8: cout.width(25);9: cout << 123 << "< End\n";10:11:12: cout << "Start >";13: cout.width(25);14: cout.fill(`*');15: cout << 123 << "< End\n";16: return 0;17: }Output: Start > 123< EndStart >******************123< End

Postavljanje zastavica

iostream objekti vode računa o svom stanju korištenjem zastavica (eng. flag). Vi možete postavljati te zastavice pozivanjem funkcije setf() i proslijeđivanjem jedne ili druge predefinirane enumerirane konstante.

Novi izraz: Za objekte kažemo da imaju stanje kad neki ili svi njihovi podaci predstavljaju uvjet koji se može mijenjati tjekom izvršavanja programa.

Na primjer, možete odlučiti prikazati ili ne nule iza decimalnog zareza. Za uključivanje nula, pozovemo setf(ios::showpoint).Enumerirane konstante su u dosegu iostream klase (ios) i prema tome se mogu punopravno pozivati preko ios::flagname, kao npr. ios::showpoint.Možete uključiti znak za pozitivne brojeve (+) sa ios::showpos. Možete promijeniti poravnavanje sa ios::left, ios::right, ili ios::internal.Konačno, možete odabrati brojevni sustav za prikazivanje brojeva sa ios::dec (decimal), ios::oct (octal--baze osam), ili ios::hex (hexadecimal--baze šesnaest). Listing 15.14 ilustrira te postavke. Kao bonus, Listing 15.14 također pokazuje setw manipulator, koji određuje širinu ali također može biti ulančan s operatorom umetanja.

Listing 15.14. Upotreba setf.

1: // Listing 15.14 - Using setf2: #include <iostream.h>3: #include <iomanip.h>4:5: int main()6: {7: const int number = 185;8: cout << "The number is " << number << endl;9:10: cout << "The number is " << hex << number << endl;

Page 253: C++ PRIRUCNIK

25311:12: cout.setf(ios::showbase);13: cout << "The number is " << hex << number << endl;14:15: cout << "The number is " ;16: cout.width(10);17: cout << hex << number << endl;18:19: cout << "The number is " ;20: cout.width(10);21: cout.setf(ios::left);22: cout << hex << number << endl;23:24: cout << "The number is " ;25: cout.width(10);26: cout.setf(ios::internal);27: cout << hex << number << endl;28:29: cout << "The number is:" << setw(10) << hex << number << endl;30: return 0;31: }Output: The number is 185The number is b9The number is 0xb9The number is 0xb9The number is 0xb9The number is 0x b9The number is:0x b9

Tokovi protiv printf() funkcije

Printf funkcija je zaostavština iz C jezika i nije ju preporučljivo koristiti zbog njenog nepodržavanaj klasa i općenite nesigurnosti vezane uz ispisne rezultate krivo definiranih tipova podataka. Ipak, mnogi ju programi i dalje vole koristiti stoga što vrlo jednostavno omogućuje formatiranje teksta. Stoga vam dajemo primjer da se podsjetite kako se ona koristi.Prije upotrebe provjerite dali ste uključili stdio.h biblioteku čiji je printf funkcija član.

Listing 15.15. Ispis sa printf().

1: #include <stdio.h>2: int main()3: {4: printf("%s","hello world\n");5:6: char *phrase = "Hello again!\n";7: printf("%s",phrase);8:9: int x = 5;10: printf("%d\n",x);11:12: char *phraseTwo = "Here's some values: ";13: char *phraseThree = " and also these: ";

Page 254: C++ PRIRUCNIK

254 C++ programer

14: int y = 7, z = 35;15: long longVar = 98456;16: float floatVar = 8.8;17:18: printf("%s %d %d %s %ld %f\n",phraseTwo,y,z,phraseThree,longVar,floatVar);19:20: char *phraseFour = "Formatted: ";21: printf("%s %5d %10d %10.5f\n",phraseFour,y,z,floatVar);22: return 0;23: }Output: hello worldHello again!5Here's some values: 7 35 and also these: 98456 8.800000Formatted: 7 35 8.800000

Datotečni ulaz i izlaz

Tokovi omogućuju uniforman način aratanja podacima bez obzira dolaze li iz hard diska ili s tastature i odlaze li na ekran ili hard disk. Za otvaranje i zatvaranje datoteka, kreirate ifstream i ofstream objekte kako je objašnjeno u slijedećih nekoliko stranica.

ofstream

Određeni objekti koji se koriste za čitanje ili pisanje u datoteke zovu se ofstream objekti. Oni su izvedeni iz iostream objekata koje ste dosad koristili.Kako biste zapisali nešto u datoteku, morate prvo kreirati ofstream objekt, i potom povezati taj objekt sa određenom datotekom na vašem disku. Za upotrebu ofstream objekata, morate uključiti fstream.h u vaš program.

PAŽNJA: Budući da fstream.h uključuje iostream.h, nema potrebe da eskplicitno uvodite iostream.

Uvjetna stanja

iostream objekti održavaju zastavice koje izvještavaju o stanju vašeg ulaza i izlaza. Svaku od zastavica možete provjeriti sa Boolean funkcijama eof(), bad(), fail(), i good(). Funkcija eof() vraća istinu ako je iostream objekt naišao na EOF, kraj datotekee. Funkcija bad() vraća TRUE ako pokušate nedozvoljenu operaciju. Funkcija fail() vraća TRUE uvijek kad je bad() istinit ili operacija propadne. Konačno, funkcija good() vraća TRUE svaki put kad su preostale tri funkcije FALSE.

Otvaranje datoteka za ulaz/izlaz

Za otvaranje datoteke myfile.cpp sa ofstream objektom, deklarirajte instancu od ofstream objekta i proslijedite ime datoteke kao parametar:

ofstream fout("myfile.cpp");Otvaranje te datoteke za ulaz radi na potpuno isti način, osim što koristi ifstream objekt:

ifstream fin("myfile.cpp");Primjetite da su fout i fin imena koja vi dodjeljujete.

Page 255: C++ PRIRUCNIK

255Jedna važna funkcija datotečnog toka koju ćete uskoro trebati je close(). Svaki objekt dat. toka koji kreirate otvara datoteku za ulaz, izlaz ili oboje. Važno je da zatvorite (close()) datoteku nakon što ste završili sa čitanjem ili pisanjem; time osiguravate da datoteka neće biti oštećena i da će svi podaci iz buffera biti snimljeni.Jednom kad su objekti povezani s datotekama, mogu se upotrebljavati kao i svaki objekt toka. Listing 15.16 ilustrira ovo.

Listing 15.16. Otvaranje datoteka za čitanje i pisanje.

1: #include <fstream.h>2: int main()3: {4: char fileName[80];5: char buffer[255]; // for user input6: cout << "File name: ";7: cin >> fileName;8:9: ofstream fout(fileName); // open for writing10: fout << "This line written directly to the file...\n";11: cout << "Enter text for the file: ";12: cin.ignore(1,'\n'); // eat the newline after the file name13: cin.getline(buffer,255); // get the user's input14: fout << buffer << "\n"; // and write it to the file15: fout.close(); // close the file, ready for reopen16:17: ifstream fin(fileName); // reopen for reading18: cout << "Here's the contents of the file:\n";19: char ch;20: while (fin.get(ch))21: cout << ch;22:23: cout << "\n***End of file contents.***\n";24:25: fin.close(); // always pays to be tidy26: return 0;27: }Output: File name: test1Enter text for the file: This text is written to the file!Here's the contents of the file:This line written directly to the file...This text is written to the file!

***End of file contents.***

Promjena podrazumijevanog ponašanja za ofstream

Podrazumijevano ponašanje prilikom otvaranja datoteke je kreiranje datoteke ako ne postoji, i potom brisanje njezinog sadržaja. Ako ne želite to podrazumijevano ponašanje, možete eksplicitno navesti drugi argument prilikom konstrukcije vašeg ofstream objekta.Valjani argumenti su:

ios::app—Dodaje na kraj postojećih datoteka umjesto brisanja.

Page 256: C++ PRIRUCNIK

256 C++ programer

ios::at—Stavlja vas na kraj datoteke, ali možete upisivati podatke bilo gdje. ios::trun—Podrazumijevani. Uzrokuje pražnjenje postojećih datoteka. ios::nocreat—Ako datoteka ne postoji, otvaranje propada. ios::noreplac—Ako datoteka već postoji, otvaranje propada.

Listing 15.17 ilustrira dodavanje na ponovno otvorenu datoteku s listinga 15.16

Listing 15.17. Dodavanja na kraj datoteke.

1: #include <fstream.h>2: int main() // returns 1 on error3: {4: char fileName[80];5: char buffer[255];6: cout << "Please re-enter the file name: ";7: cin >> fileName;8:9: ifstream fin(fileName);10: if (fin) // already exists?11: {12: cout << "Current file contents:\n";13: char ch;14: while (fin.get(ch))15: cout << ch;16: cout << "\n***End of file contents.***\n";17: }18: fin.close();19:20: cout << "\nOpening " << fileName << " in append mode...\n";21:22: ofstream fout(fileName,ios::app);23: if (!fout)24: {25: cout << "Unable to open " << fileName << " for appending.\n";26: return(1);27: }28:29: cout << "\nEnter text for the file: ";30: cin.ignore(1,'\n');31: cin.getline(buffer,255);32: fout << buffer << "\n";33: fout.close();34:35: fin.open(fileName); // reassign existing fin object!36: if (!fin)37: {38: cout << "Unable to open " << fileName << " for reading.\n";39: return(1);40: }41: cout << "\nHere's the contents of the file:\n";42: char ch;43: while (fin.get(ch))44: cout << ch;45: cout << "\n***End of file contents.***\n";46: fin.close();

Page 257: C++ PRIRUCNIK

25747: return 0;48: }Output: Please re-enter the file name: test1Current file contents:This line written directly to the file...This text is written to the file!***End of file contents.***Opening test1 in append mode...Enter text for the file: More text for the file!Here's the contents of the file:This line written directly to the file...This text is written to the file!More text for the file!***End of file contents.***

Napomena: if(fin) = (fin.good()).if(!fout) = (fout.fail()).

Binarni protiv tekst datoteka

Za razlikovanje između tekstualnih i binarnih datoteka, C++ podržava ios::binary zastavicu. Binarne datotek mogu spremiti ne samo integere i stringove, nego cijelu strukturu podataka. Svi podatke možete pisati odjednom koristeći write() metodu iz fstream.Ako koristite write(), podatke možete povratiti sa read(). Svaka od tih funkcija očekuje pokazivač na znak.Drugi argument tih funkcija je broj znakova za pisanje, kojeg možemo odrediti sa sizeof(). Listing 15.18 ilustrira pisanje sadržaja klase u datoteku.

Listing 15.18. Pisanje klase u datoteku.

1: #include <fstream.h>2:3: class Animal4: {5: public:6: Animal(int weight, long days):itsWeight(weight),itsNumberDaysAlive(days){}7: ~Animal(){}8:9: int GetWeight()const { return itsWeight; }10: void SetWeight(int weight) { itsWeight = weight; }11:12: long GetDaysAlive()const { return itsNumberDaysAlive; }13: void SetDaysAlive(long days) { itsNumberDaysAlive = days; }14:15: private:16: int itsWeight;17: long itsNumberDaysAlive;18: };19:20: int main() // returns 1 on error21: {22: char fileName[80];

Page 258: C++ PRIRUCNIK

258 C++ programer

23: char buffer[255];24:25: cout << "Please enter the file name: ";26: cin >> fileName;27: ofstream fout(fileName,ios::binary);28: if (!fout)29: {30: cout << "Unable to open " << fileName << " for writing.\n";31: return(1);32: }33:34: Animal Bear(50,100);35: fout.write((char*) &Bear,sizeof Bear);36:37: fout.close();38:39: ifstream fin(fileName,ios::binary);40: if (!fin)41: {42: cout << "Unable to open " << fileName << " for reading.\n";43: return(1);44: }45:46: Animal BearTwo(1,1);47:48: cout << "BearTwo weight: " << BearTwo.GetWeight() << endl;49: cout << "BearTwo days: " << BearTwo.GetDaysAlive() << endl;50:51: fin.read((char*) &BearTwo, sizeof BearTwo);52:53: cout << "BearTwo weight: " << BearTwo.GetWeight() << endl;54: cout << "BearTwo days: " << BearTwo.GetDaysAlive() << endl;55: fin.close();56: return 0;57: }Output: Please enter the file name: AnimalsBearTwo weight: 1BearTwo days: 1BearTwo weight: 50BearTwo days: 100

Obrada komandne linije

Mnogi OS-i, poput DOS-a ili UNIX-a, omogućuju korisniku da proslijedi parametre u program prilikom njegovog pokretanja. To se nazivaju komandno linijske opcije i obično se odvajaju razmacina u komandnoj liniji. Na prmjer:

SomeProgram Param1 Param2 Param3Ovi se parametri ne prenose u main() direktno. U svaku main() funkciju se šalju dva parametra. Prvi je broj argumenata u komandnoj liniji (broji se i ime programa, pa svaki program ima bar jedan parametar). Slijedeći parametar je polje pokazivača na znakovne stringove. Prvi argument se zove argc (argument count), ali vi ga možete nazvati kako god hoćete, to je samo stvar konvencije. Drugi se obično naziva argv (argument vector).

Page 259: C++ PRIRUCNIK

259Listing 15.19 ilustrira kako koristiti komandno linijske argumente.

Listing 15.19. Upotreba komandno-linijskih argumenata.

1: #include <iostream.h>2: int main(int argc, char **argv)3: {4: cout << "Received " << argc << " arguments...\n";5: for (int i=0; i<argc; i++)6: cout << "argument " << i << ": " << argv[i] << endl;7: return 0;8: }Output: TestProgram Teach Yourself C++ In 21 DaysReceived 7 arguments...argumnet 0: TestProgram.exeargument 1: Teachargument 2: Yourselfargument 3: C++argument 4: Inargument 5: 21argument 6: Days

Češća upotreba je prikazana modifikacijom listinga 15.18 da prima ime datoteke kao komandno linijski argument. Listing ne sadrži deklaraciju klase, koja je nepromijenjena.

Listing 15.20. Upotreba komandno linijskih argumenata.

1: #include <fstream.h>2: int main(int argc, char *argv[]) // returns 1 on error3: {4: if (argc != 2)5: {6: cout << "Usage: " << argv[0] << " <filename>" << endl;7: return(1);8: }9:10: ofstream fout(argv[1],ios::binary);11: if (!fout)12: {13: cout << "Unable to open " << argv[1] << " for writing.\n";14: return(1);15: }16:17: Animal Bear(50,100);18: fout.write((char*) &Bear,sizeof Bear);19:20: fout.close();21:22: ifstream fin(argv[1],ios::binary);23: if (!fin)24: {25: cout << "Unable to open " << argv[1] << " for reading.\n";26: return(1);27: }

Page 260: C++ PRIRUCNIK

260 C++ programer

28:29: Animal BearTwo(1,1);30:31: cout << "BearTwo weight: " << BearTwo.GetWeight() << endl;32: cout << "BearTwo days: " << BearTwo.GetDaysAlive() << endl;33:34: fin.read((char*) &BearTwo, sizeof BearTwo);35:36: cout << "BearTwo weight: " << BearTwo.GetWeight() << endl;37: cout << "BearTwo days: " << BearTwo.GetDaysAlive() << endl;38: fin.close();39: return 0;40: }Output: BearTwo weight: 1BearTwo days: 1BearTwo weight: 50BearTwo days: 100

.

Kviz

1. Što je operator umetanja i što on radi? 2. Što je operator ekstrakcije i što on radi? 3. Koja su tri oblika cin.get(), i u čemu je njihova razlika? 4. U čemu je razlika između cin.read() i cin.getline()?5. Koja je podrazumijevana širina za izlaz long integera korištenjem operatora umetanja? 6. Koja je povratna vrijednost operatora umetanja? 7. Koje parametre konstruktor na ofstream objekt uzima? 8. Što radi ios::ate argument ?

Vježbe

1. Napišite program koji piše u četri standardna iostream objekta: cin, cout, cerr, i clog.2. Napišite program za upis cijelog imena i zatim ga ispišite na ekran. 3. Napišite ponoov listing 15.9, ali da ne koristi putback() ili ignore().4. Napišite program koji prima ime datoteke kao parametar i otvara datoteku za čitanje. Pročitajte svaki znak iz datoteke i prikažite samo slova i znakove na ekran, ignorrirajući sve specijalne znakove. Zatvorite datoteku i izađite. 5. Napišite program koji prikazuje svoje komandno linijske argumente u obrnutom redoslijedu i ne prikazuje ime programa.

Page 261: C++ PRIRUCNIK

261

Lekcija 16 Predlošci

Predlošci nam dozvoljavaju da naučimo prevoditelj kako da napravi listu bilo kojeg tipa, umjesto kreiranja specifičnih tipova lista—PartsList je lista dijelova, CatList je lista mačaka. jedino u čemu se oni razlikuju je tip podatka unutar liste. S predlošcima, tip podatka na listi postaje parametar u definiciji klase. Uobičajena komponenta gotovo svake C++ bibilioteke je klasa niz. Kao što smo vidjeli sa Lists, zamorno je i neefikasno kreirati jednu klasu polje za integere, drugu za brojeve duple preciznosti, i još jednu za Animals. Predlošci nam omogućuju deklariranje parametriziranog polja klase i specificiranje tipa objekta kojeg će svaka instanca polja sadržavati.

Novi Izraz: Instancijacija je djelo stvaranja specifičnog tipa iz predloška. Pojedine klase nazivamo instancama predloška.

Parameterizirani predlošci pružaju nam mogućnost stvaranja općih klasa, te proslijeđivanje tipova kao parametara toj klasi kako bi se izgradila specifična instanca.

Definicija predloška

Parametrizirani Array objekt (predložak za niz) definiramo pisanjem:1: template <class T> // declare the template and the parameter2: class Array // the class being parameterized3: {4: public:5: Array();6: // full class declaration here7: };

Ključna riječ template se koristi na početku svake deklaracije i definicije klase predloška. Parametri predloška dolaze nakon ključne riječi template. Parametri se mijenjaju sa svakom instancom. U ovom primjeru, ključnu riječ slijedi identifikator T. Identifikator koristimo kroz ostatak definicije predloška kako bi označili parametrizirani tip. Jedna instanca te klase će zamijeniti int tamo gdje sa pojavi T appears, a druga će zamijeniti Cat.Za deklariranje int i Cat instance parameterizirane Array klase, trebali biste napisati

Array<int> anIntArray;Array<Cat> aCatArray;

Listing 16.1 prikazuje punu deklaraciju ovog ogoljelog Array predloška.

Pažnja: Listing 16.1 nije kompletan program!

Listing 16.1. Predložak Array klase.

1: Listing 16.1 A template of an array class2: #include <iostream.h>3: const int DefaultSize = 10;4:5: template <class T> // declare the template and the parameter

Page 262: C++ PRIRUCNIK

262 C++ programer

6: class Array // the class being parameterized7: {8: public:9: // constructors10: Array(int itsSize = DefaultSize);11: Array(const Array &rhs);12: ~Array() { delete [] pType; }13:14: // operators15: Array& operator=(const Array&);16: T& operator[](int offSet) { return pType[offSet]; }17:18: // accessors19: int getSize() { return itsSize; }20:21: private:22: T *pType;23: int itsSize;24: };

Izlaz: Nema nikakvog ispisa. Ovo je nekompletan program.

Analiza: Definicija predloška počinje u liniji 5, s ključnom riječi template koju slijedi parametar. U ovom slučaju, parametar je identificiran kao tip ključne riječi class, a identifikator T služi za predstavljanje parametriziranog tipa.Od linijie 6 do kraja predloška u liniji 24, ostatak deklaracije je poput svake druge deklaracije. Jedina razlika je u tome što se umjesto uobučajenog tipa objekta, ovdje pojavljuje identifikator T.

Implementiranje predloška

Puna implementacija Array predloška zahtjeva implementiranje konstruktora kopije, operatora =, itd. Listing 16.2 je primjer pogonitelja ovog predloška.

Listing 16.2. Implementacija predloška niz.

1: #include <iostream.h>2:3: const int DefaultSize = 10;4:5: // declare a simple Animal class so that we can6: // create an array of animals7:8: class Animal9: {10: public:11: Animal(int);12: Animal();13: ~Animal() {}14: int GetWeight() const { return itsWeight; }15: void Display() const { cout << itsWeight; }16: private:17: int itsWeight;

Page 263: C++ PRIRUCNIK

26318: };19:20: Animal::Animal(int weight):21: itsWeight(weight)22: {}23:24: Animal::Animal():25: itsWeight(0)26: {}27:28:29: template <class T> // declare the template and the parameter30: class Array // the class being parameterized31: {32: public:33: // constructors34: Array(int itsSize = DefaultSize);35: Array(const Array &rhs);36: ~Array() { delete [] pType; }37:38: // operators39: Array& operator=(const Array&);40: T& operator[](int offSet) { return pType[offSet]; }41: const T& operator[](int offSet) const 42: { return pType[offSet]; }43: // accessors44: int GetSize() const { return itsSize; }45:46: private:47: T *pType;48: int itsSize;49: };50:51: // implementations follow...52:53: // implement the Constructor54: template <class T>55: Array<T>::Array(int size = DefaultSize):56: itsSize(size)57: {58: pType = new T[size];59: for (int i = 0; i<size; i++)60: pType[i] = 0;61: }62:63: // copy constructor64: template <class T>65: Array<T>::Array(const Array &rhs)66: {67: itsSize = rhs.GetSize();68: pType = new T[itsSize];69: for (int i = 0; i<itsSize; i++)70: pType[i] = rhs[i];71: }

Page 264: C++ PRIRUCNIK

264 C++ programer

72:73: // operator=74: template <class T>75: Array<T>& Array<T>::operator=(const Array &rhs)76: {77: if (this == &rhs)78: return *this;79: delete [] pType;80: itsSize = rhs.GetSize();81: pType = new T[itsSize];82: for (int i = 0; i<itsSize; i++)83: pType[i] = rhs[i];84: return *this;85: }86:87: // driver program88: int main()89: {90: Array<int> theArray; // an array of integers91: Array<Animal> theZoo; // an array of Animals92: Animal *pAnimal;93:94: // fill the arrays95: for (int i = 0; i < theArray.GetSize(); i++)96: {97: theArray[i] = i*2;98: pAnimal = new Animal(i*3);99: theZoo[i] = *pAnimal;100: delete pAnimal;101: }102: // print the contents of the arrays103: for (int j = 0; j < theArray.GetSize(); j++)104: {105: cout << "theArray[" << j << "]:\t";106: cout << theArray[j] << "\t\t";107: cout << "theZoo[" << j << "]:\t";108: theZoo[j].Display();109: cout << endl;110: }111:112: for (int k = 0; k < theArray.GetSize(); k++)113: delete &theZoo[j];114: return 0;115: }Output: theArray[0]: 0 theZoo[0]: 0theArray[1]: 2 theZoo[1]: 3theArray[2]: 4 theZoo[2]: 6theArray[3]: 6 theZoo[3]: 9theArray[4]: 8 theZoo[4]: 12theArray[5]: 10 theZoo[5]: 15theArray[6]: 12 theZoo[6]: 18theArray[7]: 14 theZoo[7]: 21theArray[8]: 16 theZoo[8]: 24theArray[9]: 18 theZoo[9]: 27

Page 265: C++ PRIRUCNIK

265Funkcijski predlošci

Ako želite proslijediti objekt polje u funkciju, morate proslijediti određenu instancu pulja, a ne predložak. Prema tome, prima li SomeFunction() cijeli broj kao parametar, morate pisati

void SomeFunction(Array<int>&); // oka ne

void SomeFunction(Array<T>&); // error!jer se nezna što T& predstavlja. Također ne smijete pisati

void SomeFunction(Array &); // error!budući da nema klase Array—samo predložak i instance.Za postizanje općenitijeg pristupa, morate deklarirati funkcijski predložak.

template <class T>void MyTemplateFunction(Array<T>&); // ok

Ovdje je funkcija MyTemplateFunction() deklarirana kao funkcijski predložak.

Predlošci i prijatelji

Predlošci mogu deklarirati tri tipova prijatelja: Prijateljska klasa ili funkcija koja nije predložak. Opći prijateljski predložak ili funkcijski predložak. Prijateljski predložak ili funkcijski predložak specifičnoga tipa.

Prijateljska klasa ili funkcija koja nije predložak

Moguće je deklarirati bilo koju klasu ili funkciju kao prijatelja vašeg predloška. Listing 16.3 dodaje trivijalnu prijateljsku funkciju Intrude(), u definiciju predloška Array klase, i glavni program poziva Intrude(). Budući je riječ o prijatelju, Intrude() potom može pristupati privatnim podacima od Array. Kako nije riječ o funkcijskom predlošku, može se pozivati samo za polja cijelih brojeva.

PAŽNJA: Za uporebu listinga 16.3, kopirajte linije 1-26 listing 16.2 nakon linije 1 ovoga listinga i potom linje 51-86 listinga 16.2 nakon linije 37 ovoga listinga.

Listing 16.3. Prijateljska funkcija ne-predložak.

1: // Listing 16.3 - Type specific friend functions in templates2:3: template <class T> // declare the template and the parameter4: class Array // the class being parameterized5: {6: public:7: // constructors8: Array(int itsSize = DefaultSize);9: Array(const Array &rhs);10: ~Array() { delete [] pType; }11:12: // operators13: Array& operator=(const Array&);14: T& operator[](int offSet) { return pType[offSet]; }15: const T& operator[](int offSet) const 16: { return pType[offSet]; }17: // accessors

Page 266: C++ PRIRUCNIK

266 C++ programer

18: int GetSize() const { return itsSize; }19:20: // friend function21: friend void Intrude(Array<int>);22:23: private:24: T *pType;25: int itsSize;26: };27:28: // friend function. Not a template, can only be used29: // with int arrays! Intrudes into private data.30: void Intrude(Array<int> theArray)31: {32: cout << "\n*** Intrude ***\n";33: for (int i = 0; i < theArray.itsSize; i++)34: cout << "i: " << theArray.pType[i] << endl;35: cout << "\n";36: }37:38: // driver program39: int main()40: {41: Array<int> theArray; // an array of integers42: Array<Animal> theZoo; // an array of Animals43: Animal *pAnimal;44:45: // fill the arrays46: for (int i = 0; i < theArray.GetSize(); i++)47: {48: theArray[i] = i*2;49: pAnimal = new Animal(i*3);50: theZoo[i] = *pAnimal;51: }52:53: int j, k;54: for (j = 0; j < theArray.GetSize(); j++)55: {56: cout << "theZoo[" << j << "]:\t";57: theZoo[j].Display();58: cout << endl;59: }60: cout << "Now use the friend function to ";61: cout << "find the members of Array<int>";62: Intrude(theArray);63:63: // return the allocated memory before the arrays are destroyed.64: for (k = 0; k < theArray.GetSize(); k++)65: delete &theZoo[j];66:67: cout << "\n\nDone.\n";68: return 0;69: }Output: theZoo[0]: 0

Page 267: C++ PRIRUCNIK

267theZoo[1]: 3theZoo[2]: 6theZoo[3]: 9theZoo[4]: 12theZoo[5]: 15theZoo[6]: 18theZoo[7]: 21theZoo[8]: 24theZoo[9]: 27Now use the friend function to find the members of Array<int>*** Intrude ***i: 0i: 2i: 4i: 6i: 8i: 10i: 12i: 14i: 16i: 18

Done.

Opći prijateljski predložak ili funkcijski predložak

Bilo bi korisno dodoati operator prikazivanja u Array klasu. Jedan pristup bio bi deklariranje operatora prikaza za svaki mogući tip od Array, ali bi time skroz igubili prednost proglašavanja Array predloškom.Ono što nam treba jest operator umetanja koji radi za bilo koji tup od Array.

ostream& operator<< (ostream& Array<T>&);Kako bi ovo radilo, moramo proglasiti operator<< funkcijskim predloškom.

template <class T> ostream& operator<< (ostream&, Array<T>&)Sad kad je operator<< funkcijski predložak, trebamo samo implementaciju. Listing 16.4 pokazuje Array predložak proširen za ovu deklaraciju i ima implementiran operator<<.

PAŽNJA: Za prevođenje ovog listinga, iskopirajte linije 8-26 listinga 16.2 i umetnite ih između linija 3 i 4. Također iskopirajte linije 51-86 listinga 16.2 i umetnite ih između linija 37 i 38.

Listing 16.4. Upotreba operatora ostream.

1: #include <iostream.h>2:3: const int DefaultSize = 10;4:5: template <class T> // declare the template and the parameter6: class Array // the class being parameterized7: {8: public:9: // constructors10: Array(int itsSize = DefaultSize);

Page 268: C++ PRIRUCNIK

268 C++ programer

11: Array(const Array &rhs);12: ~Array() { delete [] pType; }13:14: // operators15: Array& operator=(const Array&);16: T& operator[](int offSet) { return pType[offSet]; }17: const T& operator[](int offSet) const 18: { return pType[offSet]; }19: // accessors20: int GetSize() const { return itsSize; }21:22: friend ostream& operator<< (ostream&, Array<T>&);23:24: private:25: T *pType;26: int itsSize;27: };28:29: template <class T>30: ostream& operator<< (ostream& output, Array<T>& theArray)31: {32: for (int i = 0; i<theArray.GetSize(); i++)33: output << "[" << i << "] " << theArray[i] << endl; return output;34: }35:36: enum BOOL { FALSE, TRUE};37:38: int main()39: {40: BOOL Stop = FALSE; // flag for looping41: int offset, value;42: Array<int> theArray;43:44: while (!Stop)45: {46: cout << "Enter an offset (0-9) ";47: cout << "and a value. (-1 to stop): " ;47: cin >> offset >> value;48:49: if (offset < 0)50: break;51:52: if (offset > 9)53: {54: cout << "***Please use values between 0 and 9.***\n";55: continue;56: }57:58: theArray[offset] = value;59: }60:61: cout << "\nHere's the entire array:\n";62: cout << theArray << endl;63: return 0;

Page 269: C++ PRIRUCNIK

26964: }

Output: Enter an offset (0-9) and a value. (-1 to stop): 1 10Enter an offset (0-9) and a value. (-1 to stop): 2 20Enter an offset (0-9) and a value. (-1 to stop): 3 30Enter an offset (0-9) and a value. (-1 to stop): 4 40Enter an offset (0-9) and a value. (-1 to stop): 5 50Enter an offset (0-9) and a value. (-1 to stop): 6 60Enter an offset (0-9) and a value. (-1 to stop): 7 70Enter an offset (0-9) and a value. (-1 to stop): 8 80Enter an offset (0-9) and a value. (-1 to stop): 9 90Enter an offset (0-9) and a value. (-1 to stop): 10 10***Please use values between 0 and 9.***Enter an offset (0-9) and a value. (-1 to stop): -1 -1Here's the entire array:[0] 0[1] 10[2] 20[3] 30[4] 40[5] 50[6] 60[7] 70[8] 80[9] 90

Prijateljski predložak ili funkcijski predložak specifičnoga tipa

Iako operator umetanja prikazan na 16.4 radi, još uvijek nije u potpunosti ono što nam je potrebno. Budući da deklaracija friend operatora u linij 29 dekarira predložak, raditi će za svaku instancu Array i svaki operator inservije prima niz bilo kojeg tipa.Za postizanje cilja, modificirati liniju 29 uklanjanjem riječi template <class T>. Potom, linij 30 treba biti

friend ostream& operator<< (ostream&, Array<T>&);To će koristiti tip (T) declariran u predlošku Array. Time će operator<< za cijeli broj samo raditi s nizon cijelih brojeva, itd.

Upotreba predlošaka

Predloške možemo tretirati kao bilo koji tip podatka. Možemo ih proslijeđivati kao parametre po vrijdenosti i referenci, te ih možemo vraćati kao povratne tipove. Listing 16.5 demonstrira kako proslijeđivati objekte predloške.

Listing 16.5. Proslijeđivanje predložaka u i iz funkcija.

1: #include <iostream.h>2:3: const int DefaultSize = 10;4:5: // A trivial class for adding to arrays6: class Animal7: {

Page 270: C++ PRIRUCNIK

270 C++ programer

8: public:9: // constructors10: Animal(int);11: Animal();12: ~Animal();13:14: // accessors15: int GetWeight() const { return itsWeight; }16: void SetWeight(int theWeight) { itsWeight = theWeight; }17:18: // friend operators19: friend ostream& operator<< (ostream&, const Animal&);20:21: private:22: int itsWeight;23: };24:25: // extraction operator for printing animals26: ostream& operator<< 27: (ostream& theStream, const Animal& theAnimal)28 {29: theStream << theAnimal.GetWeight();30: return theStream;31: }32:33: Animal::Animal(int weight):34: itsWeight(weight)35: {36: // cout << "Animal(int)\n";37: }38:39: Animal::Animal():40: itsWeight(0)41: {42: // cout << "Animal()\n";43: }44:45: Animal::~Animal()46: {47: // cout << "Destroyed an animal...\n";48: }49:50: template <class T> // declare the template and the parameter51: class Array // the class being parameterized52: {53: public:54: Array(int itsSize = DefaultSize);55: Array(const Array &rhs);56: ~Array() { delete [] pType; }57:58: Array& operator=(const Array&);59: T& operator[](int offSet) { return pType[offSet]; }60: const T& operator[](int offSet) const 61: { return pType[offSet]; }

Page 271: C++ PRIRUCNIK

27162: int GetSize() const { return itsSize; }6364: // friend function65: friend ostream& operator<< (ostream&, const Array<T>&);66:67: private:68: T *pType;69: int itsSize;70: };71:70: template <class T>72: ostream& operator<< (ostream& output, const Array<T>& theArray)73: {74: for (int i = 0; i<theArray.GetSize(); i++)75: output << "[" << i << "] " << theArray[i] << endl;76: return output;77: }78:79: void IntFillFunction(Array<int>& theArray);80: void AnimalFillFunction(Array<Animal>& theArray);81: enum BOOL {FALSE, TRUE};82:84: int main()85: {86: Array<int> intArray;87: Array<Animal> animalArray;88: IntFillFunction(intArray);87: AnimalFillFunction(animalArray);89: cout << "intArray...\n" << intArray;90: cout << "\nanimalArray...\n" << animalArray << endl;91: return 0;92: }93:94: void IntFillFunction(Array<int>& theArray)95: {96: BOOL Stop = FALSE;97: int offset, value;98: while (!Stop)99: {100: cout << "Enter an offset (0-9) ";101: cout << "and a value. (-1 to stop): " ;102: cin >> offset >> value;103: if (offset < 0)104: break;105: if (offset > 9)106: {107: cout << "***Please use values between 0 and 9.***\n";108: continue;109: }110: theArray[offset] = value;111: }112: }113:114:

Page 272: C++ PRIRUCNIK

272 C++ programer

115: void AnimalFillFunction(Array<Animal>& theArray)116: {117: Animal * pAnimal;118: for (int i = 0; i<theArray.GetSize(); i++)119: {120: pAnimal = new Animal;121: pAnimal->SetWeight(i*100);122: theArray[i] = *pAnimal;123: delete pAnimal; // a copy was put in the array124: }125: }Output: Enter an offset (0-9) and a value. (-1 to stop): 1 10Enter an offset (0-9) and a value. (-1 to stop): 2 20Enter an offset (0-9) and a value. (-1 to stop): 3 30Enter an offset (0-9) and a value. (-1 to stop): 4 40Enter an offset (0-9) and a value. (-1 to stop): 5 50Enter an offset (0-9) and a value. (-1 to stop): 6 60Enter an offset (0-9) and a value. (-1 to stop): 7 70Enter an offset (0-9) and a value. (-1 to stop): 8 80Enter an offset (0-9) and a value. (-1 to stop): 9 90Enter an offset (0-9) and a value. (-1 to stop): 10 10***Please use values between 0 and 9.***Enter an offset (0-9) and a value. (-1 to stop): -1 -1

intArray:...[0] 0[1] 10[2] 20[3] 30[4] 40[5] 50[6] 60[7] 70[8] 80[9] 90

animalArray:...[0] 0[1] 100[2] 200[3] 300[4] 400[5] 500[6] 600[7] 700[8] 800[9] 900

Standardna biblioteka predložaka

Svi glavni proizvođači kompajlera uz njih sada isporučuju i standardnu bibiloteku predložaka, Standard Template Library (STL).

Page 273: C++ PRIRUCNIK

273Cilj STL-a je davanje alternative ponovnom otkrivanju "tople vode" za česte zahtjeve. STL je testiran, debuggiran, nudi visoke performanse, i još je besplatan. Najvažnije, STL je reiskoristiv; jednom kad naučite kako koristiti STL container, možete ga koristiti u svim svojim programima.

Kviz

1. U čemu je razlika između parametra u predlošku i funkciji? 2. Koja je razlika između prijateljskog predloška specifičnog tipa i općeg prijateljskog predloška? 3. Može li se omogućiti specijalno ponašanje za jednu instancu predloška, ali ne i za druge?

Vježbe

1. Kreirajte predložak baziran na List klasi:

class List{private:

public: List():head(0),tail(0),theCount(0) {} virtual ~List(); void insert( int value ); void append( int value ); int is_present( int value ) const; int is_empty() const { return head == 0; } int count() const { return theCount; }private: class ListCell { public: ListCell(int value, ListCell *cell =):val(value),next(cell){} int val; ListCell *next; }; ListCell *head; ListCell *tail; int theCount;};

2. Napišite implementaciju List klases (bez predloška). 3. Napišite verziju kao predložak. 4. Deklarirajte 3 list objekta: lista Strings, lista Cats, i lista ints.5. BUG BUSTERS: Što ne valja sa slijedećim kodom?

List<Cat> Cat_List;Cat Felix;CatList.append( Felix );cout << "Felix is " << ( Cat_List.is_present( Felix ) ) ? "" : "not " << "present\n";

Page 274: C++ PRIRUCNIK

274 C++ programer

Lekcija 17 Iznimke i baratanje pogreškama

Sav kod koji smo dosad obradili bio je kreiran u demonstracijske svrhe. Nije baratao sa pogreškama, kako se nebismo odmicali osnovnih problema koje smo obrađivali. Programi u stvarnom svijetu moraju raznmatrati i mogućnost pojavljivanja pogrešaka.

Bugovi, pogreške, zablude i truli kod

Svi programi imaju bugove. Što je veći program, više je i bugova. To što je gornja tvrdnja istinita ne znači da je i u redu praviti bugovite programe. Pravljenje robusnih, nebugovitih programa je prioritet broj jedan za svakoga tko se misli ozbiljno baviti programiranjem.Bitno je razlikovati bugove, koji nastaju uslijed ogičke i sintaktičke pogreške, te iznimke, koje nastaju uslijed neobičnih ali predvidivih problema poput nestajanja resursa (memorije ili prostora na disku).

Iznimke

Vaš korisnik će s vremena na vrijeme ostati bez memorije i pitanje je što ćete vi učiniti. Vaše mogućnosti su:

Srušiti program. Obavijestiti korisnika i dostojanstveno se povući. Obavijestiti korisnika i dozvoliti mu da pokuša osloboditi potrebne resurse i

nastavi s radom. Preuzeti akciju popravljanja i nastaviti bez ometanja korisnika.

C++ baratanje iznimkama pruža nam sigurnu metodu za obradu predvidljivih ali neobičnih stanja koja mogu nastati prilikom pokretanja programa.

Što su iznimke

U C++, iznimka je objekt koji se prenosi iz dijela koda gdje je problem nastao u dio koda koji će se baviti problemom. Tip iznimke određuje koji će dio koda obrađivati problem.Osnovna ideja iza iznimaka je vrlo jednostavna:

Stvarno zauzimanje resursa (na primjer, rezerviranje memorije ili zaključavanje datoteke) se obično obavlja na niskom nivou unutar programa.

Logika baratanja problemom, odn. što uraditi kad operacija propadne, je obično visoko u programu, u dijelu koda za interakciju s korisnikom.

Iznimke omogućuju trenutan prelazak iz prvog u drugi dio koda.

Kako se iznimke koriste

try blokovi se kreiraju kako bi opkolili područja koda u kojima se možda nalazi problem. Na primjer:

try{SomeDangerousFunction();

Page 275: C++ PRIRUCNIK

275}

catch blokovi barataju iznimkom koju nam javi try blok. Na primjer:try{SomeDangerousFunction();}catch(OutOfMemory){// take some actions}catch(FileNotFound){// take other action}

PAŽNJA: Neki stariji kompajleri ne podržavaju iznimke.

Listing 17.1. Buđenje iznimke.

0: #include <iostream.h>1: 2: const int DefaultSize = 10;3: 4: class Array5: {6: public:7: // constructors8: Array(int itsSize = DefaultSize);9: Array(const Array &rhs);10: ~Array() { delete [] pType;}11: 12: // operators13: Array& operator=(const Array&);14: int& operator[](int offSet);15: const int& operator[](int offSet) const;16: 17: // accessors18: int GetitsSize() const { return itsSize; }19: 20: // friend function21: friend ostream& operator<< (ostream&, const Array&);22: 23: class xBoundary {}; // define the exception class24: private:25: int *pType;26: int itsSize;27: };28: 29: 30: Array::Array(int size):31: itsSize(size)32: {33: pType = new int[size];

Page 276: C++ PRIRUCNIK

276 C++ programer

34: for (int i = 0; i<size; i++)35: pType[i] = 0;36: }37: 38: 39: Array& Array::operator=(const Array &rhs)40: {41: if (this == &rhs)42: return *this;43: delete [] pType;44: itsSize = rhs.GetitsSize();45: pType = new int[itsSize];46: for (int i = 0; i<itsSize; i++)47: pType[i] = rhs[i];48: return *this;49: }50: 51: Array::Array(const Array &rhs)52: {53: itsSize = rhs.GetitsSize();54: pType = new int[itsSize];55: for (int i = 0; i<itsSize; i++)56: pType[i] = rhs[i];57: }58: 59: 60: int& Array::operator[](int offSet)61: {62: int size = GetitsSize();63: if (offSet >= 0 && offSet < GetitsSize())64: return pType[offSet];65: throw xBoundary();66: return pType[0]; // appease MSC 67: }68: 69: 70: const int& Array::operator[](int offSet) const71: {72: int mysize = GetitsSize();73: if (offSet >= 0 && offSet < GetitsSize())74: return pType[offSet];75: throw xBoundary();76: return pType[0]; // appease MSC 77: }78: 79: ostream& operator<< (ostream& output, const Array& theArray)80: {81: for (int i = 0; i<theArray.GetitsSize(); i++)82: output << "[" << i << "] " << theArray[i] << endl;83: return output;84: }85: 86: int main()87: {

Page 277: C++ PRIRUCNIK

27788: Array intArray(20);89: try90: {91: for (int j = 0; j< 100; j++)92: {93: intArray[j] = j;94: cout << "intArray[" << j << "] okay..." << endl;95: }96: }97: catch (Array::xBoundary)98: {99: cout << "Unable to process your input!\n";100: }101: cout << "Done.\n";102: return 0;103: }Output: intArray[0] okay...intArray[1] okay...intArray[2] okay...intArray[3] okay...intArray[4] okay...intArray[5] okay...intArray[6] okay...intArray[7] okay...intArray[8] okay...intArray[9] okay...intArray[10] okay...intArray[11] okay...intArray[12] okay...intArray[13] okay...intArray[14] okay...intArray[15] okay...intArray[16] okay...intArray[17] okay...intArray[18] okay...intArray[19] okay...Unable to process your input!Done.

Specificiranje više od jednog catch

Moguće je da više uvjeta uzrokuje iznimku. U tom slučaju, catch naredbe mogu biti poredane jedna iza druge, poput uvjeta u switch naredbi. Ekvivalent za default naredbu je "catch everything" naredba, indicirana sa catch(...). Listing 17.2 ilustrira višestruke uvjete iznimaka.

Listing 17.2. Višestruke iznimke.

0: #include <iostream.h>1: 2: const int DefaultSize = 10;3: 4: class Array

Page 278: C++ PRIRUCNIK

278 C++ programer

5: {6: public:7: // constructors8: Array(int itsSize = DefaultSize);9: Array(const Array &rhs);10: ~Array() { delete [] pType;}11: 12: // operators13: Array& operator=(const Array&);14: int& operator[](int offSet);15: const int& operator[](int offSet) const;16: 17: // accessors18: int GetitsSize() const { return itsSize; }19: 20: // friend function21: friend ostream& operator<< (ostream&, const Array&);22: 23: // define the exception classes24: class xBoundary {};25: class xTooBig {};26: class xTooSmall{};27: class xZero {};28: class xNegative {};29: private:30: int *pType;31: int itsSize;32: };33: 34: int& Array::operator[](int offSet)35: {36: int size = GetitsSize();37: if (offSet >= 0 && offSet < GetitsSize())38: return pType[offSet];39: throw xBoundary();40: return pType[0]; // appease MFC41: }42: 43: 44: const int& Array::operator[](int offSet) const45: {46: int mysize = GetitsSize();47: if (offSet >= 0 && offSet < GetitsSize())48: return pType[offSet];49: throw xBoundary();50: return pType[0];51: return pType[0]; // appease MFC52: }53: 54: 55: Array::Array(int size):56: itsSize(size)57: {58: if (size == 0)

Page 279: C++ PRIRUCNIK

27959: throw xZero();60: if (size < 10)61: throw xTooSmall();62: if (size > 30000)63: throw xTooBig();64: if (size < 1)65: throw xNegative();66: 67: pType = new int[size];68: for (int i = 0; i<size; i++)69: pType[i] = 0;70: }71: 72: 73: 74: int main()75: {76: 77: try78: {79: Array intArray(0);80: for (int j = 0; j< 100; j++)81: {82: intArray[j] = j;83: cout << "intArray[" << j << "] okay...\n";84: }85: }86: catch (Array::xBoundary)87: {88: cout << "Unable to process your input!\n";89: }90: catch (Array::xTooBig)91: {92: cout << "This array is too big...\n";93: }94: catch (Array::xTooSmall)95: {96: cout << "This array is too small...\n";97: }98: catch (Array::xZero)99: {100: cout << "You asked for an array";101: cout << " of zero objects!\n"; 102: }103: catch (...)104: {105: cout << "Something went wrong!\n";106: }107: cout << "Done.\n";108: return 0;109: }Output: You asked for an array of zero objects!Done.

Page 280: C++ PRIRUCNIK

280 C++ programer

Bugovi i debugiranje

Svaka moderna razvojna okolina sadrži jedan ili više moćnih debuggera. Osnovna ideja prilikom korištenja debuggera je ova: Pokrenete debugger, koji učita vaš source , te potom pokrenete vaš program iz debuggera. Ovime možete vidjeti svaku instrukcije tjekom samog izvođenja programa, te da provjeravate varijable kako se mijenjaju tjekom izvršenja vašega programa.Svi prevoditelji omogućuju kompajliranje sa i bez simbola. Prevođenje sa simbolima govori kompajleru da kreira poseban sistem označavanja source i njegovog odnosa spram generiranog programa.

Kviz

1. Što je iznimka? 2. Što je try blok?3. Što je catch naredba?

Vježbe

1. Napravite try blok, catch naredbu i jednostavnu iznimku. 2. BUG BUSTERS: Što ne valja u ovom kodu?

class xOutOfMemory{public: xOutOfMemory( const String& message ) : itsMsg( message ){} ~xOutOfMemory(){} virtual const String& Message(){ return itsMsg};private: String itsMsg; }

main(){ try { char *var = new char; if ( var == 0 ) throw xOutOfMemory(); } catch( xOutOfMemory& theException ) { cout << theException.Message() << "\n"; }}

Page 281: C++ PRIRUCNIK

281

Page 282: C++ PRIRUCNIK

282 C++ programer

Lekcija1

Kviz1. Koja je razlika između interpretera i kompajlera?

Interpreteri prolaze kroz kod i prevode program direktno pretvarajući programske instrukcije u akcije. Kompajleri prevode source code u izvršni program koji se potom može pokrenuti.

2. Kako kompajlirate izvršni kod sa svojim kompajlerom? Svaki prevoditelj je drugačiji. Provjerite prateću dokumentaciju.

3. Što čini linker? Posao linkera je međusobno povezivanje vašeg kompajliranog koda sa pratećim bibiliotekama funkcija i ostalim source kodovima. Linker nam omogućuje izgradnju našeg programa u dijelićima i potom povezivanje dijelova u jedan veliki program.

4. Koji su koraci u razvojnom ciklusu? Editiranje source koda, prevođenje, linkanje, testiranje, ponavljanje.

Vježbe1. Pogledajte u slijedeći program i pokušajte ustanoviti što on radi bez njegova pokretanja.

1: #include <iostream.h>2: int main()3: {4: int x = 5;5: int y = 7;6: cout "\n";7: cout << x + y << " " << x * y;8: cout "\n";9:return 0;10: }Inicijalizira dvije cjelobrojne varijable i potom ispisuje njihovu sumu i produkt.

2. Unesite program sa 1. vježbe, kompajlirajte ga i povežite. Što on čini ? Da li radi ono što ste mislili?

3. Unesite slijedeći program i kompajlirajte ga. Koja će se greška pojaviti?

1: include <iostream.h>2: int main()3: {4: cout << "Hello World\n";5: return 0;6: }Morate staviti # ispred riječi include u prvoj liniji.

4. Popravite grešku u 3. vježbi, rekompajlirajte , linkajte i pokrenite program. Što on radi?

Page 283: C++ PRIRUCNIK

283Ovaj program ispisuje poruku Hello World na ekran, te potom prebacuje pokazivač u novu liniju (carriage return).

Page 284: C++ PRIRUCNIK

284 C++ programer

Lekcija 2

Kviz1. Koja je razlika između kompajlera i pretprocesora?

Prilikom svakog pokretanja kompajlera, pretprocesor se pokreće prvi. On "čita" naš program, te umeće sve navedene datoteke, te izvodi ostale potrebne djelatnosti.

2. Zašto je funkcija main() posebna? main() se poziva automatski, svaki put kad pokrenemo naš program.

3. Koja su dva tipa komentara i po čemu se razlikuju? C++ komentari su dva "slash" znaka (//), i oni komentiraju sav tekst do kraja linije. C komentari dolaze u paru (/* */), i sve unutar pripadajućih parova je komentar.

4. Mogu li komentari biti duži od jedne linije? C komentari mogu. Ako želite proširiti C++ komentare na drugu liniju, morate staviti drugi blok "slash" znakova (//).

Vježbe1. Napišite program koji ispisuje "Ja volim C++" na ekran.

1: #include <iostream.h>2:3: int main()4: {5: cout << "I love C++\n";6: return 0;7: }

2. Napišite najmanji program koji se može prevesti, linkati i pokrenuti.

int main(){}

3. Unesite slijedeći program i kompajlirajte ga. Gdje je greška? Možete li ju popraviti?

1: #include <iostream.h>2: void main()3: {4: cout << Is there a bug here?";5: }U liniji 4 nedostaje otvoreni navodnik za string.

4. Popravite pogrešku sa 3. vježbe i pokrenite program.

Page 285: C++ PRIRUCNIK

285

Lekcija 3

Kviz1. Koja je razlika između integer i floating point varijable?

Integeri spremaju cijele brojeve; floating-point varijable su realni brojevi koji mogu biti prikazani pomoću mantise i eksponenta.

2. Koje su razlike među unsigned short int i long int? Ključna riječ unsigned označuje da taj integer pamti samo pozitivne brojeve. Na većini računala, short integeri zauzimaju 2 bytea, a long integeri 4.

3. Što je prednost kod upotrebe const ključne riječi u odnosu na #define?

Const varijable "preživljavaju" pretprocesor, pa kompajler može provjeriti dali se pravilno upotrebljavaju.

4. Kako razlikujemo dobro od lošeg imena varijable? Dobro imenovana varijabla nam govori o samoj svrsi varijable, npr. myAge i PeopleOnTheBus su dobra imena varijabli, a xjk i prndl su vjerojatno manje korisni.

5. Uz slijedeći enum, koja će biti vrijednost za BLUE? enum COLOR { WHITE, BLACK = 100, RED, BLUE, GREEN = 300 };BLUE = 102

6. Koje od slijedećih varijabli su dobre, koje loše, a koje nelegalne? a. Age Dobrab. !ex Nedozvoljenoc. R79J Dozvoljeno, ali loš odabir imenad. TotalIncome Dobrae. __Invalid Dozvoljeno, ali loš odabir

Vježbe1. Koji bi bio pravi tip varijable za pohranu slijedećih informacija?

a. Vaše godine. Unsigned short integerb. Poštanski broj vašega grada. Unsigned long integer or unsigned floatc. Broj zvijezda u galaksiji. Unsigned doubled. Prosječna količina padalina za siječanj. Unsigned short integer

Izmislite dobra imena varijabli za te informacije.a. myAgeb. backYardAreac. StarsInGalaxyd. averageRainFall

2. Deklarirajte konstantu za pi kao 3.14159. const float PI = 3.14159;

3. Deklarirajte float varijablu i inicijalizirajte ju koristeći pi konstantu.

Page 286: C++ PRIRUCNIK

286 C++ programer

float myPi = PI;

Page 287: C++ PRIRUCNIK

287

Lekcija 4

Kviz1. Što je izraz?

Bilo koja naredba koja vraća vrijednost.

2. Da li je x = 5 + 7 izraz? Koja mu je vrijednost? Da. 12

3. Kolika je vrijednost od 201 / 4? 50

4. Kolika je vrijednost od 201 % 4? 1

5. Ako su myAge, a, i b svi int varijable, koje su njihove vrijednosti nakon:

myAge = 39;a = myAge++;b = ++myAge; myAge: 41, a: 39, b: 41

6. Kolika je vrijednost od 8+2*3? 14

7. Koja je razlika između x = 3 i x == 3? Prva je pridruživanje, a druga provjera jednakosti.

8. Da li slijedeće vrijednosti poprimaju TRUE ili FALSE? a. 0 FALSEb. 1 TRUEc. -1 TRUEd. x = 0 FALSEe. x == 0 // pretpostavimo da x ima vrijednost 0 TRUE

Vježbe1. Napišite jednu naredbu koja ispituje dve cjelobrojne vrijednosti i mijenja veću u manju, koristeći samo jedan else uvjet.

if (x > y) x = y;else // y > x || y == x y = x;

Page 288: C++ PRIRUCNIK

288 C++ programer

2. Proučite slijedeći program. Zamislite unošenje tri broja, i napišite kakav izlaz očekujete.

1: #include <iostream.h>2: int main()3: { 4: int a, b, c;5: cout << "Please enter three numbers\n";6: cout << "a: ";7: cin >> a;8: cout << "\nb: ";9: cin >> b;10: cout << "\nc: ";11: cin >> c;12:13: if (c = (a-b))14: {cout << "a: ";15: cout << a;16: cout << "minus b: ";17: cout << b;18: cout << "equals c: ";19: cout << c << endl;}20: else21: cout << "a-b does not equal c: " << endl;22: return 0;23: }

3. Upišite program iz vježbe 2, te kreirajte exe datoteku. Unesite brojeve 20, 10 i 50 . Jeste li dobili ono što ste očekivali? Zašto niste?

Ulaz 20, 10, 50.Izlaz a: 20 b: 30 c: 10.Linija 13 pridružuje, ne provjeravajući jednakost.

4. Pogledajte program i anticipirajte njegov izlaz: 1: #include <iostream.h>2: int main()3: {4: int a = 1, b = 1, c;5: if (c = (a-b))6: cout << "The value of c is: " << c;7: return 0;8: }

5. Unesite, kompajlirajte, povežite i pokrenite program sa vježbe 4. Što je izlaz i zašto?

Budući da linija 5 pridružuje vrijednost od a-b u c, vrijednost pridruživanja je a (1) minus b (1), ili 0. Kako 0 prozivamo kao FALSE, if propada i ništa se ne ispisuje.

Page 289: C++ PRIRUCNIK

289Lekcija 5

Kviz1. Koja je razlika između prototipa funkcije i definicije funkcije?

Funkcijski prototip deklarira funkciju; definicija ju definira. Prototip završava sa; definicija ne. Deklaracija može sadržati riječ inline i podrazumijevane vrijednosti za parametre; definicija ne može. Deklaracija ne treba imati imena parametara; definicija mora.

2. Moraju li se imena parametara podudarati u prototipu, definiciji i pozivu funkcije?

Ne. Parametre identificiramo po položaju, a ne po imenu.

3. Ako funkcije ne vraća nikakvu vrijednost, kako ćemo ju deklarirati?

Kao void.

4. Ako ne deklariramo povratni tip funkcije, koji tip će biti podrazumijevan?

Svaka funkcija koja eksplicitno ne deklarira povratni tip je cjelobrojna.

5. Što je lokalna varijabla? Lokalna varijabla je varijabla proslijeđena ili deklarirana u bloku, obično funkcijskom. Vidljiva je samo u bloku.

6. Što je doseg (engl. scope) varijable? Doseg se odnosi na vidljivost i životni vijek lokalnih i globalnih varijabli.

7. Što je rekurzija? Rekurzija se obično odnosi na sposobnost funkcija da poziva samu sebe.

8. Kad koristimo globalne varijable? Globalne varijable obično koristimo kad mnogo funkcija treba pristupati istim podacima. Globalne varijable su rijetkost u C++.

9. Što je preopterećenje funkcija? Preopterećenje funkcija je sposobnost pisanja više funkcija sa istim imenom, a funkcije se razlikuju po broju i tipu parametara.

10. Što je polimorfizam? Polimorfizam je svojstvo tretiranja mnogih objekata različitih ali sličnih tipova bez obzira na njihove razlike. U C++, polimorfizam se postiže uporabom izvedenih klasa i virtualnih funkcija.

Page 290: C++ PRIRUCNIK

290 C++ programer

Vježbe1. Napišite prototip za funkciju imena Perimeter(), koja vraća unsigned long int i ima dva parametra, oba unsigned short int.

unsigned long int Perimeter(unsigned short int, unsigned short int);

2. Napišite definiciju za funkciju Perimeter() iz vježbe 1. Dva parametra predstavljaju dužinu i širinu pravokutnika. Neka funkcija vraća perimetar (dvostruka dužina pomnožena dvostrukom širinom).

unsigned long int Perimeter(unsigned short int length, unsigned short int width){ return 2*length + 2*width;}

3. BUG BUSTER: Što ne valja s funkcijom u slijedećem kodu? #include <iostream.h>void myFunc(unsigned short int x);int main(){unsigned short int x, y;y = myFunc(int);cout << "x: " << x << " y: " << y << "\n";}

void myFunc(unsigned short int x){return (4*x);}Funkcija je deklarirana kao void i ne može vraćati vrijednost.

Page 291: C++ PRIRUCNIK

2914. BUG BUSTER: Što ne valja s funkcijom u slijedećem kodu?

#include <iostream.h>int myFunc(unsigned short int x);int main(){unsigned short int x, y;y = myFunc(x);cout << "x: " << x << " y: " << y << "\n";}

int myFunc(unsigned short int x);{return (4*x);}Ova funkcija je u redu, ali postoji točka-zarez u zaglavlju definicije funkcije.

5. Napišite funkciju koja uzima dva unsigned short integer argumenta i vraća rezultat dijeljenja prvog sa drugim. Ako je drugi broj nula, ne radi dijeljenje nego vrati –1.

short int Divider(unsigned short int valOne, unsigned short int valTwo) { if (valTwo == 0) return -1; else return valOne / valTwo;}

6. Napišite program koji traži korisnika da unese dva broja i zove funkciju iz prethodne vježbe. Neka ispiše rješenje, odnosno poruku o grešci ako dobije –1.

#include <iostream.h>typedef unsigned short int USHORT;typedef unsigned long int ULONG;short int Divider(unsigned short int valone,unsigned short int valtwo);int main(){ USHORT one, two; short int answer; cout << "Enter two numbers.\n Number one: "; cin >> one; cout << "Number two: "; cin >> two; answer = Divider(one, two); if (answer > -1) cout << "Answer: " << answer; else cout << "Error, can't divide by zero!";return 0;}

Page 292: C++ PRIRUCNIK

292 C++ programer

7. Napišite program koji traži unos broja i potencije. Napišite rekurzivnu funkciju koja računa potenciju zadanog broja. Npr., ako je broj 2, a potencija 4, funkcija treba vratiti 16.

#include <iostream.h>typedef unsigned short USHORT;typedef unsigned long ULONG;ULONG GetPower(USHORT n, USHORT power);int main(){ USHORT number, power; ULONG answer; cout << "Enter a number: "; cin >> number; cout << "To what power? "; cin >> power; answer = GetPower(number,power); cout << number << " to the " << power << "th power is " <<answer << endl;return 0;}

ULONG GetPower(USHORT n, USHORT power){ if(power == 1) return n; else return (n * GetPower(n,power-1));}

Page 293: C++ PRIRUCNIK

293Lekcija 6

Kviz1. Što je to dot operator i zašto ga koristimo?

Dot operator je točka (.). Služi nam za pristupanje članovima klase.

2. Što rezervira memoriju—deklaracija ili definicija? Definicije varijabli rezerviraju memoriju. Deklaracije klasa ne zauzimaju memoriju.

3. Je li deklaracija klase njezino sučelje ili njezina implementacija? Deklaracija klase je njezino sučelje (interface); ona govori korisnicima klase kako komunicirati s njom. Sama implementacija klase je set funkcijskih članova, obično spremljenih u odvojenoj CPP datoteci.

4. Koja je razlika između privatnih i javnih podatkovnih članova? Javni podatkovni članovi su dostupni korisnicima klase. Privatni podatkovni članovi su dostupni samo funkcijskim članovima klase.

5. Mogu li funkcijski članovi biti privatni? Da. I funkcijski članovi i funkcijski podaci mogu biti privatni.

6. Mogu li podatkovni članovi biti javni? Iako podatkovni članovi mogu biti javni, dobra je programerska navika proglašavati ih privatnima i omogućiti im pristup preko javni pristupnih funkcijskih članova.

7. Ako deklarirate dva Cat objekta, mogu li oni imati različite vrijednosti unutar itsAge podatkovnog člana?

Da. Svaki objekt izveden iz klase ima vlastite podatkovne članove.

8. Da li deklaracija klase završava sa ";" ? A definicija metode ? Deklaracije završavaju sa točka-zarezom nakon zatvorene zagrade; definicija funkcije ne završava.

9. Kako bi zaglavlje za Cat funkciju imena Meow izgledalo, pod pretpostavkom da ne uzima nikakve parametre i vraća void?

void Cat::Meow()

10. Koju funkciju pozivamo za inicijalizaciju klase? Konstruktor.

Page 294: C++ PRIRUCNIK

294 C++ programer

Vježbe1. Napišite kod koji deklarira klasu zvanu Employee sa slijedećim podatkovnim članovima: age, yearsOfService, and Salary.

class Employee{ int Age; int YearsOfService; int Salary;};

2. Promjenite Employee klasu tako da joj podatkovni članovi budu privatni, a pružite javne pristupne metode za dobivanje i postavljanje svakog od podatkovnih članova.

class Employee{public: int GetAge() const; void SetAge(int age); int GetYearsOfService()const; void SetYearsOfService(int years); int GetSalary()const; void SetSalary(int salary);

private: int Age; int YearsOfService; int Salary;};

3. Napišite program koji na osnovi gornje klase stvara dva zaposlena, postavlja njihove godine, godine rada i plaću, te ispisuje njihove vrijednosti.

main(){ Employee John; Employee Sally; John.SetAge(30); John.SetYearsOfService(5); John.SetSalary(50000);

Sally.SetAge(32); Sally.SetYearsOfService(8); Sally.SetSalary(40000);

cout << "At AcmeSexist company, John and Sally have the same job.\n"; cout << "John is " << John.GetAge() << " years old and he has been with"; cout << "the firm for " << John.GetYearsOfService << " years.\n"; cout << "John earns $" << John.GetSalary << " dollars per year.\n\n"; cout << "Sally, on the other hand is " << Sally.GetAge() << "years old and has"; cout << "been with the company " << Sally.GetYearsOfService; cout << " years. Yet Sally only makes $" << Sally.GetSalary(); cout << " dollars per year! Something here is unfair.";

Page 295: C++ PRIRUCNIK

295}

4. Nastavljajući vježbu 3, pružite metodu za Employee koja izvještava koliko tisuća dolara zarađuje, zaokruženo na najbližu 1000.

float Employee:GetRoundedThousands()const{ return Salary / 1000;}

5. Promjenite Emplyee klasu tako da možete inicijalizirati godine, godine službe i plaću kad kreirate uposlenika.

class Employee{public:

Employee(int age, int yearsOfService, int salary); int GetAge()const; void SetAge(int age); int GetYearsOfService()const; void SetYearsOfService(int years); int GetSalary()const; void SetSalary(int salary);

private: int Age; int YearsOfService; int Salary;};

6. BUG BUSTERS: Šta ne valja sa slijedećom deklaracijom? class Square{public:int Side;}Deklaracija klase mora završiti sa točka-zarezom.

7. BUG BUSTERS: Zašto je slijedeća deklaracija klase beskorisna? class Cat{int GetAge()const;private:int itsAge;};Pristupna funkcija GetAge() je privatna. Zapamtite: Svi članovi klase su privatni ukoliko ne kažete drugačije.

Page 296: C++ PRIRUCNIK

296 C++ programer

8. BUG BUSTERS: Koja će tri buga u ovome kodu kompajler pronaći? class TV{public:void SetStation(int Station);int GetStation() const;private:int itsStation;};main(){TV myTV;myTV.itsStation = 9;TV.SetStation(10);TV myOtherTv(2);}Ne možete pristupati itsStation direktno. To je privatna varijabla. Ne možete pozvati SetStation() za klasu. Možete pozvati SetStation() samo za objekte. Ne možete inicijalizirati itsStation budući da nema sukladnog konstruktora.

Page 297: C++ PRIRUCNIK

297Lekcija 7

Kviz1. Kako inicijaliziramo više od jedne varijeble u for petlji?

Odvojimo inicijalizaciju sa zarezima, na primjer for (x = 0, y = 10; x < 100; x++, y++)

2. Zašto izbjegavamo goto naredbu? goto skače u bilo kome smjeru na proizvoljnu liniju koda. Time generiramo izvorni kod koji je teško razumljiv, a samim tim težak za održavanje.

3. Je li moguće napisati for petlju s tijelom koje se nikad ne izvršava? Da, ako je uvijet FALSE nakon inicijalizacije, tijelo for petlje se nikada neće izvršiti. Evo primjera: for (int x = 100; x < 100; x++)

4. Je li moguće umetniti for petlju unutar druge for petlje? Da. Bilo koja petlja može biti ugniježdena unutar druge petlje.

5. Je li moguće napraviti petlju koja nikad ne završava? Dajte primjer.

Da. Slijede primjeri i za for petlju i za while petlju: for(;;){ // This for loop never ends!}while(1){ // This while loop never ends!

}

6. Što se događa kad kreirate petlju koja nikad ne završava? Program se "sruši", te obično moramo resetirati računalo.

Vježbe1. Što je vrijednost od x naredbe kad petlja dođe do kraja?

for (int x = 0; x < 100; x++) 100

2. Napišite ugnježdenu for petlju koja ispisuje uzorak 0 na površini 10*10 znakova.

for (int i = 0; i< 10; i++){ for ( int j = 0; j< 10; j++) cout << "0"; cout << "\n";}

Page 298: C++ PRIRUCNIK

298 C++ programer

3. Napišite for naredbu koja će brojati od 100 do 200 za 2. for (int x = 100; x<=200; x+=2)

4. Napišite while petlju koja će brojati od 100 do 200 za 2.int x = 100;while (x <= 200) x+= 2;

5. Napišite do...while petlju koja bi brojala od 100 to 200 za 2.int x = 100;do{ x+=2;} while (x <= 200);

6. BUG BUSTERS: Što ne valja s ovim kodom? int counter = 0while (counter < 10){cout << "counter: " << counter;}brojač se nikada ne povećava i while petlja nikada ne završi

7. BUG BUSTERS: Što ne valja s ovim kodom? for (int counter = 0; counter < 10; counter++);cout << counter << " ";Točka-zarez nakon petlje uzrokuje to da petlja zapravo ne čini ništa.

8. BUG BUSTERS: Što ne valja s ovim kodom? int counter = 100;while (counter < 10){cout << "counter now: " << counter;counter--;}counter je inicijaliziran na 100, ali test uvjet je da bude manji od 10. Stoga tijelo petlje nikada neće biti izvršeno. Da je u liniji 1 counter postavljen na 5, petlja se nebi zaustavila dok ne dosegne najmanji mogući int. Budući da je int podrazumjevan kao označen, dobili bismo nepredviđen rezultat.

Page 299: C++ PRIRUCNIK

2999. BUG BUSTERS: Što ne valja s ovim kodom?

cout << "Enter a number between 0 and 5: ";cin >> theNumber;switch (theNumber){case 0:doZero(); case 1: // fall through case 2: // fall through case 3: // fall through case 4: // fall throughcase 5:doOneToFive();break;default:doDefault();break;}Case 0 vjerojatno treba break naredbu. Ako ne treba, trebalo bi dokumentirati to nekakvim komentarom.

Page 300: C++ PRIRUCNIK

300 C++ programer

Lekcija 8

Kviz1. Koji operator koristimo za određivanje adrese od varijable?

Address of operator (&) se koristi za određivanje adresa bilo koje varijable. 2. Koji operator koristimo za pronalaženje vrijednosti pohranjene na adresi na koju pokazuje pokazivač?

Operator dereferenciranja (*) se koristi za pristup vrijednostima na adresi u pokazivaču.

3. Što je pokazivač? Pokazivač je varijabla koja čuva adresu druge varijable.

4. Koja je razlika između adrese pohranjene u pokazivaču i vrijednosti na toj adresi?

Adresa pohranjena u pokazivaču je adresa druge varijable. Vrijednost pohranjena u pokazivaču je adresa druge varijable. Vrijednost pohranjena na toj adresi je bilo kakva vrijednost pohranjena u bilo kakvoj varijabli. Operator indirekcije (*) vraća vrijednost pohranjenu na toj adresi, koja je i sama pohranjena u pokazivaču.

5. Koja je razlika između operatora indirekcije i adress of operatora? Operator indirekcije vraća vrijednost na adresi pohranjenoj u pokazivaču. Address of operator (&) vraća memorijsku adresu varijable.

6. Koja je razlika između const int * ptrOne i int * const ptrTwo? const int * ptrOne deklarira ptrOne kao pokazivač na konstantni integer. Sam integer se ne može promijeniti koristeći taj pokazivač. int * const ptrTwo deklarira ptrTwo kao konstantan pokazivač na integer. Jednom inicijaliziran, tah se pokazivač ne može ponovno pridružiti nekoj drugoj varijabli.

Vježbe1. Što ove deklaracije rade?

a. int * pOne; deklarira pokazivač na integerb. int vTwo; deklarira integer varijabluc. int * pThree = &vTwo; deklarira pokazivač na integer i inicijalizira

ga s adresom druge varijable

2. Ako imate unsigned short varijablu imena yourAge, kako biste deklarirali pokazivač za manipuliranje sa yourAge?

unsigned short *pAge = &yourAge;

3. Pridružite vrijednost 50 varijabli yourAge koristeći pokazivač koji ste deklarirali u vježbi 2.

*pAge = 50;

Page 301: C++ PRIRUCNIK

3014. BUG BUSTERS: Što ne valja s slijedećim kodom?

#include <iostream.h>int main(){ int *pInt; *pInt = 9; cout << "The value at pInt: " << *pInt; return 0;}pInt je trebao biti inicijaliziran. Budući da nije inicijaliziran, i nije pridružen nekoj određenoj memorijskoj adresi, on pokazuje na slučajno mjesto u memoriji. Dodjelivši 9 na to slučajno mjesto je opasan bug.

6. BUG BUSTERS: Što ne valja sa slijedećim kodom? int main(){ int SomeVariable = 5; cout << "SomeVariable: " << SomeVariable << "\n"; int *pVar = & SomeVariable; pVar = 9; cout << "SomeVariable: " << *pVar << "\n";return 0;}Pretpostavljamo da je programer želio dodjeliti 9 kao vrijednost na koju pokazuje pVar. Na nesreću, 9 je pridružen kao vrijednost od pVar jer je izostavljen operator indirekcije (*).

Page 302: C++ PRIRUCNIK

302 C++ programer

Lekcija 9

Kviz1. U čemu je razlika između pokazivača i reference?

Referenca je alias, a pokazivač je varijabla koja sadrži drugu adresu. Reference ne mogu biti null i ne mogu se pridružiti.

2. Kada koristimo pokazivač, a ne referencu? Kada trebamo ponovni pridružiti na ono što pokazuje, ili kada pokazivač može biti null.

3. Što vraća new ako nema dovoljno memorije za pravljenje novog objekta?

Nul pokazivač (0).

4. Kakva je to konstantna referenca? To je skraćenica od govora "referenca na konsantan objekt."

Vježbe1. Napišite program koji deklarira int, referencu na int, i pokazivač na int. Koristeći pokazivač i referencu, promijenite vrijednost u int.

int main(){int varOne;int& rVar = varOne;int* pVar = &varOne;rVar = 5;*pVar = 7;return 0;}

2. Napišite program koji deklarira konstantan pokazivač na konstantan integer. Inicijalizirajte pokazivač na integer varijablu, varOne. Dodijelite 6 u varOne. Preko pokazivača, dodijelite 7 u varOne. Kreirajte novu varijablu, varTwo. Pridružite pokazivač novoj varijabli.

int main(){ int varOne; const int * const pVar = &varOne; *pVar = 7; int varTwo; pVar = &varTwo;return 0;}

Page 303: C++ PRIRUCNIK

3033. BUG BUSTERS: Što ne valja u ovom programu?

#include <iostream.h> class CAT { public: CAT(int age) { itsAge = age; } ~CAT(){} int GetAge() const { return itsAge;} private: int itsAge; };

CAT & MakeCat(int age); int main() { int age = 7; CAT Boots = MakeCat(age); cout << "Boots is " << Boots.GetAge() << " years old\n"; }

CAT & MakeCat(int age) { CAT * pCat = new CAT(age); return *pCat; }MakeCat vraća referencu na CAT kreiran u slobodnom spremniku. Nema načina da oslobodimo tu memoriju i to proizvodi curenje memorije.

Page 304: C++ PRIRUCNIK

304 C++ programer

4. Popravite gornji program. #include <iostream.h> class CAT { public: CAT(int age) { itsAge = age; } ~CAT(){} int GetAge() const { return itsAge;} private: int itsAge; };

CAT * MakeCat(int age); int main() { int age = 7; CAT * Boots = MakeCat(age); cout << "Boots is " << Boots->GetAge() << " years old\n"; delete Boots; return 0; } CAT * MakeCat(int age) { return new CAT(age); }

Page 305: C++ PRIRUCNIK

305

Lekcija 10

Kviz1. Kad preopterećujete funkcijske članove, kako se oni trebaju razlikovati?

Preopterećeni funkcijski članovi su funkcije u klasi koje dijele isto ime ali se razlikuju u broju i tipu njihovih parametara.

2. Koja je razlika između deklaracije i definicije? Definicija rezervira memoriju, a ne deklaracija. Gotovo sve deklaracije su definicije; glavne iznimke su deklaracije klasa, funkcijski prototipi i typedef naredbe.

3. Kada se poziva konstruktor kopije? Uvijek kada kreiramo privremenu kopiju objekta. To se događa svaki put kad objekt proslijeđujemo po vrijednosti.

4. Kada se poziva destruktor? Destruktor se poziva svaki put kad je objekt uništen, bilo da ode izvan dosega ili da pokrenete delete na pokazivaču koji pokazuje na njega.

5. Koja je razlika između konstruktora kopije i operatora pridruživanja (=)?

Operator pridruživanja djeluje na postojeći objekt, dok konstruktor kopije kreira novi.

6. Što je this pokazivač? This pokazivač je skriveni parameter svakog funkcijskog člana koji pokazuje na sam objekt.

7. Kako razlikujemo između preopterećenja prefiks i postfiks inkrement operatora?

Prefiks operator ne prima parametre. Postfiks operator prima jedan int parametar, koji se koristi kao signal kompajleru da je riječ o postfiks varijanti.

8. Možete li preopteretiti operator+ za short integer vrijednosti? Ne, ne možete preopterećivati operatore za ugrađene tipove podataka.

9. Da li je legalno U C++ preopteretiti operator++ tako da on dekrementira vrijednost u vašoj klasi?

Legalno je, ali je to loša ideja. Operatori bi trebali biti preopterećeni na takav način koji će biti razumljiv svakome čitaću vašega koda.

10. Kakvu povratnu vrijednost moraju imati operatori konverzije u svojim deklaracijama?

Nikakvu. Poput konstruktora i destruktora, oni nemaju povratne vrijednosti.

Page 306: C++ PRIRUCNIK

306 C++ programer

Vježbe1. Napišite SimpleCircle deklaraciju klase s jednim podatkovnim članom: itsRadius. Uključite podrazumijevani konstruktor, destruktor, te pristupne metode za polumjer.

class SimpleCircle{public: SimpleCircle(); ~SimpleCircle(); void SetRadius(int); int GetRadius();private: int itsRadius;};

2. Koristeći klasu kreiranu u vježbi 1, napišite implementaciju podrazumijevanog konstruktora, inicijalizirajući itsRadius s vrijednošću 5.

SimpleCircle::SimpleCircle():itsRadius(5){}

3. Koristeći istu klasu dodajte i drugi konstruktor koji prima vrijednost kao svoj parametar i pridružuje ju u itsRadius.

SimpleCircle::SimpleCircle(int radius):itsRadius(radius){}

4. Kreirajte prefiks i postfiks inkrement operator za vašu SimpleCircle klasu koji inkrementira itsRadius.

const SimpleCircle& SimpleCircle::operator++(){ ++(itsRadius); return *this;}

// Operator ++(int) postfix. // Fetch then incrementconst SimpleCircle SimpleCircle::operator++ (int){// declare local SimpleCircle and initialize to value of *this SimpleCircle temp(*this); ++(itsRadius); return temp; }

Page 307: C++ PRIRUCNIK

3075. Promjenite SimpleCircle da pohrani itsRadius u slobodnom spremniku, te popravite postojeće metode.

class SimpleCircle{public: SimpleCircle(); SimpleCircle(int); ~SimpleCircle(); void SetRadius(int); int GetRadius(); const SimpleCircle& operator++(); const SimpleCircle operator++(int);private: int *itsRadius;};

SimpleCircle::SimpleCircle(){itsRadius = new int(5);}

SimpleCircle::SimpleCircle(int radius){itsRadius = new int(radius);}

const SimpleCircle& SimpleCircle::operator++(){ ++(itsRadius); return *this;}

// Operator ++(int) postfix. // Fetch then incrementconst SimpleCircle SimpleCircle::operator++ (int){// declare local SimpleCircle and initialize to value of *this SimpleCircle temp(*this); ++(itsRadius); return temp; }

6. Napravite konstruktor kopije za SimpleCircle.SimpleCircle::SimpleCircle(const SimpleCircle & rhs){ int val = rhs.GetRadius(); itsRadius = new int(val);}

Page 308: C++ PRIRUCNIK

308 C++ programer

7. Napravite operator pridruživanja za SimpleCircle.SimpleCircle& SimpleCircle::operator=(const SimpleCircle & rhs){ if (this == &rhs) return *this; delete itsRadius; itsRadius = new int; *itsRadius = rhs.GetRadius(); return *this;}

8. Napišite program koji kreira dva SimpleCircle objeka. Koristeći podrazumijevani konstruktor na jednom i inicijaliziranjem drugog na vrijednost 9. Pozovite operator inkrementiranja za svaki i potom ispišite njihove vrijednosti. Konačno, pridružite drugi prvome i ispišite njihove vrijednosti.

#include <iostream.h>

class SimpleCircle{public: // constructors SimpleCircle(); SimpleCircle(int); SimpleCircle(const SimpleCircle &); ~SimpleCircle() {}// accessor functions void SetRadius(int); int GetRadius()const;// operators const SimpleCircle& operator++(); const SimpleCircle operator++(int); SimpleCircle& operator=(const SimpleCircle &);private: int *itsRadius;};

SimpleCircle::SimpleCircle(){itsRadius = new int(5);}

SimpleCircle::SimpleCircle(int radius){itsRadius = new int(radius);}

SimpleCircle::SimpleCircle(const SimpleCircle & rhs){ int val = rhs.GetRadius(); itsRadius = new int(val);}

Page 309: C++ PRIRUCNIK

309SimpleCircle& SimpleCircle::operator=(const SimpleCircle & rhs){ if (this == &rhs) return *this; *itsRadius = rhs.GetRadius(); return *this;}

const SimpleCircle& SimpleCircle::operator++(){ ++(itsRadius); return *this;}

// Operator ++(int) postfix. // Fetch then incrementconst SimpleCircle SimpleCircle::operator++ (int){// declare local SimpleCircle and initialize to value of *this SimpleCircle temp(*this); ++(itsRadius); return temp; }int SimpleCircle::GetRadius() const{ return *itsRadius;}int main(){ SimpleCircle CircleOne, CircleTwo(9); CircleOne++; ++CircleTwo; cout << "CircleOne: " << CircleOne.GetRadius() << endl; cout << "CircleTwo: " << CircleTwo.GetRadius() << endl; CircleOne = CircleTwo; cout << "CircleOne: " << CircleOne.GetRadius() << endl; cout << "CircleTwo: " << CircleTwo.GetRadius() << endl;return 0;}

Page 310: C++ PRIRUCNIK

310 C++ programer

9. BUG BUSTERS: Što ne valja s ovom implementacijom operatora pridruživanja?

SQUARE SQUARE ::operator=(const SQUARE & rhs){ itsSide = new int; *itsSide = rhs.GetSide(); return *this;}Morate provjeriti da li je rhs jednak this, ili će poziv iz a = a srušiti vaš program.

10. BUG BUSTERS: Što ne valja s implementacijom operatora zbrajanja?

VeryShort VeryShort::operator+ (const VeryShort& rhs){ itsVal += rhs.GetItsVal(); return *this;}Ovaj operator + mijenja vrijednost u jednom od operanda, ne stvarajući novi VeryShort objekt sa sum. Pravilan način bio bi: VeryShort VeryShort::operator+ (const VeryShort& rhs){ return VeryShort(itsVal + rhs.GetItsVal());}

Page 311: C++ PRIRUCNIK

311

Lekcija 11

Kviz1. Koji su prvi i zadnji elementi u SomeArray[25]?

SomeArray[0], SomeArray[24]

2. Kako deklarirati neko višedimenzionalno polje? Napišite grupu uglatih zagrada za svaku dimenziju. Na primjer, SomeArray[2][3][2] je trodimenzionalno polje. Prva dimenzija ima dva elementa, druga tri, i treća dva.

3. Inicijalizirajte članove polja iz drugog pitanja. SomeArray[2][3][2] = { { {1,2},{3,4},{5,6} } , { {7,8},{9,10},{11,12} } };

4. Koliko elemenata se nalazi u SomeArray[10][5][20]? 10x5x20=1,000

5. Koji je najveći broj elemenata koje možete dodati u vezane liste? Ne postoji fiksni maksimum. Ovisi o količini slobodne memorije.

6. Koji je zadnji znak stringa "Josip je dobar momak "? Nul znak.

Vježbe1. Deklarirajte dvodimenzionalno polje koje predstavlja križić-kružić poligon za igru.

int GameBoard[3][3];

2. Napišite kod koji inicijalizira sve elemente u kreiranom polju iz vj.1 na vrijednost 0.

int GameBoard[3][3] = { {0,0,0},{0,0,0},{0,0,0} }

3. Napišite deklaraciju Node klase koja drži unsigned short integere.class Node { public: Node (); Node (int); ~Node(); void SetNext(Node * node) { itsNext = node; } Node * GetNext() const { return itsNext; } int GetVal() const { return itsVal; } void Insert(Node *); void Display(); private: int itsVal; Node * itsNext; };

Page 312: C++ PRIRUCNIK

312 C++ programer

4. BUG BUSTERS: Što ne valja u slijedećem kodu? unsigned short SomeArray[5][4];for (int i = 0; i<4; i++) for (int j = 0; j<5; j++) SomeArray[i][j] = i+j;Polje ima 5 x 4 elementa, a kod inicijalizira 4 x 5.

5. BUG BUSTERS: Što ne valja u slijedećem kodu? unsigned short SomeArray[5][4];for (int i = 0; i<=5; i++) for (int j = 0; j<=4; j++) SomeArray[i][j] = 0;Trebalo je pisati i<5, a ne i<=5. Ista stvar vrijedi i za j budući da ne postoji element SomeArray[5][4].

Page 313: C++ PRIRUCNIK

313

Lekcija 12

Kviz1. Što je v-table?

V-table, ili tablica virtualnih funkcija jest uobičajen način kojim kompajleri upravljaju virtualnim funkcijama u C++u. Tablica sadrži listu adresa svih virtualnih funkcija i, ovisno o tipu na koji objekt pokazuje, poziva pravu funkciju.

2. Što je virtualni destruktor? Destruktor za bilo koju klasu može biti deklariran kao virtualan. Kada je pokazivač izbrisan, izvršni tip objekta će biti proučen i pravilno deriviran destuktor će se pokrenuti.

3. Kako pokazujemo deklaraciju virtualnog konstruktora? Ne postoje virutalni konstruktori.

4. Kako kreiramo virtualni konstruktor kopije? Kreiranjem virtualne metode u klasi, koja sama poziva konstruktor kopije.

5. Kako pozivamo funkciju bazne klase iz derivirane klase u kojoj ste zaobišli tu funkciju?

Base::FunctionName();

6. Kako pozivamo baznu funkciju iz izvedene klase u kojoj nismo zaobilazili funkciju?

FunctionName();

7. Ako bazna klasa deklarira funkciju virtualnom, a izvedena klasa ne koristi izraz virtual kad zaobilazi klasu, da li je ona još uvijek virtualna ako ju naslijedi treća generacija klase?

Da, virtualnost se naslijeđuje i ne može biti isključena.

8. Kada koristimo protected ključnu riječ? protected članovi su dostupni funkcijskim članovima deriviranih objekata.

Vježbe1. Pokaži deklaraciju virtualne funkcije koja uzima cjelobrojni parametar i vraća void.

virtual void SomeFunction(int);

2. Pokažite deklaraciju klase Square, koja se izvodi iz Rectangle, kojii se izvodi iz Shape.

class Square : public Rectangle{};

3. Ako, u vj.2, Shape ne uzima parametre, Rectangle uzima dva (length i width), ali Square uzima samo jedan (length), pokažite inicijalizaciju konstruktora za Square.

Square::Square(int length):

Page 314: C++ PRIRUCNIK

314 C++ programer

Rectangle(length, length){}

Page 315: C++ PRIRUCNIK

3154. Napišite virtualni konstruktor kopije za klasu Square (vj. 3).

class Square { public: // ... virtual Square * clone() const { return new Square(*this); } // ... };

5. BUG BUSTERS: Što ne valja? void SomeFunction (Shape);Shape * pRect = new Rectangle;SomeFunction(*pRect);Možda ništa. SomeFunction očekuje Shape objekt.Vi ste mu poslali Rectangle izveden iz Shape. Dok god ne trebate niti jedan dio specfičan za Rectangle, ovo će biti u redu. Ukoliko trebate Rectangle dijelove, morate promijeniti SomeFunction da uzima pokazivač ili referencu na Shape.

6. BUG BUSTERS: Što ne valja? class Shape(){public: Shape(); virtual ~Shape(); virtual Shape(const Shape&);};Nije moguće deklarirati virtualan konstruktor kopije.

Page 316: C++ PRIRUCNIK

316 C++ programer

Lekcija 13

Kviz1. Što je v-ptr?

V-ptr, ili virtual-function pointer – pokazivač na virtualnu funkciju, jest implementacijski detalj virtualnih funkcija. Svaki objekt u klasi sa virtualnim funkcijama ima v-ptr, koji pokazuje na tablicu virtualnih funkcija za tu klasu.

2. Ako zaobljeni pravokutnik ima ravne linije i oble rubove, a vaša RoundRect klasa naslijeđuje i iz Rectangle i Circle, a oni su naslijednici iz Shape, koliko Shapes se kreira kad kreirate RoundRect?

Ako niti jedna klasa ne naslijeđuje koristeći ključnu riječ virtual, dva Shape objekta se kreiraju: Jedan za Rectangle i jedan za Shape. Ako se ključna riječ virtual koristi za obje klase, samo jedan dijeljeni Shape se kreira.

3. Ako Horse i Bird naslijeđuju iz Animal koristeći javno virtualno naslijeđivanje, da li njihovi konstruktori inicijaliziraju Animal konstruktor? Ako Pegasus naslijeđuje i iz Horse i Bird, kako on inicijalizira Animal konstruktor?

I Horse i Bird inicijaliziraju svoju baznu klasu, Animal, u svojim konstruktorima. Pegasus čini isto, a kad se Pegasus kreira, Horse i Bird inicijalizacije od Animal se ignoriraju.

4. Deklarirajte klasu vehicle, i proglasite ju ADT-om. class Vehicle{ virtual void Move() = 0;}

5. Ako je bazna klasa ADT, i ima tri čiste virtualne funkcije, koliko od njih mora biti zaobiđeno u izvedenim klasama?

Niti jedna ne mora biti zaobiđena ukoliko ne želite načiniti klasu ne-abstraktnom. U tom slučaju sve tri moraju biti zaobiđene.

Page 317: C++ PRIRUCNIK

317Vježbe1. Prikažite deklaraciju klase JetPlane, koja naslijeđuje od Rocket i Airplane.

class JetPlane : public Rocket, public Airplane

2. Pokažite deklaraciju od 747, koji naslijeđuje iz JetPlane klase prethodne vježbe.

class 747 : public JetPlane

3. Napišite program koji izvodi Car i Bus iz klase Vehicle. Proglasite Vehicle ADT-om s dvije čiste virtualne funkcije. Napravite da Car i Bus nisu ADT-i.

class Vehicle{ virtual void Move() = 0; virtual void Haul() = 0;};

class Car : public Vehicle{ virtual void Move(); virtual void Haul();};

class Bus : public Vehicle{ virtual void Move(); virtual void Haul();};

Page 318: C++ PRIRUCNIK

318 C++ programer

Lekcija 14

Kviz1. Mogu li statički podatkovni članovi biti privatni?

Da. To su podatkovni člaovi, i pristum njima može biti kontroliran. U slučaju da su privatni, može im se pristupati samo korištenjem funkcijskih članova, ili, što je češće, statičkih funkcijskih članova.

2. Pokažite deklaraciju statičkog podatkovnog člana. static int itsStatic;

3. Pokažite deklaraciju statičkog funkcijskog pokazivača. static int SomeFunction();

4. Pokažite deklariciju za pokazivač na funkciju koji vraća long i prima integer parametar.

long (* function)(int);

5. Promijenite pokazivač iz četvrtog pitanja da pokazuje na funkcijski član klase Car.

long ( Car::*function)(int);

6. Pokažite deklaraciju za polje 10 pokazivača difiniranih u pitanju 5. (long ( Car::*function)(int) theArray [10];

Vježbe1. Napišite kratak program deklariranjem klase s jednim podatkovnim članom i jednim statičkim podatkovnim članom. Neka konstruktor inicijalizira podatkovni član i inkrementira statični podatkovni član. Neka destruktor dekrementira podatkovni član.

class myClass { public: myClass(); ~myClass(); private: int itsMember; static int itsStatic; }; myClass::myClass(): itsMember(1) { itsStatic++; } myClass::~myClass() { itsStatic--; } int myClass::itsStatic = 0; int main() {}

Page 319: C++ PRIRUCNIK

3192. Koristeći program sa vježbe 1, napišite kratki program koji kreira tri objekta i potom prikazuje njihove podatkovne članove i statički podatkovni član. Potom uništite svaki objekt i pokažite kako to djeluje na statički podatkovni član. #include <iostream.h> class myClass { public: myClass(); ~myClass(); void ShowMember(); void ShowStatic(); private: int itsMember; static int itsStatic; }; myClass::myClass(): itsMember(1) { itsStatic++; } myClass::~myClass() { itsStatic--; cout << "In destructor. ItsStatic: " << itsStatic << endl; } void myClass::ShowMember() { cout << "itsMember: " << itsMember << endl; } void myClass::ShowStatic() { cout << "itsStatic: " << itsStatic << endl; } int myClass::itsStatic = 0; int main() { myClass obj1; obj1.ShowMember(); obj1.ShowStatic(); myClass obj2; obj2.ShowMember(); obj2.ShowStatic(); myClss obj3; obj3.ShowMember(); obj3.ShowStatic(); return 0; }

Page 320: C++ PRIRUCNIK

320 C++ programer

3. Promijenite program sa vježbe 2 da koristi statički funkcijski član kako bi pristupio statičkom podatkovnom članu. Proglasite statički podatkovni član privatnim.

#include <iostream.h> class myClass { public: myClass(); ~myClass(); void ShowMember(); static int GetStatic(); private: int itsMember; static int itsStatic; }; myClass::myClass(): itsMember(1) { itsStatic++; } myClass::~myClass() { itsStatic--; cout << "In destructor. ItsStatic: " << itsStatic << endl; } void myClass::ShowMember() { cout << "itsMember: " << itsMember << endl; } int myClass::itsStatic = 0; void myClass::GetStatic() { return itsStatic; } int main() { myClass obj1; obj1.ShowMember(); cout << "Static: " << myClass::GetStatic() << endl; myClass obj2; obj2.ShowMember(); cout << "Static: " << myClass::GetStatic() << endl; myClass obj3; obj3.ShowMember(); cout << "Static: " << myClass::GetStatic() << endl; return 0; }

Page 321: C++ PRIRUCNIK

3214. Napišite pokazivač na funkcijski član za pristup ne-statičkim podatkovnim članovima u programu sa vježbe 3, i koristite taj pokazivač za ispis vrijednosti tog podatka.

#include <iostream.h> class myClass { public: myClass(); ~myClass(); void ShowMember(); static int GetStatic(); private: int itsMember; static int itsStatic; }; myClass::myClass(): itsMember(1) { itsStatic++; } myClass::~myClass() { itsStatic--; cout << "In destructor. ItsStatic: " << itsStatic << endl; } void myClass::ShowMember() { cout << "itsMember: " << itsMember << endl; } int myClass::itsStatic = 0; int myClass::GetStatic() { return itsStatic; } int main() { void (myClass::*PMF) (); PMF=myClass::ShowMember; myClass obj1; (obj1.*PMF)(); cout << "Static: " << myClass::GetStatic() << endl; myClass obj2; (obj2.*PMF)(); cout << "Static: " << myClass::GetStatic() << endl; myClass obj3; (obj3.*PMF)(); cout << "Static: " << myClass::GetStatic() << endl; return 0; }

Page 322: C++ PRIRUCNIK

322 C++ programer

5. Dodajte još dva podatkovna člana u klasu iz prethodnog pitanja. Dodajte pristupne funkcije za dobijanje vrijednosti tih članova, i dajte svim funkcijskim članovima iste povratne tipove i potpise. Koristeći pokazivač na funkcijske članove pristupite tim funkcijama.

#include <iostream.h> class myClass { public: myClass(); ~myClass(); void ShowMember(); void ShowSecond(); void ShowThird(); static int GetStatic(); private: int itsMember; int itsSecond; int itsThird; static int itsStatic; };

myClass::myClass(): itsMember(1), itsSecond(2), itsThird(3) { itsStatic++; }

myClass::~myClass() { itsStatic--; cout << "In destructor. ItsStatic: " << itsStatic << endl; }

void myClass::ShowMember() { cout << "itsMember: " << itsMember << endl; }

void myClass::ShowSecond() { cout << "itsSecond: " << itsSecond << endl; }

void myClass::ShowThird() { cout << "itsThird: " << itsThird << endl; } int myClass::itsStatic = 0;

int myClass::GetStatic() {

Page 323: C++ PRIRUCNIK

323 return itsStatic; }

int main() { void (myClass::*PMF) ();

myClass obj1; PMF=myClass::ShowMember; (obj1.*PMF)(); PMF=myClass::ShowSecond; (obj1.*PMF)(); PMF=myClass::ShowThird; (obj1.*PMF)(); cout << "Static: " << myClass::GetStatic() << endl;

myClass obj2; PMF=myClass::ShowMember; (obj2.*PMF)(); PMF=myClass::ShowSecond; (obj2.*PMF)(); PMF=myClass::ShowThird; (obj2.*PMF)(); cout << "Static: " << myClass::GetStatic() << endl;

myClass obj3; PMF=myClass::ShowMember; (obj3.*PMF)(); PMF=myClass::ShowSecond; (obj3.*PMF)(); PMF=myClass::ShowThird; (obj3.*PMF)(); cout << "Static: " << myClass::GetStatic() << endl; return 0; }

Page 324: C++ PRIRUCNIK

324 C++ programer

Lekcija 15

Kviz1. Što je operator umetanja i što on radi?

Operator umetanja (<<) je operator-član ostream objekta i koristi se za pisanje na izlazni uređaj.

2. Što je operator ekstrakcije i što on radi? Operator ekstrakcije (>>) je član-operator istream objekta i koristi se za upisivanje u programske varijable.

3. Koja su tri oblika cin.get(), i u čemu je njihova razlika? Prvi oblik get() je bez parametara. On vraća vrijednost nađenog znaka, a kada dođe do kraja datoteke vratiti će EOF (end of file). Drugi oblik cin.get() uzima znakovnu referencu kao svoj parametar; taj znak se puni sa slijedećim znakom input toka. Povratna vrijednost je iostream objekt.Treći oblik cin.get() prima polje, maksimalan broj znakova i terminirajući znak. Taj oblik get() ispunjava polje sa maksimalnim brojem znakova –1 ukoliko prije ne dođe do terminirajućeg znaka. U tom slučaju trenutno upisuje null znak i ostavlja terminirajući znak u bufferu.

4. U čemu je razlika između cin.read() i cin.getline()? cin.read() se koristi za čitanje binarnih struktura podataka. getline() se koristi za čitanje iz istream spremnika.

5. Koja je podrazumijevana širina za izlaz long integera korištenjem operatora umetanja?

Onoliko koliko je potrebno za ispisivanje cijelog broja.

6. Koji je povratni tip operatora umetanja? Referenca na istream objekt.

7. Koje parametre konstruktor na ofstream objekt uzima? Ime datoteke koju treba otvoriti.

8. Što radi ios::ate argument ? ios::ate nas stavlja na kraj datoteke, ali omogućuje pisanje u datoteci na bilo kojem mjestu.

Page 325: C++ PRIRUCNIK

325Vježbe1. Napišite program koji piše u četri standardna iostream objekta: cin, cout, cerr, i clog.

#include <iostream.h> int main() { int x; cout << "Enter a number: "; cin >> x; cout << "You entered: " << x << endl; cerr << "Uh oh, this to cerr!" << endl; clog << "Uh oh, this to clog!" << endl; return 0; }

2. Napišite program za upis cijelog imena i zatim ga ispišite na ekran.

#include <iostream.h> int main() { char name[80]; cout << "Enter your full name: "; cin.getline(name,80); cout << "\nYou entered: " << name << endl; return 0; }

3. Napišite ponovo listing 15.9, ali da ne koristi putback() ili ignore(). // Listing #include <iostream.h> int main() { char ch; cout << "enter a phrase: "; while ( cin.get(ch) ) { switch (ch) { case `!': cout << `$'; break; case `#': break; default: cout << ch; break; } } return 0; }

Page 326: C++ PRIRUCNIK

326 C++ programer

4. Napišite program koji prima ime datoteke kao parametar i otvara datoteku za čitanje. Pročitajte svaki znak iz datoteke i prikažite samo slova i znakove na ekran, ignorrirajući sve specijalne znakove. Zatvorite datoteku i izađite.

#include <fstream.h> enum BOOL { FALSE, TRUE }; int main(int argc, char**argv) // returns 1 on error { if (argc != 2) { cout << "Usage: argv[0] <infile>\n"; return(1); } // open the input stream ifstream fin (argv[1],ios::binary); if (!fin) { cout << "Unable to open " << argv[1] << " for reading.\n"; return(1); } char ch; while ( fin.get(ch)) if ((ch > 32 && ch < 127) || ch == `\n' || ch == `\t') cout << ch; fin.close(); }

5. Napišite program koji prikazuje svoje komandno linijske argumente u obrnutom redoslijedu i ne prikazuje ime programa.

#include <fstream.h> int main(int argc, char**argv) // returns 1 on error { for (int ctr = argc; ctr ; ctr--) cout << argv[ctr] << " "; }

Page 327: C++ PRIRUCNIK

327

Lekcija 16

Kviz1. U čemu je razlika između parametra u predlošku i funkciji?

Parametar za predložak kreira instancu predloška za svaki tip. Ako kreirate šest instanci predloška, šest različitih klasa ili funkcija se kreira. Parametri u funkciji mijenjaju ponašanje podataka u funkciji, ali se kreira samo jedna funkcija.

2. Koja je razlika između prijateljskog predloška specifičnog tipa i općeg prijateljskog predloška?

Opći prijateljski predložak funkcije kreira jednu funkciju za svaki tip parametrizirane klase; za svaku instancu parametrizirane klase.

3. Može li se omogućiti specijalno ponašanje za jednu instancu predloška, ali ne i za druge?

Da, kreirajte specijaliziranu funkciju za određenu instancu. Uz kreiranje Array<t>::SomeFunction(), također kreirajte i Array<int>::SomeFunction() kako bi promijenili ponašanje za polja cijelih brojeva.

Vježbe1. Kreirajte predložak baziran na List klasi:

class List{private:

public: List():head(0),tail(0),theCount(0) {} virtual ~List(); void insert( int value ); void append( int value ); int is_present( int value ) const; int is_empty() const { return head == 0; } int count() const { return theCount; }private: class ListCell { public: ListCell(int value, ListCell *cell =):val(value),next(cell){} int val; ListCell *next; }; ListCell *head; ListCell *tail; int theCount;};

Page 328: C++ PRIRUCNIK

328 C++ programer

Evo jednog načina za implementaciju toga predloška:template <class Type>class List{

public: List():head(0),tail(0),theCount(0) { } virtual ~List();

void insert( Type value ); void append( Type value ); int is_present( Type value ) const; int is_empty() const { return head == 0; } int count() const { return theCount; }

private: class ListCell { public: ListCell(Type value, ListCell *cell = 0):val(value),next(cell){} Type val; ListCell *next; };

ListCell *head; ListCell *tail; int theCount;};

2. Napišite implementaciju List klase (bez predloška). void List::insert(int value){ ListCell *pt = new ListCell( value, head ); assert (pt != 0);

// this line added to handle tail if ( head == 0 ) tail = pt;

head = pt; theCount++;}void List::append( int value ){ ListCell *pt = new ListCell( value ); if ( head == 0 ) head = pt; else tail->next = pt; tail = pt; theCount++;}

int List::is_present( int value ) const

Page 329: C++ PRIRUCNIK

329{ if ( head == 0 ) return 0; if ( head->val == value || tail->val == value ) return 1; ListCell *pt = head->next; for (; pt != tail; pt = pt->next) if ( pt->val == value ) return 1; return 0;}

3. Napišite verziju kao predložak. template <class Type>List<Type>::~List(){ ListCell *pt = head; while ( pt ) { ListCell *tmp = pt; pt = pt->next; delete tmp; } head = tail = 0;}template <class Type>void List<Type>::insert(Type value){ ListCell *pt = new ListCell( value, head ); assert (pt != 0); // this line added to handle tail if ( head == 0 ) tail = pt; head = pt; theCount++;}

template <class Type>void List<Type>::append( Type value ){ ListCell *pt = new ListCell( value ); if ( head == 0 ) head = pt; else tail->next = pt; tail = pt; theCount++;}

template <class Type>

Page 330: C++ PRIRUCNIK

330 C++ programer

int List<Type>::is_present( Type value ) const{ if ( head == 0 ) return 0; if ( head->val == value || tail->val == value ) return 1; ListCell *pt = head->next; for (; pt != tail; pt = pt->next) if ( pt->val == value ) return 1; return 0;}

4. Deklarirajte 3 list objekta: lista Strings, lista Cats, i lista ints.List<String> string_list;List<Cat> Cat_List;List<int> int_List;

5. BUG BUSTERS: Što ne valja sa slijedećim kodom? List<Cat> Cat_List;Cat Felix;CatList.append( Felix );cout << "Felix is " << ( Cat_List.is_present( Felix ) ) ? "" : "not " << "present\n";

Cat nema definiran oerator == ; sve operacije koje uspoređuju vrijednosti u list čelijama, kao što je is_present, će rezultirati pogreškama prilikom prevođenja.

Page 331: C++ PRIRUCNIK

331

Lekcija 17

Kviz1. Što je iznimka?

Iznimka je objekt kreiran kao rezultat pokretanja ključne riječi throw. Koristi se kao signal iznimne situacije, te se proslijeđuje kako bi pozvao prvu catch naredbu koja barata njenim tipom.

2. Što je try blok? Try blok je niz naredbi koje mogu generirati iznimku.

3. Što je catch naredba? catch naredba ima potpis tipa iznimke kojom barata. Ona slijedi try blok i djeluje kao primalac iznimke nastale u try bloku.

Vježbe1. Napravite try blok, catch naredbu i jednostavnu iznimku.

#include <iostream.h>class OutOfMemory {};int main(){

try { int *myInt = new int; if (myInt == 0) throw OutOfMemory(); } catch (OutOfMemory) { cout << "Unable to allocate memory!\n"; }return 0; }

Page 332: C++ PRIRUCNIK

332 C++ programer

2. BUG BUSTERS: Što ne valja u ovom kodu? class xOutOfMemory{public: xOutOfMemory( const String& message ) : itsMsg( message ){} ~xOutOfMemory(){} virtual const String& Message(){ return itsMsg};private: String itsMsg; }

main(){ try { char *var = new char; if ( var == 0 ) throw xOutOfMemory(); } catch( xOutOfMemory& theException ) { cout << theException.Message() << "\n"; }}U procesu baratanja "out of memory" stanjem, string objekt je kreiran iz konstruktora xOutOfMemory. Ova iznimka može nastati samo kad je program ostao bez dostupne memorije, pa alokacija mora propasti. Moguće je da će pokušaj kreiranja ovog stringa izazvati mrtvu petlju dok se program ne sruši. Ako je taj string stvarno potreban, možete rezervirati memoriju u static spremniku prije početka programa, i onda ga po potrebi koristiti kada dođe do iznimke.