» »

Programski jezik C++

Kot vas večina verjetno ve, današnji računalniki razumejo le bitni jezik, ki mu pravimo tudi strojna koda (machine code). Če torej hočemo, da naš računalnik izvede neko nalogo ali opravilo, mu moramo to podati v obliki, ki jo razume, torej v enicah in ničlah. Ker pa nam, ljudem, razmišljanje v bitnem jeziku ni najbolj naravno (zaenkrat), se je pojavila potreba po nekem višjem, človeku bolj razumljivem jeziku (high-level language), ki ga nato lahko prevedemo v strojno kodo.

O strojni kodi govorimo kot o prvi generaciji programskih jezikov. Prvi korak k višjim programskim jezikom ter druga generacija programskih jezikov je zbirni jezik (assembler), ki pa še vedno zahteva ogromno znanja ter razumevanja o delovanju računalnika. Kmalu pa so se začeli pojavljati prvi pravi višji programski jeziki (trejta generacija), najprej modularni ter kasneje objektni programski jeziki. Kaj torej je višji programski jezik? Višji programski jezik nam omogoča, da računalniku podajamo ukaze ter naloge z vsakdanjimi izrazi, kot sta recimo beri in piši. Omeniti velja, da obstajajo tudi programski jeziki četrte generacije, kamor ponavadi uvrščamo programske jezike za delo s podatkovnimi bazami (naprimer SQL) ter programski jeziki pete generacije, kjer je večji poudarek na vizualnem delu kot pa na samem programiranju (primi in povleci vmesnik).

Zelo kratka zgodovina C++

Konec sedemdestih je Bjarne Stroustrup (takrat inženir pri Bell Labs) začel eksperimentirati z razširitvijo zelo razširjenega programskega jezika C. Programski jezik C++ je nadgradnja programskega jezika C, nekatere dodatke pa je Bjarne Stroustrup črpal iz že obstoječih jezikov, kot sta Simula67 ter Algol68. Ime C++ se prvič pojavi v letu 1983, v drugi polovici osemdesetih let pa C++ doživi korenite spremembe. Leta 1989 je pod okriljem ANSI (American National Standards Institution) organizacije ustanovljen oddelek za standardizacijo programskega jezika C++ (J16), kmalu zatem pa ustanovijo tudi ISO (International Standardization Organization) oddelek za mednarodno standardizacijo. Specifikacije standarda so tudi zamrznili za naslednjih pet let, dovoljene spremembe so le popravki morebitnih napak.

Prevajanje ali interpretiranje?

Standardiziran programski jezik torej imamo, kako pa ta višji programski jezik pretvorimo v strojni jezik? To lahko storimo s prevajanjem ali pa z interpretacijo programske kode.

Kot vemo, je računalniški program le zaporedje ukazov, če se zaporedje ukazov ponavlja, pa govorimo o algoritmih. Kot lahko že iz imena sklepamo, bodo prevajalniki (compilers) nekaj prevajali. Le-ti delujejo v navezi s tako imenovanimi povezovalniki (linkers) tako, da prevajalnik najprej prevede celoten program v binarne datoteke (object files), ki jih zatem povezovalnik poveže v izvršni program (compile & link). Tak način ustvarjanja strojne kode nam omogoča najhitrejše delovanje naših programov, vendar pa zaradi tega trpi prenosljivost programov (portability), saj moramo program prevesti za vsako platformo (strojna oprema in operacijski sistem) posebej.

Tu pa vstopijo interpreterji. Če govorimo o prevajanju kot o nekem končnem postopku, pa je zgodba z interpretacijo rahlo drugačna. Interpreter namreč iz naše programske kode bere ukaz za ukazom, ki ga nato takoj dekodira ter tudi izvede. Za delovanje interpreterjev torej potrebujemo nekakšno programsko ogrodje (framework), ki je specifično za vsako platformo posebej. Vendar pa program, ki je napisan za takšno okolje, deluje na vseh ogrodjih ter posledično na vseh platformah. Na prvi pogled je torej interpretacija precej boljša, vendar pa ta prenosljivost zahteva svoj davek -- hitrost delovanja programov. V veliki večini se za programski jezik C++ uporabljajo prevajalniki. Čeprav takšen način ne omogoča široke prenosljivosti, kot jo pozna interpretacija, pa se z dobrim dizajnom programa ter pripadajoče programske kode lahko takšni prenosljivosti močno približamo.

Delo s C++

Kaj torej potrebujemo, če želimo razvijati programsko opremo v jeziku C++? V osnovni ne potrebujemo ničesar drugega kot nek tekstovni urejevalnik za pisanje programske (izvorne) kode ter prevajalnik, ki to kodo prevede in poveže v izvršni program (executable). Ker pa zna biti razvoj večjih projektov v tekstovnem urejevalniku precej težaven, imamo danes na voljo celotna razvojna okolja (IDE). Poleg naprednega urejevalnika, vgrajenega prevajalnika in razhroščevalnika (debugger) lahko razvojna okolja vsebujejo tudi sisteme za avtomatsko generiranje dokumentacije, različna načrtovalna orodja itd. ... Večina razvojnih okolij je plačljivih, tu in tam pa se najdejo celo nekatera brezplačna.

