You are on page 1of 309

Skripta informatičke škole Proanima

Lekcija 1 Uvod u C++

Danas ćemo naučiti:


• Zašto je C++ postao industrijski standard za razvoj softvera
• 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.

Programi
Obratite 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 programiranje


Do 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.
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će
3. 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, svejdno, počeli otkrivati nedostatci 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-orjentirano programiranje pokušava odgovoriti na te potrebe, pružajući tehnike za upravljanje
kompleksnim problemima, podržavanjem ponovnog korištenja komponenti i grupranjem podataka i zadatcima
koji manipuliraju tim podacima.
Srž objektno orjentiranog programiranja je u tretiranju podataka i procedura koje djeluju na podatke kao
jednoga «objekta», samodostatne jedinke koja ima vlastiti identitet i određene karakteristike.
Razvojni ciklus
Da 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
najvjorvatnije 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: }
Nakon što smo unesli program i kreirali izvršnu datoteku, pokušajmo ju pokrenuti. Na ekranu bi se trebalo
pojaviti:

Hello World!

Kviz
1. 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žbe
1. 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 sradi 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?


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 upetrebljena 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 funkicje
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. Ssve 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.
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 cout
2:
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: 5
The manipulator endl writes a new line to the screen.
Here is a very big number: 70000
Here is the sum of 8 and 5: 13
Here's a fraction: 0.625
And a very very big number: 4.9e+07
Don't forget to replace Jesse Liberty with your name...
Jesse Liberty is a C++ programmer!

Komentari
Kad pišete program, uvijek je 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 komentara
C++ 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.
Slash-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 comment
6: and it extends until the closing
7: star-slash comment mark */
8: cout << "Hello World!\n";
9: // this comment ends at the end of the line
10: cout << "That comment ended!\n";
11:
12: // double slash comments can be alone on a line
13: /* as can slash-star comments */
14: return 0;
15: }
Hello World!
That comment ended!

Komentari na vrhu datoteke


Dobra 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 World
File: Hello.cpp
Function: Main (complete program listing in this file)
Description: Prints the words "Hello world" to the screen
Author: 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"
************************************************************/

Funkcije
Iako je i main() funkcija, zbog svoje (ne)običnosti nam to i nije dobar primjer. Tipične fumkcije su 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 Function
4: // prints out a useful message
5: void DemonstrationFunction()
6: {
7: cout << "In Demonstration Function\n";
8: }
9:
10: // function main - prints out a message, then
11: // calls DemonstrationFunction, then prints out
12: // 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 main
In Demonstration Function
Back in main

Upotreba funkcija
Funkcije ili vraćaju vrijenost 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.
Tijelo 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 5
Calling Add()
In Add(), received 3 and 5
Back in main().
c was set to 8
Exiting...

Kviz
1. 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žbe
1. 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()
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.
myVariabl
Varijabla e

RAM

Adresa
100 101 102 103 104 105
Slika3.1. Shematski prikaz memorije.

Rezerviranje memorije
Kad 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 vezane uz te termine. Ne bi bilo loše ni podsjetiti
se matematike vezane uz dekadske, binarne i heksadecimalne brojevne sustave.

Veličina varijabli
Na 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 tebala 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 cjeli brojevi dolaze u dva oblika: signed i unsigned. Ideja iza koja se krije iza toga je da ponekad
trebate negativne brojeve, a ponekad ne trebate. Cjeli 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 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 varijabli


Još 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
mjesa 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 2 0 do 65,535
int bytea
short int 2 -32,768 do 32,767
bytea
unsigned long int 4 0 do 4,294,967,295
bytea
long int 4 -2,147,483,648 do
bytea 2,147,483,647
int (16 bit) 2 -32,768 do 32,767
bytea
int (32 bit) 4 -2,147,483,648 do
bytea 2,147,483,647
unsigned int (16 2 0 do 65,535
bit) bytea
unsigned int (32 4 0 do 4,294,967,295
bit) bytea
char 1 byte 256 znakovnih vrijednosti
float 4 1.2e-38 do 3.4e38
bytea
double 8 2.2e-308 do 1.8e308
bytea

Definiranje varijable
Mož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. Legakna 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 tjeka 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 tjedna 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 slova


C++ 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 opsi
varijable sadržan u dvije riječi, popularni načini imenovanja su moj_auto ili mojAuto.
Ključne riječi
Neke 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 odjednom


Moguće je kreirati više varijabli istoga tipa u jednoj naredbi, navodeći tip, te zatim pobrojati sve varijable tog
tipa odvojene zarezom. Npr. :
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 nemožete
mješati tipove u jednoj definicijskoj naredbi.

Dodjeljivanje vrijednosti vašim varijablama


Vrijednost 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 variables
2: #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 result
10: // of multiplying Width by Length
11: 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:5
Length: 10
Area: 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).
U stvarnosti, zapravo kreiramo sinonim, važno je to razlikovati od stvaranja novog tipa (što ćemo raditi u lekciji
6). typdef 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 keyword
3: #include <iostream.h>
4:
5: typedef unsigned short int USHORT; //typedef defined
6:
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:5
Length: 10
Area: 50

Kada koristiti short a kada long


Veliki 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;
11: return 0;
12: }
Output: small number:65535
small number:0
small number:1

Signed integer je drugačiji oblik cjelobrojni oblik u kojoj 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:32767
small number:-32768
small number:-32767

Znakovi
Znakovne 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

Specijalni znakovi
\n new line
\t tab
\b backspace
\" double quote
\' single quote
\? question mark
\\ backslash
Konstante
Kao i varijable, i konstante su mjesta za pohranu podataka. Za razliku od varijabli, konstante se ne mogu
mijenjati, odn., ne možemo im tjekom 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 konstante
Enumerairane 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:
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: else
16: cout << "\nOkay, I'll put in the vacation day.\n";
17: return 0;
18: }
Output: What day would you like off (0-6)? 1
Okay, I'll put in the vacation day.
What day would you like off (0-6)? 0
You're already off on weekends!

Kviz
1. 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. Age
b. !ex
c. R79J
d. TotalIncome
e. __Invalid

Vježbe
1. 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.
Lekcija 4 Izrazi i naredbe
Naredbe
U 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 praznina
Praznine (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 poptuno 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 naredbi
Na 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.


Izrazi
U 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.2
PI // float const koja vraća vrijednost 3.14
SecondsPerMinute // int const koja vraća 60

Složeni izrazi
x = a + b;
ne samo da zbraja a i b nego i dodjeljuje rezultat u x, ali i vraća vrijednost toga ipridruž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: 35
a: 9 b: 7 x: 16 y: 16

Operatori
Operator 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.
Operator pridruživanja
Operator 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 operatori
Postoji 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 and
2: // integer overflow
3: #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: 50
Now difference is: 4294967246

Cjelobrojno dijeljenje i moduli


Cjelobrojno 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 destoj akciji. Savaki broj
čija vrijednost je 0 kad vadimo modul 10 i tog broja je višekratnik broja 10. Tako je 1 % 10 jenako 1, 2 % 10 je
2 itd., sve do 10 % 10, čiji 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 temp
myAge = 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 vrijenost od
myAge i pridruži rezultat u myAge.
Još jednostavnije za pisati, ali malo teže za čitati je
myAge += 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 vrijdenost 4 na početku, imao bbi 6 nakon ove naredbe.
Također postoje i operatori obnavljajućeg oduzimanja (-=), dijeljenja (/=), množenja (*=), i modula (%=).

Inkrement i dekrement
Najč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 postfiks
I 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 pridurž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
int 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šemo
int 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 of
2: // prefix and postfix increment and
3: // decrement operators
4: #include <iostream.h>
5: int main()
6: {
7: int myAge = 39; // initialize two integers
8: int yourAge = 39;
9: cout << "I am: " << myAge << " years old.\n";
10: cout << "You are: " << yourAge << " years old\n";
11: myAge++; // postfix increment
12: ++yourAge; // prefix increment
13: 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 old
You are 39 years old
One year passes
I am 40 years old
You are 40 years old
Another year passes
I am 40 years old
You are 41 years old
Let's print it again
I am 41 years old
You are 41 years old
Redosljed izvođenja operatora u složenim naredbama
x = 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 lijevana 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 zagrada
Za 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.

Priroda Istine
U C++, nula se smatralaž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 operatori
Relacijski 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 Operato Primjer Vrijednos
r t
Equals == 100 == false
50; true
50 == 50;
Not Equals != 100 != true
50; false
50 != 50;
Greater Than > 100 > 50; true
50 > 50; false
Greater Than >= 100 >= true
or Equals 50; true
50 >= 50;
Less Than < 100 < 50; false
50 < 50; false
Less Than <= 100 <= false
or Equals 50; true
50 <= 50;

if naredba
Obič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)
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 statement
2: // used with relational operators
3: #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: ";
28: 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: 10
Enter the score for the Yankees: 10
A tie? Naah, can't be
Give me the real score for the Yanks: 8
Knew 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;
else
naredba;

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


1: // Listing 4.5 - demonstrates if statement
2: // with else clause
3: #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: else
14: cout << "\nOops. The second is bigger!";
15:
16: return 0;
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 nested
2: // if statement
3: #include <iostream.h>
4: int main()
5: {
6: // Ask for two numbers
7: // Assign the numbers to bigNumber and littleNumber
8: // If bigNumber is bigger than littleNumber,
9: // see if they are evenly divisible
10: // If they are, see if they are the same number
11:
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: else
26: cout << "They are evenly divisible!\n";
27: }
28: else
29: cout << "They are not evenly divisible!\n";
30: }
31: else
32: cout << "Hey! The second one is larger!\n";
33: return 0;
34: }
Output: Enter two numbers.
First: 10
Second: 2
They are evenly divisible!
Upotreba zagrada unutar ugnježdenih if-ova
if (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 braces
2: // are important in nested if statements
3: #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: 20
Less than 10, Thanks!

Listing 4.8. Demonstracija pravilne upotrebe vitičastih zagrada unutar if naredbe


1: // Listing 4.8 - demonstrates proper use of braces
2: // in nested if statements
3: #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

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 ILI
Logič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 drug. 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 NOT
Logič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 prioriteti
Relacijski 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 dobiti 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) )

č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 operator
Kondicionalni operator (?:) je jedini operator u C++ koji koristi tri izraza:
(izraz1) ? (izraz2) : (izraz3)
Ova linija se čita kao "Ako je izraz1 istina, vrati vrijednost u izraz2, 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 operator
2: //
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: else
17: 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;
27: }
Output: Enter two numbers.
First: 5
Second: 8
z: 8
z: 8

Kviz
1. Š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. 0
b. 1
c. -1
d. x = 0
e. x == 0 // pretpostavimo da x ima vrijednost 0

Vježbe
1. Napišite jednu naredbu koja ispituje dvije 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: else
21: 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?
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?


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, odnosno u liniji koja
slijedi nakon poziva funkcije.
Dobro dizajnirane funkcije izvršavaju specifične i lako razumljive zadaće. Komplicirani zadaci bi trebali 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, odnosno bilo koje druge funkcije ako
prethodno nije bila deklarirana. Deklaracija funkcije se često naziva i njenim prototipom.

Deklariranje funkcije
Postoje 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.
• 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 prototipovi
Mnoge 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 eksplicitno 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 prototypes
2:
3: typedef unsigned short USHORT;
4: #include <iostream.h>
5: USHORT FindArea(USHORT length, USHORT width); //function prototype
6:
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 ";
21: 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? 100
How long is your yard? 200
Your yard is 20000 square feet

Primjeri funkcijskih prototipa


