C++ osnove i ostale tricarije

Klixova večernja škola - tutoriali, savjeti, praktična rješenja
Post Reply
User avatar
triconja
Posts: 16211
Joined: 29/04/2012 07:04

#1 C++ osnove i ostale tricarije

Post by triconja »

Lekcija 0.1

Eto ko bi se ko malo bavio programiranjem a izabere c++ moze da programira u istome bez ikakvog predznanja iz c-a (zapravo se preporucuje da se nema predznanja jer se lakse neke stvari skontaju i tako to). Na temu c-osnove-i-ostale-tricarije-pitanja-i-o ... 50648.html postavljajte pitanja i programske kodove pa cemo prolaziti skupa i uocavati greske i tako to :D .

C++ je objektno orjentisani jezik, sto znaci da se prave objekti (tip podataka sa par tipova uvezenim u jedan naziv), najjednostavniji primjer je FudbalskiTim (objekat, naziv objekta je FudbalskiTim) a sadrzi par tipova u sebi (koji mogu biti golovi i ostali folovi a i ime tima i tako to). Ovaj dio je previse napredan da bi se koristio u prvom izlaganju pa zato da krenem od nekog pocetka, a cini mi se da je najbolje krenuti od Hello world primjera. Od cega se on sadrzi je lahko objasniti a kako radi zahtjeva malo dublje poznavanje tokova (ali i to je jednostavno i nauci se kroz praksu).

Code: Select all

#include <iostream>

int main(){
    //izlazni tok i salje kursor u novi red
    std::cout << "Hello world!" << std::endl;
    return 0;
}
Da bi ovaj program radio mora se ukljuciti biblioteka iostream koja sadrzi izlazni tok std::cout i std::endl. Laicki receno std::cout salje Hello world! na izlazni uredjaj a std::endl prenosi kursor u novi red. Sad da malo zagrebemo u to sta znaci int main() i return 0;. int main() je osnovna funkcija u kojoj se citav program izvrsava i u njoj se navode funkcije i varijable koje cemo koristiti a return 0; znaci da vrati 0 ako se program pravilno zavrsi. U ovom slucaju program ce se pravilno zavrsiti jer smo naveli sve potrebne biblioteke koje su potrebne za pravilno izvrsenje programa.

Malo "komplikovaniji" primjer gdje se sabiraju dva broja i rezultat se ispisuje na izlazni uredjaj se moze napraviti dodavanjem tri linije koda i malom izmjenom std::cout linije.

Code: Select all

#include <iostream>

int main(){
    //deklaracija varijabli
    int a, b, suma;
    //unos varijabli
    std::cin >> a >> b;
    //dodjela rezultata a + b varijabli suma
    suma = a + b;
    //ispis rezultata
    std::cout << "Suma je: " << suma;
    return 0;
}
Sad se prvi put susrecemo sa tipovima podataka i uzeo sam int jer je jedan od jednostavnijih tipova upravo zbog primjera sume i sabiranja brojeva. int predstavlja cjelobrojni tip (ako unosimo brojeve koji nisu cijeli brise se sve iza decimalnog zareza, npr. ako unesemo 9.99 program ce ocitati u varijablu int tipa broj 9 dok ce ostale cifre zanemariti) i naveli smo tri promjenjive a, b i sumu i unosimo ih redom a pa b. std::cin predstavlja ulazni tok i sadrzaj koji kucamo preko tastature unosi u varijablu redom od lijeva na desno (u nasem slucaju prvo ce ocitati a na vrijednost koju unesemo pa onda b) i onda se u sumi dodijeli zbir tih vrijednosti i na kraju ispisujemo na izlazni uredjaj Suma je: x (x predstavlja broj koji je suma unesenih brojeva). Treba naglasiti da se pri unosu podataka u program mora voditi racuna da se svaka pojedinacna varijabla popunjava nizom "znakova" koji su uneseni jedan iza drugog bez razmaka pa kad kursor dodje na poziciju razmaka onda upisuje novi niz znakova u novu varijablu i tako sve dok se sve varijable ne ocitaju. U slucaju da unesemo previse podataka u nasem slucaju nece se desiti nista i program ce nastaviti da radi bez ikakvih problema, ali trebamo voditi racuna o broju informacija koje se unose jer sve informacije (nizovi znakova) koje unesemo ce ostati u ulaznom toku ukoliko nisu dodijeljene nekoj varijabli i kasnije mogu dovesti do neocekivanog ponasanja programa. Vazno je zapamtiti da je deklaracija linija gdje pisemo ime tipa pa promjenjiva.

Sad da kazem nesto o "Hello world!" i "Suma je: " jer ste se sigurno zapitali sta to predstavlja i koja mu je funkcija u samom programu. Niz znakova koji su pod navodnicima se naziva string i program kad naidje na liniju koda std::cout << "neki string"; ispisuje taj string pod navodnicima (bez navodnika) na izlazni tok (prava istina je da se pravi bezimena varijabla ali to je najbolje ostaviti za malo kasnije). Upisivanje i ispis recenice je dosta slican kao upisivanje u cjelobrojne varijable i ispis njihove sume pa su programu potrebne minorne ispravke da bi radio sa stringovima.

Code: Select all

#include <iostream>

int main(){
    //deklaracija niza od 20 znakova
    char rijec[20];
    std::cout << "Unesite rijec: ";
    std::cin >> rijec;
    std::cout <<"Unesena rijec je: " << rijec;
   return 0;
}
U gornjem programu smo deklarisali niz od 20 znakova sa nazivom rijec i unosimo sa tastature neku rijec koja ce se na kraju programa i ispisati. Vazna napomena je da se nizovi deklarisu tako sto navedemo tip (int ili char, a ima jos dosta tipova koje nismo spominjali) pa naziv varijable i onda zagrade [] i u njima broj elemenata. Veoma je vazno voditi racuna o broju elemenata jer pokusaj pristupu elementu koji je van "opsega" moze dovesti do nezeljenih posljedica a i samog kraha programa. U slucaju da zelimo pristupiti nekom elementu rijeci to radimo tako sto navedemo naziv varijable i [x] gdje je x pozicija u nizu. Numeracija u c++ pocinje od nule tako da npr. ako smo unijeli "Pokusaj" a zelimo da pristupimo trecem znaku (k) i ispisemo ga to mozemo uciniti tako sto dodamo novu liniju kod std::cout << rijec[2];. U slucaju da ovaj program pokusate koristiti za unos recenice program nece raditi kako treba jer kao sto sam naveo std::cout >> varijabla; radi tako sto iz ulaznog toka izdvaja niz znakova do prvog razmaka i tako bi vam program pojeo (a i dalje bi ostalo u memoriji) ostatak recenice nakon prve rijeci i ispisao samo prvu rijec.