Ko kodo enkrat spišete, jo je torej potrebno prevesti. Če uporabljate razvojno okolje, je prevajanje oddaljeno le en klik ali pritisk na določeno funkcijsko tipko, lahko se pa tudi lotite ročnega prevajanja v ukazni vrstici. Čeprav je C++ višji programski jezik, lahko programska koda precej hitro postane težko razumljiva in čitljiva, še posebej, če programer nima določenega stila pisanja programske kode. Tu se pojavi potreba po dokumentaciji. Le-ta lahko obsega obsežene razlage, kaj programi počno, ali pa le spremljajoče komentarje, ki tretji osebi pomagajo razumeti, kaj dotična vrstica kode sploh pomeni. Seveda se lahko lotimo pisanja dokumentacije sami, recimo v html ali doc formatu, vendar zna biti to zamudno. Zato imamo seveda danes na voljo kar nekaj orodij, ki nam olajšajo delo z dokumentacijo. Eno takšnih (zastonjskih) je DoxyGen, ki zna iz ustrezno komentirane programske kode izluščiti dokumentacijo ter jo pretvoriti v različne očesu prijazne formate (html, latex itd.).

O vodiču

Vse, kar boste v tem vodiču prebrali, lahko vsebuje takšne in drugačne napake. Za vse, kar počnete na osnovi tega vodiča, ste odgovorni sami. Če spišete program za testiranje procesorskih enot, ki med testnim delovanjem zažge vaš računalnik, vaše zavese in omaro ter posledično ostanete brez strehe nad glavo, ste si krivi sami! Zdaj ko ste dodobra prestrašeni, vam lahko predstavim bled načrt tega vodiča. Začeli bomo seveda z osnovami, kot so osnovna sintaksa (slovnica) jezika, spremenljivke, funkcije, pogojni stavki ter zanke, nadaljevali pa z bolj zahtevnimi temami, kot so dinamične spremenljivke, kazalci, reference, ter končali z objekti, polimorfizmom, abstrakcijo, itd ...

Vmes bom poskušal vriniti še organiziranost ter urejenost izvorne kode, C++ preprocesor, statične in dinamične knjižnice ter še marsikaj drugega. Skozi vse vodiče se bom držal enotnega stila kodiranja ter načela o prenosljivosti programske kode. To pomeni, da bo vso izvorno kodo možno prevesti na katerikoli platformi, ki podpira standardiziran C++ prevajalnik. Na dnu strani boste vedno našli morebitne povezave, na katerih najdete še veliko več uporabnih informacij. Tokrat je to seznam razvojnih okolij, prevajalnikov, kot tudi povezave na C++ standard. Čeprav tehta slabih 700 strani in je napisan v angleškem jeziku, je zelo priporočljivo, če ga vsaj rahlo preletite (za orientacijo).

Program, pomnilnik, vidna območja

Že na začetku sem omenil, da je program zaporedje ukazov. Vsak program pa ima tudi tako imenovano vstopno točko (entry point), ki operacijskemu sistemu pove, kaj, kako in katere ukaze v programu naj začne izvajati. V programskem jeziku C++ se ta vstopna točka imenuje funkcija main. Seveda obstajajo tudi izjeme in ena izmed njih so čistokrvne Win32 aplikacije, ki zahtevajo, da se njihova vstopna točka imenuje WinMain.

Velika večina programov za delovanje potrebuje delovni pomnilnik. Programom pisanim v C++ jeziku, sta na voljo dve vrsti delovnega pomnilnika. Za prvo, ki se imenuje urejena kopica (sklad, stack), skrbi operacijski sistem sam. To pomeni, da bo ves pomnilnik, ki ga je naš program zavzel na urejeni kopici, samodejno počiščen. Za drugo vrsto pomnilnika, ki se imenuje neurejena kopica (heap), pa moramo skrbeti mi sami. Če torej na neurejeni kopici zavzamemo nekaj pomnilnika (dinamično zaseganje, dynamic allocation), ga moramo ob zaključku programa tudi sami sprostiti ter ga tako vrniti operacijskemu sistemu. Če tega ne storimo (namerno ali nenamerno), pride do tako imenovanega uhajanja pomnilnika (memory leak). Ker pomnilnik ni bil vrnjen operacijskemu sistemu, ga le-ta še vedno vidi kot uporabljenega. In ker naš računalnik vsebuje omejeno količino pomnilnika si, tega nikakor ne smemo privoščiti. No ja, seveda je vse odvisno od našega namena ...

Velja omeniti še vidna območja ali zor funkcij in spremenljivk (scope). V grobem se vidna območja delijo na globalna ter lokalna, pri objektnem programiranju pa govorimo tudi o članskem vidnem območju. To v osnovi pomeni, da je funkcija ali spremenljivka deklarirana v globalnem območju (v nasprotju z v lokalnem prostoru deklariranimi) vidna in dosegljiva, povsod v programu. Večje programe lahko po načelu deli in vladaj razdelimo na manjše podprograme ali funkcije, kjer ima vsak podprogram svoje vidno območje. Spremenljivke, deklarirane v nekem podprogramu, so torej vidne samo tam. Vidno območje ali zor lahko tudi posebej naredimo z uporabo zavitih oklepajev. Primere si bomo ogledali kasneje.

Deklaracija in definicija