long FindArea(long length, long width);
void PrintMessage(int messageNumber);
int GetChoice();
BadFunction();
Primjeri definicija funkcije
long 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";
}
Lokalne varijable
Ne 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: 212
Here's the temperature in Celsius: 100
Please enter the temperature in Fahrenheit: 32
Here's the temperature in Celsius: 0
Please enter the temperature in Fahrenheit: 85
Here's the temperature in Celsius: 29.4444
Evo 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: ";
10: 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 varijable
Varijable 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(); // prototype
3:
4: int x = 5, y = 7; // global variables
5: 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: 5
y from main: 7

x from myFunction: 5
y from myFunction: 10

Back from myFunction!


x from main: 5
y from main: 7
Globalne varijable: Upozorenje
U 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 variables
2: // scoped within a block
3:
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;
34: }
Output: In main x is: 5
In myFunc, local x: 8
In block in myFunc, x is: 8
Very local x: 9
Out of block, in myFunc, x: 8
Back 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 funkcijama
Iako 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 = 8
unsigned long squared = square(cubed); // squared = 64
unsigned long tripled = triple(squared); // tripled = 196
unsigned long Answer = double(tripled); // Answer = 392

Parameteri su lokalne varijable


Argumenti 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 value
2:
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";
28:
29: }
Output: Main. Before swap, x: 5 y: 10
Swap. Before swap, x: 5 y: 10
Swap. After swap, x: 10 y: 5
Main. After swap, x: 5 y: 10

Povratne vrijednosti
Funkcija 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 return
2: // statements
3:
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";
23: 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: else
34: return -1;
35: cout << "You can't get here!\n";
36: }
Output: Enter a number between 0 and 10,000 to double: 9000
Before doubler is called...
input: 9000 doubled: 0
Back from doubler...
input: 9000 doubled: 18000
Enter a number between 0 and 10,000 to double: 11000
Before doubler is called...
input: 11000 doubled: 0
Back from doubler...
input: 11000 doubled: -1

Default parametri
Za 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.
Listing 5.7. Demonstracija podrazumijevanih vrijednosti parametara.
1: // Listing 5.7 - demonstrates use
2: // of default parameter values
3:
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: 10000


Second time area equals: 5000
Third time area equals: 2500

Preopterećenje funkcija
C++ 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
paramaetara a različitih povratnih tipova uzrokuju grešku prilikom kompajliranja.

Novi 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 - demonstrates
2: // function polymorphism
3:
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";
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: 6500
myLong: 65000
myFloat: 6.5
myDouble: 6.5e+20
In Double(int)
In Double(long)
In Double(float)
In Double(double)
DoubledInt: 13000
DoubledLong: 130000
DoubledFloat: 13
DoubledDouble: 1.3e+21
Umetnute (engl. inline) funkcije
Kad 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 functions
2:
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: 20
Target: 40
Target: 80
Target: 160

Rekurzija
Funkcija 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 pzove 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 isiptuje argument (n). Ako je n<3 vraća 1; inače, fib() poziva samu seb (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.
Listing 5.10. Demonstracija rekurzije za račun Fibonaccijevog niza.
1: // Listing 5.10 - demonstrates recursion
2: // Fibonacci find.
3: // Finds the nth Fibonacci number
4: // Uses this algorithm: Fib(n) = fib(n-1) + fib(n-2)
5: // Stop conditions: n = 2 || n = 1
6:
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: else
36: {
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: 5
Processing 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

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

Kviz
1. 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žbe
1. 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);
}

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.
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 tipova


Već 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 članovi
Nove 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 briganja 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.
Funkcije unutar klase obično manipuliraju podatkovnim članovima. Njih nazivamo funkcijskim članovima ili
metodama (engl. member functions, methods). Metode klase Automobil moglebi 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 klase
Za 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 objekta
Objekt vašeg novog tipa definirate isto kao i cjelobrojnu varijablu:
unsigned int GrossWeight; // definicija neoznačenog cijelog broja
Cat 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 objekti
Vi 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 klase


Jednom 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.
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 broj
x = 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 imati


Pokuš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 Frisky
Frisky.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 javnih


Dodatne 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
Cat 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 and
2: // definition of an object of the class,
3:
4: #include <iostream.h> // for cout
5:
6: class Cat // declare the class object
7: {
8: public: // members which follow are public
9: int itsAge;
10: int itsWeight;
11: };
12:
13:
14: void main()
15: {
16: Cat Frisky;
17: Frisky.itsAge = 5; // assign to the member variable
18: 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.

Načiniti podatkovne članove privatnima


Kao 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 declaration
2: // Data members are private, public accessor methods
3: // mediate setting and getting the values of the private data
4:
5: class Cat
6: {
7: public:
8: // public accessors
9: unsigned int GetAge();
10: void SetAge(unsigned int Age);
11:
12: unsigned int GetWeight();
13: void SetWeight(unsigned int Weight);
14:
15: // public member functions
16: Meow();
17:
18: // private member data
19: 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;
Frisky.SetAge(5); // postavi Friskijevu starost koristeći javni pristup

Privatnost protiv sigurnosti


Deklariranjem 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 car


int bought; // a local variable of type int
OldFaithful.SetYear(84) ; // assign 84 to the year
bought = OldFaithful.GetYear(); // set bought to 84
OldFaithful.Start(); // call the start method

Implementacija metoda klase


Kao š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 and
2: // definition of class methods,
3:
4: #include <iostream.h> // for cout
5:
6: class Cat // begin declaration of the class
7: {
8: public: // begin public section
9: int GetAge(); // accessor function
10: void SetAge (int age); // accessor function
11: void Meow(); // general function
12: private: // begin private section
13: int itsAge; // member variable
14: };
15:
16: // GetAge, Public accessor function
17: // returns value of itsAge member
18: int Cat::GetAge()
19: {
20: return itsAge;
21: }
22:
23: // definition of SetAge, public
24: // accessor function
25: // returns sets itsAge member
26: void Cat::SetAge(int age)
27: {
28: // set member variable its age to
29: // value passed in by parameter age
30: itsAge = age;
31: }
32:
33: // definition of Meow method
34: // returns: void
35: // parameters: None
36: // action: Prints "meow" to screen
37: void Cat::Meow()
38: {
39: cout << "Meow.\n";
40: }
41:
42: // create a cat, set its age, have it
43: // meow, tell us its age, then meow again.
44: 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 destruktori
Postoje 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 ovdje
Weight = 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();
Podrazumijevani konstruktori i destruktori
Ako 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 objeekti
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 and
2: // destructor for the Cat class
3:
4: #include <iostream.h> // for cout
5:
6: class Cat // begin declaration of the class
7: {
8: public: // begin public section
9: Cat(int initialAge); // constructor
10: ~Cat(); // destructor
11: int GetAge(); // accessor function
12: void SetAge(int age); // accessor function
13: void Meow();
14: private: // begin private section
15: int itsAge; // member variable
16: };
17:
18: // constructor of Cat,
19: Cat::Cat(int initialAge)
20: {
21: itsAge = initialAge;
22: }
23:
24: Cat::~Cat() // destructor, takes no action
25: {
26: }
27:
28: // GetAge, Public accessor function
29: // returns value of itsAge member
30: int Cat::GetAge()
31: {
32: return itsAge;
33: }
34:
35: // Definition of SetAge, public
36: // accessor function
37:
38: void Cat::SetAge(int age)
39: {
40: // set member variable its age to
41: // value passed in by parameter age
42: itsAge = age;
43: }
44:
45: // definition of Meow method
46: // returns: void
47: // parameters: None
48: // action: Prints "meow" to screen
49: void Cat::Meow()
50: {
51: cout << "Meow.\n";
52: }
53:
54: // create a cat, set its age, have it
55 // 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.
Meow.
Now Frisky is 7 years old.

Konstantni funkcijski članovi


Ako 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 implementacije


Kao š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.
UPOZORENJE: Listing 6.5 se ne može kompajlirati!

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


1: // Demonstrates compiler errors
2:
3:
4: #include <iostream.h> // for cout
5:
6: class Cat
7: {
8: public:
9: Cat(int initialAge);
10: ~Cat();
11: int GetAge() const; // const accessor function
12: 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 action
25: {
26: cout << "Cat Destructor\n";
27: }
28: // GetAge, const function
29: // but we violate const!
30: int Cat::GetAge() const
31: {
32: return (itsAge++); // violates const!
33: }
34:
35: // definition of SetAge, public
36: // accessor function
37:
38: void Cat::SetAge(int age)
39: {
40: // set member variable its age to
41: // value passed in by parameter age
42: itsAge = age;
43: }
44:
45: // definition of Meow method
46: // returns: void
47: // parameters: None
48: // action: Prints "meow" to screen
49: void Cat::Meow()
50: {
51: cout << "Meow.\n";
52: }
53:
54: // demonstrate various violations of the
55 // interface, and resulting compiler errors
56: int main()
57: {
58: Cat Frisky; // doesn't match declaration
59: Frisky.Meow();
60: Frisky.Bark(); // No, silly, cat's can't bark.
61: Frisky.itsAge = 7; // itsAge is private
62: 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 metoda


Svaka 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.

ZAPAMTI: 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 implementacija
Baš 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; } // inline
void 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;
} // inline
void 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.
Listing 6.6. Cat class deklaracija u cat.hpp
1: #include <iostream.h>
2: class Cat
3: {
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 functions


2: // and inclusion of header files
3:
4: #include "cat.hpp" // be sure to include the header files!
5:
6:
7: Cat::Cat(int initialAge) //constructor
8: {
9: itsAge = initialAge;
10: }
11:
12: Cat::~Cat() //destructor, takes no action
13: {
14: }
15:
16: // Create a cat, set its age, have it
17: // 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.

Klase s drugim klasama kao podatkovnim članovima


Nije 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.hpp
2: #include <iostream.h>
3: class Point // holds x,y coordinates
4: {
5: // no constructor, use default
6: 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 declaration
15:
16:
17: class Rectangle
18: {
19: public:
20: Rectangle (int top, int left, int bottom, int right);
21: ~Rectangle () {}
22:
23: int GetTop() const { return itsTop; }
24: int GetLeft() const { return itsLeft; }
25: int GetBottom() const { return itsBottom; }
26: int GetRight() const { return itsRight; }
27:
28: Point GetUpperLeft() const { return itsUpperLeft; }
29: Point GetLowerLeft() const { return itsLowerLeft; }
30: Point GetUpperRight() const { return itsUpperRight; }
31: Point GetLowerRight() const { return itsLowerRight; }
32:
33: void SetUpperLeft(Point Location) {itsUpperLeft = Location;}
34: void SetLowerLeft(Point Location) {itsLowerLeft = Location;}
35: void SetUpperRight(Point Location) {itsUpperRight = Location;}
36: void SetLowerRight(Point Location) {itsLowerRight = Location;}
37:
38: void SetTop(int top) { itsTop = top; }
39: void SetLeft (int left) { itsLeft = left; }
40: void SetBottom (int bottom) { itsBottom = bottom; }
41: void SetRight (int right) { itsRight = right; }
42:
43: int GetArea() const;
44:
45: private:
46: Point itsUpperLeft;
47: Point itsUpperRight;
48: Point itsLowerLeft;
49: Point itsLowerRight;
50: int itsTop;
51: int itsLeft;
52: int itsBottom;
53: int itsRight;
54: };
55: // end Rect.hpp

Listing 6.9. RECT.CPP.


1: // Begin rect.cpp
2: #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 multiply
26: int Rectangle::GetArea() const
27: {
28: int Width = itsRight-itsLeft;
29: int Height = itsTop - itsBottom;
30: return (Width * Height);
31: }
32:
33: int main()
34: {
35: //initialize a local Rectangle variable
36: 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: 3000
Upper 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.

Strukture
Vrlo 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 stvar


Vjerojatno 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.

Kviz
1. Š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?
Vježbe
1. 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);
}
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

Petlje
Mnogi 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 naredba


U 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.1
2: // Looping with goto
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter = 0; // initialize counter
9: loop: counter ++; // top of the loop
10: cout << "counter: " << counter << "\n";
11: if (counter < 5) // test the value
12: goto loop; // jump to the top
13:
14: cout << "Complete. Counter: " << counter << ".\n";
15: return 0;
16: }
Output: counter: 1
counter: 2
counter: 3
counter: 4
counter: 5
Complete. Counter: 5.

Zašto je goto prognan


Goto 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 petlje
while 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.2
2: // Looping with while
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter = 0; // initialize the condition
9:
10: while(counter < 5) // test condition still true
11: {
12: counter++; // body of the loop
13: cout << "counter: " << counter << "\n";
14: }
15:
16: cout << "Complete. Counter: " << counter << ".\n";
17: return 0;
18: }
while 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 10
int x = 0;
while (x < 10)
cout << "X: " << x++;

Još kompliciranije while naredbe


Testirani 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.3
2: // Complex while statements
3:
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 conditions
20: while (small < large && large > 0 && small < MAXSMALL)
21:
22: {
23: if (small % 5000 == 0) // write a dot every 5k lines
24: cout << ".";
25:
26: small++;
27:
28: large-=2;
29: }
30:
31: cout << "\nSmall: " << small << " Large: " << large << endl;
32: return 0;
33: }
Output: Enter a small number: 2
Enter a large number: 100000
small: 2.........
Small: 33335 Large: 33334