U slucaju unosa niza cijelih brojeva situacija je malo komplikovanija jer se mora unositi element po element uz pomoc petlje a to radimo na sljedeci nacin, deklarisemo niz od x cijelih brojeva i pristupamo svakom elementu pojedinacno od pocetka do kraja i dodjeljujemo mu neku vrijednost. Jednostavan primjer je (vise necu navoditi #include <iostream> jer je iostream "univerzalna" biblioteka koja se koristi u vecini programa, znaci podrazumijeva se da sam je ukljucio)

Code: Select all

int main(){
    int niz[5];
    //for petlja ide do 5 jer smo deklarisali pet elemenata u nizu, koristimo znak manje jer brojac krece od nule
    for(int i = 0; i < 5; i++){
        std::cout << "Unesite " << i + 1 << " element: ";
        //unosimo po redu elemente niza (od prvog do zadnjeg)
        std::cin >> niz[i];
    }
    for(int i = 0; i < 5; i++){
        //ispisujemo redom elemente niza
        std::cout << i+1 << ". element ima vrijednost: " << niz[i] << std::endl;
    }
    return 0;
}
Deklarisem niz od 5 cijelih brojeva i saljem ga u for petlju. U () zagradama se nalazi dio koji se uglavnom koristi za deklaraciju brojacke varijable pa neki uslov koji se mora ispuniti pa korak po kojem se mijenja ta varijabla. for petlja radi tako sto se deklarise varijabla i i postavi na nulu (samo pri nailasku na petlju) pa se zatim testira uslov (u slucaju da uslov nije ispunjen izlazi se iz petlje) i na kraju i++ je promjena varijable. U konkretnom slucaju i++ znaci da se varijabla povecava za 1 (i++ je identicki jednako i = i + 1 a samo i = i + 1 znaci da varijabli sa imenom i [koja je sa lijeve strane znaka jednakosti] dodjeljujemo vrijednost koja je zbir vrijednosti koju je imala ta varijabla i na to dodajemo 1 tako da prelaskom u novi red ta varijabla ce biti uvecana za 1). Izraz u {} zagradama se naziva tijelo petlje i ono se izvrsava samo ukoliko je ispunjen uslov (srednji dio for(x;y;z), tj. izraz y). Bitno je naglasiti da se u ovom slucaju mora koristiti znak ; za odvajanje izraza u for petlji jer znak zarez ima drugu funkciju koja je komplikovana da se objasni (uglavnom za razdvajanje dva izraza koristite tackazarez i sve ce biti uredu) jer npr. u liniji std::cin >> a,b; samo ce ocitati vrijednost u a dok ce b ostati zanemareno. Tijelo petlje sa sastoji od ispisa da se unese i - ti element (i + 1, jer numeracija pocinje od nule pa tako prvi element niza u programu ima indeks 0, drugi 1 i peti 4). Sigurno ce biti pitanja a sta znaci i < 5 i sto se ide do cetiri a unosimo 5 elemenata a sama varijabla niz[5] ima peticu sto znaci da ima pet elemenata, naime i < 5 znaci da se i testira sve dok ne dostigne vrijednost 5 jer u slucaju da i ima vrijednost 0,1,2,3,4 to je sve logicki manje od 5 dok 5 nije manje od 5 nego je jednako, indeksacija ide do 4 jer kao sto sam naveo numeracija ide od 0 do 4 (ako izbrojite sve elemente mozete zakljuciti da zaista ima 5 elemenata). Onda bi niz[5] tom logikom trebao da ima 6 elemenata, ali zapitajte se sta znaci kad imate skup od nula jabuka u korpi i koja je razlika ako imate skup od pet jabuka u korpi (koliko imate ukupno elemenata u prvom slucaju i u drugom slucaju)? Ista logika vrijedi za c++, znaci pisemo ukupan broj elemenata prilikom deklaracije dok kod pristupa koristimo numeraciju od nule (koristimo za jedan manji indeks nego u stvarnosti).

Evo jedan zadacic za vas koji ovo citate, deklarisite skup od 10 elemenata gdje korisnik unosi sve elemente a ispisuje se suma svih elemenata. Ako bude kakvih poteskoca javite, mislim da nisam nista fulio u uvodnom predavanju. U slucaju nejasnoca sva pitanja postavljajte na temi c-osnove-i-ostale-tricarije-pitanja-i-o ... 50648.html.

Ako moderatori misle da je tema losa ili pogresna po nekom osnovu, neka brisu halal im plajvaz. :D

edit: bolje formatiranje koda gdje se vide tab razmaci (zahvaljujem na sugestiji) i ispravio sam par greski.
Last edited by triconja on 23/07/2018 13:37, edited 5 times in total.
User avatar
arman1
Posts: 5286
Joined: 06/11/2006 12:06
Location: Na selu čuvam stoku. U gradu se čuvam od stoke.
Grijem se na: ljubav bližnjih svoj
Vozim: šta klepim
Horoskop: BOG
Contact:

#2 Re: C++ osnove i ostale tricarije

Post by arman1 »

Samo nastavi :thumbup:
User avatar
triconja
Posts: 16211
Joined: 29/04/2012 07:04

#3 Re: C++ osnove i ostale tricarije

Post by triconja »

Lekcija 0.2

U ovom predavanju pokusat cu da kod i objasnjenje napravim preglednijim a to ce biti lakse jer sam najosnovnije vec presao, pa vise nece biti potrebe za kilometarskim objasnjavanjem najosnovnijih stvari. Preporucujem svima koji ovo citaju da koriste http://cpp.sh/, i da svaki zadatak prekucaju ako hoce da vjezbaju jer od kopiranja nema selameta. :D

Lijep stil pisanja koda (da bude sto pregledniji ostalim ljudima) se sastoji u tome da nakon svakih viticastih zagrada (gdje god bi se one mogle iskoristiti) u novom redu koristimo tipku TAB tako da pomjerimo citav kod unaprijed za nekoliko praznih mjesta, i naravno pisati komande u novi red radi sto bolje preglednosti. Naravno, potrebno je i varijable imenovat tako da imaju neke logike sa problemom koji se rjesava, npr. ako se radi program o knjigovodstvu potrebno je da varijable imaju naziv koji ce nama ili nekome drugom ko bude nekad pregledao program da olaksa citanje istog. Kad se kuca program od 300+ linija koda veoma lahko je da se zaborave sve varijable koje su koristene, pogotovo nakon sedmicu ili vise.

Zadatak koji sam postavio na kraju, vidim da niko nije pokusao da ga rijesi a evo rjesenje (isti kod bi bilo pozeljno da prekucate u cpp.sh pa i sami da vidite kako izgleda rjesenje.

Code: Select all

#include <iostream>

int main(){
    int a[10];
    //inicijalizacija varijable na neku vrijednost (u nasem slucaju na nulu) prilikom deklaracije
    int suma(0);
    for(int i = 0; i < 10; i++){
        std::cin >> a[i];
        suma += a[i];
    }
    std::cout << "Suma je: " << suma;
    return 0;
}
Ovaj kod je objasnjen u proslom postu a jedina nepoznanica je ta sta znaci int suma(0). Naime potrebno je sve varijable koje cemo koristiti inicijalizirat na neku vrijednost ako cemo na tu vrijednost dodavati (sabiranjem ili mnozenjem) neke vrijednosti, kao u ovom slucaju dodajemo na prethodnu vrijednost sume vrijednost koju smo unijeli u niz a (na poziciji i). U slucaju da ne dodijelimo neku vrijednost inicijalizovanoj varijabli moze se desiti da ce u trenutku izvrsavanja programa sam program da nadje dio u memoriji koji nije neki drugi program zauzeo i zauzme ga za potrebe izvrsavanja programa ali ce ostaviti istu vrijednost koja se u tom trenutku nalazila na tom mjestu.

Znakovi ++, -- su zanimljivi jer mogu da imaju prefiksnu i postfiksnu funkciju (bit ce zanimljivo koristiti ili bolje receno sam definirati ulogu istih kad budemo dosli do klasa i preklapanja operatora) ali najlakse je zapamtiti da varijabla++ znaci povecaj prethodni sadrzaj varijable za jedan, dok varijabla-- znaci smanji prethodni sadrzaj varijable za jedan.

Znakovi +=, -=, /=, %=, *= se koriste kod l vrijednosti (l je najlakse zapamtiti left, tj. mogu se koristiti sa lijeve strane jednakosti sto znaci da im se moze dodijeliti neka vrijednost, nije moguce npr. koristiti 5 += 10; jer 5 nije varijabla tj. nije l - value) i imaju ulogu kao njihove duze varijante, npr. a += b; je isto kao a = a + b; i tako vrijedi za sve operatore koje smo naveli u ovom paragrafu.

Operator / i % se razlikuju u tome da je prvi dijeljenje na kakvo smo navikli, 5/3 je jedan i nesto dok je operator sa posto znakom malo drugaciji. On ce za 5%3 dati rezultat 2, jer laicki receno daje ostatak prilikom dijeljenja. I jedan i drugi su korisni u raznim problemima pa je bitno da se zapamti da su razliciti i da rezultat sa / nije jednak rezultatu sa %.

Zanimljiv primjer u kojem cemo koristiti operator % slijedi

Code: Select all

#include <iostream>

int main(){
    int a(0);
    for(int i = 0; i < 10; i++){
        //testiranje na parne brojeve (brojeve koji kad se dijele sa dva daju cijeli broj, tj. nema ostatka prilikom dijeljenja)
        if(i % 2 == 0)
        a++;
    }
    std::cout << "U datom opsegu ima " << a << " parnih brojeva";
    return 0;
}
Ovdje se susrecemo sa if(i % 2 == 0) i potrebno je objasnjenje da bismo mogli koristiti istu i zbog sto boljeg razumijevanja komplikovanijih programa. if se vecinom kaze if izraz i bitno je da vidimo koji je iskaz u () zagradama i kakvo je tijelo if izraza. U slucaju da je iskaz tacan onda se izvrsava tijelo if izraza. U nasem primjeru testiramo nesto sto nije l - value (nije varijabla sa lijeve strane izraza) na jednakost necemu sa desne strane. U slucaju logickih operatora moguce je sa lijeve strane koristiti bilo kakvu vrijednost, i u ovom slucaju smo koristili r value (ona vrijednost koja se ne vezuje za varijablu, npr. a = 5; a == 5 je koristenje l - vrijednosti za testiranje na jednakost dok je a / 1 == 5 koristenje r - vrijednosti na jednakost jer a / 1 stvara novu varijablu koja je bezimena (pa odatle i r - vrijednost).

Postoji jos operatora kod logickih izraza i oni su (neke smo vec koristili) ==, !=, <, >, <=, >=. Redom, prvi je testiranje na jednakost, drugi je testiranje na nejednakost, a treci i cetvrti su testiranje da li lijeva strana je manja ili veca od desne, peti i sesti su testiranje da li je lijeva strana manja ili jednaka odnosno veca ili jednaka desnoj.

Zanimljiv problem za pocetnike je ispisivanje znakova na izlazni uredjaj pa evo da ja uradim jedan primjer a vama cu postaviti zadatak da i vi pokusate da uradite (naravno malo tezi, ako nesto nije jasno slobodno pitajte):

Code: Select all

int main(){
    int stranica;
    //beskonacna for petlja
    for(;;){
        std::cout << "Unesite stranicu trougla: ";
        std::cin >> stranica;
        //VEOMA BITNO, TESTIRANJE ZA IZLAZAK IZ PETLJE
        if(stranica > 0 && stranica % 2 == 0) break;
    }
    std::cout << "Trokut sa stranicom duzine " << stranica << std::endl;
    //dupla for petlja, druga je ugnijezdjena u prvu
    for(int i = 0; i < stranica; i++){
        //druga (unutrasnja for petlja)
        for(int j = 0; j < stranica; j++){
            //uslov kad je j == 0 znaci da je kursor na lijevoj strani, j == i znaci da je brojac unutrasnje petlje jednak brojacu vanjske i tako tvori dijagonalu, i == stranica - 1 znaci da je kursor na dnu, tj. popodi strukturu
            if(j == 0 || j == i || i  == stranica - 1)
                std::cout << "*";
            //u slucaju da je brojac na poziciji koja ne zadovoljava uslov iznad onda se puni praznim mjestima. Pokusajte koristiti neki drugi znak pa ce vam biti jasno o kakvim je pozicijama rijec
            else std::cout << " ";
        }
        //kad se zavrsi unutrasnja for petlja onda se vanjska pomjeri u red ispod
        std::cout << std::endl;
    }
    return 0;
}
U zadnjem programu smo koristili beskonacnu for petlju i duplu for petlju a i logicke operatore pa hajmo redom, beskonacna for petlja se moze koristiti za testiranje unosa i jedino u slucaju da je if izraz tacan napusta se for petlja (kaze se prekida) i program ide dalje. Kod nas u slucaju da se unese bilo koji broj koji je manji od nule i nije paran program ce i dalje vrtiti beskonacnu for petlju sve dok se ne ispuni uslov u if. Kad se ispuni uslov onda se izvrsava tijelo if izraza a ono ima komandu break; koja znaci da prekine trenutnu for petlju. Dupla for petlja se koristi kad imamo problem gdje su nam potrebna dva brojaca i neke relacije medju njima i koristi se uglavnom samo unutrasnja (tj. druga) petlja dok vanjska sluzi za neki logicki test ili u nasem slucaju prelazak u novi red.

Logicki operatori su && (and ili na nasem i), || (or ili na nasem ili) i ! (not ili na nasem negacija) i koriste se da se ulanci vise iskaza u jednu cjelinu (tautologiju malo procitati i vidjeti tablice istinitosti!). U primjeru iznad koristimo && da testiramo da li je broj veci od nule i u isto vrijeme paran i ako jeste da se izvrsi tijelo if izraza, tj. izlazak iz petlje dok u drugom slucaju testiramo if(j == 0 || j == i || i == stranica - 1) kad je brojac unutrasnje for petlje jednak nuli ili (logicki operator) kad je jednak prvom brojacu ili (logicki operator) kad je jednak stranica - 1 (vidi indeksaciju i numeraciju u proslom postu). U slucaju da je ispunjem uslov onda se na izlazni uredjaj salje znak * (zvjezdica) a kad nije ispunjen onda se salje razmak, tj. " ".

Bitno je naglasiti i sta radi else, naime u slucaju kad imamo neki uslov u if izrazu koji znamo da bi mogao biti netacan a imamo zadatak kako da tretiramo neispravne podatke (ono sta se testira u iskazu) onda koristimo else i time tok programa preusmjeravamo u novo tijelo (else tijelo, a inace bi program se izvrsavao na principu sljedeca linija).

Zadatak za vas je da napisete program koji od korisnika trazi da unese duzinu stranice kvadrata i da isti nacrta na izlazni uredjaj (monitor) sa znakom "+". U slucaju da korisnik unese peticu program treba da nacrta trokut kao u gornjem primjeru. Hint: nacrtano je pola kvadrata u proslom primjeru, potrebno je izvrsiti minorne prepravke da bi program crtao kvadrate i bilo bi pozeljno da malo eksperimentisete sa svim linijama koda i u slucaju da vam nesto nije jasno, pitajte na temi c-osnove-i-ostale-tricarije-pitanja-i-o ... 50648.html
User avatar
triconja
Posts: 16211
Joined: 29/04/2012 07:04

#4 Re: C++ osnove i ostale tricarije

Post by triconja »

Lekcija 0.3

Ovdje cemo preci funkcije i prenos varijabli po referenci i vrijednosti. Za vas koje zanima kako izgleda jedan program evo nesto iz ultimate++ i sa strane poredjenje kako je uradjeno u drugim programima, zanimljivo je pravo
https://www.ultimatepp.org/www$uppweb$vsqt$en-us.html
https://www.ultimatepp.org/www$uppweb$v ... en-us.html
https://www.ultimatepp.org/www$uppweb$vswx$en-us.html

Preporucujem vam da za tipove podataka i biblioteke koje koristimo vidite koje su funkcije u istima pa i njih pokusajte samostalno koristiti jer je c++ opsiran sto se funkcija tice pa bilo bi tesko preci sve jednu funkciju na ovim lekcijama, a za sva pitanja i pomoc mozete se obratiti na pitanja i odgovori. Ova stranica je prejaka za funkcije i tipove podataka http://www.cplusplus.com/ :D

Mozda cu poceti koristiti qt pa malo kodove ovdje postavljat (mozda i nova tema) neke najjednostavnije primjere dok se ne ustelim.

Funkcije su laicki receno dijelovi koda koji se ispisu van main() funkcije i kasnije se pozivaju uz pomoc "precice" bez da se kuca stalno jedan te isti kod. Tipovi podataka koje sam ostao duzan su string (mora se ukljuciti biblioteka string da radi, a to je naprednija verzija niza znakova koja se moze u hodu povecavati i smanjivati), float (za realne brojeve, tj. one koji imaju nesto iza decimalnog zareza), double (za realne brojeve ali je opseg veci nego float) i long (upotrebljen sam moze znaciti da se podrazumijeva da ispred pise int, uloga mu je da poveca opseg brojcanog tipa ispred kojeg je upotrebljen).

Zanimljivi su unsigned i bool, ovaj prvi se koristi ispred tipa i ogranicava opseg tako da su legalni samo nenegativni brojevi tog tipa a drugi je tip koji je logicka vrijednost tacno ili netacno.

Jedna funkcija koja prenosi varijablu po vrijednosti

Code: Select all

#include <iostream>

//prenos po vrijednosti, nista ne vracamo pa je povratni tip void
void FunkcijaZaZbir(double a, double b){
    std::cout << "Zbir je: " << a + b;
}

int main(){
    double prvi, drugi;
    std::cout << "Unesite prvi i drugi broj: ";
    std::cin >> prvi >> drugi;
    FunkcijaZaZbir(prvi, drugi);
    return 0;
}
Da se primjetiti da se u funkciji koristi odvajanje varijabli zarezom (u slucaju for petlje to je bio tackazarez) pa o tome treba voditi racuna!

Prvi put se susrecemo sa void povratnim tipom (sve sto stoji ispred imena funkcije je povratni tip, za glavnu funkciju main() povratni tip je int) sto znaci da se ne vraca nista iz funkcije, void znaci praznina, prazan skup pa nema nikakvih vrijednosti koje pripadaju tom tipu, dok npr. kod int povratne vrijednosti imamo cijele brojeve u opsegu od minimalnog do maksimalnog. U slucaju da pokusamo da vratimo nesto iz nase funkcije upotrebom return program se nece kompajlirati. Vracanje rezultata iz funkcije je veoma bitno u slucaju kada rezultat hocemo iskoristiti da neku varijablu inicijalizujemo na neku vrijednost, da joj dodijelimo neku vrijednost ili u slucaju upotrebe u logickim iskazima.

Jednostavan primjer gdje koristimo povratnu vrijednost iz funkcije da prekinemo beskonacnu for petlju i zavrsimo program

Code: Select all

#include <iostream>

//povratni tip je double i vracamo zbir a i b
double FunkcijaZaZbir(double a, double b){
    std::cout << "Zbir je: " << a + b << std::endl;
    return a + b;
}

int main(){
    double prvi, drugi, zbir;
    for(;;){
        std::cout << "Unesite prvi i drugi broj: ";
        std::cin >> prvi >> drugi;
        //dodjeljujemo rezultat funkcije varijabli zbir
        zbir = FunkcijaZaZbir(prvi, drugi);
        //PREKIDANJE FOR PETLJE
        if(zbir > 20) 
            break;
        else 
            std::cout << "Zbir je manji od 20, pokusajte opet!" << std::endl;
    }
    return 0;
}
Zamislimo da imamo zadatak da ne smijemo koristiti return naredbu (tj. da mora povratni tip funkcije biti void) a moramo vratiti sumu na neki nacin iz funkcije, na prvu izgleda nemoguce ali upotrebom referenci taj problem se lahko moze rijesiti. Potrebno je iskoristiti specijalni operator & ispred imena varijable da bi se ta varijabla prenosila po referenci u funkciju. Laicki receno prenos po referenci nije nista drugo nego prenos te varijable u funkciju sa opcijom mijenjanja iste u samoj funkciji pa se taj nacin prenosa u funkciju naziva pass by reference (prenos po referenci). Jednostavnom izmjenom koda mozemo dobiti rjesenje problema

Code: Select all

#include <iostream>

//prenosimo c po referenci
void FunkcijaZaZbir(double a, double b, double &c){
    std::cout << "Zbir je: " << a + b << std::endl;
    c = a + b;
}

int main(){
    double prvi, drugi, zbir;
    for(;;){
        std::cout << "Unesite prvi i drugi broj: ";
        std::cin >> prvi >> drugi;
        //imena varijabli prilikom prenosa u funkciju i u samoj funkciji ne moraju biti ista tako da vas ovo ne buni, prvi se u funkciji zove a i prenesen je po vrijednosti, drugi se zove b a prenesen je po vrijednosti dok je suma u funkciji c i prenesena je po referenci
        FunkcijaZaZbir(prvi, drugi, zbir);
        if(zbir > 20) 
            break;
        else 
            std::cout << "Zbir je manji od 20, pokusajte opet!" << std::endl;
    }
    return 0;
}
Sintaksa prilikom prenosa varijable u funkciju po referenci je prvo napisemo tip podatka pa operator & pa ime varijable. Ostatak programa je ostao skoro isti osim samog poziva funkcije jer u ovom slucaju funkcija nista ne vraca pa se moze koristiti kao posebna linija koda (bez znakova jednakosti i drugih operatora) jer ona samo mijenja jednu vrijednost koja joj se posalje (u nasem slucaju vrijednost varijable zbir).

Nesto bitno o cemu se treba voditi racuna su tipovi podataka, najbolje bi bilo da se koristi isti tip podatka prilikom deklaracije promjenjive, prilikom prenosa u funkciju i kao povratni tip funkcije da bismo izbjegli posljedice automatske konverzije. Naveo sam primjer da int odsjeca sve poslije decimalnog zareza (za rezultat funkcije 2.99 ako je povratna vrijednost int dobit cemo 2 nakon konverzije) pa o tome treba voditi racuna, postoje neke ugradjene pretvorbe a imaju i funkcije koje pretvaraju jedan tip podataka u drugi, npr. std::to_string(varijabla_brojcanog_tipa) koja pretvara broj u string. Kroz ove konverzije je najbolje sam proci, isprobati sve varijante i vidjeti kakve su posljedice i tako cete najbolje nauciti i zapamtiti sta je dobro a sta nije.

Zanimljiva stvar su funkcije bez parametara (i sama main funkcija je bez parametara) zbog same jednostavnosti poziva, naime potrebno je samo napisati ime_funkcije(). Evo jedan zanimljiv primjer za kraj

Code: Select all

#include <iostream>
#include <string>

//funkcija bez parametara, povratni tip je void
void Nadrealisti(){
    std::cout << "Pa zmijuga loma, hahahahahaha" << std::endl;
}

int main(){
    std::string odgovor;
    std::cout << "Kako se kaze mala zmija?" << std::endl;
    //dodjela niza znakova iz ulaznog toka do znaka novog reda u varijablu odgovor
    std::getline(std::cin, odgovor);
    //funkcija iz biblioteke string koja testira jednakost stringa nad kojim je pozvana(varijabla.funkcija() znaci da je funkcija() pozvana nad varijablom) sa stringom u zagradi
    if(odgovor.compare("Zmijuga loma") == 0)
        std::cout << "Necemo se tako igrat";
    else
        Nadrealisti();
    return 0;
}
U ovom primjeru smo koristili funkcije koje su karakteristicne za string pa hajmo redom, std::getline(std::cin, odgovor); dodjeljuje niz znakova sa ulaznog toka u string (odgovor) sve dok ne naidje na znak novog reda (kad se pritisne enter u ulazni tok se smjesta \n), odgovor.compare("Zmijuga loma") == 0) vraca logicku vrijednost tacno u slucaju da je ono sto je pohranjeno u odgovor identicno onome sto je pod navodnicima (u slucaju da nije isto vraca neku vrijednost koja je razlicita od nule pa onda neki broj nije jednak nuli pa je rezultat iskaza netacno).

Zadatak za vas koji citate je da napisete tri funkcije koje ce iscrtavati redom trokut, kvadrat i slovo N i jednu funkciju koja ce ispisati ukupan broj iskoristenih znakova. Sva pitanja i zadatke postavljajte na c-osnove-i-ostale-tricarije-pitanja-i-o ... 50648.html da se ova tema cuva cistom radi bolje preglednosti.
User avatar
triconja
Posts: 16211
Joined: 29/04/2012 07:04

#5 Re: C++ osnove i ostale tricarije

Post by triconja »

Lekcija 0.4

Memorija i pokazivaci

U jezicima c postoji nesto sto se zove pokazivac a funkcija mu je da pokazuje na dio memorije. Memorija koju cemo ovdje spominjati je laicki dio u memoriji koji kompjuter rezervise za rad programa i ta memorija se nakon sto se zauzme nekom varijablom ostaje zauzeta sve do kraja bloka i u slucaju da hocemo da zauzmemo novi dio memorije onda se trazi prvi slobodan dio u memoriji koji je dovoljno velik da primi varijablu (razliciti tipovi imaju razlicitu velicinu, tj. zauzimaju razlicitu kolicinu memorije).

U slucaju jednostavne deklaracije varijable, npr. int a; u memoriji se trazi slobodna lokacija gdje bi se ta varijabla mogla "smjestiti". Uzmimo na primjer da niz kutija predstavlja neki dio memorije kojoj program ima pristup.

Image

U slucaju da su kutije 0, 1, i 2 zauzete (tj. vec imaju varijablu u "sebi") onda ce se u slucaju gornje deklaracije varijabla a smjestiti u kutiju broj 3. U slucaju ovakve deklaracije varijabla ce najvjerovatnije imati neku vrijednost koja je vec bila u kutiji (a u nekim slucajevima ce biti inicijalizirana na nulu ali se ne treba na to previse oslanjati). Ovdje je zanimljiva jedna stvar a to je da varijabli mozemo saznati adresu uz pomoc operatora &. Ukoliko ovaj operator stavimo ispred varijable onda on kao rezultat vraca adresu na kojoj je varijabla smjestena.

Code: Select all

#include <iostream>

int main()
{
    int a(9);
    std::cout << "Varijabla a ima vrijednost " << a << " i nalazi se na lokaciji: " << &a;
}
Ovaj kod ispisuje

Code: Select all

Varijabla a ima vrijednost 9 i nalazi se na lokaciji: 0x7ffd09dde77c
Gornji primjer pokazuje da nakon zauzimanja mjesta u memoriji i inicijalizacije varijable na vrijednost 9 naredba std::cout ce uz samu vrijednost varijable ispisati i njenu adresu u memoriji. Ove adrese u vecini slucajeva nisu toliko ni bitne jer svakim pokretanjem program ce naci dio memorije koji je bas u tom trenutku slobodan i ne mora znaciti da ce to biti ista adresa kao i u proslom pokretanju.

Postoje i varijable kojima je zadatak da cuvaju adresu varijable koja nam je zanimljiva i oni se nazivaju pokazivaci. Njihova deklaracija je malo drugacija nego deklaracija "pravih" varijabli a vrsi se na sljedeci nacin tip_podatka* ime_varijable. Vrijedi opet ista prica da neinicijalizirane varijable je potrebno inicijalizirati na neku vrijednost (u slucaju pokazivaca kompajler sa cpp.sh ce ga inicijalizirati na nulu tj. dobit cemo nullptr ili nul pokazivac) jer u slucaju razlicitih kompajlera rezultati mogu biti razliciti.

Prepravkom gornjeg primjera mozemo koristenjem pokazivaca pristupiti adresi bloka memorije koji zauzima varijabla na sljedeci nacin

Code: Select all

#include <iostream>

int main()
{
    int a(9);
    //deklaracija pokazivacke varijable
    int* b;
    //dodjela adrese pokazivackoj varijabli (adresu dobijamo upotrebom adresnog operatora ispred varijable)
    b = &a;
    std::cout << "Varijabla a ima vrijednost " << a << " i nalazi se na lokaciji: " << b;
}
U slucaju da zelimo da pristupimo vrijednosti koja se nalazi u kutiji putem pokazivaca koristimo operator dereferenciranja ispred pokazivacke promjenjive *pokazivac; i on kao rezultat daje vrijednost varijable na koju pokazivac pokazuje. U gornjem primjeru ako bismo dodali na std::cout jos par instrukcija ispred tackazareza i to redom << ", a vrijednost varijable na koju pokazivac pokazuje je: " << *b kao rezultat bi dobili sljedeci ispis

Code: Select all

Varijabla a ima vrijednost 9 i nalazi se na lokaciji: 0x7ffd9f7956cc, a vrijednost varijable na koju pokazivac pokazuje je: 9
.

Zanimljivo je dodati da pokusaj ispisivanja adrese nul pokazivaca upotrebom std::cout << nul_pokazivac; kao rezultat daje nulu a pokusaj pristupanja vrijednosti na koju pokazuje daje gresku i dovodi do kraha programa (jer ne pokazuje ni na koju vrijedost u memoriji).

Pravu jacinu pokazivaci dobijaju u slucaju nizova i tu je njihova upotreba najveca (mada se u novije vrijeme vise koriste napredniji "nizovi" pa se ova funkcionalnost slabije koristi mada je korisno da je upoznamo). U slucaju ispisa elemenata niza (u proslim primjerima) smo imali primjer upotrebe pokazivacke varijable mada to nismo eksplicitno naglasili pa hajmo redom da dodjemo do toga korak po korak.

Image
Prilikom deklaracije niza zauzme se dio u memoriji koji moze primiti toliko elemenata niza (dobar primjer su kutije). Zamislimo da smo deklarisali niz od sest elemenata i da kutije predstavljaju te elemente u memoriji. U slucaju pokusaja ispisa elementa niza (npr. kutije sa brojem tri, tj. cetvrtog elementa) kao rezultat cemo dobiti neku vrijednost koja se zadesila u kutiji (neki kompajleri ce postaviti to na nulu a neki nece). Koristili smo pristup elementima preko for petlje uz pomoc indeksacije a isti rezultat smo mogli dobiti upotrebom pokazivaca jer postoji jedna caka u slucaju nizova a to je da ime niza upotrebljeno samo za sebe predstavlja adresu pocetka tog niza. Tako na primjer ukoliko deklarisemo niz od 6 elemenata i hocemo ispisati adresu pocetka niza to mozemo uraditi na sljedeci nacin:

Code: Select all

#include <iostream>

int main()
{
    //vrsimo inicijalizaciju niza od 6 elemenata tako da su pripadne vrijednosti elemenata redom 1,2,3,4,5,6
    int a[6]{1,2,3,4,5,6};
    std::cout << "Niz se nalazi na lokaciji: " << a;
}
Ispis izgleda ovako

Code: Select all

Niz se nalazi na lokaciji: 0x7ffc633040d0
.

Za pristup elementima smo koristili npr. za pristup trecem elementu a[2] sto smo mogli dobiti upotrebom pokazivacke logike na zanimljiv nacin. Naime potrebno je koristiti operator dereferenciranja ispred pokazivaca za koji hocemo da vidimo element na koji pokazuje i to je to. U slucaju da zelimo pristupiti trecem elementu mozemo koristiti *(a+2) i rezultat te akrobacije ce biti element koji se nalazi na trecem mjestu niza. a[2] i *(a+2) su ekvivalentni a vecinom se koristi prvi pristup.

Neko ce se zapitati sta znaci a+2, i koja je funkcija takve konstrukcije a laicki receno to znaci uzmi adresu pocetka niza a i pomjeri se za dva mjesta unaprijed. Isto smo mogli postici koristenjem pokazivaca b i njegovim pomjeranjem za dva mjesta unaprijed operatorom ++. U tom slucaju kod bi izgledao ovako

Code: Select all

#include <iostream>

int main()
{
    int a[6]{1,2,3,4,5,6};
    int *b;
    b = a;
    b++;
    b++;
    std::cout << "Vrijednost treceg elementa je: " << *b;
}
U ovom slucaju je zanimljivo da smo dodjelu vrsili bez upotrebe adresnog operatora jer kao sto je vec receno ime niza upotrebljeno bez uglastih (uglatih) zagrada daje adresu pocetka niza. U slucaju da smo htjeli adresu treceg elementa bez pomjeranja pokazivaca dva mjesta unaprijed to smo mogli postici upotrebom b = &a[2];, jer u tom slucaju imamo sljedecu konstrukciju (ekvivalentnu) *(a + 2) koja kao rezultat daje element koji se nalazi na trecem mjestu niza a, a adresni operator ispred tog svega &(*(a+2)) znaci daj adresu elementa koji se nalazi na trecoj poziciji niza.

Code: Select all

#include <iostream>

int main()
{
    int a[6]{1,2,3,4,5,6};
    int *b;
    b = &(*(a+2));
    std::cout << "Vrijednost treceg elementa je: " << *b;
}
Ostaje nam jos prenos nizova u funkcije. Da bi nizove prenosili u funkciju moramo koristiti pokazivace ali prilikom prenosa gubi se jedna informacija o nizu a to je velicina niza a to znaci da kao jos jedan parametar moramo prenositi i velicinu samog niza.

Code: Select all

#include <iostream>

//niz u funkciju saljemo tako sto u parametre funkcije navedemo tip_podatka* ime_varijable 
void Niz(int *a){
    std::cout << "Velicina niza u funkciji je: " << sizeof(a);
}

int main()
{
    int *p;
    std::cout << "Velicina pokazivaca je: " << sizeof(p) << std::endl;
    int a[6]{1,2,3,4,5,6};
    std::cout << "Velicina niza prije funkcije je: " << sizeof(a) << std::endl;
    //saljemo niz u funkciju a kao sto je ranije naglaseno ime niza upotrijebljeno samo bez uglastih zagrada ima funkciju pokazivaca na prvi element niza pa smo zato i u funkciji Niz morali da koristimo isti tip podatka tj. pokazivac se mora prenositi u pokazivac da bi funkcija radila (moraju biti isti tipovi parametara i onoga sto saljemo u funkciju)
    Niz(a);
}
Ovaj program ce ispisati

Code: Select all

Velicina pokazivaca je: 8                                                                                                                            
Velicina niza prije funkcije je: 24                                                                                                                  
Velicina niza u funkciji je: 8
Gornji primjer je zanimljiv zbog toga sto u slucaju pokazivaca p rezultat operatora sizeof daje rezultat 8 a velicinu niza daje 24. A kad isti niz posaljemo u funkciju onda je velicina tog "niza" ista kao i velicina pokazivaca pa se da zakljuciti da se izgubila informacija o velicini niza. Malo objasnjenje o ovim brojevima, naime velicina u bajtima za pokazivac na cjelobrojnu vrijednost je 8 dok je velicina jednog cjelobrojnog elementa 4 bajta pa onda velicina niza od 6 cjelobrojnih elemenata je 24 jer je 4 * 6 = 24.

Zasto je ovo bitno? Evo jedan primjer, postoji nesto u c++ sto se zove rangovska petlja (range loop) a koristi se gdje god se moze koristiti umjesto obicne for petlje upravo zbog njene izvedbe i jednostavnosti. Naime da bi ispisali gornji niz potrebno je da umjesto klasicnog inicijalizovanja brojaca i kucanja uslova i koraka brojaca i same indeksacije niza unutar for petlje to se jednostavno izvrsi na sljedeci nacin

Code: Select all

int main()
int main()
{
    int a[6]{1,2,3,4,5,6};
    //koristimo auto kao tip elementa iz kolekcije a jer kompajler moze sam da zakljuci koji je tip da ne kucamo neke konstrukcije koje znaju biti po nekoliko rijeci za tip (npr. kada imamo vektor pametnih pokazivaca na liste dablova  :lol: )
    for(auto element : a)
        std::cout << element << " ";
}
Gornji nacin ispisa i kretanja kroz same elemente je dosta jednostavniji od klasicnog nacina pa se uglavnom koristi. Pokusaj koristenja istog nacina u funkciji niz nece raditi, jer prava istina je da tip podatka nad kojim radi rangovska petlja mora dati rezultat za funkcije begin i end (koje redom kao rezultat vracaju iterator na prvi i na iza zadnjeg elementa i tako for petlja zna kad je dostigla kraj i "velicinu" niza). U slucaju prenosa niza u funkciju gubi se funkcionalnost niza pa isti isjecak nece raditi jer smo kao parametre funkcije Niz deklarisali samo pokazivac na cjelobrojnu vrijednost a na pokazivac koji ne pokazuje na konkretni niz ne mogu se iskoristiti begin i end pa zato rangovska petlja nece ni raditi u tom slucaju. To je razlog sto i operator sizeof daje kao rezultat velicinu samog pokazivaca u funkciji a ne niza koji joj je proslijedjen u funkciju.

Isti problem se javlja prilikom vracanja niza iz funkcije jer se citav niz ne moze prenositi a tako ni vracati iz funkcije nego se to mora raditi uz pomoc pokazivaca.

Code: Select all

int* NoviNiz(){
    //veoma vazno deklarisati niz da bude staticki
    static int a[5]{6,7,8,9,10};
    return a;
}

int main()
{
    int a[5]{1,2,3,4,5};
    int* PokazivacNaNizIzFunkcije;
    //dodjeljujemo pokazivacu pokazivac na prvi element novog niza
    PokazivacNaNizIzFunkcije = NoviNiz();
    std::cout << "Prvi niz ima sljedece elemente: ";
    //koristimo rangovsku for pelju
    for(auto element : a)
        std::cout << element << " ";
    //prelazak u novi red
    std::cout << std::endl;
    std::cout << "A drugi niz ima sljedece elemente: ";
    //koristimo rucno brojanje, tj. klasicnu for petlju
    for(int i = 0; i < 5; i++){
        std::cout << *PokazivacNaNizIzFunkcije << " ";
        PokazivacNaNizIzFunkcije++;
    }
}
Veoma vazno je da se niz u funkciji deklarise kao staticki jer c++ ima jedno pravilo a to je da nakon svakog bloka (viticaste zagrade) sve varijable obrisu (za sada ce tako biti, dok ne udjemo u naprednije vode) ili jednostavnije ne moze im se pristupiti pa tako to isto vrijedi i za niz u funkciji a upotrebom static ispred deklaracije kazemo kompajleru ne diraj tu promjenjivu do kraja programa i ona ce ostati u memoriji do kraja programa tj. kompajler je nece obrisati na kraju bloka.

Evo jedan zadatak koji pokazuje da se pokazivaci mogu koristiti i sa lijeve strane znaka jednakosti i tada imaju ulogu varijable na koju pokazuju

Code: Select all

int main()
{
    int a, b;
    int* p;
    a = 5; b = 6;
    //pokazivacu dodjeljujemo adresu varijable a
    p = &a;
    //onome na sta pokazivac p pokazuje dodjeljujemo vrijednost 1
    *p = 1;
    //program ispisuje 1 6
    std::cout << a << " " << b;
}
VEOMA VAZNA NAPOMENA!!!
U slucaju da pokazivac ode van opsega niza dereferenciranjem se mogu dobiti neke vrijednosti ali to nije preporucljivo raditi! Vodite racuna o opsegu niza i pokazivace usmjeravajte samo na niz! Sta ce se desiti ako pokrenemo sljedeci program?

Code: Select all

int main()
{
    int a, b;
    int* p;
    a = 5; b = 6;
    p = &a;
    //pomjeramo pokazivac za jedan pored varijable a
    p++;
    //mijenjamo vrijednost te lokacije na 1
    *p = 1;
    //ispisuje 5 6 1
    std::cout << a << " " << b << " " << *p;
}
Gdje taj pokazivac pokazuje tacno? Sta ce on tacno izmijeniti? Eee, na to ni ja ne znam odgovor :lol: .

Evo jedan zadatak za vas koji pratite ovaj uvod u c++, napisite program koji od korisnika trazi da unese 10 elemenata koji su cjelobrojnog tipa u niz. Zatim da od korisnika trazi da ide naprijed ili nazad i koristeci kljucnu rijec "naprijed" ili "nazad" i tako da se krece kroz niz, npr. ako korisnik na pocetku kaze naprijed program treba da ispise drugi element niza, ako kaze nazad treba da ispise prvi element niza. Za kretanje kroz niz koristiti pokazivac i voditi racuna o opsegu (tj. da se ne izadje van niza). Kad korisnik napise "stop" program treba da nacrta slovo V koje ima visinu i duzinu kao broj koji se nalazi u nizu kad je korisnik rekao stop (ako se zaustavi na 16 onda treba da je visina i duzina 16 znakova)

Ovim smo presli najosnovnije stvari koje su potrebne da bi se mogli baviti malo slozenijim problemima. Kao sto sam rekao posto je jezik c++ preopsiran najbolje bi bilo da kako koje tipove podataka spominjem da trazite funkcije koje rade sa tim tipovima podataka i iste koristite pa ako dodje do kakvog problema da postavite pitanje i da vam ja ili neko drugi pokusamo objasniti.

Sva pitanja i zadatke postavljajte na temi c-osnove-i-ostale-tricarije-pitanja-i-o ... 50648.html .
User avatar
triconja
Posts: 16211
Joined: 29/04/2012 07:04

#6 Re: C++ osnove i ostale tricarije

Post by triconja »

Lekcija 1

Napredniji tipovi podataka

Kad sam pricao o nizovima naveo sam par njihovih mana a napredniji "nizovi" se koriste u situacijama kad nam je bitno da nema nekih ogranicenja od obicnih nizova. Najcesci razlog je ogranicena velicina niza, jer niz citavo vrijeme ostaje onakve velicine kako smo ga i deklarisali (onaj broj u uglastim zagradama [] je broj elemenata i on takav ostaje do kraja programa bez mogucnosti da se isti promijeni) dok kod npr. vektora dodavanje i brisanje elemenata je omoguceno. Jos jedna ogromna prednost drugih kolekcija je mogucnost prenosa kolekcije u funkciju i vracanja iste bez ikakvog gubitka podataka o velicini (ne moraju se prenositi pomocu pokazivaca). Za sve kolekcije koje cu spomenuti veoma vazno je da se ukljuce istoimene biblioteke!

Vektori
Vektori su jednostavnija kolekcija za manipulaciju od ostalih tipova pa bi red bio da se krene od najlakseg. Naime vektori imaju ugradjeno u sebe mogucnosti koje su nedostajale klasicnim nizovima i preporucuje se da se koriste vektori umjesto klasicnih nizova gdje god je moguce. Evo jedan jednostavan primjer gdje se vidi prednost vektora u odnosu na klasicne nizove, veoma vazno je ukljuciti biblioteku <vector>

Code: Select all

#include <iostream>
#include <vector>
#include <string>

std::vector<double> Funkcija(std::vector<double> vektor){
    std::string string;
    bool test(true);
    std::cout << "Unesite elemente vektora, a za kraj unesite stop ";
    for(;;){
        std::cin >> string;
        //kao granicu brojaca koristimo velicinu dinamickog stringa
        for(int i = 0; i < string.size(); i++){
            //testiramo svaki znak stringa da li sadrzi brojeve, ako nadjemo da je jedno odstupanje onda prekidamo for petlju
            if(string[i] >= '0' && string[i] <= '9')
                test = true;
            else
                test = false;
            if(!test)
                break;
        }
        //ako su svi znakovi brojevi onda pretvaramo string u broj, ovo je veoma bitno jer stod u slucaju pogresnih parametara daje gresku
        if(test)
            vektor.push_back(std::stod(string));
        //u slucaju da smo unijeli stop prekida vanjsku for petlju
        else if(!test && string.compare("stop") == 0)
            break;
        //u slucaju da unos nije broj niti stop
        else
            std::cout << "Niste unijeli validne informacije!" << std::endl;
    }
    //vraca vektor iz funkcije
    return vektor;
}

void Ispis(std::vector<double> vektor){
    std::cout << std::endl << "Elementi su: ";
    //koristimo rangovsku for petlju
    for(auto i : vektor)
        std::cout << i << " ";
}

int main(){
    std::vector<double> vektor;
    vektor = Funkcija(vektor);
    Ispis(vektor);
    return 0;
}
Vektor se deklarira tako sto napisemo std::vector<tip_podatka> naziv_vektora. Na kraj vektora dodajemo elemente uz pomoc funkcije push_back(isti tip podatka kao sto je i tip podatka elementa) i veoma je bitno da se tipovi slazu jer u suprotnom dolazi do automatske konverzije ako je ista moguca. U nasem primjeru koristili smo std::stod(string) sto znaci pretvori string u double, a to radi bez greske jer smo testirali da li su samo brojevi u stringu i tek u tom slucaju vrsili konverziju stringa u broj. Vazno je jos naglasiti da smo testiranje vrsili na znakove (to je razlog koristenja '0' i '9') a ne na brojeve kako bi neko mislio da je logicno jer u tom slucaju dolazi do konverzije pa rezultat testiranja nece biti tacan.

Dek
Dekovi su veoma slicni vektorima po principu rada pa se mogu uglavnom koristiti iste funkcije kao sto se koriste sa vektorima osim pristupa elementima preko pokazivaca. Naime kao sto sam vec naveo u klasicnim nizovima elementi se smjestaju jedan pored drugog (isti je princip i kod vektora) pa je moguce koristiti pokazivac i vrsiti inkrementaciju i dekrementaciju adrese da bi pristupili svim elementima. Kod dekova princip je malo drugaciji, prilikom deklaracije i dodavanja novih elemenata trazi se lokacija u memoriji koja je u tom trenutku slobodna, ne mora nuzno biti lokacija petog elementa poslije lokacije cetvrtog a ispred sestog (kao sto je slucaj kod vektora i nizova) nego oni mogu biti razbacani po memoriji.

Jedna od prednosti ovog nacina zauzimanja memorije je olaksano (i brzo) dodavanje i brisanje prvog elementa, ako hocemo dodati element na pocetak deka nema potrebe za pomjeranjem svakog sljedeceg elementa za jedno mjesto unaprijed jer postoji funkcija slicna onoj vektorskoj ali za dodavanje elementa naprijed i veoma je efikasna (nije potrebno vrsiti povecavanje kolekcije za 1 pa pomjeranje svakog elementa naprijed za jedno mjesto pa tek onda dodjeliti pocetku neku vrijednost, primjer ispod)

Code: Select all

#include <iostream>
#include <vector>

int main(){
    std::vector<double> vektor{2,3,4,5};
    double broj;
    std::cout << "Unesi broj koji ce biti smjesten na pocetak vektora: ";
    std::cin >> broj;
    //povecavamo velicinu vektora za 1 da bi mogli ubaciti element ispred
    vektor.resize(5);
    //pomjeranje elemenata za jedno mjesto naprijed
    for(int i = vektor.size(); i > 0; i--){
        double temp = vektor[i-1];
        vektor[i] = temp;
    }
    //prvi element je sada broj koji smo unijeli
    vektor[0] = broj;
    //ispis elemenata vektora
    for(auto i : vektor)
        std::cout << i << " ";
    return 0;
}
Vazno je vrsiti pomjeranje jer funkcija resize vrsi povecavanje (ili smanjenje) kolekcije na onoliko koliko je navedeno u zagradi i na kraj doda nule (ili obrise zadnje elemente u slucaju smanjivanja). Navedeni princip je slican funkciji insert, mogli smo samo napisati vektor.insert(0, broj); ali je dobro nauciti sta se desava upravo zbog ranog ucenja prednosti i nedostataka nekih tipova. Gore opisani problem kod dekova se rijesi veoma jednostavno

Code: Select all

#include <iostream>
#include <deque>

int main(){
    std::deque<double> dek{2,3,4,5};
    double broj;
    std::cout << "Unesi broj koji ce biti smjesten na pocetak deka: ";
    std::cin >> broj;
    dek.push_front(broj);
    for(auto i : dek)
        std::cout << i << " ";
    return 0;
}
Da bi zavrsio sa dekovima moram reci nesto malo i o iteratorima. Iteratori, laicki receno, su naprednija vrsta pokazivaca koji rade tako sto imaju uvid u princip rada kolekcije pa znaju gdje se nalazi element u memoriji (veoma bitno kod kolekcija kod kojih su elementi razbacani u memoriji). Koristili smo vec iteratore u rangovskim for petljama tako da oni ne bi trebali predstavljati neki problem. Vazna upotreba iteratora je pri koristenju funkcija iz biblioteke algorithm, uglavnom za sortiranje kolekcije i izmjenu vrijednosti citave kolekcije uz pomoc nekog kriterija. Preporucujem da procitate sami o iteratorima jer je materija veoma opsirna http://www.cplusplus.com/reference/iterator/ .

Lista
Liste su jedan od efikasnijih kolekcija jer su dekovi na steroidima. Kako je kod dekova za ubacivanje prvog elementa tako je kod listi kod bilo kojeg elementa. One ovo postizu na nacin da imaju interni pokazivac na prethodni i sljedeci element pa tako kad god je potrebno dodati novi element potrebno je i interne pokazivace usmjeriti da pokazuju na taj element, npr. ako dodamo element na trecu poziciju onda ce pokazivac sa druge i cetvrte pozicije pokazivati na novi element dok kod brisanja treceg elementa pokazivac drugog i cetvrtog elementa ce se povezati tako da ce cetvrti postati treci bez ikakvih pomjeranja ostalih elemenata u kolekciji.

Postoje jednostruko i dvostruko povezane liste. Mana listi u odnosu na dekove i vektore je u tome sto se elementima ne moze pristupiti uglastim zagradama nego se mora npr. da bi pristupili trecem elementu iteratorom od pocetka ici dva puta naprijed. Evo jedan primjer za liste i malo cu se dotaci iteratora

Code: Select all

#include <iostream>
#include <list>
#include <iterator>

int main()
{
    std::list<int> lista;
    //deklaracija iteratora liste
    std::list<int>::iterator iterator;
    int broj;
    std::cout << "Unesite 5 elemenata liste ";
    for(int i = 0; i< 5; i++){
        std::cin >> broj;
        //push_back radi i za liste
        lista.push_back(broj);
    }
    broj = 0;
    //iterator postavljam na pocetak liste
    iterator = lista.begin();
    std::cout << std::endl << "Koji element vas zanima? ";
    int pozicija;
    std::cin >> pozicija;
    //testiram opseg i ako je pogresan vracam 1 umjesto 0
    if(pozicija > lista.size() || pozicija < 0){
        std::cout << "Pogresna pozicija";
        return 1;
    }
    //pomjeram iterator do trazene pozicije
    for(;;){
        iterator++;
        broj++;
        if(broj == pozicija - 1) 
            break;
    }
    //sadrzaju iteratora se pristupa isto kao i sadrzaju pokazivaca koristeci operator dereferenciranja
    std::cout << "Broj na poziciji " << pozicija << " je " << *iterator;
    return 0;
}
Last edited by triconja on 06/08/2018 17:36, edited 3 times in total.
User avatar
triconja
Posts: 16211
Joined: 29/04/2012 07:04

#7 Re: C++ osnove i ostale tricarije

Post by triconja »

Stek
Je kolekcija kod koje se moze pristupiti elementima samo sa jedne strane (vrha) i koja prati konvenciju zadnji element ubacen se prvi izbacuje (LIFO ili last in first out). Ne moze se kretati kroz stek uz pomoc iteratora sto znaci da rangovske for petlje ne rade. Jedan primjer

Code: Select all

#include <iostream>
#include <stack>

int main()
{
    std::stack<double> stek;
    double broj;
    for(int i = 0; i < 5; i++){
        std::cin >> broj;
        stek.push(broj);
    }
    std::cout << "Elementi steka su: ";
    while(stek.size() != 0){
        std::cout << stek.top() << " ";
        stek.pop();
    }
    return 0;
}
Elementi se ubacuju u stek upotrebom funkcije push(objekat) a pristupa se funkcijom top() sto znaci daj element koji je na vrhu. Da bismo ispisali elemente steka moramo ga isprazniti upotrebom funkcije pop() koja brise element na vrhu pa opet pristupamo sljedecem koji je na vrhu pa ga brisemo sve dok se stek ne isprazni. Ovdje smo koristili while petlju zbog jednog uslova stek.size() != 0, on radi tako sto prati velicinu steka nakon svakog prolaska kroz petlju i kad se stek isprazni while petlja se zavrsava.

Red
Red je slican steku a razlikuju se pristupu elementima, red je FIFO (sto znaci first in first out) i on se ponasa ocekivano prilikom ispisa, tj. elementi sa vrha se ispisuju onim redom kako smo ih unosili. Primjer

Code: Select all

#include <iostream>
#include <queue>

int main()
{
    std::queue<double> red;
    double broj;
    for(int i = 0; i < 5; i++){
        std::cin >> broj;
        red.push(broj);
    }
    std::cout << "Elementi steka su: ";
    while(red.size() != 0){
        std::cout << red.front() << " ";
        red.pop();
    }
    return 0;
}
Umjesto upotrebe top() da bi pristupili elementu na vrhu ovdje koristimo funkciju front() (mozemo koristiti i funkciju back() da pristupimo zadnjem elementu) i opet koristimo pop(). Red kao i stek ne podrzavaju upotrebu iteratora pa ne rade rangovske for petlje za ispis elemenata (elemente moramo "rucno" ispisivati).

Postoji jos red sa prioritetom (priority_queue) koji se razlikuje od obicnog reda u poretku elemenata (slicniji je steku nego redu jer koristi top() za pristup elementu na vrhu), naime na vrhu reda sa prioritetom ce biti najveci element, ili jednostavnije receno elementi ce izlaziti sortirani od najveceg ka najmanjem. Kao i druga dva ne moze se koristiti rangovska petlja za pristup elementima nego se mora rucno pristupati (jer iteratori ne rade nad ovim tipom).

Dvodimenzionalne kolekcije
Postoje problemi kod kojih upotreba kolekcije tako da imitira matricu moze dosta olaksati rjesavanje problema pa zato je bitno znati kako i zasto koristiti matrice. Naime moguce je kreirati i grbave matrice koje imaju svoju upotrebu ali to je malo predugacko za ovaj post pa cu to u narednim postovima obraditi.

Koristeci obicne nizove matricu kreiramo tako sto upotrijebimo dva para uglastih zagrada jedne do drugih na sljedeci nacin int matrica[5][5];. Na ovaj nacin smo deklarisali matricu 5x5 cijelih brojeva (intova) sa nazivom matrica. Pristup elementima se vrsi tako sto u prvu zagradu unesemo broj reda a u drugu broj kolone.

Zanimljiva je upotreba naprednijih kolekcija za kreiranje visedimenzionalnih "nizova" zbog njihove dinamicnosti (nismo ograniceni statickom velicinom kao kod klasicnih nizova). Jedan primjer gdje korisnik unosi koliko elemenata zeli u svakom redu upotrebom vektora (obratiti paznju na pristup elementima i konstrukciju matrice i rangovsku for petlju)

Code: Select all

#include <iostream>
#include <vector>
#include <string>

int main()
{
    std::vector<std::vector<double>> matrica;
    std::vector<double> red;
    std::string string;
    bool test(true);
    
    std::cout << "Unos grbave matrice, za novi red kucati novi a za kraj kucati kraj" << std::endl;
    for(;;){
        std::cin >> string;
        for(int i = 0; i < string.size(); i++){
            if(string[i] < '0' || string[i] > '9')
                test = false;
            else test = true;
            if(!test)
                break;
        }
        if(test)
            red.push_back(std::stod(string));
        //ako korisnik ukuca novi onda ubaci citav vektor u vektor(matricu) povecavajuci velicinu istog za jedan i brise vektor red da bi bio prazan za nove elemente
        else if(string == "novi"){
            matrica.push_back(red);
            red.clear();
        }
        //ako korisnik unese kraj testiraj da li je ista unoseno u vektor red i ako jeste ubaci to u matricu
        else if(string == "kraj"){
            if(red.size())
                matrica.push_back(red);
            break;
        }
        else
            std::cout << "Pogresan unos, pokusajte ponovo" << std::endl;
    }
    //rangovska for petlja za dvodimenzionalni vektor (matricu)
    std::cout << "Unesena matrica" << std::endl;
    for(auto m : matrica){
        for(auto n : m)
            std::cout << n << " ";
        std::cout << std::endl;
    }
}
Jedan zadacic za vas koji ovo citate, napisite funkciju koja trazi od korisnika da unese broj a zatim vraca vektor kojem su elementi fibonacijevi brojevi kojih ima onoliko koliki je broj.

Malo tezi zadatak, napisite program koji kreira vektor vektora (matricu) tako da su elementi te matrice paskalov trokut (morate kreirati grbavu matricu na nacin koji sam gore opisao) i ispisuje prvih 15 redova iste.
U slucaju nejasnoca sva pitanja postavljajte na temi c-osnove-i-ostale-tricarije-pitanja-i-o ... 50648.html.
Post Reply