Preprosto povedano nam deklaracija pove kaj, definicija pa kako. Če torej deklariramo spremenljivko, se lahko sklicujemo na njo, če pa želimo vedeti nje vrednost, pa mora le-ta biti tudi definirana. Vse spremenljivke in funkcije morajo biti deklarirane, v kolikor jih želimo uporabljati v programu. Ko neki spremnljivki pripišemo vrednost, postane ta tudi definirana. Nekateri tipi spremenljivk lahko namreč ob deklaraciji brez definicije vsebujejo naključno postavljene bite (junk mem). Podobno velja tudi za funkcije. Če napišemo samo glavo funkcije (prototip), postane le-ta deklarirana, ko ji pa pripišemo še telo funkcije (function body), postane tudi definirana. Na hitro naj še omenim, da ima C++ zelo "rad" ti dve stvari ločeni.

Slovnica (syntax)

Programski jezik C++ zahteva zelo natančno slovnico izvorne kode, za nameček pa je tudi občutljiv na velike in male črke (beseda in BeSeDA sta torej dve različni zadevi). Večina izjav v programskem jeziku C++ mora biti zaključena s podpičjem, seveda pa obstaja nekaj izjem, katere bomo spoznali kasneje. C++ pa ni pretirano občutljiv na prazne prostorčke (space). Programsko kodo lahko tudi komentiramo na dva načina. Prvi, ki ga začnemo z "//", je enovrstični, drugi, ki pa ga moramo začeti z "/*" in končati z "*/", pa odstavčni komentar (block comment). Poglejmo si primer:

// Moj Program
// verzija 0.0.1
/* OPIS:
Kaj že počnemo?
Pišemo komentarje, ki se lahko raztezajo
čez več vrstic. */
/*
20.2.2022
*/
// I did this!

Spremenljivke

Spremenljivke so košček naslovljivega pomnilnika, ločijo pa se po tipu ter velikosti. Tip spremenljivke nam pove, kaj vsi ti biti, ki jih neka spremenljivka zasede, sploh pomenijo. Ali gre za črko, število, ali kaj tretjega.


Psevdokoda za deklaracijo spremenljivke:
TIP IME_SPREMNLJIVKE;

Psevdokoda za definicijo spremenljivke:
IME_SPREMNLJIVKE = NEKA_VREDNOST;

Ne pozabite, da mora biti spremenljivka najprej deklarirana, šele potem jo lahko tudi definiramo. Seveda pa lahko ta dva koraka združimo:
TIP IME_SPREMENLJIVKE = NEKA_VREDNOST;

Najmanjša velikost spremenljivke, ki jo lahko naslovimo v programskem jeziku C++, je en (1) byte, torej osem bitov, kar znese 256 (28) različnih stanj. Takšen tip se v C++ imenuje char (character), vanj pa lahko vpisujemo cela števila. Poleg njega poznamo tudi celoštevični int, ki ponavadi zasede 4 byte. Poznamo tudi manjše ter večje celoštevilčne tipe, ki zasedejo 2 ali 8 byte-ov, vendar pa je njihova deklaracija različna od prevajalnika do prevajalnika. Pri definiciji spremneljivk lahko uporabljamo tudi predznake.

Primeri:

/*
    Primeri deklaracij ter definicij celoštevilčnih spremenljivk.
*/

// deklaracija

char a; 
int  b;
// deklaracija ter definicija, uporaba preznaka

char c =  10;
int  d = +10;
char e = -10;
int  f = +10;

C++ pozna tudi modifikatorje tipov (samo za celoštevilčne tipe!): short, long, unsigned, signed. short ter long se uporabljata v navezi s tipom int, določata pa velikost le-tega (zopet odvisno od posameznih prevajalnikov).
Psevdokoda:
MODIFIKATOR TIP IME_SPREMENLJIVKE;
MODIFIKATOR TIP IME_SPREMENLJIVKE = NEKA_VREDNOST;


Primer:

// uporaba modifikatorjev short ter long

short int malo   = 100;      // velikost spremenljivke naj bi bila 2 byte-a

long  int veliko = 10000;    // velikost spremenljivke naj bi bila 8 byte-ov

V prvem primeru modifikator short spremeni osnovni tip int, ki je velik 4 byte, v celoštevilčni tip, ki je velik 2 byte-a. V drugem primeru pa modifikator long poveča velikost osnovnega tipa int na 8 byte-ov. Pri uporabi modifikatorjev lahko tip int izpustimo, saj je prevajalnik dovolj pameten, da ugotovi, da gre za celoštevilčni tip int.

Primer:
// uporaba modifikatorjev short ter long

short  malo   = 100;      // velikost spremenljivke naj bi bila 2 byte-a