continue i break
Ponekad ć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.4
2: // Demonstrates break and continue
3:
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 loop
26: while (small < large && large > 0 && small < 65535)
27:
28: {
29:
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 loop
46:
47: cout << "\nSmall: " << small << " Large: " << large << endl;
48: return 0;
49: }
Output: Enter a small number: 2
Enter a large number: 20
Enter a skip number: 4
Enter a target number: 6
skipping on 4
skipping 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) petlja


Testirani 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.5
2: // Demonstrates a while true loop
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter = 0;
9:
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 petlja
Mož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.6
2: // Demonstrates skipping the body of
3: // 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?: 2
Hello!
Hello!
Counter is OutPut: 0

How many hellos?: 0


Counter 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 value
counter = 1;
ali ovo je očito "naštimavanje", ružno i neelegantno rješenje.

do...while
do...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.7
2: // Demonstrates do while
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter;
9: cout << "How many hellos? ";
10: cin >> counter;
11: do
12: {
13: cout << "Hello\n";
14: counter--;
15: } while (counter >0 );
16: cout << "Counter is: " << counter << endl;
17: return 0;
18: }
Output: How many hellos? 2
Hello
Hello
Counter is: 0
do...while nareba
Sintaksa za do...while naredbu je slijedeća:
do
naredba
while (uvjet);

Primjer 1
// count to 10
int x = 0;
do
cout << "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.8
2: // Looping with while
3:
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.
Često je takve slučajeve elegantnije riješiti for petljom, kao u listingu 7.9
Listing 7.9. demonstriranje for petlje.
1: // Listing 7.9
2: // Looping with for
3:
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 puta
for (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;
}
Napredne for petlje
for 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.10
2: // demonstrates multiple statements in
3: // for loops
4:
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: 0
i: 1 j: 1
i: 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.11
2: // For loops with null statements
3:
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!
Counter: 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 illustrating
2: //empty for loop statement
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter=0; // initialization
9: int max;
10: cout << "How many hellos?";
11: cin >> max;
12: for (;;) // a for loop that doesn't end
13: {
14: if (counter < max) // test
15: {
16: cout << "Hello!\n";
17: counter++; // increment
18: }
19: else
20: break;
21: }
22: return 0;
23: }
Output: How many hellos?3
Hello!
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 petlje


Toliko 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.
Listing 7.13. Ilustracija nul naredbe u for petlji
1: //Listing 7.13
2: //Demonstrates null statement
3: // as body of for loop
4:
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: 0
i: 1
i: 2
i: 3
i: 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 petlje
Petlje 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.14
2: //Illustrates nested for loops
3:
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";
21: }
22: return 0;
23: }
Output: How many rows? 4
How many columns? 12
What character? x
xxxxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxxx

Sažetak: petlje
U 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 naredbe
U 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;
}
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.16
2: // Demonstrates switch statement
3:
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 through
16: case 4: cout << "Nice Pick!\n"; // fall through
17: case 3: cout << "Excellent!\n"; // fall through
18: case 2: cout << "Masterful!\n"; // fall through
19: 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: 3
Excellent!
Masterful!
Incredible!

Enter a number between 1 and 5: 8


Too large!
Uporaba switch naredbe za menije
Listing 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.17
2: //Using a forever loop to manage
3: //user interaction
4: #include <iostream.h>
5:
6: // types & defines
7: enum BOOL { FALSE, TRUE };
8: typedef unsigned short int USHORT;
9:
10: // prototypes
11: 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 switch
43:
44: if (exit)
45: break;
46: } // end forever
47: 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: else
75: cout << "Task Three!\n";
76: }
Output: **** Menu ****
(1)Choice one.
(2)Choice two.
(3)Choice three.
(4)Redisplay menu.
(5)Quit.

:1
Task One!
**** Menu ****
(1)Choice one.
(2)Choice two.
(3)Choice three.
(4)Redisplay menu.
(5)Quit.

:3
Task Three!
**** Menu ****
(1)Choice one.
(2)Choice two.
(3)Choice three.
(4)Redisplay menu.
(5)Quit.

:5

Kviz
1. 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žbe
1. Š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 = 0
while (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
case 2: // fall through
case 3: // fall through
case 4: // fall through
case 5:
doOneToFive();
break;
default:
doDefault();
break;
}
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 operator
2: // and addresses of local variables
3:
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: }
Output: shortVar: 5 Address of shortVar: 0x8fc9:fff4
longVar: 65535 Address of longVar: 0x8fc9:fff2
sVar: -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 variable


unsigned short int * pAge = 0; // make a pointer
pAge = &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 variable


unsigned 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.
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 ;

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 pointers


2:
3: #include <iostream.h>
4:
5: typedef unsigned short int USHORT;
6: int main()
7: {
8: USHORT myAge; // a variable
9: USHORT * pAge = 0; // a pointer
10: myAge = 5;
11: cout << "myAge: " << myAge << "\n";
12:
13: pAge = &myAge; // assign address of myAge to pAge
14:
15: cout << "*pAge: " << *pAge << "\n\n";
16:
17: cout << "*pAge = 7\n";
18:
19: *pAge = 7; // sets myAge to 7
20:
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: 7
myAge: 7
myAge = 9
myAge: 9
*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 pointer
10:
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 pointer
18:
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: 0x355E
pAge: 0x355C
*pAge: 5
myAge: 5 yourAge: 10
&myAge: 0x355C &yourAge: 0x355E
pAge: 0x355E
*pAge: 10
&pAge: 0x355A
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 mož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 dosega 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 eksplicitno ne
oslobodite. Ako rezervirate memoriju 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
pristupom 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
alocirati, 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. Vi morate provjeriti vaš pokazivač svaki put kad zatražite novu memoriju.

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 dalje pokazivač te mu
može biti dodijeljena nova vrijednost. Listing 8.4 demonstrira alociranje varijable u slobodnom spremniku,
korištenje te varijable, a zatim njezino brisanje.
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.4
2: // Allocating and deleting a pointer
3:
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";
17: 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žuje 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 nedostupno budući da pokazivač
sada pokazuje na drugo memorijsko područje. Ne postoji način da ponovo pristupimo originalnoj 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.

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 ste 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 destruktor 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.5
2: // Creating objects on the free store
3:
4: #include <iostream.h>
5:
6: class SimpleCat
7: {
8: public:
9: SimpleCat();
10: ~SimpleCat();
11 private:
12 int itsAge;
13 };
14
15 SimpleCat::SimpleCat()
16 {
17 cout << "Constructor called.\n";
18 itsAge = 1;
19 }
20
21 SimpleCat::~SimpleCat()
22 {
23 cout << "Destructor called.\n";
24 }
25
26 int main()
27 {
28 cout << "SimpleCat Frisky...\n";
29 SimpleCat Frisky;
30 cout << "SimpleCat *pRags = new SimpleCat...\n";
31 SimpleCat * pRags = new SimpleCat;
32 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.6
2: // Accessing data members of objects on the heap
3:
4: #include <iostream.h>
5:
6: class SimpleCat
7: {
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);
22: cout << "Frisky is " << Frisky->GetAge() << " years old\n";
23: delete Frisky;
24: return 0;
25: }
Output: Frisky is 2 years old
Frisky 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.7
2: // Pointers as data members
3:
4: #include <iostream.h>
5:
6: class SimpleCat
7: {
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";
40: delete Frisky;
41: return 0;
42: }
Output: Frisky is 2 years old
Frisky is 5 years old

Analiza: Klasa SimpleCat je deklarirana da ima dve varijable—obje 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 eksplicitno, kao što to pokazuje listing 8.8.

Listing 8.8. Upotreba this pokazivača.

1: // Listing 8.8
2: // Using the this pointer
3:
4: #include <iostream.h>
5:
6: class Rectangle
7: {
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;
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, ne bi bilo
smisla gnjaviti vas s 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. Viseći pokazivač se stvara kad pozovete delete na
pokazivač, time oslobađajući memoriju na koju pokazuje, a kasnije ga pokušate upotrijebiti da mu prethodno
niste pridružili novu vrijednost.
To je kao da je vaš prijatelj 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;


int * 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 tome 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 constant


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

const pokazivači i const 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.10
2: // Using pointers with const methods
3:
4: #include <iostream.h>
5:
6: class Rectangle
7: {
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()
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 feet
pConstRect width: 5 feet
pConstPtr width: 5 feet
pRect width: 10 feet
pConstRect width: 5 feet
pConstPtr 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 funkcijski član; zato 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?
5. 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;
}
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.1
2: // Demonstrating the use of References
3:
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;
17: cout << "rSomeRef: " << rSomeRef << endl;
18: return 0;
19: }
Output: intOne: 5
rSomeRef: 5
intOne: 7
rSomeRef: 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.2
2: // Demonstrating the use of References
3:
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: 5
rSomeRef: 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 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.3
2: //Reassigning a reference
3:
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: 5
rSomeRef: 5
&intOne: 0x213e
&rSomeRef: 0x213e
intOne: 8
intTwo: 8
rSomeRef: 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; // wrong
Vi 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.4
2: // References to class objects
3:
4: #include <iostream.h>
5:
6: class SimpleCat
7: {
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;
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 2
CAT 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.

Prosljeđ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 value
2:
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";
28:
29: }
Output: Main. Before swap, x: 5 y: 10
Swap. Before swap, x: 5 y: 10
Swap. After swap, x: 10 y: 5
Main. 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 reference


2:
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 *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: 10
Swap. Before swap, *px: 5 *py: 10
Swap. After swap, *px: 10 *py: 5
Main. 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 reference


2: // 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;
27:
28: cout << "Swap. After swap, rx: " << rx << " ry: " << ry << "\n";
29:
30: }

Output: Main. Before swap, x:5 y: 10


