Programski jezici, C++
II Programiranje pomoüu programskog jezika C++ Da bismo napisali program, neophodna su nam dva specijalizovana programa: jedan koji koristimo da napišemo izvornu datoteku, i drugi (kompajler) sa kojim dobijamo izvršnu datoteku. Danas se ova dva prgrama najþešüe kombinuju u jedinstven paket – razvojno okruženje. Najpoznatije C++ okruženje predstavlja Microsoftov proizvod Visual C++.NET. Meÿutim, veüina ovakvih programa je komercijalna i nije dostupna svim korisnicima. Ipak, moguüe je naüi veliki broj besplatnih C++ okruženja. Jedno od takvih okruženja je i Dev-C++. Najnoviju verziju pomenutog programa moguüe je naüi na web stranici firme BloodshedSoftware (http://bloodshed.net/download.html tj. http://bloodshed.net/dev/devcpp.html ).
1 Programski paket Dev-C++ 1.1 Instalacija Instalacija Dev-C++ programa ni u þemu se ne razlikuje od veüine instalacionih datoteka u Windows okruženjima. Dovoljno je dvostrukim klikom pokrenuti instalacionu datoteku (npr. devcpp-4.9.9.2_setup.exe) i pratiti poruke na ekranu. Nakon instalacije preporuþljivo je podesiti neke opcije, mada to nije od velike važnosti za programe koji üe se koristiti na ovom kursu. Ipak, u meniju Tools, i podmeniju Compiler options, prvo treba izabrati opciju Code Generation. U prozoru koji se dobije opciju Enable Exception Handling treba aktivirati (staviti na yes). Zatim treba izabrati opciju Linker i aktivirati Generate Debugging Information. Na ovaj naþin instalacija je završena. 1.2 Kreiranje prvog programa 1.2.1 Upisivanje koda Nakon pokretanja programa Dev-C++, prvi korak u kreiranju koda je otvaranje novog zadatka. Dev-C++ pruža više opcija, ali üe se u ovom kursu uglavnom raditi na jednostavnim datotekama sa izvršnim kodom (opcija File/New/Source File). Nakon odabira ove opcije, otvara se prozor u koji treba upisati kod, na primjer #include using namespace std; int main() { cout << “Hello, world!” << endl; system("PAUSE"); return 0; }
Nakon upisa kod saþuvamo na nekom mjestu na hard disku, npr. pod imenom hello.cpp
1
Programski jezici, C++ 1.2.2 Generisanje izvršne datoteke Nakon što se saþuva ovaj kod, treba ga kompajlirati, tj. proizvesti izvršnu mašinsku datoteku. Ovo se izvodi korištenjem kombinacije tipki Ctrl+F9 na tastaturi, ili izborom opcije Compile u meniju Execute, ili pritiskom na ikonu u nizu alata. Nakon startanja procesa kompajliranja, pojavljuje se prozor sa porukama koje prate proces kompajliranja. Dev-C++ daje poruku u sluþaju da naÿe bilo kakvu grešku u programu. U sluþaju da nema grešaka, stvara se izvršna datoteka koja se naziva hello.exe. 1.3 Pokratanje programa Pokretanje programa, koji smo prethodno kompajlirali, u Dev-C++ okruženju izvodi se izborom opcije Run u meniju Execute, kombinacijom tipki Ctrl+F10 na tastaturi, ili pritiskom na ikonu Prethodna dva procesa (kompajliranje i pokretanje) moguüe je objediniti pritiskom na tipku F9 na tastaturi, izborom opcije Compile&Run u meniju Execute, ili izborom ikone . Program je moguüe pokrenuti i van Dev-C++ okruženja, dvostrukim klikom na izvršnu datoteku hello.exe.
2 Osnove programiranja 2.1 Struktura programa C++ program se sastoji od jedne ili više cjelina za prevoÿenje, pri þemu ove cjeline predstavljaju dio programa koji treba kompajlirati odvojeno od drugih cjelina. Taþnije, cjelina za prevoÿenje je rezultat primjene preliminarne faze kompilacije, koja se naziva predprocesiranje, na izvornu datoteku (source). Izvorna datoteka obiþno poþinje sa jednom ili više (predprocesorskih) direktiva #include, pri þemu svaka od njih navodi predprocesor da kopira deklaracije entiteta (funkcija, globalnih varijabli, tipova, itd), koji su definisani u ostalim cjelinama za prevoÿenje. Posmatrajmo primjer iz prethodnog poglavlja: 1 2 3 4 5 6 7 8
#include using namespace std; int main() { cout << “Hello, world!” << endl; system("PAUSE"); return 0; }
U liniji 1 pozvana je datoteka iostream. Prvi karakter (#) predstavlja symbol, koji daje signal predprocesoru. Svaki put kada pokrenemo kompajler, predprocesor je veü pokrenut. U principu, predprocesor þita kroz izvornu datoteku i traži linije koje poþinju sa ovim karakterom, tako da ih predprocesor proÿe prije nego kompajler starta sa radom. Ukratko ova linija znaþi: Ono što slijedi je 2
Programski jezici, C++ ime datoteke. Naÿi tu datoteku i odmah je proþitaj. Uglaste zagrade (<>) daju naredbu predprocesoru da naÿe zadatu datoteku koja je dio standardne biblioteke (u datom primjeru to je datoteka koja sadrži definicije za ispis i upis). U sluþaju kada bismo htjeli uvrstiti neku svoju datoteku, umjesto zagrada bismo koristili znake navoda. Dakle, ova linija kaže predprocesoru da naÿe datoteku koja se zove iostream i da je odmah proþita. Naravno, sadržaj tražene datoteke bismo mogli upisati u izvornu datoteku bez korištenja direktive #include. Linija 2 omoguüuje pristup standardnom entitetu (namespace) koji se naziva std. Bez ove linije, linija 5 bi se morala izvršiti na drugaþiji naþin (std::cout << ....) Linijom 3 poþinje stvarni program sa funkcijom koja se naziva main(). Svaki C++ program sadrži ovu funkciju. Funkcija predstavlja dio koda koji odraÿuje odreÿenu radnju. Inaþe, program može da ima proizvoljan broj funkcija, pri þemu je funkcija main() specijalna. Kada god se program starta, ona se automatski poziva. Sve funkcije poþinju zagradom { i završavaju zagradom }, a sve izmeÿu ovih zagrada predstavlja dio funkcije. Glavni dio programa je linija 5, koja predstavlja neku naredbu, tj. raþunarski korak koji daje neku vrijednost. Kraj naredbe uvijek zavrþava taþka-zarezom. Naredba u datom primjeru šalje string "Hello world \n" na tok cout (output stream). String je svaki niz karaktera koji se nalazi izmeÿu znaka navoda. Posljednji karakter u datom stringu (\n) je karakter koji oznaþava novi red (vidi poglavlje II.2.10). Stream je objekat koji izvršava ulazne i izlazne naredbe. cout je standardni izlazni stream u C++ (standardni izlazni stream je obiþno ekran). Simbol << je izlazni operator (usmjerivaþ toka) kojem je lijevi operand izlazni stream, a desni izraz, i koji uzrokuje da se ovaj posljednji posalje na prvopomenuti. Dakle, u ovom sluþaju string "Hello world \n" se šalje na cout, tj. uzrokuje njegov ispis na ekranu. Linija 6 zaustavlja izvršenje programa, kako bismo bili u moguünosti vidjeti rezultat njegovog rada. Bez ove linije program bi se nakon pokretanja izvršio, a konzola bi se zatvorila tako brzo da bismo imali osjeüaj da program nije ništa ni uradio.
2.2 Proces kompajliranja Kompajliranje C++ programa obuhvata nekoliko koraka, koji su veüinom nevidljivi za korisnika: x prvo, C++ predprocecsor ide kroz program i izvodi instrukcije koje su specificirane predprocesorskim direktivama (npr. #include). Rezultat ovoga je modificirani tekst programa koji više ne sadrži nikakve direktive. x zatim, C++ kompajler prevodi programski kod. Kompajler može biti pravi C++ kompajer koji pravi osnovni (asemblerski ili mašinski) kod, ili samo prevodilac, koji kod prevodi u C jezik. U drugom sluþaju, rezultujuüi C kod je zatim proveden kroz C kompajler kako bi se napravio osnovni kod. U oba sluþaja, rezultat može biti nepotpun zbog toga što program poziva podprogramske biblioteke koje nisu definisane u samom programu. x Na kraju, linker završava objektni kod njegovim povezivanjem sa objektnim kodom bilo kojeg modula biblioteka koji program može pozvati. Konaþan rezultat je izvršna datoteka. Slika II.2.1 ilustruje prethodno navedene korake i za C++ prevodilac i za C++ prirodni kompajler. U praksi su sve ove komande obiþno izvršene jednom komandom (npr. CC), a korisnik ni ne vidi datoteke koje su se napravile u meÿufazama.
3
Programski jezici, C++ C++ program
C++ prevodilac
C++ program
C++ prirodni kompajler
C kod
C kompajler
Objektni kod
linker
Izvršna datoteka
Slika II.2.1 C++ kompilacija 2.3 Varijable Varijabla je simboliþko ime za memorijsku lokaciju u koju se mogu pohraniti podaci i naknadno ih pozvati. Varijable se koriste za þuvanje vrijednosti podataka tako da se iste mogu koristiti u raznim proraþunima u programu. Sve varijable imaju dvije važne osobine: x Tip, koji se postavlja kada se varijabla definiše (npr. cijeli broj, realni broj, karakter, ...) Kada se jednom definiše, tip varijable u C++ se ne može promijeniti. x Vrijednost, koja se može promijeniti davanjem nove vrijednosti varijabli. Vrsta vrijednosti koja se može pridružiti nekoj varijabli zavisi od njenog tipa. Na primjer, integer varijabla može da uzima samo vrijednosti cijalih brojeva (npr. -5, 13, ..) Kada se varijabla definiše, njena vrijednost je nedefinisana sve dok joj se ne pridruži neka. Pridruživanje vrijednosti nekoj varijabli po prvi put naziva se inicijalizacija. Neophodno je obezbijediti da se svaka varijabla inicijalizira prije nego se koristi. Takoÿer je moguüe da se varijabla definiše i inicializira u isto vrijeme, što je vrlo praktiþno. Naredni primjer pokazuje razliþite naþine definisanja i inicijaliziranja varijabli. #include using namespace std; main() { int a,b,c; float x = 4.32; int e,f,g; char ime; e = 4; f = g = 12; ime = 'C' }
2.4 Memorija Za pohranjivanje izvršnog koda kao i podataka sa kojima progam manipuliše, kompjuter ima na raspolaganju RAM memoriju (Read Access Memory). Memorija se može zamisliti kao neprekidan 4
Programski jezici, C++ niz bita, od kojih svaki može da pohrani binarni broj (0 ili 1). Obiþno je memorija podijeljena na grupe od 8 uzastopnih bita (ovo predstavlja bajt, byte). Bajtovi su uzastopno adresirani, tako da je svaki bajt jedinstveno predstavljen svojom adresom (Slika II.2.2). adresa
...
1211
1212
1213
1214
1215
1216
1217
bajt
bajt
bajt
bajt
bajt
bajt
bajt
1
1
0
1
0
0
0
...
Memorija
1
bit
Slika II.2.2 Bitovi i bajtovi u memoriji C++ kompajler generira izvršni kod, koji mapira ulazne veliþine na memorijske lokacije. Na primjer, definicija varijable int zarada = 500;
navodi kompajler da alocira nekoliko bajta kako bi predstavio varijablu zarada. Taþan broj bajta koji je alociran i metod koji se koristi za binarnu reprezentaciju cijelog broja zavisi od specifiþnosti C++ implementacije, ali uzmimo da se radi o 2 bajta. Kompajler koristi adresu prvog bajta na koju se alocira zarada kako bi oznaþio varijablu. Prethodna jednakost uzrokuje da se vrijednost 500 pohrani u ova dva bajta koja su alocirana (Slika II.2.3)
...
1211
1212
1213
bajt
bajt
bajt
1214
1215
10110011 10110011
1216
1217
bajt
bajt
...
Memorija
zarada
Slika II.2.3 Reprezentacija cijelog broja u memoriji Treba napomenuti da je organizacija memorije i korištenje adresa koji se odnose na podatke veoma važno za programera, dok taþna binarna reprezentacija podataka koje on koristi to nije.
2.5 Ulazno/izlazne naredbe Najþešüi naþin na koji program komunicira sa vanjskim svijetom je preko jednostavnih ulazno/izlaznih (IO) operacija. C++ omoguüuje dva korisna operatora za ovu svrhu: >> za ulaz, i << za izlaz. U ranijem tekstu pokazana je upotreba operatora <<. Naredni primjer pokazuje upotrebu operatora >>.
5
Programski jezici, C++ 1 2
#include using namespace std;
3 4 5 6 7
int main (void) { int radniDani = 22; float radniSati = 7.5; float satnica, plata; cout << "Kolika je satnica? "; cin >> satnica;
8 9 10 11 12 13 14
plata = cout << cout << cout <<
radniDani * radniSati * satnica; "Plata = "; plata; '\n';
}
Linija 9 þita ulaznu vrijednost, koju unosi korisnik i kopira je u satnica. Ulazni operator >> uzima ulazni stream kao lijevi operand (cin je standardni C++ ulazni stream koji odgovara podacima unesenim pomoüu tastature), a varijablu (na koju se kopira ulazni podatak) kao desni operand. 2.6 Komentari Komentar je dio opisnog teksta koji objašnjava neke aspekte programa. Kompajler u potpunosti ignoriše komentare u programu, tako da je jedina svrha koju komentar ima, da pomognu onome koji üe þitati program. C++ daje dvije moguünosti pisanja komentara: x Bilo šta napisano nakon //, pa do kraja date linije smatra se komentarom x Bilo šta napisano izmeÿu /* i */ smatra se komentarom. 1 *1 2 3 4 5 6 7 8 9 10 11 12 13
#include using namespace std; /* Ovaj program racina ukupnu platu radnika, koja se zasniva na ukupnom broju radnih sati i satnici. */ int main (void) { int radniDani = 22; float radniSati = 7.5; float satnica = 33.50; float plata;
// // // //
Broj radnih dana u mjesecu Broj radnih sati u danu Satnica Ukupna mjesecna plata
plata = radniDani * radniSati * satnica; cout << "Plata = " << plata << '\n'; }
Jasno da je da se prvi primjer može koristiti za komentar jedne i samo jedne linije (ili dijela jedne linije), dok se posljednjim može komentarisati tekts upisan u više linija.
2.7 Imena Programski jezici koriste imena kako bi se oznaþile razliþite cjeline koje þine program. Osim imena varijabli, ovdje spadaju i imena funkcija, tipova, te makroa. C++ postavlja sljedeüa pravila za 6
Programski jezici, C++ pravilno kreiranje imena (ili identifikatora). Ime traba da se sastoji od jednog ili više karaktera, od kojih bilo koji može biti slovo (tj, slova engleske abecede a-z i A-Z), broj (0-9) i znak "_", pri þemu na prvom mjestu ne može da bude broj. Uz to, velika i mala slova se razlikuju, tako da se, na primjer, varijable zarada i Zarada razlikuju. C++ ne postavlja nikakvo ograniþenje na broj karaktera u nekom identifikatoru. Meÿutim, veüina implementacija ima ovo ograniþenje, ali je ono toliko veliko da ne predstavlja nikakav problem (npr. i do 255 karaktera). Treba imati na umu da postoje odrežene rijeþi u C++ koje su rezervisane, tako da identifikatori ne mogu uzimati njihova imena. Te rijeþi se nazivaju rezervisane ili kljuþne rijeþi i date su u tabeli Tabela II.2.1 Kljuþne (rezervisane) rijeþi u C++ asm auto break case catch char class const
continue default delete do double else enum extern
float for friend goto if inline int long
new operator private protected public register return short
signed sizeof static struct switch template this throw
try typedef union unsigned virtual void volatile while
2.8 Cijeli brojevi Cijeli broj (integer) se može definisati pomoüu tipova short, int i long. Jedina razlika je u tome da int koristi više ili barem isto bajta kao short, a long koristi više ili barem isto bajta nego int. Ovo zavisi od raþunara na kojem se radi. Konvencija je da cjelobrojne varijable uzimaju u obzir i pozitivne i negativne brojeve (kaže se da su signed). Meÿutim, u sluþaju kada se koriste kao unsigned, onda mogu uzeti samo pozitivne vrijednosti (sa 0).
2.9 Realni brojevi Realni broj se može definisati sa tipom float i double. double koristi više bajta i time omoguüuje veüu opseg i veüu taþnost pri predstavljanju realnih brojeva. 2.10 Karakteri Varijabla karakter se definiše tipom char. Ona obuhvata jedan jedini bajt koji sadrži kod datog karaktera. Ovaj broj je numeriþka vrijednost i zavisi od sistema kodiranja karaktera koji se koristi (to je zavisno od mašine). Najþešüi sistem je ACSII (Amercan Standard Code for Information Interchange). Na primjer, karakter A ima ASCII kod 65, a karakter a 97. Kao i interger i karakter može da bude signed i unsigned. Tako, signed karakter može da sadrži numeriþke vrijednosti izmeÿu -128 i 127, a unsigned 0 do 255.
7
Programski jezici, C++ Karakteri koji imaju posebnu namjenu (ne predstavljaju karaktere koji se ispisuju) predstavljaju se pomoüu escape sekvenci, kao npr: ‘\n’ – novi red ‘\t’ – novi tabulator ‘\v’ – novi vertikalni tabulator ‘\b’ – backspace ‘\’’ – znak navoda (apostrof) ‘\”’ – dvostruki znak navoda ‘\\’ – backslash (/) ‘\a’ – zvuþni signal
2.11 Stringovi String je uzastopni niz karaktera koji se završavaju nultim karakterom. Tako je, barem, bilo u jeziku C. Ipak, u C++ uveden je novi tip podataka koji se naziva C++ string klasa, pa je moguüe na mnogo prirodniji naþin manipulisati varijablama sa više karaktera. Sljedeüi primjer pokazuje njenu upotrebu. #include using namespace std; main() { char ch; do { cout << "Pritisnite K ili k za kraj, a bilo koju tipku za nastavak \n"; cin >> ch; if (ch != 'K' && ch != 'k') cout << "Zelite nastaviti?\n"; else cout << "Kraj programa"; } while (ch != 'K' && ch != 'k'); }
Ovaj primjer dobro funkcioniše jer završava program ako se unese "k" ili "K", odnosno program se nastavlja ako se unese bilo koji drugi karakter. Problem nastaje ako korisnik programa pritisne samo tipku ENTER. U tom sluþaju objekat "cin" oþekuje da se unese neka vrijednost, pa tek onda pritisne ENTER.
3. Operatori Ovo poglavlje obraÿuje ugraÿene C++ operatore koji se koriste za stvaranje izraza, pri þemu izraz predstavlja bilo kakav proraþun koji daje neku vrijednost. C++ nudi operatore za izvršavanje aritmetiþkih, ralacijskih, logiþkih, bitwise i uslovnih izraza. Takoÿer nudi veoma korisne “popratne efekte” (side-effect) kao što su pridruživanje, inkrement i dekrement. 3.1 Aritmetiþki operatori C++ nudi pet osnovnih operatora, koji su sumirani u Tabeli II.3.1. 8
Programski jezici, C++ Tabela II.3.1 Aritmetiþki operatori Operator
+ * / %
Ime Sabiranje Oduzimanje Množenje Dijeljenje Ostatak pri dijeljenju
Primjer
12 + 4.9 3.98 - 4 2 * 3.4 9 / 2.0 13 % 3
// // // // //
daje daje daje daje daje
16.9 -0.02 6.8 4.5 1
Osim ostatka pri djeljenju (%) svi aritmetiþki operatori prihvataju miješanje cijelih i realnih brojeva. Opüenito, ako su oba operanda cijeli brojevi, i rezultat je cijeli broj. Meÿutim, ako je jedan od operanada realan, onda je i rezulat realan (tipa double). Kada su oba operanda pri dijeljenju cijeli brojevi, rezultat je takoÿer cijeli broj (tzv. cjelobrojno dijeljenje). U tom sluþaju rezultat se zaokružuje na donju vrijednost, tj. 9 / 2 -9 / 2
// daje 4, a ne 4.5! // daje -5,a ne -4.5!
S obzirom da neželjeno cjelobrojno dijeljenje predstavlja jednu od najþešüih greški u programiranju, neophodno je da promijenimo jedan od operanada da bude realan broj, kao npr. int int double
cijena = 100; volumen = 80; jedinicnaCijena = cijena / (double) volumen;
// daje 1.25
Operator % daje ostatak pri dijeljenju dva cijela broja (oba operanda moraju biti cijeli brojevi), npr. 13%3 daje 1
3.2 Relacijski operatori C++ nudi 6 relacijskih operatora za raþunanje brojnih veliþina (Tabela II.3.2) Tabela II.3.2 Relacijski operatori Operator
== != < <= > >=
Ime Jednakost Nejednakost Manje od Manje ili jednako Veüe od Veüe ili jednako
Primjer
5 == 5 5 != 5 5 < 5.5 5 <= 5 5 > 5.5 6.3 >= 5
// // // // // //
daje daje daje daje daje daje
1 0 1 1 0 1
Treba zapamtiti da se operatori <= i >= mogu korisiti samo u tom obliku, a da =< i => ne znaþe ništa u ovom kontekstu. Operandi nekog relacijskog operatora moraju biti brojevi. No, i karakteri su ispravni operandi pošto predstavljaju brojnu vrijednost (sjetimo se ASCII tabele). Relacijski operaotri se ne smiju korisiti ze poreÿenje stringova, pošto se u tom sluþaju porede njihove adrese, a ne sadržaj. U tom sluþaju, rezlutat je neodreÿen. Ipak, postoje funkcije koje mogu porediti i leksikografsku razliku dva stringa. 9
Programski jezici, C++ 3.3 Logiþki operatori Za kombinovanje logiþkih izraza C++ nudi tri logiþka operatora (Tabela II.3.3). Sliþno relacijskim operatorima, rezultat pri korištenju logiþkih operatora je 0 (false) ili 1 (true). Tabela II.3.3 Logiþki operatori Operator
! && ||
Ime Logiþka negacija Logiþko i Logiþko ili
Primjer
!(5 == 5) 5 < 6 && 6 < 6 5 < 6 || 6 < 5
// daje 0 // daje 1 // daje 1
Logiþka negacija je unarni opearator, tj. ima samo jedan operand kojem daje negativnu vrijednost. 3.4 Inkrementalni i dekrementalni operatori Takozvani auto inkremetalni (++) i auto dekrementalni (--) operatori obezbijeÿuju prigodan naþin za poveüavanje, odnosno smanjivanje brojne varijable za 1. Upotreba ovih operatora je sumirana u Tabeli II.3.4., pri þemu se predpostavlja da je int k = 5;
Tabela II.3.4 Inkrement i dekrement operatori. Operator
++ ++ ---
Ime Inkrement (prefiks) Inkrement (postfiks) Dekrement (prefiks) Dekrement (postfiks)
Primjer
++k k++ --k k--
+ + + +
10 10 10 10
// // // //
daje daje daje daje
16 15 14 15
Kao što se vidi, oba operatora se mogu korisiti u prefiksnom ili postfiksnom obliku. Razlika je velika, jer kada se operator koristi u prefiksnom obliku prvo se primijenjuje operator, a onda se u izrazu koristi rezultat. Kada se koristi u postfiksnom obliku, prvo se raþuna izraz, a onda se primijenjuje operator. Oba operatora se mogu primijeniti kako na cjelobrojne, tako i na realne brojeve, iako se ova karakteristika veoma rijetko koristi na realnim brojevima. 3.5 Operatori pridruživanja Operator pridruživanja se koristi za pohranjivanje vrijednosti na neku memorijsku lokaciju (koja je obiþno pridružena nekoj varijabli). Lijevi operand operatora treba biti neka lijeva_vrijednost, dok desni operand može biti proizvoljni izraz. Desni operand se izraþuna i pridruži lijevoj strani. Pri tome lijeva_vrijednost predstavlja bilo šta što zauzima neku memorijsku lokaciju na koju se može pohraniti neka veliþina, može biti varijabla, te zasnovana na pointerima i referencama. Operator pridruživanja može imati mnogo varijanti, koje se dobijaju njegovim kombinovanjem sa aritmetiþkim i bitwise operatorima. Ove varijante su date u sljedeüoj tabeli.
10
Programski jezici, C++ Tabela 2.3.5 Operatori pridruživanja Operator
= += -= *= /= %=
Primjer
Ekivalentno sa
n n n n n n
n n n n n
= 25 += 25 -= 25 *= 25 /= 25 %= 25
= = = = =
n n n n n
+ * / %
25 25 25 25 25
Kako operator pridruživanja sam po sebi predstavlja izraz þija se vrijednost pohranjuje u lijevi operand, on se može korisititi kao desni operand za narednu operaciju pridruživanja, odnosno može se napisati: int m, n, p; m = n = p = 100; m = (n = p = 100) + 2;
// znaþi: n = (m = (p = 100)); // znaþi: m = (n = (p = 100)) + 2;
m = 100; m += n = p = 10;
// znaþi: m = m + (n = p = 10);
ili
2.6 Uslovni (ternarni) operator Uslovni operator treba tri operanda (odatle ime ternarni). On ima opštu formulu: operand1 ? operand2 : operand3 operand1 se izraþunava, i tretira se kao logiþki uslov. Ako je rezultat razliþit od nule, tada se izraþunava operand2. U suprotnom, izraþunava se operand3. Na primjer: int m = 1, n = 2; int min = (m < n ? m : n);
// min dobija vrijednost 1
Provjeriti šta je rezultat sljedeüe upotrebe uslovnog operatora: int min = (m < n ? m++ : n++);
Postoji još nekoliko vrsta operatora (npr. zarez operator, sizeof operator), ali o njema neüe biti rijeþi u ovom kursu.
4 Naredbe Ovo poglavlje opisuje razne oblike C++ naredbi koje služe za pisanje programa. Kao i veüina ostalih programskih jezika, C++ nudi razliþite vrste naredbi koje se koriste u razliþite svrhe. Tako se deklaracione naredbe koriste za definisanje varijabli, naredbe pridruživanja za jednostavne proraþune, itd. U narednom teksu biüe objašnjene neke od njih.
11
Programski jezici, C++ 4.1 Jednostavne i složene naredbe Jednostavna naredba je svaka naredba koja završava taþka-zarezom. Definicije varijabli i izrazi koji završavaju sa taþka-zarezom su neki primjeri: int i; ++i; double d = 10.5; d + 5;
// // // //
deklaraciona naredba naredba sa popratnim efektom deklaraciona naredba beskorisna naredba
Posljednji primjer pokazuje beskorisnu naredbu, jer nema nikakvih popratnih efekata. Najjednostavniji oblik naredbe je linija koja sadrži samo taþka-zarez, tzv. null-naredba. No, i ovakva naredba ponekad ima smisla, što üe se pokazati u kasnijem tekstu. Mnogostruke naredbe se mogu kombinovati u složene naredbe kada se grupišu izmeÿu velikih zagrada ({}), kao na primjer { int min, i = 10, j = 20; min = (i < j ? i : j); cout << min << '\n'; }
Ovakve naredbe su korisne iz dva razloga: a) omoguüuju da se mnogostruka naredba postavi tamo gdje bi inaþe mogla da se postavi samo jedna, i b) omoguüuju da se u program uvede scope ( prostor). Scope predstavlja dio programa unutar kojeg varijabla ostaje definisana. Izvan scope-a ona to više to nije. Ovo je veoma važna osobina o kojoj üe više biti rijeþi kada se budu objašnjavale funkcije. 4.2 Naredba if Ponekad je poželjno da se izvrši odreÿena naredba koja zavisi od ispunjenja nekog uslova. Upravo tu moguünost pruža if naredba, þiji je opšti oblik: if (izraz) naredba;
Prvo se izvršava izraz, i ako je rezultat razliþit od nule izvršava se naredba. U suprotnom, ništa se ne dešava. Na primjer, ako bismo željeli provjeriti da li je pri djeljenju djelilac razliþit od nule, imali bismo: if (djelilac != 0) Kolicnik=djelitelj/djelilac;
Da bismo izvršili više naredbi koje ovisi o nekom istom uslovu, koristimo složenu naredbu, tj. sve naredbe stavljamo izmeÿu zagrada. Varijanta if naredbe koja omoguüuje da se specificiraju dvije alternativne naredbe, jedna koja se izvršava kada je uslov ispunjen i druga kada nije, se naziva if-else naredba i ima oblik: if (izraz) naredba1; else naredba2;
12
Programski jezici, C++ Ovdje se najprije izvršava izraz, i ako je rezultat razliþit od nule, izvršava se naredba1. U suprotnom, izvršava se naredba2, što pokazuje i sljedeüi primjer: #include using namespace std; main() { int x; cout << "Unesite neki broj"; cin >> x; if (x % 2 == 0) cout << "Broj je paran" << endl; else cout << "Broj je neparan" << endl; }
Pored prethodno navedenih varijanti, postoji i ugniježdena if naredba, u kojoj se javljaju više od dvije alternative. Primjer takve varijente je: if (callHour > 6) { if (duzinaPoziva <= 5) cijena = duzinaPoziva * tarifa1; else cijena = 5 * tarifa + (duzinaPoziva - 5) * tarifa2; } else cijena = osnovnaCijena;
4.3 Naredba switch switch naredba omoguüuje izbor izmeÿu više alternativa, koje su zasnovane na vrijednosti izraza. Opšti oblik switch naredbe je: switch (izraz) { case konstanta_1: naredbe; ... case konstanta_n: naredbe; default: naredbe; }
Prvo se raþuna izraz (koji se naziva switch tag), a zatim se rezultat poredi sa svakom od numeriþkih konstanti (koje se nazivaju labele), po redu kako se javljaju, dok se ne poklopi sa jednom od komponenti. Nakon toga se izvršavaju naredbe koje slijede. Izvršavanje se izvodi sve dok se ne naiÿe na naredbu break ili dok se ne izvrše sve naknadne naredbe. Posljednji sluþaj (default) može, a i ne mora da se koristi, i pokreüe se ako nijedna od prethodnih konstanti nije zadovoljena. Klasiþni primjer ocijenjivanja nekog rada na osnovu osvojenih bodova dat je u daljem teksu: 13
Programski jezici, C++ #include using namespace std; main() { int ocj; cout << "Unesite ocjenu: "; cin >> ocj; switch (ocj) { case 5: cout << "Imate 90 – 100 bodova" << endl; break; case 4: cout << "Imate 80 – 89 bodova" << endl; break; case 3: cout << "Imate 70 – 79 bodova" << endl; break; case 2: cout << "Imate 60 – 69 bodova" << endl; break; default: cout << "Imate ispod 60 bodova" << endl; } }
4.4 Naredba while Naredba while (naziva se i while petlja) omoguüuje ponavljanje neke naredbe sve dok je ispunjen neki uslov. Opšti oblik ove naredbe je: while (izraz) naredba;
Prvo se izraþunava izraz (naziva se i uslov petlje). Ako je rezultat razliþit on nule tada se izvršava naredba (naziva se i tijelo petlje) i cijeli proces se ponavlja. U suprotnom, proces se zaustavlja. Na primjer, ako bismo željeli izraþunati zbir svih brojeva od 1 do n, upotreba while naredbe bi izgledala kao: i = 1; sum = 0; while (i <= n) sum += i++;
Interesantno je da nije neuobiþajeno za while naredbu da ima prazno tijelo petlje, tj. null-naredbu. Takav primjer je problem nalaženja najveüeg neparnog faktora nekog broja while (n % 2 == 0 && n /= 2) ;
Ovdje uslov petlje izvršava sve neophodne kalkulacije, tako da nema potrebe za tijelom. 4.5 Naredba do Naredba do (naziva se i do petlja) je sliþna naredbi while, osim što se prvo izvršava tijelo petlje, a zatim se provjerava uslov. Opšti oblik naredbe je: 14
Programski jezici, C++ do naredba; while (izraz);
Prvo se izvršava naredba, a zatim provjerava izraz. Ako je izraz razliþit od nule cijeli proces se ponavlja. U suprotnom, petlja se zaustavlja. do petlja se manje koristi nego while petlja. Obiþno se koristi kada se tijelo petlje mora izvrþiti
najmanje jedanput bez obzira na ispunjenje uslova. Takav primjer je ponovljeno unošenje nekog broja i izraþunavanje njegovog kvadrata dok se ne unese 0: do { cin >> n; cout << n * n << '\n'; } while (n != 0);
Za razliku od while petlje, do petlja se nikada ne koristi sa praznim tijelom prvenstveno zbog jasnoüe. 4.6 Naredba for Naredba for (for petlja) je sliþna naredbi while, ali ima dvije dodatne komponente: izraz koji se izraþunava samo jednom prije svega, i izraz koji se izraþunava jednom na kraju svake iteracije. Opšti oblik naredbe for je: for (izraz1; izraz2; izraz3) naredba;
Prvo se izraþunava izraz1. Svakim prolazom proz petlju se izraþunava izraz2. Ako je rezultat razliþit od nule izraþunava se izraz3. U suprotnom petlja se zaustavlja. Oblik while petlje koja je ekvivalenta do petlji je: izraz1; while (izraz2) { naredba; izraz3; }
for petlja se najþešüe koristi u situacijama kada se neka promjenljiva poveüava ili smanjuje za
neku veliþinu u svakoj iteraciji, odnosno kada je broj iteracija unaprijed poznat. Sljedeüi primjer raþuna zbir svih brojeva od 1 do n: sum = 0; for (i = 1; i <= n; ++i) sum += i;
Bilo koja od komponenti u petlji može biti prazna. Na primjer, ako se uklone prvi i treüi izraz, onda do petlja liþi na while petlju: 15
Programski jezici, C++ for (; i != 0;) bilo-sta;
// je ekvivalentno sa: while (i != 0) // bilo-sta;
Uklanjanje svih izraza u petlji daje beskonaþnu petlju: for (;;)
// beskonaþna petlja bilo-sta;
Pošto petlje predstavljaju naredbe, mogu se pojaviti unutar drugih petlji (tzv. ugniježdene petlje). Na primjer: for (int i = 1; i <= 3; ++i) for (int j = 1; j <= 3; ++j) cout << '(' << i << ',' << j << ")\n";
daje parove skupa {1,2,3}
5 Funkcije Ovo poglavlje opisuje funkcije, koje definiše korisnik, kao jedan od glavnih graÿevinskih blokova u C++ programiranju. Funkcije obezbijeÿuju prikladan naþin upakivanja nekog numeriþkog recepta, koji se može koristiti koliko god je to puta potrebno. 5.1 Definicija funkcije Definicija funkcije se sastoji od dva glavna dijela: zaglavlja ili interfejsa, i tijela funkcije. Interfejs (neki ga nazivaju i prototip) definiše kako se funkcija može koristiti. On se sastoji od tri dijela: x Imena. Ovo je, u stvari, jedinstveni identifikator. x Parametara (ili potpisa funkcije). Ovo je niz od nula ili više identifikatora nekog tipa koji se koriste za proslijeÿivanje vrijednosti u i iz funkcije. x Tipa funkcije. Ovo specificira tip vrijednosti koji funkcija vraüa. Funkcija koja ne vraüa nijednu vrijednost bi trebala da ima tip void. Tijelo funkcije sadrži raþunske korake (naredbe) koji þine neku funkciju. Korištenje funkcije se izvodi njenim pozivanjem. Poziv funkcije se sastoji od imena funkcije, praüenim zagradama za pozivanje (). Unutar ovih zagrada se pojavljuje nula ili više argumenata koji se odvajaju zarezom. Broj argumenata bi trebao odgovarati broju parametara funkcije. Svaki argument je izraz þiji tip bi trebao odgovarati tipu odgovarajuüeg parametra u interfejsu funkcije. Kada se izvršava poziv funkcije, prvo se raþunaju argumenti i njihove rezultujuüe vrijednosti se pridružuju odgovarajuüim parametrima. Nakon toga se izvršava tijelo funkcije. Na kraju, funkcija vraüa vrijednost (ako ista postoji) pozivu. Sljedeüi primjer ilustrativno pokazuje definiciju jednostavne funkcije koja izraþunava vrijednost stepen cijelog broja na neki cijeli broj.
16
Programski jezici, C++ 1 2 3 4 5 6 7
int Stepen (int baza, unsigned int eksponent) { int rezultat = 1; for (int i = 0; i < eksponent; ++i) rezultat *= baza; return rezultat; }
Linija 1 definiše interfejs funkcije. Ona poþinje tipom funkcije koji se vraüa (u ovom sluþaju int). Nakon toga je dato ime funkcije (Stepen), a zatim njena lista parametara. Funkcija Stepen ima dva parametra (baza i eksponent) koji su tipa int. Sintaksa parametara je sliþna sintaksi definisanja varijabli, tj. nakon tipa daje se ime parametra. Meÿutim, nije moguüe nakon tipa dati niz parametara odvojenih zarezom, kao u int Stepen (int baza, eksponent)
// Ovo je pogrešno!
Zagrada { u liniji 2 predstavlja poþetak tijela funkcije. Linija 3 definiše lokalnu varijablu. Linije 4 i 5 raþunaju stepen varijable baza na varijablu eksponent pomoüu for petlje. Rezultat se pohranjuje u varijablu rezultat. U liniji 6 vraüa se vrijednost rezultat kao rezultat funkcije. Zagrada } u liniji 7 predstavlja kraj tijela funkcije. Naredni primjer pokazuje kako se funkcija poziva. Posljedica poziva funkcije je da se vrijednosti argumenata 2 i 8 pridružuju parametrima baza i eksponent, respektivno, a zatim se raþuna tijelo funkcije. #include using namespace std; main (void) { cout << "2 ^ 8 = " << Stepen(2,8) << '\n'; }
Kada se program pokrene daje sljedeüi izlaz: 2 ^ 8 = 256
Opüenito, funkcija se treba definisati prije nego se koristi. To se može uraditi na više naþina, kao što je pokazano u narednim primjerima. Primjer 1. #include using namespace std; double Stepen (int baza, int eksponent) { double rezultat = 1; for (int i = 0; i < eksponent; ++i) rezultat *= baza;
17
Programski jezici, C++ return rezultat; } main () { int a,b; cout << "Unesi bazu:"; cin >> a; cout << "\nUnesi eksponent:"; cin >> b; cout << a<<"^" <
Primjer 2. Treba napomenuti da se deklaracija funkcije sastoji od njenog prototipa, tako da je za deklarisanje dovoljno ispisati samo njen prototip. Na taj naþin, kompletna definicija funkcije se može dati kasnije, kao što je pokazano u narednom primjeru. Takoÿer je moguüe izostaviti nazive parametara u deklaraciji, ali to nije preporuþljivo. #include using namespace std; double Stepen (int baza, int eksponent);
// deklarisanje funkcije
/* moguce je funkciju daklarisati i na sljedeci nacin double Stepen (int, int); */ main () { int a,b; cout << "Unesi bazu:"; cin >> a; cout << "\nUnesi eksponent:"; cin >> b; cout << a<<"^" <
Primjer 3. Radi preglednosti veoma je korisno sakupiti sve funkcije u posebne datoteke, i umjesto njihovog definisanja u sklopu izvršne datoteke, treba samo proþitati tu datoteku. Na primjer, ako je definicija funkcije Stepen data u datoteci StepenInt.h, onda bi prehodni program bio: #include #include “StepenInt.h” using namespace std; main () { int a,b;
18
Programski jezici, C++ cout << "Unesi bazu:"; cin >> a; cout << "\nUnesi eksponent:"; cin >> b; cout << a<<"^" <
Ovdje treba paziti gdje se datoteka StepenInt.h nalazi. U primjeru koji je dat, ona se nalazi u istom direktoriju kao i izvršna datoteka. U suprotnom, treba dati taþan (relativni ili apsolutni) položaj (path) iste. 5.2 Parametri i argumenti C++ podržava dva oblika parametara: vrijednost i referencu. Parametar po vrijednosti prima kopiju vrijednosti argumenata koja im se prenosi. Kao posljedica toga, ako funkcija napravi bilo kakvu promjenu na parametrima, ovo neüe promijeniti vrijednosti argumenta. Na primjer, #include using namespace std; void Foo (int broj) { broj = 0; cout << "broj = " << broj << '\n'; } int main () { int x = 10; Foo(x); cout << "x = " << x << '\n'; system("PAUSE"); return 0; }
parametar broj u funkciji Foo je parametar po vrijednosti. On se ponaša kao lokalna varijabla u funkciji. Kada se funkcija pozove i vrijednost x se prenese, varijabla broj primi kopiju vrijednosti varijable x. Kao rezultat toga, iako varijabla num u funkciji mijenja vrijednost na 0, to neüe utjecati na varijablu x. Program üe dati sljedeüi izlaz: broj = 0; x = 10;
Za razliku od parametra po vrijednosti, parametar po referenci prima argument koji se prenosi i sve obavlja direktno na njemu. Bilo koja promjena parametra po referenci u samoj funkciji, direktno se odnosi i na argument, tj. i on se mijenja. Da bismo definisali parametar po referenci, potrebno je dodati simbol & iza tipa parametra u interfejsu funkcije, tj. u predhodnom primjeru interfejs funkcije Foo bio bi void Foo (int& num)
U kontekstu pozivanja funkcija, a na osnovu prethodno iznesenog, razlikujemo dvije vrste pridruživanja: priduživanje prema vrijednosti i pridruživanje prema referenci. U praksi (u pogledu funkcija) se mnogo više koristi priduživanje prema vrijednosti. 19
Programski jezici, C++ 5.3 Globalne i lokalne varijable (globalni i lokalni scope) Za sve što se definiše izvan programskog scope-a se kaže da ima globalni scope. Tako, sve funkcije koje smo do sada koristili imaju globalni scope, i predstavljaju globalne funkcije. No, i varijable se mogu defnisati u globalnom scope-u, tj. izvan svih funkcija koje se koriste u programu. Na primjer: int godina = 1994; int Maksimum (int, int); int main (void) { //... }
// globalna varijabla // globalna funkcija // globalna funkcija
Treba zapamtiti da su globalne varijable automatski inicijalizirane na vrijednost nula. Pošto su globalni entiteti vidljivi na svim programskim novoima, oni moraju biti jedinstveni na nivu programa. To znaþi da se globalne varijable ili funkcije na globalnom scope-u ne mogu definisati više nego jedanput, iako se ime funkcije može definisati više puta sve dok su im parametri (njen potpis) jedinstveni. Globalni entiteti su opüenito pristupaþni bilo gdje u programu. Svaki blok u programu definiše lokalni scope. Na taj naþin, tijelo funkcije predstavlja lokalni scope. Parametri funkcije imaju isti scope kao i tijelo funkcije. Varijable koje su definisane unutar lokalnog scope-a su vidljive samo u tom scope-u. Lokalni scope može da bude ugniježden, pri þemu unutrašnji scope poništava vanjski. Na primjer, u int xyz; // xyz je globalna varijabla void Foo (int xyz) // xyz je lokalna varijabla u tijelu funkcije Foo { if (xyz > 0) { double xyz; // xyz je lokalna varijabla u ovom bloku //... } }
Imamo tri razliþita scope-a, od kojih svaki ima razliþitu varijablu xyz. Opüenito, životni vijek varijable je ograniþen na njen scope. Tako, na primjer, globalne varijable traju svo vrijeme izvršenja programa, dok se lokalne varijable kreiraju kada se uÿe u njihov scope, a uništavaju kada se izlazi iz njihovog scope-a. Memorijski prostor za lokalne varijable je rezervisan prije izrvršenja programa, dok je memorijski prostor za lokalne varijable alociran ‘u hodu’ u toku izvršenja programa. Pošto lokalni scope poništava globalni, to lokalne varijable sa istim imenom kao globalne varijable onemoguüavaju pristup globalnim varijablama unutar lokalnog scope-a. Na primjer, u int greska; void Greska (int greska) { //... }
globalna varijabla greska je nepristupaþna unutar funkcije Greska, pošto je poništena lokalnim parametrom greska. Ovaj problem se može prevaziüi korištenjem unarnog operatora :: (unary scope operator), koji globalni entitet uzima kao argument, kao u primjeru 20
Programski jezici, C++ int greska; void Greska (int greska) { //... if (::greska != 0) //... }
// odnosi se na globalnu varijablu error
5.4 Rekurzivne funkcije Za funkciju koja poziva samu sebe kažemo da je rekurzivna. Rekurzija je opšta programska metoda koja se primijenjuje na probleme koji se definišu u odnosu na same sebe. Na primjer, problem raþunanja faktorijela je primjer rekurzivne funkcije. Faktorijel je definisan sa: x Faktorijel od 0 je 1. x Faktorijel pozitivnog broja n je n puta faktorijel od n-1 Posljednji dio definicije jasno pokazuje da je faktorijel definisan u odnosu na samog sebe, te se stoga može predstaviti rekurzivnom funkcijom, npr. int Faktorijel (unsigned int n) { return n == 0 ? 1 : n * Faktorijel(n-1); }
U principu, sve rekurzivne funkcije se mogu napisati koristeüi iteracije. Naime, treba imati u vidu da u sluþaju velikog broja poziva funkcija (u primjeru faktorijela je to veliki broj n), dolazi do zauzimanja velikog memorijskog prostora (tzv. runtime stack), pa je upotreba iteracija bolje rješenje. No, u nekim sluþajevima elegantno i jednostavno rekurzivno rješenje može da bude bolja opcija. U sluþaju faktorijela, iterativna opcija je bolja, pa bi funkcija imala oblik: int Factorijel (unsigned int n) { int rezultat = 1; while (n > 0) rezultat *= n--; return rezultat; }
5.5 Optereüene (overloaded) funkcije U prethodnim poglavljima je pomenuto da je moguüe definisati više funkcija sa istim imenom, pri þemu njen potpis mora biti drugaþiji. Ovaj postupak se naziva optereüivanje (overloading, specijalni oblik polimorfizma) i predstavlja jednu od osnovnih karakteristika OOP. Ako bismo željeli da naša funkcija Stepen ima moguünost korištenja realnih brojeva umjesto cijelih, ili þak da izraþunava vrijednost stepena broja 2 na neki realan broj, onda bismo definisali dvije dodatne funkcije sa istim imenom, ali razliþitim potpisom. Sljedeüi primjer pokazuje korištenje ovakvih funkcija. #include #include using namespace std; int Stepen (int baza, int eksponent) {
21
Programski jezici, C++ int
rezultat = 1;
for (int i = 0; i < eksponent; ++i) rezultat *= baza; return rezultat; } double Stepen (double baza, double eksponent) // funkcija Stepen sa realnim parametrima { return exp(eksponent*log(baza)); } double Stepen (double eksponent) // funkcija parametrom za izraþunavanje stepena broja 2 { return Stepen(2.0,eksponent); }
Stepen
sa
jednim
main (void) { double a,b; cout << "Unesi bazu:"; cin >> a; cout << "\nUnesi eksponent:"; cin >> b; cout << a<<"^" <
Iz primjera se vidi da sve funkcije imaju isto ime, ali razliþite tipove, a u drugom sluþaju i razliþit broj parametara. Treba paziti da pri pozivu pojedinih funkcija svi tipovi argumenata odgovaraju tipovima parametara (broj 2 je cijeli broj, dok je 2.0 realan!!!)
6. Polja Ovo poglavlje objašnjava polja i ilustruje njihovu upotrebu pri definisanju varijabli. Polje se sastoji od niza objekata (nazivaju se i elementi niza), koji su istog tipa i zauzimaju neprekidan memorijski prostor. Opüenito, samo polje ima simboliþko ime, a ne njegovi elementi. Svaki elemenat je identificiran njegovim indeksom, koji pokazuje položaj nekog elementa u nizu. Broj elemenata u nizu naziva se dimenzija polja. Dimenzija polja je fiksirana i prethodno odreÿena, i ne može se promijeniti u toku izvršenja programa. Polja su pogodna za predstavljanje podataka koji se sastoje od mnogo sliþnih, individualnih objekata. Primjeri za to su lista imena, tabela gradova i njihovih sezonskih temperatura. 6.1 Definisanje i inicializacija polja Varijabla polje je definisana specificiranjem njegove dimenzije i tipa njegovih elemenata. Na primjer, polje koje obuhvata 10 mjerenja visine (od kojih je svaka cijeli broj) može se definisati na sljedeüi naþin: int visina[10];
Pristup individualnim elementima nekog niza vrši se indeksiranjem niza. Prvi elemenat niza uvijek ima indeks 0. Na taj naþin, visina[0] i visina[9] oznaþavaju prvi i posljednji elemenat niza 22
Programski jezici, C++ visina, respektivno. Svaki elemenat niza se može tretirati kao varijabla tipa cijeli broj. Tako, na primjer, da bi smo treüem elementu ovog niza pridružili vrijednost 177, pisali bismo: visina[2] = 177;
Pokušaj pristupa nepostojeüem elementu nekog niza (na primjer visina[-1] ili visina[10]) može uzrokovati ozbiljnu grešku (tzv. runtime greška, ili greška 'indeks izvan granica’). Procesiranje bilo kojeg niza obiþno ukljuþuje korištenje petlje, koja ide kroz niz od elementa do elementa. Sljedeüi primjer pokazuje funkciju koja raþuna srednju vrijednost nekog niza: const int velicina = 3; double Srednja (int broj[velicina]) { double srednja = 0; for (int i = 0; i < velicina; ++i) srednja += broj[i]; return srednja/velicina; }
Kao i kod ostalih varijabli, vrijednosti elemenata niza se mogu inicijalizirati. U tu svrhu koriste se zagrade{}, izmeÿu kojih se specificira lista poþetnih vrijednosti elemenata niza koje su odvojene zarezom. Na primjer, int broj[3] = {5, 10, 15};
definiše niz broj, i inicijalizira tri elementa ovog niza na vrijednosti 5, 10, 15, respektivno. Ovo je tzv. eksplicitno inicijaliziranje. Kada je broj vrijednosti u inicijalizatoru manji od dimenzije niza, ostali elementi su inicijalizirani na nulu, kao u sluþaju: int broj[3] = {5, 10};
// broj[2] je inicijaliziran na 0
Kada se koristi potpuni inicijalizator (broj poþetnih vrijednosti odgovara dimenziji niza), dimenzija niza postaje suvišna i može se izostaviti, tj. broj elemenata je implicitan u inicijalizatoru (implicitna inicijalizacija). U ovom sluþaju inicijalizacija niza broj se može izvršiti i na sljedeüi naþin: int broj[] = {5, 10, 15}; // dimenzija nije potrebna
Još jedan primjer u kojem se dimenzija niza može izostaviti je kada je niz parametar u nekoj funkciji. Na primjer, funkcija Average iz ranijeg primjera se može napisati na bolji naþin, ako se postavi da dimenzija niza nije fiksirana na neku konstantnu vrijednost, nego se dodaje još jedan parametar, tj. double Srednja (int broj[], int velicina) { double srednja = 0; for (int i = 0; i < velicina; ++i) srednja += broj[i]; return srednja/velicina; }
Kompletan program bi, u tom sluþaju, bio:
23
Programski jezici, C++ #include using namespace std; double Srednja (int broj[],int velicina) { double srednja = 0; for (int i = 0; i < velicina; ++i) srednja += broj[i]; return srednja/velicina; } main () { int velicina; cout <<"Broj elemenata ...."; cin >> velicina; int n[velicina]; for(int i=0;i>n[i]; } cout << "Srednja vrijednost je .... "<<Srednja(n,velicina) << "\n"; system("PAUSE"); }
Sljedeüi program predstavlja primjer pomoüu kojeg se unosi, sortira i ispisuje neki niz. Pri tome, sortiranje je izvedeno od najveüeg prema najmanjem elementu niza. #include using namespace std; int main () { // DEKLARACIJA int x[10]; int y[10]; int i, j, n; // UNOSENJE cout << "Unesite broj clanova polja: "; cin >> n; for (i = 0; i < n; i++) { cout << "Unesite clan br. " << i << ": "; cin >> x[i]; y[i] = x[i]; } // SORTIRANJE for (i = 0; i < n-1; i++) { for (j = i+1; j < n; j++) { if (y[i] < y[j]) swap(y[i],y[j]); } } // STAMPANJE cout << "x:" << '\t' << "y:" << endl; for (i = 0; i < n; i++) { cout << x[i] << '\t' << y[i] << endl; } }
24
Programski jezici, C++ Interesantno je napomenuti da je i C++ string niz karaktera. Tako char
str[] = "HELLO";
definiše varijablu str kao niz šest (6) karaktera: pet slova i prazan karakter (null-character). Završni prazan karakter postavlja kompajler. Za razliku od toga, char
str[] = {'H', 'E', 'L', 'L', 'O'};
definiše varijablu str kao niz od 5 elemenata. 6.2 Multidimenzionalni nizovi Niz može da ima i više nego jednu dimenziju (dvije, tri, i više). Ipak, organizacija niza u memoriji je ista kao i prije, tj. neprekidna sekvenca elemenata. Percepcija programera, pak, je drugaþija. Na primjer, pretpostavimo da je srednja vrijednost temperatura po godišnjim dobima za tri australijska grada data sljedeüom tabelom Tabela II.6.1 Srednje temperature gradova Sidnej Melburn Brizbejn
Proljeüe
Ljeto
Jesen
Zima
26 24 28
34 32 38
22 19 25
17 13 20
Ovo se može predstaviti dvodimenzionalnim nizom cijelih brojeva: int
godDobTemp[3][4];
Ovo je u memoriji predstavljeno kao neprekidan niz od 12 elemenata tipa cijeli broj. Programer, meÿutim, može to zamisliti kao tri reda od po 4 elementa u svakom, kao na Sl. II.6.1. ...
26
34
22
17
First row
24
32
Second row
Prvi red
19
Drugi red
13
28
38
25
20
...
Third row
Treüi red
Slika II.6.1 Organizacija varijable godDobTemp u memoriji Kao i kod jednodimezionalnih nizova, elementima nizova se pristupa preko indeksa. No, neophodan je dodatni indeks za svaku dimenziju. Na primjer, srednja temperatura u Sidneju u toku ljeta, data je elementom godDobTemp[0][1]. Inicijalizacija niza se može obaviti pomoüu ugniježdenog inicijalizatora, kao: int godDobTemp[3][4] {26, 34, 22, {24, 32, 19, {28, 38, 25, };
= { 17}, 13}, 20}
Pošto se ovaj dvodimenzionalni niz mapira kao jednodimenzionalni niz od 12 elemenata, moguüe je koristiti i: int godDobTemp[3][4] = {
25
Programski jezici, C++ 26, 34, 22, 17, 24, 32, 19, 13, 28, 38, 25, 20 };
Ipak, bolja opcija je korištenje ugniježdenog inicijalizatora, pošto ne samo da je pregledniji, nego daje i dodatne moguünosti. Na primjer, ako nam je samo prvi elemenat svakog reda razliþit od nule, a ostali su jednaki nuli, mogli bismo koristiti: int godDobTemp[3][4] = {{26}, {24}, {28}};
Takoÿer je moguüe izostaviti prvu dimenziju (implicitna inicijalizacija), kao u: int godDobTemp[][4] = { {26, 34, 22, 17}, {24, 32, 19, 13}, {28, 38, 25, 20} };
Procesiranje multidimenzionalnih nizova je sliþno jednodimenzionalnim, s tim da se moraju korisiti ugniježdene petlje. Sljedeüi primjer pokazuje pronalaženje maksimalnog elementa u dvodimenzionalnom nizu iz prethodnih primjera. #include using namespace std; const int redovi const int kolone
= 3; = 4;
int godDobTemp[redovi][kolone] = { {26, 34, 22, 17}, {24, 32, 19, 13}, {28, 38, 25, 20} }; int najvecaTemp (int temp[redovi][kolone]) { int najveca = 0; for (int i = 0; i < redovi; ++i) for (int j = 0; j < kolone; ++j) if (temp[i][j] > najveca) najveca = temp[i][j]; return najveca; } main () { cout << najvecaTemp(godDobTemp) << "\n"; system("PAUSE"); }
Napomena: Treba paziti kako se inicijalizira vrijednost kontrolne varijable najveca u prethodnom primjeru. Ovdje se koristila vrijednost nula. Meÿutim, najbolje bi bilo kada bi se inicijalizirala na vrijednost prvog elementa niza, jer u sluþaju svih negativnih elemenata niza, nula bi rezultat funkcije bez obzira da li je þlan niza ili ne.
7. Datoteke Ovo poglavlje pokazuje kako se podaci dobiveni pokretanjem nekog programa mogu saþuvati njihovim pohranjivanjem na neku datoteku. S obzirom da þuvanje podataka nema svrhu ako tim podacima ne možemo da pristupamo, biüe objašnjeno i kako proþitati podatke sa neke datoteke. 26
Programski jezici, C++ Datoteka, pri tome, predstavlja skup podataka koji su snimljeni na neku formu trajne memorije (hard disk, CD-ROM, floppy disk, ...). Datoteci se pristupa preko njenog imena (filename), u þijem sastavu se obiþno nalazi ekstenzija (dio imena iza taþke), koja oznaþava tip podataka u datoteci (npr. .doc za Microsoft Word, .xls Microsoft Excel, .cpp za C++ izvršnu datoteku, itd.). U osnovi, postoje dvije vrste datoteka: tekstualne i binarne. Tekstualne datoteke sadrže tekst, dok binarne mogu sadržavati i kompleksnije vrste podataka, kao što su slike, izvršni programi, baze podataka, itd. Tekstualnim datotekama je nešto jednostavnije pristupiti, pisati podatke u njih, te þitati sa njih. Upravo to je i razlog zbog þega üe se primjeri u ovom poglavlju odnositi samo na njih. 7.1 Standardna biblioteka fstream U ranijim poglavljima koristili smo standardnu biblioteku iostream (io se odnosi na input/output), koja pored ostalog, daje moguünost ispisivanja na standardni izlaz (ekran, monitor) pomoüu cout, te þitanje sa standardnog upisa (tastatura) pomoüu cin. Meÿutim, ova datoteka nam ne omoguüava da podatke trajno saþuvamo. U tu svrhu se koristi standardna biblioteka fstream (f se odnosi na datoteku, tj. file), koja omoguüava pisanje na i þitanje sa datoteka. Ovo se postiže pozivanjem sadržaja fstream sa: #include
Datoteka fstream definiše tri nova tipa podataka: x ofstream. Ovaj tip podataka predstavlja stream za izlazne datoteke (o se odnosi na output). Pravac izlaza je sa programa na datoteku. Ovaj tip podataka se koristi za kreiranje datoteka i pisanje informacija na njih. Ne može se koristiti za þitanje datoteka. x ifstream. Ovaj tip podataka predstavlja stream za ulazne datoteke (i se odnosi na input). Pravac ulaza je sa datoteke prema programu. Ovaj tip podataka se koristi za þitanje informacija sa datoteka. Ne može se koristiti za kreiranje datoteka i pisanje na njih. x fstream. Ovaj tip podataka predstavlja opüenito stream za datoteke, ima karateristike i ofstream i ifstream. On može kreirati datoteke, pisati na njih i þitati sa njih.
7.2 'Životni’ ciklus pristupa datotekama Kada program pristupa datotekama, bez obzira da li ih þita, ili na njih piše, ili þini oboje, on prolazi kroz sljedeüe korake: x Datoteka prvo mora da se otvori. Ovo otvara put u komunikaciji izmeÿu datoteke i stream objekta u programu (fstream, ifstream, ofstream), koji se koristi u pristupu datoteci. x Nakon otvaranja program þita sa datoteke, piše na nju, ili þini oboje. x Na kraju, program zatvara datoteku. Ovo je bitan korak, pošto održavanje komunikacije izmeÿu datoteke i stream objekta zahtijeva resurse, tako da zatvaranje datoteke oslobaÿa ove resurse kada više nisu potrebni. Uz to, postoji moguünost da se kasnije u programu ne može pristupiti datoteci ako nije zatvorena prije prethodnog pristupa. 7.2.1 Otvaranje datoteka Bez obzira da li se sadržaj datoteke treba proþitati ili se na datoteku trebaju ispisati neki podaci, datoteka prvo treba da se otvori. Naredna poglavlja pokazuju kako se to obavlja. 27
Programski jezici, C++ Otvaranje datoteke za pisanje Datoteke sa pisanje se mogu otvoriti pomoüu fstream i ofstream objekata na dva naþina: (i) pomoüu metode (member function) open, ili (ii) pomoüu konstruktora (constructor). Otvaranje pomoüu funkcije open Prvi argument funkcije open je ime i lokacija datoteke koja se treba otvoriti. Meÿutim, moguüe je dodati i drugi argument zavisno od toga da li se fstream i ofstream poziva funkciju open, ili se želi neki drugi modul od onog koji se daje. Datoteka u koju želimo pisati podatke ne mora postojati. U sluþaju da ne postoji, ona üe se automatski kreirati pod imenom i na lokaciji koju smo upisali. Lokacija se može dati kao relativna (relative path) i apsolutna (absolute path). Pri tome, relativni put predstavlja lokaciju u odnosu na naš program, tj. datoteka se stvara u direktoriju u kojem se nalazi i naš program. Ovo postižemo na sljedeüi naþin: ofstream izlaz; izlaz.open("studenti.txt");
Za razliku od relativnog puta, apsolutni put predstavlja lokaciju koja zapoþinje slovom drajva, sadržeüi sve direktorije i poddirektorije dok se ne doÿe do datoteke. Na primjer, ako je datoteka studenti.txt u direktoriju Pedagoski, a ovaj je podirektorij direktorija UNZE, a sve se nalazi na tvrdom disku sa slovom C, onda bi se datoteka otvorila na sljedeüi naþin: ofstream izlaz; izlaz.open("C:\\MFZE\\Pedagoski\\studenti.txt");
Vidimo da se u tom sluþaju koriste po dva znaka \\, jer samo jedan izmeÿu navodnika predstavlja escape-sekvencu (poglavlje II.2.10). Bez obzira da li koristimo relativni ili apsolutni put, argument za funkciju open ne mora da bude neko ime (rijeþ), nego i (string) varijabla, kao što je to u sljedeüem primjeru: ofstream izlaz; char imeDatoteke[80]; cout << "Unesite ime datoteke: "; cin >> imeDatoteke; izlaz.open(imeDatoteke);
Važno je zapamtiti da je korištenje relativnog puta bolja opcija, jer se može desiti da neki direktorij u apsolutnom putu ne postoji, naroþito ako se program koristi na nekom drugom raþunaru (sa drugaþijim direktorijima). Korištenje drugog argumenta u funkciji open definiše modul u kojem se datoteka treba otvoriti. Neke od opcija (tzv. file mode flag), koje se mogu koristiti date su u Tabeli II.7.1. Tabela II.7.1 Opcija (file mode flag) ios::app ios::binary ios::in ios::out
Opis Postojeüi sadržaj datoteke je oþuvan i sav izlaz se ispisuje na kraj datoteke. Informacija se na datoteku piše u binarnom obliku. Infomacija üe se þitati sa datoteke. Datoteka se ne kreira ako ne postoji. Informacija üe se zapisati u datoteku. Postojeüi sadržaj datoteke je poništen.
28
Programski jezici, C++ Ako za otvaranje datoteke koristimo ofstream objekat, onda ne moramo koristiti dodatne argumente, ali treba zapamtiti da u tom sluþaju možemo samo upisivati informaciju da datoteku, ali ne i þitati sa nje. Ako bismo, na primjer, željeli da pratimo greške izazvane pokretanjem nekog programa i željeli saþuvati sve zapise o tome, koristili bismo opciju ios::app, tj. ofstream izlaz; izlaz.open("studenti.txt", ios::app);
Meÿutim, ako se za otvaranje datoteke za pisanje koristi fstream objekat, treba se dodati još jedan argument, tj. opcija za pisanje (ios::in). U ovom sluþaju bi bilo: fstream izaz; izlaz.open("studenti.txt", ios::out);
Otvaranje pomoüu konstruktora Konstruktori su funkcije koje se automatski pozivaju kada se pokušava kreirati primjerak (instanca) nekog objekta (instanca prema objektu je isto što i varijabla prema tipu podatka). Oni mogu biti optereüeni (overloaded), tako da isti objekat može imati konstruktor sa nijednim, jednim, dva, ili više argumenata. U prethodnim primjerima (npr. fstream izlaz;) korištene su naredbe sa konstruktorima bez argumenata. Naredni primjeri pokazuju upotrebu konstruktora sa jednim i dva argumenta, respektivno: ofstream izaz("studenti.txt"); fstream izaz("studenti.txt",ios::out);
Primjena konstruktora, u stvari, omoguüava deklarisanje i inicijalizaciju primjerka nekog objekta. Kao i u sluþaju deklarisanja i inicijalizacije varijabli, korištenje jednog od naþina otvaranja datoteke zavisi od samog programa i naših potreba. Otvaranje datoteka za þitanje Sve što je reþeno u prethodnom poglavlju može se primijeniti i na otvaranje datoteka za þitanje. Jedina razlika je što se, uz korištenje objekta fstream, umjesto objekta ofstream koristi iostream objekat. Uz to, datoteka sa koje se þita mora postojati, jer se pokretanjem jednog od prethodnih objekata ne kreira datoteka. Stoga, otvaranje datoteke za þitanje se može otvoriti na jedan od sljedeüih naþina: ifstream ulaz; ulaz.open("studenti.txt"); fstream ulaz; ulaz.open("studenti.txt", ios::in); //obavezno dodati argument ios::in ifstream ulaz("studenti.txt"); fstream ulaz("studenti.txt", ios::in);
Otvaranje datoteka za þitanje i pisanje Kao što je ranije reþeno, objekat fstream se može koristiti za otvaranje datoteka i za pisanje i za þitanje. U tu svrhu koristi se sljedeüa sintaksa: 29
Programski jezici, C++ fstream izlazUlaz; izlazUlaz.open("studenti.txt", ios::in | ios::out);
ili pomoüu konstruktora: fstream izlazUlaz ("studenti.txt", ios::in | ios::out);
U oba primjera korišten je tzv. bitwise operator (|), koji ima isto znaþenje kao logiþki operator || (ili). Provjera da li je datoteka otvorena Prije nego poþnemo þitati podatke sa neke datoteke, korisno je znati da li ona uopšte postoji. Provjera se može izvršiti na dva naþina. Ako se datoteka ne može otvoriti za þitanje, onda je: (i) vrijednost ifstream objekta jednaka NULL (nula), (ii) vrijednost funkcije fail objekta ifstream je true(1). Sljedeüi primjer ilustruje korištenje oba naþina. #include #include using namespace std; int main (){ ifstream ulaz; ulaz.open("studenti.txt"); cout << "(ulaz) = " << ulaz << endl; cout << "(ulaz.fail()) = " << ulaz.fail() << endl; return 0; }
Ako datoteka studenti.txt ne postoji nakon izvršenja programa dobijamo: (ulaz) = 0 (ulaz.fail()) = 1
U sluþaju da postoji izlaz bi, na primjer, bio: (ulaz) = 0x22fed4 (ulaz.fail()) = 0
Pri þemu 0x22fed4 predstavlja memorijsku lokaciju (adresu) ifstream varijable ulaz. Za razliku od ifstream objekta, ofstream objekat koji pokušava otvoriti datoteku koja ne još postoji nije NULL, a njegova fail funkcija ima vrijednost false(0). To je zbog toga što operativni sistem kreira datoteku, ako ona ne postoji. Ipak, i u ovom sluþaju je korisno provjeriti da li datoteka postoji. Naime, ako datoteka postoji, ali ima osobinu read-only, dobiüemo negativan odgovor o njenom postojanju (vrijednost iostream objekta je NULL, a funkcije fail je true, tj. 1). 7.2.2 Zatvaranje datoteka Svaka otvorena datoteka se treba zatvoriti prije nego se napusti program. To je zbog toga što svaka otvorena datoteka zahtijeva sistemske resurse. Osim toga, neki operativni sistemi imaju ograniþenje na broj otvorenih datoteka kojima se ne 'manipuliše'. Zatvranje datoteka se vrši pomoüu funkcije close. Sljedeüi primjeri pokazuju njenu upotrebu pri zatvaranju datoteka za pisanje i þitanje: 30
Programski jezici, C++ ofstream izlaz; izlaz.open("studenti.txt"); // skup naredbi outfile.close(); ifstream ulaz; ulaz.open("studenti.txt"); // skup naredbi ulaz.close();
7.2.3 Pisanje na datoteke Pisanje podataka na datoteku se izvodi pomoüu operatora za ispisivanje (<<), kao što je to bio sluþaj sa ispisivanjem na ekran (cout <<). Jedina razlika je u tome što se ovdje koristi fstream ili iostrem objekat, a ne cout objekat. Sljedeüi program pokazuje primjer upisivanja podataka na datoteku studenti.txt: #include #include using namespace std; int main (){ char podaci[80]; ofstream izlaz; izlaz.open("studenti.txt"); cout << "Zapisivanje u datoteku" << endl; cout << "=======================" << endl; cout << "Upisite razred: "; cin.getline(podaci, 80); izlaz << podaci << endl; cout << "Unesite broj studenata: "; cin >> podaci; cin.ignore(); izlaz << podaci << endl; izlaz.close(); return 0; }
Upotrebom drugog argumenta pri otvaranju datoteke za pisanje (ios::app) možemo dodavati sadržaj na postojeüu datoteku kao što to pokazuje sljedeüi primjer. U ovom primjeru zapisivanje se prekida nakon unosa znakova ***. #include #include <string> #include using namespace std; int main () { string x; ofstream izlaz; izlaz.open("podaci.txt", ios::app); while (x != "***") { cout << "Unesite neki tekst (za kraj unesite ***):" << endl; cin >> x; izlaz << x << endl; } izlaz.close(); }
31
Programski jezici, C++ 7.2.4 ýitanje sa datoteka Analogno prethodnom poglavlju, þitanje podataka sa datoteka obavlja se pomoüu operatora za þitanje (>>) kao što je to sluþaj sa ispisivanjem sa tastature (cin>>). Sljedeüi primjer nadopunjava onaj iz prethodnog poglavlja, tj. nakon što korisnik upisuje informacije na datoteku, program þita iste podatke i ispisuje ih na ekran. #include #include using namespace std; int main (){ string podaci; ofstream izlaz; izlaz.open("studenti.txt"); cout << "Upisivanje u datoteku" << endl; cout << "=====================" << endl; cout << "Unesite razred: "; getline(cin,podaci); izlaz << podaci << endl; cout << "Unesite broj studenata: "; cin >> podaci; cin.ignore(); izlaz << podaci << endl; izlaz.close(); ifstream ulaz; cout << "Citanje sa datoteke" << endl; cout << "===================" << endl; ulaz.open("studenti.txt"); getline(ulaz,podaci); cout << podaci << endl; getline(ulaz,podaci); cout << podaci << endl; ulaz.close(); return 0; }
Meÿutim, vidimo da je u ovom sluþaju korištena C++ string klasa umjesto varijable podaci tipa char. Na taj naþin se pomoüu funkcije getline može ispisati ime razreda koje ima više od jedne rijeþi. Prethodni primjer se može napisati i na programerski adekvatniji naþin upotrebom funkcija za þitanje i pisanje na datoteku. #include #include #include <string> using namespace std; bool upisiDatoteku (ofstream&, char*); bool citajDatoteku (ifstream&, char*); int main (){ string podaci; bool status; ofstream izlaz; status = upisiDatoteku(izlaz, "studenti.txt"); if (!status) { cout << "Datoteka za ispisivanje otvoriti\n"; cout << "Program se zavrsava\n"; system("PAUSE");
se
ne
moze
32
Programski jezici, C++
return 0; } else { cout << "Pisanje u datoteku" << endl; cout << "==================" << endl; cout << "Upisite razred: "; getline(cin, podaci); izlaz << podaci<< endl; cout << "Unesite broj studenata: "; cin >> podaci; cin.ignore(); izlaz << podaci<< endl; izlaz.close(); } ifstream ulaz; status = citajDatoteku(ulaz, "studenti.txt"); if (!status) { cout << "Datoteka za citanje se ne moze otvoriti\n"; cout << "Program se zavrsava\n"; system("PAUSE"); return 0; } else { cout << "Citanje se datoteke" << endl; cout << "===================" << endl; getline(ulaz, podaci); while(!ulaz.fail()) { cout << podaci << endl; getline(ulaz, podaci); } ulaz.close(); } system("PAUSE"); return 0; } bool upisiDatoteku (ofstream& datoteka, char* strDatoteka) { datoteka.open(strDatoteka); if (datoteka.fail()) return false; else return true; } bool citajDatoteku (ifstream& datoteka, char* strDatoteka) { datoteka.open(strDatoteka); if (datoteka.fail()) return false; else return true; }
33