long   veliko = 10000;    // velikost spremenljivke naj bi bila 8 byte-ov
Modifikatorja signed in unsigned pa določata, ali imajo naši celoštevilčni tipi tudi
predznak, oziroma ali lahko z njimi zapisujemo tudi negativna števila. Vsi osnovni celoštevilčni tipi znajo zapisovati tudi negativna števila (privzeti način). Vzemimo kot primer tip char. Ker je velik 1 byte, lahko z njim predstavimo 256 različnih možnosti (stanj). Tako lahko z njim zapišemo števila od -128 do +127 (ne pozabite ničle!). Če na tipu char uporabimo modifikator unsigned, negativna števila odpadejo, tako da lahko vanj zapišemo števila od 0 do 255. Isto velja tudi za vse druge celoštevilčne tipe.
Primer:
// uporaba modifikatorjev signed ter unsigned
char a;           // -127 do +128, signed je privzeta vrednost
signed char b;    // -127 do +128
unsigned char c;  //    0 do 255
int d;            // - 21474836648 do +21474836647
unsigned int e;   //    0 do 42944967296
Kaj pa se zgodi če spremenljivki, ki je deklarirana kot char, poskušamo prirediti (definirati) neko vrednost, ki je večja, kot jo lahko tak tip sprejme? Prevajalniki to ponavadi dovolijo, vendar ob tem izpljunejo opozorilo. Ko kaj takega tudi storimo, se vrednost, ki jo poskušamo prirediti, preprosto povedano zavrti. Kaj to pomeni? Oglejmo si primere:
char a;         // v char lahko zapišemo števila od -128 do +127
     a =  0;    // ni težav
     a = -100;  // ni težav
     a = +100;  // ni težav
     a = +129;  // težava!  vrednost spremenljivke a postane -127, kako? 129  - 256 = -127
     a =  135;  // težava!  vrednost spremenljivke a postane -121, kako? 135  - 256 = -121
     a =  1000; // težava!  vrednost spremenljivke a postane -24,  kako? 1000 - (4*256) = -24
     a = -200;  // težava!  vrednost spremenljivke a postane +56,  kako? -200 + 256 = +56
Če je torej vrednost spremenljivke prekoračena v negativnem območju, ji prištevamo največjo nepredznačeno vrednost (v primeru tipa char je to 256) toliko časa, dokler vrednost spremenljivke ne doseže dovoljeno območje. Če pa je vrednost spremenljivke prekoračena v pozitivnem območju, pa ji zopet moramo odštevati maksimalno vrednost toliko časa, dokler ne dosežemo dovoljenega območja. Podobno se obnašajo tudi spremenljivke, definirane kot unsigned:
unsigned char a;          // 0 do 255
              a =  100;   // ni težav
              a =  300;   // težave, vrednost spremenljivke a postane 44,  kako? 300 - 256 = 44
              a = -300;   // težave, vrednost spremenljivke a postane 212, kako? 
							-300 + (2*256) = 212, dvakrat pridemo naokoli
              a = 1000;   // težave, vrednost spremenljivke a postane 232, kako? 
							1000 - (3*256) = 232, trikrat pridemo naokoli

Kot sem že povedal, je deklaracija nekaterih celoštevilčnih tipov odvisna od prevajalnika. Tako naprimer Microsoft-ov C++ prevajalnik uporablja __int64 za celoštevilčni tip velikosti 8 byte-ov, GNU-jev C/C++ prevajalnik pa long long. Če ste v dvomih, preverite v dokumentaciji svojega prevajalnika. Poleg celoštevilčnih tipov poznamo tudi realna števila (števila z zapisom v plavajoči vejici, float). Tipa, ki jih določata, sta float (4 byte) ter double (8 byte-ov).

Primer:

/* primeri realnih števil */
/* 
   Celi ter decimalni del realnega števila morata biti ločena z decimalno piko! 
   Pri definiciji spremenljivk tip float "moramo" za podano vrednostjo podati še malo 
   črko f, ki prevajalniku pove, da želimo, da to število res obravnava kot float. 
*/
float    f =    3.14f;
float    g = -666.06453454f;

// pri spremenljivkah tipa double pa to ni potrebno (črka f)
double   h = 100.0001;

// realna števila lahko definiramo tudi tako (znanstveni zapis, mali ali veliki "e")
float    tisoc     = 1e3f;     // 1 * 10^3 = 1000
float    milijon    = 1e6f;     // 1 * 10^6 = 1000000
float    milijarda  = 1e+9f;    // 1 * 10^9 = 1000000000

float    mili      = 1E-3f;    // 1 * 10^-3 = 0.001
float    mikro     = 1E-6f;    // 1 * 10^-6 = 0.000001
float    nano      = 1E-9f;    // 1 * 10^-9 = 0.000000001

double   majhno    = -666e-100;
double   veliko    = +666E+100;

Števila torej znamo zapisovati, kaj pa črke ter pripadajoči simboli? Tudi tu imamo več možnosti. Nekako najbolj uporabljena sta ASCII in UNICODE zapis. Zapis simbola po ASCII tabeli je velik en byte, medtem ko UNICODE uporablja za zapis dva byte-a. UNICODE zapis je primeren predvsem za razvijanje prenosljivih programov za tuje trge (oziroma jezike). ASCII pa nam nudi preprost ter predvsem manj pomnilniško požrešen zapis. Omeniti velja še to, da ASCII zapis uporablja tako imenovano kodno tabelo, ki je odvisna od
nastavitev sistema (jezik ter država, od tod izvirajo težave s šumniki). No, UNICODE pa uporablja drugačen zapis, namreč vsak simbol ima svojo unikatno vrednost, pa najsi gre za naše šumnike ali kake druge čičkeračketm. Črke in simboli so v računalniku zapisane kot številke, šele ko jih začnemo "gledati" ali izpisovati, dobijo svojo pravo obliko, ki jo mi razumemo. Tako, naprimer, ima velika črka A vrednost 65, črka B vrednost 66 itd. Primer ASCII tabele si lahko ogledate tukaj. Kaj pa uporaba takšnih spremenljivk? Za uporabo ASCII zapisa je tako dovolj en byte (tip char), za uporabo UNICODE zapisa pa potrebujemo dva byte-a. Lahko uporabimo short ali pa že za to namenjen tip wchar_t (wide char).