Swap. Before swap, rx:5 ry:10
Swap. After swap, rx:10 ry:5
Main. 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.8
2: // Returning multiple values from a function
3:
4: #include <iostream.h>
5:
6: typedef unsigned short USHORT;
7:
8: short Factor(USHORT, USHORT*, USHORT*);
9:
10: int main()
11: {
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: else
27: 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: else
37: {
38: *pSquared = n*n;
39: *pCubed = n*n*n;
40: Value = 0;
41: }
42: return Value;
43: }
Output: Enter a number (0-20): 3
number: 3
square: 9
cubed: 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.9
2: // Returning multiple values from a function
3: // using references
4:
5: #include <iostream.h>
6:
7: typedef unsigned short USHORT;
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: else
29: 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 code
37: else
38: {
39: rSquared = n*n;
40: rCubed = n*n*n;
41: return SUCCESS;
42: }
43: }
Output: Enter a number (0 - 20): 3
number: 3
square: 9
cubed: 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.
Postoji 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.10
2: // Passing pointers to objects
3:
4: #include <iostream.h>
5:
6: class SimpleCat
7: {
8: public:
9: SimpleCat (); // constructor
10: SimpleCat(SimpleCat&); // copy constructor
11: ~SimpleCat(); // destructor
12: };
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: }
42:
43: // FunctionOne, passes by value
44: SimpleCat FunctionOne(SimpleCat theCat)
45: {
46: cout << "Function One. Returning...\n";
47: return theCat;
48: }
49:
50: // functionTwo, passes by reference
51: 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.11
2: // Passing pointers to objects
3:
4: #include <iostream.h>
5:
6: class SimpleCat
7: {
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: {
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 pointer
59: 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: }
Output: Making a cat...
Simple Cat constructor...
Frisky is 1 years old
Frisky is 5 years old
Calling FunctionTwo...
FunctionTwo. Returning...
Frisky is now 5 years old
Frisky is 5 years old
Simple 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.12
2: // Passing references to objects
3:
4: #include <iostream.h>
5:
6: class SimpleCat
7: {
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: {
33: 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 object
53: 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 old
Frisky is 5 years old
Calling FunctionTwo...
FunctionTwo. Returning...
Frisky is now 5 years old
Frisky is 5 years old
Simple 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 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.13
2: // Returning a reference to an object
3: // which no longer exists
4:
5: #include <iostream.h>
6:
7: class SimpleCat
8: {
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:
19: 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.14
2: // Resolving memory leaks
3: #include <iostream.h>
4:
5: class SimpleCat
6: {
7: public:
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: 0x2bf4
rCat 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 inicijaliziranje 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 CAT
4: {
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: }
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 functions


2: #include <iostream.h>
3:
4: typedef unsigned short int USHORT;
5: enum BOOL { FALSE, TRUE};
6:
7: // Rectangle class declaration
8: class Rectangle
9: {
10: public:
11: // constructors
12: Rectangle(USHORT width, USHORT height);
13: ~Rectangle(){}
14:
15: // overloaded class function DrawShape
16: void DrawShape() const;
17: void DrawShape(USHORT aWidth, USHORT aHeight) const;
18:
19: private:
20: USHORT itsWidth;
21: USHORT itsHeight;
22: };
23:
24: //Constructor implementation
25: Rectangle::Rectangle(USHORT width, USHORT height)
26: {
27: itsWidth = width;
28: itsHeight = height;
29: }
30:
31:
32: // Overloaded DrawShape - takes no values
33: // Draws based on current class member values
34: void Rectangle::DrawShape() const
35: {
36: DrawShape( itsWidth, itsHeight);
37: }
38:
39:
40: // overloaded DrawShape - takes two values
41: // draws shape based on the parameters
42: void Rectangle::DrawShape(USHORT width, USHORT height) const
43: {
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 functions
55: int main()
56: {
57: // initialize a rectangle to 30,5
58: 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):
************************************************************
************************************************************

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 functions


2: #include <iostream.h>
3:
4: typedef unsigned short int USHORT;
5: enum BOOL { FALSE, TRUE};
6:
7: // Rectangle class declaration
8: class Rectangle
9: {
10: public:
11: // constructors
12: 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 implementation
22: Rectangle::Rectangle(USHORT width, USHORT height):
23: itsWidth(width), // initializations
24: itsHeight(height)
25: {} // empty body
26:
27:
28: // default values used for third parameter
29: void Rectangle::DrawShape(
30: USHORT width,
31: USHORT height,
32: BOOL UseCurrentValue
33: ) const
34: {
35: int printWidth;
36: int printHeight;
37:
38: if (UseCurrentValue == TRUE)
39: {
40: printWidth = itsWidth; // use current class values
41: printHeight = itsHeight;
42: }
43: else
44: {
45: printWidth = width; // use parameter values
46: printHeight = height;
47: }
48:
49:
50: for (int i = 0; i<printHeight; i++)
51: {
52: for (int j = 0; j< printWidth; j++)
53: {
54: cout << "*";
55: }
56: cout << "\n";
57: }
58: }
59:
60: // Driver program to demonstrate overloaded functions
61: int main()
62: {
63: // initialize a rectangle to 10,20
64: Rectangle theRect(30,5);
65: cout << "DrawShape(0,0,TRUE)...\n";
66: theRect.DrawShape(0,0,TRUE);
67: cout <<"DrawShape(40,2)...\n";
68: theRect.DrawShape(40,2);
69: return 0;
70: }
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).

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.3
2: // Overloading constructors
3:
4: #include <iostream.h>
5:
6: class Rectangle
7: {
8: 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: 5
Rect1 length: 10
Enter a width: 20

Enter a length: 50

Rect2 width: 20
Rect2 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 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 parameters
itsAge(5), // initialization list
itsWeight(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 funkcionira 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.5
2: // Copy constructors
3:
4: #include <iostream.h>
5:
6: class CAT
7: {
8: public:
9: CAT(); // default constructor
10: CAT (const CAT &); // copy constructor
11: ~CAT(); // destructor
12: 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()
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: 5
Setting frisky to 6...
Creating boots from frisky
frisky's age: 6
boots' age: 6
setting frisky to 7...
frisky's age: 7
boots' 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.


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.6
2: // The Counter class
3:
4: typedef unsigned short USHORT;
5: #include <iostream.h>
6:
7: class Counter
8: {
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:
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 inkrementiranja


Preoptereć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.7
2: // The Counter class
3:
4: typedef unsigned short USHORT;
5: #include <iostream.h>
6:
7: class Counter
8: {
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;
29: i.Increment();
30: cout << "The value of i is " << i.GetItsVal() << endl;
31: return 0;
32: }
Output: The value of i is 0
The 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.8
2: // The Counter class
3:
4: typedef unsigned short USHORT;
5: #include <iostream.h>
6:
7: class Counter
8: {
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();
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 0
The value of i is 1
The 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.9
2: // operator++ returns a temporary object
3:
4: typedef unsigned short USHORT;
5: #include <iostream.h>
6:
7: class Counter
8: {
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: {};
25:
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 0
The value of i is 1
The value of i is 2
The 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.10
2: // operator++ returns a nameless temporary object
3:
4: typedef unsigned short USHORT;
5: #include <iostream.h>
6:
7: class Counter
8: {
9: public:
10: Counter();
11: Counter(USHORT val);
12: ~Counter(){}
13: USHORT GetItsVal()const { return itsVal; }
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 0
The value of i is 1
The value of i is 2
The 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.

Upotreba 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.11
2: // Returning the dereferenced this pointer
3:
4: typedef unsigned short USHORT;
5: #include <iostream.h>
6:
7: class Counter
8: {
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: }
Output: The value of i is 0
The value of i is 1
The value of i is 2
The 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.12
2: // Returning the dereferenced this pointer
3:
4: typedef unsigned short USHORT;
5: #include <iostream.h>
6:
7: class Counter
8: {
9: public:
10: Counter();
11: ~Counter(){}
12: USHORT GetItsVal()const { return itsVal; }
13: void SetItsVal(USHORT x) {itsVal = x; }
14: const Counter& operator++ (); // prefix
15: const Counter operator++ (int); // postfix
16:
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 0
The value of i is 1
The value of i is 2
The value of a: 3 and i: 3
The value of a: 3 and i: 4

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.13
2: // Add function
3:
4: typedef unsigned short USHORT;
5: #include <iostream.h>
6:
7: class Counter
8: {
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: }
34:
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: 2
varTwo: 4
varThree: 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.14
2: //Overload operator plus (+)
3:
4: typedef unsigned short USHORT;
5: #include <iostream.h>
6:
7: class Counter
8: {
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;
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: 2
varTwo: 4
varThree: 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);

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 here
catTwo = 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.15
2: // Copy constructors
3:
4: #include <iostream.h>
5:
6: class CAT
7: {
8: public:
9: CAT(); // default constructor
10: // 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;
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: 5
Setting frisky to 6...
whiskers' age: 5
copying 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.

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.16
2: // This code won't compile!
3:
4: typedef unsigned short USHORT;
5: #include <iostream.h>
6:
7: class Counter
8: {
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 ;0
29: }
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.

1: // Listing 10.17
2: // Constructor as conversion operator
3:
4: typedef unsigned short USHORT;
5: #include <iostream.h>
6:
7: class Counter
8: {
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 ugrađ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.18
2: // conversion operator
3:
4: typedef unsigned short USHORT;
5: #include <iostream.h>
6:
7: class Counter
8: {
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.

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?

VeryShort VeryShort::operator+ (const VeryShort& rhs)


{
itsVal += rhs.GetItsVal();
return *this;
}
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 - Arrays


2: #include <iostream.h>
3:
4: int main()
5: {
6: int myArray[5];
7: int i;
8: for ( i=0; i<5; i++) // 0-4
9: {
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]: 3
Value for myArray[1]: 6
Value for myArray[2]: 9
Value for myArray[3]: 12
Value for myArray[4]: 15
0: 3
1: 6
2: 9
3: 12
4: 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.
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.2
2: // Demonstrates what happens when you write past the end
3: // of an array
4:
5: #include <iostream.h>
6: int main()
7: {
8: // sentinels
9: long sentinelOne[3];
10: long TargetArray[25]; // array to fill
11: 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:
47: return 0;
48: }

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

SentinelOne[0]: 0
SentinelTwo[0]: 0
SentinelOne[1]: 0
SentinelTwo[1]: 0
SentinelOne[2]: 0
SentinelTwo[2]: 0

Assigning...
Test 2:
TargetArray[0]: 20
TargetArray[24]: 20
TargetArray[25]: 20

SentinelOne[0]: 20
SentinelTwo[0]: 0
SentinelOne[1]: 0
SentinelTwo[1]: 0
SentinelOne[2]: 0
SentinelTwo[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.
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.3
2: // Dimensioning arrays with consts and enumerations
3:
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]
Sva 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 objects


2:
3: #include <iostream.h>
4:
5: class CAT
6: {
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: 1
cat #2: 3
cat #3: 5
cat #4: 7
cat #5: 9

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.
Listing 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]: 0
SomeArray[0][1]: 0
SomeArray[1][0]: 1
SomeArray[1][1]: 2
SomeArray[2][0]: 2
SomeArray[2][1]: 4
SomeArray[3][0]: 3
SomeArray[3][1]: 6
SomeArray[4][0]: 4
SomeArray[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.
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 objects


2:
3: #include <iostream.h>
4:
5: class CAT
6: {
7: public:
8: CAT() { itsAge = 1; itsWeight=5; }
9: ~CAT() {} // destructor
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 * 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: }

Output: Cat #1: 1


Cat #2: 3
Cat #3: 5
...
Cat #499: 997
Cat #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 10
pCat++; // 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.
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 store


2:
3: #include <iostream.h>
4:
5: class CAT
6: {
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++)
38: {
38: cout << "Cat #" << i+1 << ": ";
39: cout << Family[i].GetAge() << endl;
40: }
41:
42: delete [] Family;
43:
44: return 0;
45: }
Output: Cat #1: 1
Cat #2: 3
Cat #3: 5
...
Cat #499: 997
Cat #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:
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 buffers


2:
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 World
Here'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: ";
9: cin.get(buffer, 79); // get up to 79 or newline
10: cout << "Here's the buffer: " << buffer << endl;
11: return 0;
12: }
Output: Enter the string: Hello World
Here'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 island
String2: 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().

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 island
String2: 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.12
2:
3: #include <iostream.h>
4: #include <string.h>
5:
6: // Rudimentary string class
7: class String
8: {
9: public:
10: // constructors
11: String();
12: String(const char *const);
13: String(const String &);
14: ~String();
15:
16: // overloaded operators
17: 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 accessors
24: unsigned short GetLen()const { return itsLen; }
25: const char * GetString() const { return itsString; }
26:
27: private:
28: String (unsigned short); // private constructor
29: char * itsString;
30: unsigned short itsLen;
31: };
32:
33: // default constructor creates string of 0 bytes
34: String::String()
35: {
36: itsString = new char[1];
37: itsString[0] = `\0';
38: itsLen=0;
39: }
40:
41: // private (helper) constructor, used only by
42: // class methods for creating a new string of
43: // 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 String
53: 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 constructor
63: String::String (const String & rhs)
64: {
65: itsLen=rhs.GetLen();
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 memory
73: String::~String ()
74: {
75: delete [] itsString;
76: itsLen = 0;
77: }
78:
79: // operator equals, frees existing memory
80: // then copies string and size
81: 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, returns
95: // reference to character so it can be
96: // changed!
97: char & String::operator[](unsigned short offset)
98: {
99: if (offset > itsLen)
100: return itsString[itsLen-1];
101: else
102: return itsString[offset];
103: }
104:
105: // constant offset operator for use
106: // on const objects (see copy constructor!)
107: char String::operator[](unsigned short offset) const
108: {
109: if (offset > itsLen)
110: return itsString[itsLen-1];
111: else
112: return itsString[offset];
113: }
114:
115: // creates a new string by adding current
116: // string to rhs
117: String String::operator+(const String& rhs)
118: {
119: unsigned short totalLen = itsLen + rhs.GetLen();
120: 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 nothing
130: 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: }
Output: S1: initial test
S1: Hello world
tempTwo: ; nice to be here!
S1: Hello world; nice to be here!
S1[4]: o
S1: Hellx World; nice to be here!
S1[999]: !
S3: Hellx World; nice to be here! Another string
S4: 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.
Vezane 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.13
2: // Linked list simple implementation
3:
4: #include <iostream.h>
5:
6: // object to add to list
7: class CAT
8: {
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 Node
20: {
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;
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: // Insert
51: // Orders cats based on their ages
52: // Algorithim: If you are last in line, add the cat
53: // Otherwise, if the new cat is older than you
54: // and also younger than next in line, insert it after
55: // this one. Otherwise call insert on the next in line
56: // ************************************
57: void Node::Insert(Node* newNode)
58: {
59: if (!itsNext)
60: itsNext = newNode;
61: else
62: {
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: else
73: 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)
85: 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): 1
New Cat's age? (0 to quit): 9
New Cat's age? (0 to quit): 3
New Cat's age? (0 to quit): 7
New Cat's age? (0 to quit): 2
New Cat's age? (0 to quit): 5
New Cat's age? (0 to quit): 0
My cat is 1 years old
My cat is 2 years old
My cat is 3 years old
My cat is 5 years old
My cat is 7 years old
My cat is 9 years old
Deleting node...
Deleting node...
Deleting node...
Deleting node...
Deleting node...
Deleting node...
Deleting node...
Exiting...
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;
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

Životinja

Sisavac Reptil

Konj Pas

Ovčar Terijer

Jorkšir Pit Bull

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.

Ž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 Mammal
Tip 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 inheritance


2:
3: #include <iostream.h>
4: enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
5:
6: class Mammal
7: {
8: public:
9: // constructors
10: Mammal();
11: ~Mammal();
12:
13: //accessors
14: int GetAge()const;
15: void SetAge(int);
16: int GetWeight() const;
17: void SetWeight();
18:
19: //Other methods
20: void Speak();
21: void Sleep();
22:
23:
24: protected:
25: int itsAge;
26: int itsWeight;
27: };
28:
29: class Dog : public Mammal
30: {
31: public:
32:
33: // Constructors
34: Dog();
35: ~Dog();
36:
37: // Accessors
38: BREED GetBreed() const;
39: void SetBreed(BREED);
40:
41: // Other methods
42: // 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 object


2:
3: #include <iostream.h>
4: enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
5:
6: class Mammal
7: {
8: public:
9: // constructors
10: Mammal():itsAge(2), itsWeight(5){}
11: ~Mammal(){}
12:
13: //accessors
14: 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 methods
20: 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 Mammal
30: {
31: public:
32:
33: // Constructors
34: Dog():itsBreed(YORKIE){}
35: ~Dog(){}
36:
37: // Accessors
38: BREED GetBreed() const { return itsBreed; }
39: void SetBreed(BREED breed) { itsBreed = breed; }
40:
41: // Other methods
42: 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.

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 Mammal
7: {
8: public:
9: // constructors
10: Mammal();
11: ~Mammal();
12:
13: //accessors
14: 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 methods
20: 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 Mammal
30: {
31: public:
32:
33: // Constructors
34: Dog();
35: ~Dog();
36:
37: // Accessors
38: BREED GetBreed() const { return itsBreed; }
39: void SetBreed(BREED breed) { itsBreed = breed; }
40:
41: // Other methods
42: 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),
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 old
Dog 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.

1: //Listing 12.4 Overloading constructors in derived classes


2:
3: #include <iostream.h>
4: enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
5:
6: class Mammal
7: {
8: public:
9: // constructors
10: Mammal();
11: Mammal(int age);
12: ~Mammal();
13:
14: //accessors
15: 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 methods
21: 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 Mammal
31: {
32: public:
33:
34: // Constructors
35: 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: // Accessors
43: BREED GetBreed() const { return itsBreed; }
44: void SetBreed(BREED breed) { itsBreed = breed; }
45:
46: // Other methods
47: void WagTail() { cout << "Tail wagging...\n"; }
48: void BegForFood() { cout << "Begging for food...\n"; }
49:
50: private:
51: BREED itsBreed;
52: };
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: {
107: 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.Sp 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.
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 class


2:
3: #include <iostream.h>
4: enum BREED { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
5:
6: class Mammal
7: {
8: public:
9: // constructors
10: Mammal() { cout << "Mammal constructor...\n"; }
11: ~Mammal() { cout << "Mammal destructor...\n"; }
12:
13: //Other methods
14: void Speak()const { cout << "Mammal sound!\n"; }
15: void Sleep()const { cout << "shhh. I'm sleeping.\n"; }
16:
17:
18: protected:
19: int itsAge;
20: int itsWeight;
21: };
22:
23: class Dog : public Mammal
24: {
25: public:
26:
27: // Constructors
28: Dog(){ cout << "Dog constructor...\n"; }
29: ~Dog(){ cout << "Dog destructor...\n"; }
30:
31: // Other methods
32: 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


2:
3: #include <iostream.h>
4:
5: class Mammal
6: {
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 Mammal
20: {
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 step
Mammal 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.
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 Mammal
6: {
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 Mammal
21: {
22: public:
23: void Move()const;
24:
25: };
26:
27: void Dog::Move() const
28: {
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.

Virtualne 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 methods


2:
3: #include <iostream.h>
4:
5: class Mammal
6: {
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 Mammal
18: {
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: {
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 step
Woof!

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 turn


2:
3: #include <iostream.h>
4:
5: class Mammal
6: {
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 Mammal
16: {
17: public:
18: void Speak()const { cout << "Woof!\n"; }
19: };
20:
21:
22: class Cat : public Mammal
23: {
24: public:
25: void Speak()const { cout << "Meow!\n"; }
26: };
27:
28:
29: class Horse : public Mammal
30: {
31: public:
32: void Speak()const { cout << "Winnie!\n"; }
33: };
34:
35: class Pig : public Mammal
36: {
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: 5
Woof!
Meow!
Winnie!
Oink!
Mammal speak!

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.
Iako 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 value


2:
3: #include <iostream.h>
4:
5: enum BOOL { FALSE, TRUE };
6: class Mammal
7: {
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 Mammal
17: {
18: public:
19: void Speak()const { cout << "Woof!\n"; }
20: };
21:
22: class Cat : public Mammal
23: {
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;
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: 1
Woof
Woof
Mammal Speak!
(1)dog (2)cat (0)Quit: 2
Meow!
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 Clone() 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 constructor


2:
3: #include <iostream.h>
4:
5: class Mammal
6: {
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 Mammal
24: {
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 Mammal
40: {
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: };
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: 1
2: Mammal constructor...
3: Dog constructor...
4: (1)dog (2)cat (3)Mammal: 2
5: Mammal constructor...
6: Cat constructor...
7: (1)dog (2)cat (3)Mammal: 3
8: 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!
16: 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&);
};
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);
else
gallop(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 Horse
3:
4: #include <iostream.h>
5:
6: class Horse
7: {
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 Horse
16: {
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: else
34: 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: 1
Horses 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.

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 Inheritance
3:
4: #include <iostream.h>
5:
6: class Horse
7: {
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 Bird
17: {
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 Bird
31: {
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;
45: 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: else
53: 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: else
63: 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: 1
Horse constructor...
(1)Horse (2)Pegasus: 2
Horse constructor... Bird constructor... Pegasus constructor...
(1)Bird (2)Pegasus: 1
Bird constructor...
(1)Bird (2)Pegasus: 2
Horse 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...
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.4
2: // Calling multiple constructors
3: #include <iostream.h>
4: typedef int HANDS;
5: enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown } ;
6: enum BOOL { FALSE, TRUE };
7:
8: class Horse
9: {
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 Bird
28: {
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 Bird
52: {
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: {
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: else
88: 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:
Member 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.5
2: // Common base classes
3: #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 bird
10: {
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 : public Animal
27: {
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 Animal
47: {
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 Bird
69: {
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: 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.
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.6
2: // Virtual inheritance
3: #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 bird
10: {
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 Animal
27: {
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 : virtual public Animal
47: {
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 Bird
69: {
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,
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 Animal
class Bird : virtual public Animal
class Pegasus : public Horse, public Bird
Primjer 2:
class Schnauzer : virtual public Dog
class Poodle : virtual public Dog
class 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>
4:
5: enum BOOL { FALSE, TRUE };
6:
7: class Shape
8: {
9: public:
10: Shape(){}
11: ~Shape(){}
12: virtual long GetArea() { return -1; } // error
13: virtual long GetPerim() { return -1; }
14: virtual void Draw() {}
15: private:
16: };
17:
18: class Circle : public Shape
19: {
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 Shape
38: {
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++)
58: cout << "x ";
59:
60: cout << "\n";
61: }
62: }
63:
64: class Square : public Rectangle
65: {
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";
112: }
113: return 0;
114: }

Output: (1)Circle (2)Rectangle (3)Square (0)Quit: 2


xxxxxx
xxxxxx
xxxxxx
xxxxxx
(1)Circle (2)Rectangle (3)Square (0)Quit:3
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
(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.

Listing 13.8. Apstraktni tipovi podataka.

1: class Shape
2: {
3: public:
4: Shape(){}
5: ~Shape(){}
6: virtual long GetArea() = 0; // error
7: virtual long GetPerim()= 0;
8: virtual void Draw() = 0;
9: private:
10: };
Output: (1)Circle (2)Rectangle (3)Square (0)Quit: 2
xxxxxx
xxxxxx
xxxxxx
xxxxxx
(1)Circle (2)Rectangle (3)Square (0)Quit: 3
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
(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 functions
2:
3: #include <iostream.h>
4:
5: enum BOOL { FALSE, TRUE };
6:
7: class Shape
8: {
9: public:
10: Shape(){}
11: ~Shape(){}
12: virtual long GetArea() = 0; // error
13: virtual long GetPerim()= 0;
14: virtual void Draw() = 0;
15: private:
16: };
17:
18: void Shape::Draw()
19: {
20: cout << "Abstract drawing mechanism!\n";
21: }
22:
23: class Circle : public Shape
24: {
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 Shape
44: {
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:
71:
72: class Square : public Rectangle
73: {
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: 2
xxxxxx
xxxxxx
xxxxxx
xxxxxx
Abstract drawing mechanism!
(1)Circle (2)Rectangle (3)Square (0)Quit: 3
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
Abstract 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.
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 members


2:
3: #include <iostream.h>
4:
5: class Cat
6: {
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()
22: {
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 old
There are 4 cats left!
Deleting the one which is 1 years old
There are 3 cats left!
Deleting the one which is 2 years old
There are 2 cats left!
Deleting the one which is 3 years old
There 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.

Listing 14.2. Pristupanje statičkim članovima bez objekta.

1: //Listing 14.2 static data members


2:
3: #include <iostream.h>
4:
5: class Cat
6: {
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!
There 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 members


2:
3: #include <iostream.h>
4:
5: class Cat
6: {
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];
38: CatHouse[i] = 0;
39: }
40: return 0;
41: }
Output: There are 5 cats left!
Deleting the one which is 2 years old
There are 4 cats left!
Deleting the one which is 3 years old
There are 3 cats left!
Deleting the one which is 4 years old
There are 2 cats left!
Deleting the one which is 5 years old
There 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 members


2:
3: #include <iostream.h>
4:
5: class Cat
6: {
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: {
24: 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
{
public:
static int GetHowMany() { return HowManyCats; }
private:
static int HowManyCats;
};
int Cat::HowManyCats = 0;
int main()
{
int howMany;
Cat theCat; // define a cat
howMany = theCat.GetHowMany(); // access through an object
howMany = 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 pointers


2:
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;
18: 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;
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: 1
x: 1 y: 2
New value for ValOne: 2
New value for ValTwo: 3
x: 2 y: 3
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 3
x: 2 y: 3
x: 8 y: 27
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 2
x: 8 y: 27
x: 64 y: 729
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 4
x: 64 y: 729
x: 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 pointers


2:
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: 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: 1
x: 1 y: 2
New value for ValOne: 2
New value for ValTwo: 3
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 3
x: 2 y: 3
x: 8 y: 27
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 2
x: 8 y: 27
x: 64 y: 729
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 4
x: 64 y: 729
x: 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 functions


2:
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&); // polje pointera na funkcije
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;
28: 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: 2
New Value for ValOne: 2
New Value for ValTwo: 3
x: 2 y: 3
x: 4 y: 9
x: 64 y: 729
x: 729 y: 64
x: 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 pointers


2:
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&); // deklaracija
10: enum BOOL { FALSE, TRUE };
11:
12: int main()
13: {
14: int valOne=1, valTwo=2;
15: int choice;
16: BOOL fQuit = FALSE;
17:
18: void (*pFunc)(int&, int&); dekl. Pokaz. Na funkciju
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); // poziv
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: 1
x: 1 y: 2
New value for ValOne: 2
New value for ValTwo: 3
x: 2 y: 3
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 3
x: 2 y: 3
x: 8 y: 27
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 2
x: 8 y: 27
x: 64 y: 729
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 4
x: 64 y: 729
x: 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.

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 _readable


2:
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)
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: 1
x: 1 y: 2
New value for ValOne: 2
New value for ValTwo: 3
x: 2 y: 3
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 3
x: 2 y: 3
x: 8 y: 27
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 2
x: 8 y: 27
x: 64 y: 729
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 4
x: 64 y: 729
x: 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 methods


2:
3: #include <iostream.h>
4:
5: enum BOOL {FALSE, TRUE};
6: class Mammal
7: {
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 Mammal
18: {
19: public:
20: void Speak()const { cout << "Woof!\n"; }
21: void Move() const { cout << "Walking to heel...\n"; }
22: };
23:
24:
25: class Cat : public Mammal
26: {
27: public:
28: void Speak()const { cout << "Meow!\n"; }
29: void Move() const { cout << "slinking...\n"; }
30: };
31:
32:
33: class Horse : public Mammal
34: {
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;
73: }
74: return 0;
75: }
Output: (0)Quit (1)dog (2)cat (3)horse: 1
(1)Speak (2)Move: 1
Woof!
(0)Quit (1)dog (2)cat (3)horse: 2
(1)Speak (2)Move: 1
Meow!
(0)Quit (1)dog (2)cat (3)horse: 3
(1)Speak (2)Move: 2
Galloping
(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 functions


2:
3: #include <iostream.h>
4:
5: enum BOOL {FALSE, TRUE};
6:
7: class Dog
8: {
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: };
18:
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: else
47: {
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: 1
Woof!
(0)Quit (1)Speak (2)Move (3)Eat (4)Growl (5)Whimper (6)Roll Over (7)Play Dead: 4
Grrr
(0)Quit (1)Speak (2)Move (3)Eat (4)Growl (5)Whimper (6)Roll Over (7)Play Dead: 7
Is 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.
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.
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.
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 cin


2:
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:
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: 2
Long: 70000
Double: 987654321
Float: 3.33
Unsigned: 25
Int: 2
Long: 70000
Double: 9.87654e+08
Float: 3.33
Unsigned: 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.

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 cin


2:
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: Jesse
Here it is: Jesse
Your entire name: Jesse Liberty
Here 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 cin


2:
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: ";
21: 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: 2
Long: 30303
Double: 393939397834
Float: 3.33
Word: Hello
Unsigned: 85

Int: 2
Long: 30303
Double: 3.93939e+11
Float: 3.33
Word: Hello
Unsigned: 85

Int, Long, Double, Float, Word, Unsigned: 3 304938 393847473 6.66 bye -2

Int: 3
Long: 304938
Double: 3.93847e+08
Float: 6.66
Word: 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.

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 parameters


2: #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: Hello
ch: H
ch: e
ch: l
ch: l
ch: o
ch:

World
ch: W
ch: o
ch: r
ch: l
ch: d
ch:
(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


2: #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: one
a: o
b: n
c: 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 array


2: #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 time
stringOne: Now is the time
Enter string two: For all good
StringTwo: For

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 three
stringOne: one two three
Enter string two: four five six
stringTwo: four
Enter 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);
11: 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 time
String oneonce upon a time
Enter string two: String two:
Now try again...
Enter string one: once upon a time
String one: once upon a time
Enter string two: there was a
String 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: else
13: cout << ch;
14: while (cin.peek() == `#')
15: cin.ignore(1,'#');
16: }
17: return 0;
18: }
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 << flush
Ovo 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;
14:
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 land
One if by
One 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 output


2: #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< End
Start > 123< Next >456< End
Start >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().

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< End
Start >******************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 setf


2: #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;
11:
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 185
The number is b9
The number is 0xb9
The number is 0xb9
The number is 0xb9
The number is 0x b9
The 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: ";
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 world
Hello again!
5
Here's some values: 7 35 and also these: 98456 8.800000
Formatted: 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.
Jedna 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 input
6: cout << "File name: ";
7: cin >> fileName;
8:
9: ofstream fout(fileName); // open for writing
10: 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 name
13: cin.getline(buffer,255); // get the user's input
14: fout << buffer << "\n"; // and write it to the file
15: fout.close(); // close the file, ready for reopen
16:
17: ifstream fin(fileName); // reopen for reading
18: 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 tidy
26: return 0;
27: }
Output: File name: test1
Enter 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.
• 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 error
3: {
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();
47: return 0;
48: }
Output: Please re-enter the file name: test1
Current 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 Animal
4: {
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 error
21: {
22: char fileName[80];
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: Animals
BearTwo weight: 1
BearTwo days: 1
BearTwo weight: 50
BearTwo 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 Param3
Ovi 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).
Listing 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 Days
Received 7 arguments...
argumnet 0: TestProgram.exe
argument 1: Teach
argument 2: Yourself
argument 3: C++
argument 4: In
argument 5: 21
argument 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 error
3: {
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: }
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: 1
BearTwo days: 1
BearTwo weight: 50
BearTwo 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.
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 parameter
2: class Array // the class being parameterized
3: {
4: public:
5: Array();
6: // full class declaration here
7: };
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 class


2: #include <iostream.h>
3: const int DefaultSize = 10;
4:
5: template <class T> // declare the template and the parameter
6: class Array // the class being parameterized
7: {
8: public:
9: // constructors
10: Array(int itsSize = DefaultSize);
11: Array(const Array &rhs);
12: ~Array() { delete [] pType; }
13:
14: // operators
15: Array& operator=(const Array&);
16: T& operator[](int offSet) { return pType[offSet]; }
17:
18: // accessors
19: 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 can
6: // create an array of animals
7:
8: class Animal
9: {
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;
18: };
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 parameter
30: class Array // the class being parameterized
31: {
32: public:
33: // constructors
34: Array(int itsSize = DefaultSize);
35: Array(const Array &rhs);
36: ~Array() { delete [] pType; }
37:
38: // operators
39: Array& operator=(const Array&);
40: T& operator[](int offSet) { return pType[offSet]; }
41: const T& operator[](int offSet) const
42: { return pType[offSet]; }
43: // accessors
44: int GetSize() const { return itsSize; }
45:
46: private:
47: T *pType;
48: int itsSize;
49: };
50:
51: // implementations follow...
52:
53: // implement the Constructor
54: 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 constructor
64: 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: }
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 program
88: int main()
89: {
90: Array<int> theArray; // an array of integers
91: Array<Animal> theZoo; // an array of Animals
92: Animal *pAnimal;
93:
94: // fill the arrays
95: 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 arrays
103: 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]: 0
theArray[1]: 2 theZoo[1]: 3
theArray[2]: 4 theZoo[2]: 6
theArray[3]: 6 theZoo[3]: 9
theArray[4]: 8 theZoo[4]: 12
theArray[5]: 10 theZoo[5]: 15
theArray[6]: 12 theZoo[6]: 18
theArray[7]: 14 theZoo[7]: 21
theArray[8]: 16 theZoo[8]: 24
theArray[9]: 18 theZoo[9]: 27
Funkcijski 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>&); // ok
a 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 templates


2:
3: template <class T> // declare the template and the parameter
4: class Array // the class being parameterized
5: {
6: public:
7: // constructors
8: Array(int itsSize = DefaultSize);
9: Array(const Array &rhs);
10: ~Array() { delete [] pType; }
11:
12: // operators
13: 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
18: int GetSize() const { return itsSize; }
19:
20: // friend function
21: 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 used
29: // 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 program
39: int main()
40: {
41: Array<int> theArray; // an array of integers
42: Array<Animal> theZoo; // an array of Animals
43: Animal *pAnimal;
44:
45: // fill the arrays
46: 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
theZoo[1]: 3
theZoo[2]: 6
theZoo[3]: 9
theZoo[4]: 12
theZoo[5]: 15
theZoo[6]: 18
theZoo[7]: 21
theZoo[8]: 24
theZoo[9]: 27
Now use the friend function to find the members of Array<int>
*** Intrude ***
i: 0
i: 2
i: 4
i: 6
i: 8
i: 10
i: 12
i: 14
i: 16
i: 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 parameter
6: class Array // the class being parameterized
7: {
8: public:
9: // constructors
10: Array(int itsSize = DefaultSize);
11: Array(const Array &rhs);
12: ~Array() { delete [] pType; }
13:
14: // operators
15: Array& operator=(const Array&);
16: T& operator[](int offSet) { return pType[offSet]; }
17: const T& operator[](int offSet) const
18: { return pType[offSet]; }
19: // accessors
20: 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 looping
41: 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;
64: }

Output: Enter an offset (0-9) and a value. (-1 to stop): 1 10


Enter an offset (0-9) and a value. (-1 to stop): 2 20
Enter an offset (0-9) and a value. (-1 to stop): 3 30
Enter an offset (0-9) and a value. (-1 to stop): 4 40
Enter an offset (0-9) and a value. (-1 to stop): 5 50
Enter an offset (0-9) and a value. (-1 to stop): 6 60
Enter an offset (0-9) and a value. (-1 to stop): 7 70
Enter an offset (0-9) and a value. (-1 to stop): 8 80
Enter an offset (0-9) and a value. (-1 to stop): 9 90
Enter 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
Here'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 arrays
6: class Animal
7: {
8: public:
9: // constructors
10: Animal(int);
11: Animal();
12: ~Animal();
13:
14: // accessors
15: int GetWeight() const { return itsWeight; }
16: void SetWeight(int theWeight) { itsWeight = theWeight; }
17:
18: // friend operators
19: friend ostream& operator<< (ostream&, const Animal&);
20:
21: private:
22: int itsWeight;
23: };
24:
25: // extraction operator for printing animals
26: ostream& operator<<
27: (ostream& theStream, const Animal& theAnimal)
28 {
29: theStream << theAnimal.GetWeight();
30: return theStream;
31: }
32:
33: Animal::Animal(int weight):
34: itsWeight(weight)
35: {
36: // cout << "Animal(int)\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 parameter
51: class Array // the class being parameterized
52: {
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]; }
62: int GetSize() const { return itsSize; }
63
64: // friend function
65: 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:
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 array
124: }
125: }
Output: Enter an offset (0-9) and a value. (-1 to stop): 1 10
Enter an offset (0-9) and a value. (-1 to stop): 2 20
Enter an offset (0-9) and a value. (-1 to stop): 3 30
Enter an offset (0-9) and a value. (-1 to stop): 4 40
Enter an offset (0-9) and a value. (-1 to stop): 5 50
Enter an offset (0-9) and a value. (-1 to stop): 6 60
Enter an offset (0-9) and a value. (-1 to stop): 7 70
Enter an offset (0-9) and a value. (-1 to stop): 8 80
Enter an offset (0-9) and a value. (-1 to stop): 9 90
Enter 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).
Cilj 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";

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();
}
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 Array
5: {
6: public:
7: // constructors
8: Array(int itsSize = DefaultSize);
9: Array(const Array &rhs);
10: ~Array() { delete [] pType;}
11:
12: // operators
13: Array& operator=(const Array&);
14: int& operator[](int offSet);
15: const int& operator[](int offSet) const;
16:
17: // accessors
18: int GetitsSize() const { return itsSize; }
19:
20: // friend function
21: friend ostream& operator<< (ostream&, const Array&);
22:
23: class xBoundary {}; // define the exception class
24: private:
25: int *pType;
26: int itsSize;
27: };
28:
29:
30: Array::Array(int size):
31: itsSize(size)
32: {
33: pType = new int[size];
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) const
71: {
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: {
88: Array intArray(20);
89: try
90: {
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
5: {
6: public:
7: // constructors
8: Array(int itsSize = DefaultSize);
9: Array(const Array &rhs);
10: ~Array() { delete [] pType;}
11:
12: // operators
13: Array& operator=(const Array&);
14: int& operator[](int offSet);
15: const int& operator[](int offSet) const;
16:
17: // accessors
18: int GetitsSize() const { return itsSize; }
19:
20: // friend function
21: friend ostream& operator<< (ostream&, const Array&);
22:
23: // define the exception classes
24: 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 MFC
41: }
42:
43:
44: const int& Array::operator[](int offSet) const
45: {
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 MFC
52: }
53:
54:
55: Array::Array(int size):
56: itsSize(size)
57: {
58: if (size == 0)
59: 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: try
78: {
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.

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";
}
}
Lekcija1

Kviz
1. 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žbe
1. 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?


Ovaj program ispisuje poruku Hello World na ekran, te potom prebacuje pokazivač u novu liniju
(carriage return).
Lekcija 2

Kviz
1. 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žbe
1. 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.


Lekcija 3

Kviz
1. 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 Dobra
b. !ex Nedozvoljeno
c. R79J Dozvoljeno, ali loš odabir imena
d. TotalIncome Dobra
e. __Invalid Dozvoljeno, ali loš odabir

Vježbe
1. Koji bi bio pravi tip varijable za pohranu slijedećih informacija?
a. Vaše godine. Unsigned short integer
b. Poštanski broj vašega grada. Unsigned long integer or unsigned float
c. Broj zvijezda u galaksiji. Unsigned double
d. Prosječna količina padalina za siječanj. Unsigned short integer
Izmislite dobra imena varijabli za te informacije.
a. myAge
b. backYardArea
c. StarsInGalaxy
d. 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.


float myPi = PI;
Lekcija 4

Kviz
1. Š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 FALSE
b. 1 TRUE
c. -1 TRUE
d. x = 0 FALSE
e. x == 0 // pretpostavimo da x ima vrijednost 0 TRUE

Vježbe
1. 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;
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: else
21: 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.
Lekcija 5

Kviz
1. 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.
Vježbe
1. 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.
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);
}
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;
}

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));
}
Lekcija 6

Kviz
1. Š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.
Vježbe
1. 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.";
}

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.
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.
Lekcija 7

Kviz
1. 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žbe
1. Š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";
}
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 = 0
while (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.
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
case 2: // fall through
case 3: // fall through
case 4: // fall through
case 5:
doOneToFive();
break;
default:
doDefault();
break;
}
Case 0 vjerojatno treba break naredbu. Ako ne treba, trebalo bi dokumentirati to nekakvim
komentarom.
Lekcija 8

Kviz
1. 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žbe
1. Što ove deklaracije rade?
a. int * pOne; deklarira pokazivač na integer
b. int vTwo; deklarira integer varijablu
c. 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;
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;
}
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 (*).
Lekcija 9

Kviz
1. 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žbe
1. 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;
}
3. 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.
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);
}
Lekcija 10

Kviz
1. 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.
Vježbe
1. 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 increment
const SimpleCircle SimpleCircle::operator++ (int)
{
// declare local SimpleCircle and initialize to value of *this
SimpleCircle temp(*this);
++(itsRadius);
return temp;
}
5. 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 increment
const 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);
}
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);
}
SimpleCircle& 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 increment
const 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;
}
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());
}
Lekcija 11

Kviz
1. 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žbe
1. 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;
};
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].
Lekcija 12

Kviz
1. Š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žbe
1. 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):
Rectangle(length, length){}
4. 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.
Lekcija 13

Kviz
1. Š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.
Vježbe
1. 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();
};
Lekcija 14

Kviz
1. 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ž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.
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()
{}
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.
#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;
}
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;
}
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.
#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;
}
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()
{
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;
}
Lekcija 15

Kviz
1. Š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.
Vježbe
1. 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;
}
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] << " ";
}
Lekcija 16

Kviz
1. 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ž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;
};
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


{
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>


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.
Lekcija 17

Kviz
1. Š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žbe
1. 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;
}
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.
SADRŽAJ

Lekcija 1 Uvod u C++


Kratka povijest C++ .................................................................................................. 3
Programi .................................................................................................................... 3
Proceduralno, strukturirano i objektno-orijentirano programiranje .......................... 3
Razvojni ciklus .......................................................................................................... 5

Lekcija 2 Dijelovi C++ programa


Dijelovi C++ programa ............................................................................................. 7
Komentari ................................................................................................................. 8
Tipovi komentara ...................................................................................................... 8
Komentari na vrhu datoteke ...................................................................................... 9
Funkcije ................................................................................................................... 10
Upotreba funkcija .................................................................................................... 10