Primer:
char    a = 'A';     // ascii zapis
wchar_t b = 'B';     // unicode zapis 
char    c = 65;      // ascii zapis, tokrat uporabimo kar ascii vrednost ('A')

Kot vidite, se za definiranje vrednosti posameznih znakov uporabljajo enojni narekovaji. Za definiranje tipov, ki predstavljajo besedo ali celo besedilo, pa se uporabljajo dvojni narekovaji.


Poleg teh osnovnih tipov pozna C++ tudi logični bool tip, ki je po velikosti in zgradbi enak tipu char, ter tako imenovani ničelni tip void (nič oziroma praznina), ki pa ga bomo obdelali pri funkcijah ter kazalcih. Povejmo še na kratko o imenih, ki jih lahko oziroma ne smemo dajati spremenljivkam. Kot sem že omenil, pozna C++ nekaj svojih ključnih besed (keywords) ter seveda tudi množico operatorjev. Imena spremenljivk torej ne smejo imeti enakih imen kot ključne besede. Pred seboj vidite seznam vseh ključnih besed, nekatere že poznate, ostale pa bomo še
spoznali: 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, wchar_t, while.

C++ pozna tudi množico operatorjev, kar nam onemogoča uporabo njihov simbolov v imenih spremneljivk. Le-te si bomo ogledali malce kasneje.

Imena spremenljivk se tudi ne smejo začeti s števko (recimo 3aaa), lahko pa vsebujejo števke na katerikoli drugi poziciji (recimo aaa123, a1bb itd.).

Od vseh ostalih simbolov, ki jh lahko najdemo na tipkovnici, lahko v imenih uporabljamo še: $ (dolar), _ (podvezaj) ter mogoče še kateri (podrobnosti v C++ standardu), vendar pa zna biti njihova uporaba precej nesmiselna, kajti uporaba takšnih simbolov lahko naredi našo kodo precej nerazumljivo.

Funkcije

Dva osnovna dela vsake funkcije sta glava in telo. Z glavo določimo tip funkcije, njeno ime in možne parametre. C++ pozna nekaj ključnih besed, ki dodajo poseben pomen našim funkcijam, vendar pa jih trenutno ne potrebujemo. Poglejmo si psevdokodo deklaracije neke funkcije:

KLJUČNA_BESEDA TIP_KI_GA_FUNKCIJA_VRAČA IME_FUNKCIJE (TIP_PARAMETRA1, TIP_PARAMETRA2, TIP_PARAMETRAN);

Število parametrov je lahko tudi enako nič, v kolikor ni, pa morajo biti parametri ločeni z vejicami. C++ dopušča tudi funkcije, katerih število argumenotv je lahko spremenljivo. O tem pa malce kasneje. Pri deklaraciji funkcije imena parametrov niso pomembna za naš prevajalnik, zato jih lahko izpustimo. Imena parametrov pa potrebujemo pri definiciji funkcij. Vsaka funkcija vrača poljuben tip. Ali ta vrnjen tip res kaj predstavlja (je spremenljivka poljubnega tipa), pa je odvisno od programerja. Funkcije lahko vračajo tudi poseben tip, imenovan void. Ta pove prevajalniku, da funkcija ne vrača ničesar.

Čeprav funkcije tipa void, ne vračajo ničesar, lahko še vedno uporabljamo ključno besedo return, ter tako recimo predčasno končamo neko funkcijo. Za imena funkcij veljajo ista pravila kot za imena spremenljivk. Ko je funkcija enkrat deklarirana, jo lahko tudi definiramo, dodamo ji telo. Lahko tudi rečemo, da jo implementiramo. Telo funkcije mora biti podano med zavitima oklepajema:

KLJUČNA_BESEDA TIP_KI_GA_FUNKCIJA_VRAČA IME_FUNKCIJE (TIP_PARAMETRA1 IME_PARAMETRA1, TIP PARAMETRA2 IME_PARAMETRA2, TIP_PARAMETRAN IME_PARAMETRAN)
{
// PROGRAMSKA KODA (TELO)
}

Če funkcija nekaj vrača, moramo to tudi eksplicitno storiti s ključno besedo return. Psevdokoda za takšno vračanje:

return NEKAJ; return;

Poglejmo si nekaj primerov:
/* 
   deklaracije funkcije, ki sešteje dve celi števili (velikost 4 byte) ter vrne rezultat kot 
   celo število (velikost 4 byte) 
*/
int SestejStevili(int, int);

/* 
   deklaracija ter definicija funckije, ki odšteje dve števili (int), ter vrne rezultat kot 
   celo število (int) 
*/
int OdstejStevili(int SteviloA, int SteviloB)
{
    // potrebujemo spremenljivko, ki bo hranila rezultat
    int Rezultat = 0;
    // Rezultat = SteviloA - SteviloB;
    return(Rezultat);
}