Lekcija 3 Varijable i konstante


Varijable i konstante ................................................................................................ 12
Što je varijabla? ....................................................................................................... 12
Rezerviranje memorije ............................................................................................ 12
Veličina varijabli ..................................................................................................... 13
Integeri s predznakom i bez njega (engl. signed i unsigned) .................................. 13
Osnovni tipovi varijabli .......................................................................................... 14
Definiranje varijable ............................................................................................... 14
Osjetljivost na velika i mala slova .......................................................................... 15
Ključne riječi ........................................................................................................... 15
Stvaranje više varijabli odjednom ........................................................................... 15
Dodjeljivanje vrijednosti vašim varijablama .......................................................... 16
typedef ..................................................................................................................... 16
Kada koristiti short a kada long .............................................................................. 17
Znakovi ................................................................................................................... 18
Specijalni znakovi ................................................................................................... 18
Konstante ................................................................................................................ 19
Enumirane konstante ............................................................................................... 19
Lekcija 4 Izrazi i naredbe
Naredbe ................................................................................................................... 21
Umetanje praznina .................................................................................................. 21
Blokovi naredbi ....................................................................................................... 21
Izrazi ....................................................................................................................... 22
Složeni izrazi ........................................................................................................... 22
Operatori ................................................................................................................. 22
Operator pridruživanja ............................................................................................ 23
Matematički operatori ............................................................................................. 23
Cijelobrojno dijeljenji i moduli ............................................................................... 23
Kombiniranje pridruživanja i matematičkih operatora ........................................... 24
Inkrement i dekrement ............................................................................................ 24
Perfiks i potfiks ....................................................................................................... 24
Redosljed izvođenja operatora u složenim naredbama ........................................... 26
Gnježdenje zagrada ................................................................................................. 26
Priroda istina ........................................................................................................... 27
Relacijski operatori ................................................................................................. 27
if naredba ................................................................................................................ 27
else .......................................................................................................................... 29
Kompleksne if naredbe ........................................................................................... 30
Upotreba zagrada unutar ugnježdenih if-ova .......................................................... 31
Logički operatori ..................................................................................................... 32
Logički I (engl. AND) ............................................................................................. 32
Logički ILI ...............................................................................................................32
Logički NOT ........................................................................................................... 32
Relacijski prioriteti .................................................................................................. 32
Kondicionalni operator ........................................................................................... 33

Lekcija 5 Funkcije
Što je funkcija ......................................................................................................... 36
Deklaracij i definicija funkcije ................................................................................ 36
Deklariranje funkcije .............................................................................................. 36
Funkcijski prototipovi ............................................................................................. 37
Lokalne varijable .................................................................................................... 39
Globalne varijable ................................................................................................... 40
Globalne varijable: Upozorenje .............................................................................. 41
Upotreba funkcija kao parametara drugim funkcijama .......................................... 42
Parametri su lokalne varijable ................................................................................. 42
Povratne vrijednosti ................................................................................................ 43
Default parametri .................................................................................................... 44
Preopterećenje funkcija ........................................................................................... 45
Umetnute (engl.inline) funkcije .............................................................................. 48
Rekurzija ................................................................................................................. 49
Lekcija 6 Osnovne klase
Osnovne klase ......................................................................................................... 53
Stvaranje novih tipova ............................................................................................ 53
Zašto stvarati novi tip? ............................................................................................ 53
Klase i članovi ......................................................................................................... 53
Deklariranje klase ................................................................................................... 54
Definiranje objekta .................................................................................................. 54
Klase i objekti ......................................................................................................... 54
Pristupanje članovima klase .................................................................................... 54
Ako ne deklarirate, klasa neće imati ....................................................................... 55
Privatni protiv javnih .............................................................................................. 55
Načiniti podatkovne članove privatnima ................................................................ 57
Privatnost protiv sigurnosti ..................................................................................... 58
Implementacija metoda klase .................................................................................. 59
Konstruktori i destruktori ........................................................................................ 60
Podrazumijevani konstruktori i destruktori ............................................................ 61
Konstantni funkcijski članovi ................................................................................. 63
Sučelje protiv implementacije ................................................................................. 63
Zašto koristiti kompajler za hvatanje pogrešaka? ................................................... 65
Gdje staviti deklaracije klasa i definicije metoda ................................................... 65
Inline implementacija .............................................................................................. 66
Klase s drugim klasama kao podatkovnim članovima ............................................ 68
Strukture .................................................................................................................. 70
Zašto dvije ključne riječi rade istu stvar ................................................................. 70