// deklaracija ter definicija zelo uporabne funkcije
// posebaj povdarimo, da funkcija ne sprejema parametrov
void NarediPravNic(void)
{ }

// deklaracija ter definicija še ene zelo uporabne funkcije
void NarediPravNicSeEnkrat()
{ }

// implementacija funkcije SestejStevili
int SestejStevili(int SteviloA, int SteviloB)
{
    // potrebujemo spremenljivko, ki bo hranila rezultat
    int Rezultat = 0;
    // Rezultat = SteviloA + SteviloB;
    return(Rezultat); 
}

// Lahko pa kodo še malce skrajšamo ter napišemo takole
int SestejStevili(int SteviloA, int SteviloB)
{
    return(SteviloA + SteviloB);
}
/* 
    Na prvi pogled funkcija sama naredi začasno spremenljivko, ki hrani rezultat ter 
    katere vrednost vračamo. Vendar pa temu ni tako. Za to poskrbijo operatorji. 
*/

Za razliko od spremenljivk, funkcij ne moremo redefinirati!

Operatorji

C++ operatorje si predstavljamo z različnimi simboli ali besedami, ki imajo za nas nek logičen pomen. Če uporabimo simbol "+", bi radi nekaj seštevali, če uporabimo "*", pa bi nekaj množili. C++ pozna tako aritmetične kot tudi logične operatorje, zaenkrat pa si poglejmo samo aritmetične. Osnovne tipe lahko tako med seboj seštevamo ("+"), odštevamo ("-"), množimo("*"), delimo ("/") ter jim prirejamo neke nove vrednosti ("="). Poznamo tudi nekaj posebnih operatorjev, ki jih bomo spoznali, ko jih bomo znali uporabljati.

Operatorji so po svoji zgradbi enaki funkcijam. Imajo tip, ki ga vračajo, ime (simbol) ter seznam parametrov, ki pa je ponavadi samo eden. Operatorji se pokažejo kot zelo uporabni pri objektnem programiranju, kjer si jih bomo tudi bolj natančno ogledali. Operatorji imajo različno težo oziroma pomembnost (vrstni red izvajanja). Vse operatorje, ki jih C++ pozna ter njihov vrstni red izvajanja (teža), si lahko ogledate spodaj. Mnoge od njih verjetno še ne poznate. Operatorji so ločeni po kategorijah (horizontalna črtkana črta), tisti na vrhu imajo največjo težo, tisti na dnu pa najmanjšo.

::                  //operator za določanje globalnega vidnega območja
::                  //operator za določanje vidnega območja v objektu (member)
//--------------------------------------------------------------------
()                  //oklepaji za klic funkcije
()                  //oklepaji pri klicu konstruktorja
.                   //izbor člana v nekem objektu
->                  //izbor člana v nekem objektu, kot kazalec
[]                  //dostop do elementa v polju
const_cast          //posebna vrsta pretvorbe enega tipa v drugega (cast)
dynamic_cast        //posebna vrsta pretvorbe enega tipa v drugega (cast)
reinterpret_cast    //posebna vrsta pretvorbe enega tipa v drugega (cast)
static_cast         //posebna vrsta pretvorbe enega tipa v drugega (cast)
typeid              //identifikacija tipa v realnem času (RTTI, Real Time Type Information)
++   --             //inkrementacija, dekrementacija (kot popona) sej je po?
//--------------------------------------------------------------------
++   --             //inkrementacija, dekrementacija (kot predpona)
+ -                 //predznak
!                   //logični NE 
~                   //bitni komplement
&                   //naslov spremenljivke
*                   //dereference 
new                 //dinamično zaseganje pomnilnika za objekt
delete              //dinamično sproščanje pomnilnika za objekt
new []              //dinamično zaseganje pomnilnika za polje
delete []           //dinamično sproščanje pomnilnika za polje
sizeof              //izračun velikost objekta
(type)              //osnovna pretvorba enega tipa v drugega (C style cast)
//--------------------------------------------------------------------
.*                  //razklic nad članom (boljši prevod še iščemo)
->*                 //indirekten razklic nad članom (boljši prevod še iščemo)
//--------------------------------------------------------------------
*    /    %         //množenje, deljenje
//--------------------------------------------------------------------
+    -              //seštevanje, odštevanje
//--------------------------------------------------------------------
<<   >>             //vhodni ter izhodni operator
//--------------------------------------------------------------------
<    <=   >   >=    //operatorji za določanje neenakosti
//--------------------------------------------------------------------
==   !=             //operatorji za določanje enakosti
//--------------------------------------------------------------------
&                   //bitni AND
//--------------------------------------------------------------------
^                   //bitni XOR
//--------------------------------------------------------------------
|                   //bitni OR
//--------------------------------------------------------------------
&&                  //logični IN
//--------------------------------------------------------------------
||                  //logični ALI
//--------------------------------------------------------------------
?:                  //pogojni operator
//--------------------------------------------------------------------
=                   //prirejanje, nastavitev
*=                  //množenje in prirejanje
/=                  //deljenje in prirejanje
%=                  //deljenje z ostankom in prirejanje
+=                  //seštevanje in prirejanje
-=                  //odštevanje in prirejanje
//--------------------------------------------------------------------
throw               //met izjeme
//--------------------------------------------------------------------
,                   //tudi to je operator (ne samo ločilo), dve izjavi združi v eno