Lekcija 7 Još o programskom toku


Petlje ....................................................................................................................... 72
Korijeni petlje: goto naredba .................................................................................. 72
Zašto je goto prognan ............................................................................................. 73
while petlja .............................................................................................................. 73
while naredba .......................................................................................................... 74
Još kompliciranje while naredbe ............................................................................. 74
continue i break ....................................................................................................... 75
while (1) petlja ........................................................................................................ 76
do…while petlja ...................................................................................................... 77
do…while naredba ................................................................................................... 79
for petlja .................................................................................................................. 79
Napredne for petlje ................................................................................................. 81
Prazno tijelo for petlje ............................................................................................. 82
Ugnježdene petlje .................................................................................................... 83
Sažetak: petlje ......................................................................................................... 84
switch naredbe ......................................................................................................... 84
Uporaba switch naredbe za menije ......................................................................... 86
Lekcija 8 Pokazivači
Što je pokazivač ...................................................................................................... 90
Spremanje adrese u pokazivač ................................................................................ 91
Imena pokazivača .................................................................................................... 92
Operator indirekcije ................................................................................................ 92
Pokazivači, adrese i varijable .................................................................................. 92
Manipuliranje podacima upotrebom pokazivača .................................................... 93
Ispitivanje adresa .................................................................................................... 94
Pokazivači ............................................................................................................... 95
Zašto koristiti pokazivače? ..................................................................................... 95
Stog i slobodna memorija ....................................................................................... 95
delete ....................................................................................................................... 97
Curenje memorije .................................................................................................... 98
Stvaranje objekata u slobodnom spremištu ............................................................. 99
Brisanje objekata ..................................................................................................... 99
Pristupanje podatkovnim članovima ..................................................................... 100
Podatkovni članovi na slobodnom spremniku ...................................................... 101
this pokazivač ....................................................................................................... 102
”Viseći” pokazivači (engl.stray or dangling pointers) .......................................... 103
const pokazivači .................................................................................................... 103
const pokazivači i const funkcijski članovi .......................................................... 104
const this pokazivači ............................................................................................. 105

Lekcija 9 Reference
Što je referenca ..................................................................................................... 107
Upotreba operatora adrese & na referencama ....................................................... 108
Što može biti referencirano ................................................................................... 110
Reference ............................................................................................................... 111
Nul pokazivači i nul reference .............................................................................. 111
Prosljeđivanje argumentat funkcije po referenci ................................................... 111
Pravljenje swap() funkcije s pokazivačima ........................................................... 113
Implementiranje swap() s referencama ................................................................. 114
Razumijevanje funkcijskih zaglavlja i prototipova ............................................... 115
Vraćanje višestrukih vrijednosti ............................................................................ 115
Vraćanje vrijednosti po referenci .......................................................................... 116
Proslijeđivanje po referencama za efikasnost ....................................................... 117
Proslijeđivanje const pokazivača .......................................................................... 119
Reference kao alternativa ...................................................................................... 121
const reference ...................................................................................................... 122
Kada koristiti reference a kada pokazivače .......................................................... 122
Miješanje referenci i pokazivača .......................................................................... 123
Ne vraćajte referencu na objekt koji nije u dosegu! ............................................. 123
Vraćanje reference na objekt u slobodnom spremištu .......................................... 124
Lekcija 10 Napredne funkcije
Napredne funkcije ................................................................................................. 127
Preopterećeni funkcijski članovi ........................................................................... 127
Upotreba podrazumijevanih vrijednosti ................................................................ 129
Odabir između podrazumijevanih vrijednosti i preopterećenih funkcija .............. 131
Podrazumijevani konstruktor ................................................................................ 131
Preopterećenje konstruktora .................................................................................. 131
Inicijaliziranje objekta .......................................................................................... 133
Konstruktor kopiranja ........................................................................................... 133
Preopterećenje operatora ....................................................................................... 136
Pisanje funkcije inkrementiranja ........................................................................... 137
Preopterećeni prefix operator ................................................................................ 138
Povratni tipovi u preopterećenim operatorskim funkcijama ................................. 139
Vraćanje bezimenih privremenih .......................................................................... 140
Upotreba this pokazivača ...................................................................................... 141
Preopterećenje postfiks operatora ......................................................................... 143
Razlika između prefiksa i postfiksa ...................................................................... 143
Operator zbrajanja ................................................................................................. 144
Preopterećeni operator + ....................................................................................... 146
Preopterećeni binarni operatori ............................................................................. 147
Operator pridruživanja .......................................................................................... 147
Operatori konverzije ............................................................................................. 149
Operatori konverzije ............................................................................................. 151

Lekcija 11 Polja
Što je polje? ........................................................................................................... 154
Elementi polja ....................................................................................................... 154
Pisanje nakon kraja polja ...................................................................................... 155
"Fence Post" pogreške .......................................................................................... 157
Inicijaliziranje polja .............................................................................................. 157
Deklaracija polja ................................................................................................... 158
Polja ...................................................................................................................... 158
Polja objekata ........................................................................................................ 159
Višedimenzionalna polja ....................................................................................... 160
Inicijaliziranje višedimenzionalnog polja ............................................................. 160
Riječ o memoriji ................................................................................................... 161
Nizovi pokazivača ................................................................................................. 162
Deklariranje polja u slobodnom spremniku .......................................................... 163
Pokazivač na polje protiv polja pokazivača .......................................................... 163
Pokazivači i imena polja ....................................................................................... 164
Brisanje polja u slobodnom spremniku ................................................................. 165
Polja znakova ........................................................................................................ 165
strcpy() i strncpy() ................................................................................................. 167
String klase ............................................................................................................ 168
Vezane liste i ostale strukture ................................................................................ 173
Lekcija 12 Naslijeđivanje
Što je naslijeđivanje? ............................................................................................ 177
Naslijeđivanje i derivacija ..................................................................................... 177
Životinjsko carstvo ................................................................................................ 178
Sintaksa derivacije ................................................................................................ 178
Privatne protiv zaštićenih ...................................................................................... 179
Konstruktori i destruktori ...................................................................................... 180
Proslijeđivanje argumenata baznim konstruktorima ............................................. 182
Zaobilaženje funkcija ............................................................................................ 186
Skrivanje metoda osnovne klase ........................................................................... 187
Zaobilaženje protiv skrivanja ................................................................................ 188
Pozivanje bazne metode ........................................................................................ 188
Virtualne metode ................................................................................................... 190
Kako virtualne funkcije rade ................................................................................. 193
Ne možete stići tamo odavde ................................................................................ 193
Rezanje .................................................................................................................. 194
Virtualni destruktori .............................................................................................. 195
Virtualni konstruktori kopije ................................................................................. 195

Lekcija 13 Polimorfizam
Problemi s jednostrukim naslijeđivanjem ............................................................. 199
Višestruko naslijeđivanje ...................................................................................... 201
Dijelovi višestruko naslijeđenog objekta .............................................................. 203
Konstruktori u višestruko naslijeđenim objektima ............................................... 203
Problem dvosmislenosti ........................................................................................ 205
Naslijeđivanje iz dijeljene bazne klase ................................................................. 206
Virtualno naslijeđivanje ........................................................................................ 209
Deklariranje klasa za virtualno naslijeđivanje ...................................................... 211
Abstraktni tipovi podataka (Abstract Data Types-ADT) ...................................... 211
Čiste virtualne funkcije ......................................................................................... 214
Implementiranje čistih virtualnih funkcija ............................................................ 215

Lekcija 14 Specijalne klase i funkcije


Statički podatkovni članovi ................................................................................... 219
Statički funkcijski članovi ..................................................................................... 223
Statički funkcijski članovi ..................................................................................... 224
Pokazivači na funkcije .......................................................................................... 225
Zašto koristiti pokazivače na funkcije? ................................................................. 227
Polja pokazivača na funkcije ................................................................................ 229
Proslijeđivanje pokazivača na funkcije u druge funkcije ..................................... 230
Upotreba typedef s pokazivačima na funkcije ...................................................... 231
Pokazivači na funkcijske članove ......................................................................... 233
Polja pokazivača na funkcijske članove ............................................................... 235

Lekcija 15 Tokovi
Osnovno o tokovima ............................................................................................. 238
Enkapsulacija ........................................................................................................ 238
Buffering ............................................................................................................... 238
Tokovi i bufferi ..................................................................................................... 239
Standardni I/O objekti ........................................................................................... 239
Input upotrebom cin .............................................................................................. 239
Stringovi ................................................................................................................ 240
Problemi sa stringovima ....................................................................................... 241
Ostali funkcijski članovi cin ................................................................................. 242
Jednostuki znakovni input ..................................................................................... 243
Dobijanje stringova iz standardnog inputa ........................................................... 244
Upotreba cin.ignore() ............................................................................................ 245
peek() i putback() .................................................................................................. 246
Pražnjenje izlaza ................................................................................................... 247
Povezane funkcije ................................................................................................. 247
Upotreba cout.width() ........................................................................................... 248
Postavljanje Fill znakova ...................................................................................... 248
Postavljanje zastavica ........................................................................................... 249
Tokovi protiv printf() funkcije .............................................................................. 250
Datotečni ulaz i izlaz ............................................................................................. 251
ofstream ................................................................................................................. 251
Uvjetna stanja ........................................................................................................ 251
Otvaranje datoteka za ulaz/izlaz ........................................................................... 251
Promjena podrazumijevanog ponašanja za ofstream ........................................... 252
Binarni protiv tekst datoteka ................................................................................. 254
Obrada komandne linije ........................................................................................ 255

Lekcija 16 Predlošci
Definicija predloška ...............................................................................................258
Implementiranje predloška .....................................................................................259
Funkcijski predlošci ...............................................................................................262
Prijateljska klasa ili funkcija koja nije predložak ..................................................262
Opći prijateljski predložak ili funkcijski predložak ...............................................264
Prijateljski predložak ili funkcijski predložak specifičnoga tipa ...........................266
Upotreba predlošaka ...............................................................................................266
Standardna biblioteka predložaka ..........................................................................269

Lekcija 17 Iznimke i baratanje pogreškama


Bugovi, pogreške, zablude i truli kod ................................................................... 271
Iznimke ................................................................................................................. 271
Što su iznimke ....................................................................................................... 271
Kako se iznimke koriste ........................................................................................ 271
Specificiranje više od jednog catch ..................................................................... 274
Bugovi i debugiranje ............................................................................................. 276

C++: Pitanja i Odgovori


Lekcija 1 ................................................................................................................ 279
Lekcija 2 ................................................................................................................ 281
Lekcija 3 ................................................................................................................ 282
Lekcija 4 ................................................................................................................ 283
Lekcija 5 ................................................................................................................ 285
Lekcija 6 ................................................................................................................ 289
Lekcija 7 ................................................................................................................ 293
Lekcija 8 ................................................................................................................ 296
Lekcija 9 ................................................................................................................ 298
Lekcija 10 .............................................................................................................. 301
Lekcija 11 .............................................................................................................. 307
Lekcija 12 .............................................................................................................. 309
Lekcija 13 .............................................................................................................. 311
Lekcija 14 .............................................................................................................. 313
Lekcija 15 .............................................................................................................. 319
Lekcija 16 .............................................................................................................. 322
Lekcija 17 .............................................................................................................. 326

You might also like