Poglejmo si še nekaj primerov uporabe operatorjev:

int a = 10;     // prirejanje
int b = 20;     // prirejanje
int c = a + b;  // seštevanje ter prirejanje, c = 30
    c = c + 10; // c = 40
    c += 10;    // c = 50

int d = 1;
d = d + 1;      // d = 2
// najlepši operator :), razliko med njima bomo omenili kdaj drugič
d++;            // d = 3
++d;            // d = 4 

float e = 10;   
      e = e * 100;  // e = 1000
      e *= 100;     // e = 100000
      e /= 100;     // e = 100

Klicanje funkcij

Kot lahko verjetno predvidite, to storimo tako, da v programski kodi napišemo ime funckije ter ji podamo morebitne parametre. V psevdokodi takole:

IME_FUNKCIJE(IME_PARAMETRA1, IME_PARAMETRA2);

V praksi pa takole:

// deklariramo in definiramo funkcijo, ki jo bomo klicali
int VrniNic()
{
     return(0);
}

// deklariramo in definiramo funkcijo, ki jo bomo klicali
int Mnozi(int SteviloA, int SteviloB)
{
     return(SteviloA * SteviloB);
}

// (A1 * A2) + B
int MnoziInSestej(int SteviloA1, int SteviloA2, int SteviloB)
{
     // deklariramo spremenljivko, ki bo hranila rezultat
     int Rezultat;

     /*
     	klic funkcije, ki vrne vrednost nič; tudi ko kličemo funkcijo, ki ne sprejema 
        parametrov, ne smemo pozabite na oklepaje!
        spremenljivko Rezultat definiramo z vrednostjo, ki jo vrne funkcija VrniNic()
     */
     Rezultat = VrniNic();

     /*
 	kličemo funkcijo Mnozi, ki ji podamo dva parametra (števili), ter vrednost, ki jo vrne
        zapišemo v spremenljivko Rezultat
     */
     Rezultat = Mnozi(SteviloA1, SteviloA2);
      
     /* 
        funkcijo lahko pokličemo tudi tako, a je njena uporabna tukaj vprašljiva, 
        saj si ne zapišemo rezultata 
     */
     Mnozi(SteviloA1, SteviloA2);

     /*
     	spremenljivki Rezultat prištejemo še število B ter vrnemo rezultat
     */
     Rezultat = Rezultat + SteviloB;
     return(Rezultat);

     /*
	lahko tudi tako, brez oklepajev
	return Rezultat;

     /* 
         tudi tako lahko, saj funkcija Mnozi vrne tip int
         return( Mnozi(SteviloA1, SteviloA2) + SteviloB ); 
     */
}

Funkcija main()

C++ standard nam omogoča dve verziji funkcije main:

// prva verzija
int main();

// druga verzija
int main(int, char**);

Ker funkcija main vrača int, moramo uporabiti return. Kaj pomenita tisti dve zvezdici, naj tokrat ostane še skrivnost. Nekateri prevajalniki pa nam celo omogočajo, da funkcijo main deklariramo na kakršenkoli način. Eden takšnih je Microsoft-ov C++ prevajalnik, ki je vključen v programski paket Visual Studio 6. No, mi se bomo držali standarda.

Prvi program

No, pa ga napišimo! Če uporabljate kakšno razvojno okolje, kreirajte nov projekt (kot Console Application). Čeprav končnice datotek niso pomembne za naše prevajalnike, je ustaljena praksa, da izvorno kodo shranjujemo v datoteke s končnico .cpp (kot c plus plus). Projektu (če ta že ne vsebuje prazne datoteke) dodajte še novo *.cpp datoteko (recimo program.cpp). Preden napišemo kaj uporabnega, si na kratko oglejmo, kako poteka komunikacija med programom ter uporabnikom.

Standardna C++ aplikacija je narejena kot konzolska aplikacija, je torej brez grafičnega vmesnika, na voljo imamo le ukazno vrstico. Tukaj bomo spoznali še standardne objekte za delo z vhodno/izhodnimi tokovi. Njihovo delovanje bo zaenkrat ostalo skrito, saj za trenutno razumevanje niso pomembni. Pomembno je le to, da z njihovo pomočjo lahko vpisujemo ter izpisujemo vrednosti naših spremenljivk v programih. Njihova uporaba je sila preprosta:

// omogočimo uporabo V/I tokov
#include <iostream>


// funkcija main
int main()
{
    using namespace std;
    cout << "Program 001" << endl;
    int Stevilo = 100;
    cout <<  Stevilo << endl;
    cin  >>  Stevilo;
    cout <<  Stevilo << endl;
    return(0);
}

Prva vrstica kode naj vas ne bega preveč. Trenutno morate vedeti samo, da nam ta vrstica omogoči uporabo standardnih vhodno/izhodnih tokov (Input/Output streams, std::cout ter std::cin). Standardni tok cout izpisuje vrednosti, tok cin pa jih vpisuje v podane spremenljivke. Dva operatorja, ki se pojavljata zgoraj (usmeritev levo ter desno) sta le zelo praktičen način za izpis in vpis spremenljivk. Objekt endl je tudi del standardnih tokov, ki pove našemu "izpisovalcu", naj se pomakne v novo vrstico (end line). V prvi vrstici funkcije main torej omogočimo uporabo V/I tokov, v drugi vrstici pa vidimo, kako lahko izpisujemo besede oziroma tekst (podan mora biti v dvojnih narekovajih). Poskusimo napisati program, ki bo omogočal, da uporabnik vnese dve števili, ki jih bo nato seštel ter množil z uporabo samostojnih funkcij in ob zaključku oba rezultata izpisal na zaslon

// omogočimo uporabo V/I tokov
#include <iostream>

int Sestej(int SteviloA, int SteviloB)
{
     return(SteviloA + SteviloB);
}

int Mnozi(int SteviloA, int SteviloB)
{
     return(SteviloA * SteviloB);
}

// funkcija main
int main()
{
     // uporabljamo imenski prostor "std"
     using namespace std;

     // potrebujemo tri spremenljivke
     int SteviloA, SteviloB, Rezultat;      

     // pozdrav
     cout << "Program za sestevanj in odstevanje stevil!" << endl;

     // vpisujemo prvo stevilo
     cout << "Vnesite stevilo A, ter pritisnite ENTER:";
     cin  >> SteviloA;

     // vpisujemo drugo stevilo
     cout << "Vnesite stevilo B, ter pritisnite ENTER:";
     cin  >> SteviloB;

     // sestejemo
     Rezultat = Sestej(SteviloA, SteviloB);
     cout << "Vsota= " << Rezultat << endl;

     // mnozimo
     Rezultat = Mnozi(SteviloA, SteviloB);
     cout << "Produkt= " << Rezultat << endl;

     // vrnemo nič
     return(0);


Program prepišite v datoteko *.cpp ter jo prevedite in poženite (Visual Studio 6, Ctrl+F5, Dev-Cpp pa tipka F9). Če se vam program pred nosom prehitro zapre (predvsem v Dev-Cpp okolju), lahko program poženete prek konzole (cmd.exe na Win32 platformi) ali pa dodajte na konec programa (pred ključno besedo return) še ti dve vrstici:
 cout << "Pritisnite ENTER za izhod..." << endl;
     cin.ignore();
     cin.get();


Funkciji ignore() ter get() sta del standardnega vhodnega toka. Funkcija ignore() poskrbi, da se trenutni vhodni tok počisti (ignorira), nato pa funkcija get() čaka uporabnikov vnos znaka. V našem primeru funkciji uporabimo kot časovno pavzo, ki se konča, ko pritisnemo tipko ENTER (vnos lahko naredimo tudi tako, da vnesemo poljuben znak in pritisnemo tipko ENTER).

Zaključek

Za tokrat naj bo dovolj. Prihodnjič si bomo ogledali, kaj so to pogojni stavki in zanke, ter spoznali nekaj načinov, kako organizirati programsko kodo. Če imate kakršnokoli vprašanje ali komentar, se oglasite na forumu.


Povezave

Standard:
klik (1996 - html format)
klik (1996 - pdf format)
klik (1997 - html format)

Zastonjska razvojna okolja:
Anjuta (linux)
KDevelop (linux)
Dev-Cpp (win32)

Prevajalniki:
FreeCountry
BloodShed
MingW

Orodja:
DoxyGen

Uporabne povezave:
Slo-Tech C++ Povezave
ACCU C/C++ Resources
LUPG
Šablone v C++

Šablone v C++

Ta vodič je namenjen vsem, ki se že znajdete v C++, obvladate C, pa vam še na misel ni prišlo, da bi kdaj uporabili C++ in pa seveda vsem ki vam je pri srcu kateri od enostavnejših jezikov ter se sprašujete zakaj sploh kdo še sili v programiranje s C++. Če ...

Preberi cel članek »

Varnost v PHPju

Varnost v PHPju

Dandanes ogromno ljudi uporablja PHP z namenom, da hitro sestavijo svoje dimanično generirane spletne strani. Ob tem seveda nihče ne pomisli na varnost oziroma na pisanje &quot;varne&quot; kode - brez oziroma z malo možnosti vdora in zlorabe strežnika, na katerem so skripte postavljene. ...

Preberi cel članek »

Perl RegEx

Perl RegEx

Sistemski administratorji, programerji in drugi zahtevnejši uporabniki se velikokrat srečujemo z zahtevo po obdelavi različnih kosov besedila takšnih in drugačnih oblik. Včasih gre za sezname, drugič za HTML strani, tretjič za velika besedila. Včasih želimo ...

Preberi cel članek »

DirectX 9.0 - Osnove programiranja

DirectX 9.0 - Osnove programiranja

V prejšnjih dveh člankih smo si ogledali vse novosti, ki jih je Microsoft uvedel v DirectX. Ker verjamem, da mnogi med vami niso ravno programerji, vas pa vseeno zanima, kako izgleda življenje na drugi strani, sem se odločil, da vam to vsaj malo približam. V tem članku bo ...

Preberi cel članek »

Register Combiners vs. Pixel Shaders

Register Combiners vs. Pixel Shaders

Tokrat bo članek nekoliko bolj tehnične narave. Namen tega članka pa je prikazati kontrast med register combinerji (na primeru NVidine implementacije) in pixel shaderji v DirectX 8.x oziroma klasičnim SetTextureStageState mehanizmom v DirectXu. Za vse, ki se bodo ustrašili, ...

Preberi cel članek »