Nauči C od nule.
Step-by-step vodič s primjerima koji zapravo rade. Svaka linija koda je objašnjena — nema preskakanja, nema pretpostavki.
Tvoj prvi program
Svaki programer počinje s istim programom — ispisom teksta na ekran. Ovaj mali program uključuje gotovo sve što svaki C program mora imati. Razumijevanje ove strukture je temelj svega što slijedi.
#include <stdio.h>
int main() {
printf("Zdravo, svijete!\n");
return 0;
}
#include <stdio.h> — "include" znači uvezi biblioteku.
stdio.h je standardna biblioteka za ulaz i izlaz (Standard Input/Output).
Bez nje ne možemo koristiti printf. Znak # označava
preprocessor direktivu — instrukciju koja se obradi prije kompajliranja.
int main() — svaki C program mora imati funkciju main.
To je točka ulaska — odakle program počinje s izvođenjem. Riječ int
znači da funkcija vraća cijeli broj (integer). Zagrade () su tu jer je
main funkcija, a vitičaste zagrade { } označavaju blok koda koji
pripada toj funkciji.
printf("Zdravo, svijete!\n") — print formatted,
funkcija za ispis teksta. Tekst mora biti u navodnicima. Znak \n
je escape sequence za novi red — bez njega cursor ostaje na istoj liniji.
Na kraju obavezno točka-zarez ; — svaka naredba u
C-u završava točkom-zarezom.
return 0; — vraća vrijednost 0 operativnom sustavu.
Po konvenciji, 0 znači uspjeh, bilo koji drugi broj znači grešku.
Ovo govori OS-u da je program završio normalno.
\n — novi red | \t — tab |
\\ — obrnuta kosa crta | \" — navodnik unutar stringa
Varijable i tipovi podataka
Varijabla je imenovana memorijska lokacija u kojoj čuvaš podatak. Svaka varijabla ima tip koji određuje koliko memorije zauzima i kakav podatak može sadržavati. C je strongly typed jezik — tip moraš navesti unaprijed i ne možeš ga promijeniti.
Osnovni tipovi podataka
| Tip | Veličina | Raspon vrijednosti | Primjer |
|---|---|---|---|
| int | 4 bajta | −2,147,483,648 do 2,147,483,647 | 42, −7, 1000 |
| float | 4 bajta | ~7 decimala preciznosti | 3.14, −1.5 |
| double | 8 bajta | ~15 decimala preciznosti | 3.14159265358 |
| char | 1 bajt | jedan znak | 'A', 'z', '5' |
| long | 8 bajta | veći raspon od int | 9000000000 |
#include <stdio.h>
int main() {
int godine = 20;
float visina = 1.85;
double pi = 3.14159265358979;
char slovo = 'A';
printf("Imam %d godina.\n", godine);
printf("Visok sam %.2f metara.\n", visina);
printf("Pi je otprilike %.10f\n", pi);
printf("Prvo slovo: %c\n", slovo);
return 0;
}
int godine = 20; — deklariramo varijablu tipa int
s imenom godine i odmah joj dodjeljujemo vrijednost 20.
Operator = je operator dodjele (ne provjera jednakosti!).
float visina = 1.85; — decimalni broj s jednostrukom preciznošću.
Za svakodnevnu upotrebu float je dovoljno precizan.
double pi = 3.14159... — decimalni broj s dvostrukom preciznošću
(duplo više memorije od float, duplo više decimala). Koristiti kada preciznost
je važna.
char slovo = 'A'; — jedan znak. Pišemo u jednostrukim navodnicima
(za razliku od stringa koji ide u dvostruke). Interno, char je cijeli broj
(ASCII vrijednost znaka).
printf:
%d — cijeli broj (int) |
%f — decimalni broj |
%.2f — decimalni s 2 decimale |
%c — znak |
%s — string |
%ld — long int
snake_case (mala slova,
podvlaka između riječi): broj_studenata, ukupna_suma.
Imena ne smiju početi brojem niti sadržavati razmake.
Operatori
Operatori su simboli koji govore kompajleru da izvrši određenu operaciju nad varijablama ili vrijednostima. C ima aritmetičke, relacijske, logičke i assignment operatore.
#include <stdio.h>
int main() {
int a = 10, b = 3;
printf("%d + %d = %d\n", a, b, a + b);
printf("%d - %d = %d\n", a, b, a - b);
printf("%d * %d = %d\n", a, b, a * b);
printf("%d / %d = %d\n", a, b, a / b);
printf("%d %% %d = %d\n", a, b, a % b);
// Inkrement i dekrement
int x = 5;
x++;
printf("Nakon x++: %d\n", x);
x--;
printf("Nakon x--: %d\n", x);
return 0;
}
int a = 10, b = 3; — možemo deklarirati više varijabli istog tipa
u jednoj liniji, razdvojene zarezom.
a / b kada se dva cijela broja dijele, rezultat
je cijeli broj (bez decimala). 10 / 3 = 3, ne 3.33. Ovo se zove
integer division. Da bi dobio decimale, jedan od njih mora biti float:
(float)a / b.
a % b — modulo operator vraća ostatak pri dijeljenju.
10 % 3 = 1 jer 10 = 3×3 + 1. Korisno za provjeru parnosti
(broj % 2 == 0 = parni broj).
U printf, %% ispisuje literal znak %.
x++ — post-inkrement: povećava vrijednost za 1.
Ekvivalentno s x = x + 1 ili x += 1.
Razlika između x++ (post) i ++x (pre) je vidljiva
samo kada se koriste unutar izraza.
= je dodjela, == je
usporedba. Pisanje if (a = 5) umjesto if (a == 5)
neće dati grešku kompajliranja, ali program će raditi pogrešno.
Uvjetni iskazi — if / else
Uvjetni iskazi omogućuju programu da donosi odluke. Ovisno o tome je li neki uvjet istinit ili lažan, program izvodi različite blokove koda. U C-u, 0 je lažno, sve ostalo je istinito.
#include <stdio.h>
int main() {
int rezultat = 75;
if (rezultat >= 90) {
printf("Odlican (5)\n");
} else if (rezultat >= 75) {
printf("Vrlo dobar (4)\n");
} else if (rezultat >= 60) {
printf("Dobar (3)\n");
} else if (rezultat >= 50) {
printf("Dovoljan (2)\n");
} else {
printf("Nedovoljan (1)\n");
}
// Kraći zapis za jednostavne uvjete
int broj = 7;
if (broj % 2 == 0) {
printf("%d je paran broj.\n", broj);
} else {
printf("%d je neparan broj.\n", broj);
}
return 0;
}
else nema uvjet i hvata sve što nije ispunilo prethodne uvjete
(fallback). Nije obavezan, ali je dobra praksa.
if (rezultat >= 90) — uvjet u zagradama mora biti izraz koji
daje istinu (true) ili laž (false). Operatori usporedbe:
== jednako, != nije jednako,
> veće, < manje,
>= veće ili jednako, <= manje ili jednako.
broj % 2 == 0 — ako je ostatak pri dijeljenju
s 2 jednak nuli, broj je paran. Kombiniramo dva operatora: %
(modulo) i == (usporedba).
#include <stdio.h>
int main() {
int dob = 22;
int ima_vozacku = 1; // 1 = true, 0 = false
// && = I (oba uvjeta moraju biti istinita)
if (dob >= 18 && ima_vozacku) {
printf("Moze voziti.\n");
}
// || = ILI (barem jedan uvjet mora biti istinit)
int je_vikend = 0;
int je_praznik = 1;
if (je_vikend || je_praznik) {
printf("Ne radi se!\n");
}
// ! = negacija (obrće istinu/laž)
int je_zatvoren = 0;
if (!je_zatvoren) {
printf("Dućan je otvoren.\n");
}
return 0;
}
dob >= 18 && ima_vozacku — istinito samo ako je osoba punoljetna
I ima vozačku.
!je_zatvoren
znači "nije zatvoren", tj. otvoren je. !0 je istinito,
!1 je lažno.
for petlja
Petlja je mehanizam koji omogućuje ponavljanje bloka koda više puta.
Bez petlji, za ispis 100 redova morali bismo pisati 100 linija koda.
S petljom, to je 3 linije. for petlja se koristi kada
unaprijed znamo koliko puta nešto treba ponoviti.
Kako radi for petlja
int i = 0i < 10i++ — pa nazad na korak 2#include <stdio.h>
int main() {
// Ispis brojeva od 1 do 5
for (int i = 1; i <= 5; i++) {
printf("Broj: %d\n", i);
}
printf("---\n");
// Zbrajanje od 1 do 100
int suma = 0;
for (int i = 1; i <= 100; i++) {
suma += i;
}
printf("Suma 1 do 100 = %d\n", suma);
return 0;
}
for (int i = 1; i <= 5; i++) — tri dijela odvojena točka-zarezom:
1. dio:
int i = 1 — stvaramo varijablu i (brojač)
i postavljamo je na 1. Izvodi se jednom.
2. dio:
i <= 5 — uvjet koji se provjerava
prije svakog ponavljanja. Dok je i manje ili jednako 5, petlja nastavlja.
3. dio:
i++ — povećaj i za 1 nakon svakog
prolaska kroz tijelo. Ovo osigurava da petlja jednom završi.
{ }. Pri prvom prolasku i = 1,
pa ispiše "Broj: 1". Zatim i++ → i = 2, provjeri uvjet
(2 <= 5, istinito), ispiše "Broj: 2"... i tako dalje do i = 6 kada uvjet
postaje lažan i petlja staje.
suma += i — compound assignment operator.
Ekvivalentno s suma = suma + i. Nakon svakog prolaska, dodajemo
trenutnu vrijednost i na ukupnu sumu. Gauss je dokazao da je
suma od 1 do 100 uvijek 5050 — provjeri!
#include <stdio.h>
int main() {
// Odbrojavanje (korak -1)
printf("Odbrojavanje:\n");
for (int i = 5; i >= 1; i--) {
printf("%d...\n", i);
}
printf("Start!\n\n");
// Korak od 2 (samo parni)
printf("Parni od 2 do 10:\n");
for (int i = 2; i <= 10; i += 2) {
printf("%d ", i);
}
printf("\n\n");
// Ugniježđene petlje (množenje tablica)
printf("Tablica množenja (1-5):\n");
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= 5; j++) {
printf("%4d", i * j);
}
printf("\n");
}
return 0;
}
i-- kao korak — petlja ide unazad.
Počinje od 5, smanjuje za 1 svaki put, staje kad i postane manji od 1.
Uvjet sad gleda >= umjesto <=.
i += 2 — korak nije morao biti 1. Ovdje se i povećava
za 2 svaki put pa prolazimo samo kroz parne vrijednosti.
Može biti i i += 5, i *= 2 ili bilo što.
i), unutarnja kontrolira stupac (j).
Za svaki i, unutarnja petlja prođe sve vrijednosti j.
Ukupno izvođenja: 5 × 5 = 25 puta.
%4d — ispiši broj u polju širine 4 znaka (za poravnanje stupaca).
break — odmah izlazi iz petlje, prekida sve ponavljanje.continue — preskače ostatak trenutnog prolaska i ide na sljedeći.Primjer:
if (i == 3) continue; — preskoči ispis broja 3, nastavi s 4.
while petlja
while petlja se koristi kada ne znamo unaprijed koliko puta
treba nešto ponoviti — samo znamo uvjet pod kojim petlja treba nastaviti.
Tipičan primjer: čekaj korisnički unos dok ne unese ispravnu vrijednost,
čitaj datoteku dok ne dođeš do kraja, igraj igru dok igrač ima živote.
#include <stdio.h>
int main() {
// Dijeli broj s 2 dok ne postane manji od 1
int broj = 100;
printf("Pocetni broj: %d\n", broj);
while (broj >= 1) {
printf("%d -> ", broj);
broj = broj / 2;
}
printf("kraj\n\n");
// Digitalna suma broja (zbroj znamenki)
int n = 12345;
int suma = 0;
printf("Znamenke broja %d:\n", n);
while (n > 0) {
int znamenka = n % 10; // izvuci zadnju znamenku
suma += znamenka;
printf(" Znamenka: %d (suma: %d)\n", znamenka, suma);
n = n / 10; // ukloni zadnju znamenku
}
printf("Suma znamenki: %d\n", suma);
return 0;
}
while (broj >= 1) — provjeri uvjet. Ako je broj
veći ili jednak 1, uđi u petlju. Važno: varijabla broj
mora biti deklarirana izvan petlje, jer se mijenja unutar nje i
provjerava u uvjetu.
broj = broj / 2 — ovo je korak koji mijenja stanje.
Bez ovoga, broj se nikad ne bi promijenio i dobili bismo
beskonačnu petlju koja nikad ne završava. Uvijek se pitaj:
"Što mijenjam unutar petlje da bi uvjet jednom postao lažan?"
int znamenka = n % 10 — trik za izvlačenje zadnje znamenke broja.
12345 % 10 = 5 (ostatak pri dijeljenju s 10).
n = n / 10 — integer division briše zadnju znamenku.
12345 / 10 = 1234 (nema decimala). Sljedeći krug:
1234 % 10 = 4, i tako dalje dok n ne postane 0.
do-while — uvjet se provjerava na kraju
do-while je varijanta gdje se tijelo izvodi barem jednom
— bez obzira na uvjet. Provjera dolazi tek na kraju prvog prolaska.
#include <stdio.h>
int main() {
int broj;
int pokusaj = 0;
// Simulacija unosa - korisnik mora unijeti pozitivan broj
// (ovdje hardkodiramo -5 kao "korisnički unos" za demo)
do {
broj = -5; // zamišljamo da korisnik ovo unosi
pokusaj++;
printf("Pokusaj %d: unesen broj %d\n", pokusaj, broj);
if (pokusaj >= 3) break; // za demo, izađi nakon 3 pokušaja
} while (broj <= 0);
printf("Petlja izvrsena %d put(a).\n", pokusaj);
return 0;
}
do { — tijelo petlje odmah počinje, bez provjere uvjeta.
Garantira da se unutrašnjost izvrši barem jednom. Korisno za menije,
validaciju unosa, i situacije gdje moraš nešto napraviti pa onda odlučiti
treba li ponoviti.
} while (broj <= 0); — uvjet se provjerava nakon tijela.
Obrati pažnju na točku-zarez na kraju — jedina petlja u C-u koja zahtijeva
; iza zatvorene zagrade.
for — znaš točan broj ponavljanja (prolaz kroz niz, petlja N puta)while — ne znaš broj ponavljanja, provjera je na početkudo-while — tijelo mora izvesti barem jednom, provjera na kraju
Funkcije
Funkcija je imenovan blok koda koji radi određenu zadaću. Umjesto da isti kod pišeš na 10 mjesta, napišeš ga jednom kao funkciju i pozoveš je gdje god trebaš. Dobar program je skup malih, jasnih funkcija — svaka odgovorna za jedno i samo jedno.
#include <stdio.h>
// Definicija funkcije koja prima dva broja i vraća njihov zbroj
int zbroji(int a, int b) {
return a + b;
}
// Funkcija bez povratne vrijednosti (void)
void ispisi_liniju(int duljina) {
for (int i = 0; i < duljina; i++) {
printf("-");
}
printf("\n");
}
// Funkcija koja računa faktorijel rekurzivno
int faktorijel(int n) {
if (n <= 1) return 1;
return n * faktorijel(n - 1);
}
int main() {
int x = 8, y = 3;
printf("%d + %d = %d\n", x, y, zbroji(x, y));
ispisi_liniju(20);
printf("5! = %d\n", faktorijel(5));
printf("7! = %d\n", faktorijel(7));
return 0;
}
int zbroji(int a, int b) — anatomija funkcije:int = povratni tip (što funkcija vraća)zbroji = ime funkcije(int a, int b) = parametri (ulazni podaci, s tipovima)
return a + b; — izračunaj a + b i vrati rezultat
pozivaču. return odmah završava funkciju. Povratna vrijednost mora
odgovarati deklariranom tipu (int).
void ispisi_liniju(int duljina) — void znači da funkcija
ne vraća nikakvu vrijednost. Koristi se za funkcije koje samo
"rade nešto" (ispisuju, mijenjaju podatke) bez rezultata.
faktorijel(5) = 5 * faktorijel(4) = 5 * 4 * faktorijel(3)...
sve dok ne dođe do faktorijel(1) koji vraća 1 (bazni slučaj).
Svaka rekurzija mora imati bazni slučaj koji zaustavi pozivanje!
Nizovi (Arrays)
Niz je kolekcija varijabli istog tipa smještena uzastopno u memoriji.
Umjesto 5 zasebnih varijabli ocjena1, ocjena2, ..., imaš jedan
niz ocjene[5]. Svi elementi su dostupni putem indeksa koji počinje od 0.
#include <stdio.h>
int main() {
// Deklaracija i inicijalizacija
int ocjene[5] = {4, 3, 5, 4, 5};
// Ispis svakog elementa
for (int i = 0; i < 5; i++) {
printf("Ocjena %d: %d\n", i + 1, ocjene[i]);
}
// Pronalazak maximalne ocjene
int max = ocjene[0];
for (int i = 1; i < 5; i++) {
if (ocjene[i] > max) {
max = ocjene[i];
}
}
printf("Maksimalna ocjena: %d\n", max);
// Izračun prosjeka
int suma = 0;
for (int i = 0; i < 5; i++) {
suma += ocjene[i];
}
printf("Prosjek: %.1f\n", (float)suma / 5);
return 0;
}
int ocjene[5] = {4, 3, 5, 4, 5};int = tip elemenata, [5] = veličina niza (broj elemenata),
{...} = inicijalne vrijednosti. Elementi su:
ocjene[0]=4, ocjene[1]=3, ocjene[2]=5...
Indeks počinje od 0! Zadnji element je ocjene[4], ne ocjene[5].
ocjene[i] — pristup elementu na poziciji i.
For petlja s i od 0 do 4 prolazi kroz sve elemente redom.
Ovo je standardan obrazac za prolaz kroz niz.
max = ocjene[0]), zatim prođi kroz ostale.
Svaki put kada nađeš nešto veće od trenutnog max, ažuriraj max.
(float)suma / 5 — cast pretvara suma
iz int u float prije dijeljenja, tako da dobijemo decimalni
rezultat. Bez toga, 21 / 5 = 4 (integer division), ne 4.2.
Stringovi
U C-u ne postoji poseban tip za tekst — string je zapravo
niz znakova (char array) koji završava specijalnim
znakom '\0' (null terminator). To je jedini način da
funkcije znaju gdje string završava.
#include <stdio.h>
#include <string.h>
int main() {
char ime[50] = "Ana";
char prezime[50] = "Kovac";
char puno_ime[100];
// Spajanje stringova
strcpy(puno_ime, ime); // kopiraj "Ana" u puno_ime
strcat(puno_ime, " "); // dodaj razmak
strcat(puno_ime, prezime); // dodaj "Kovac"
printf("Ime: %s\n", puno_ime);
printf("Duljina: %zu znaka\n", strlen(puno_ime));
// Usporedba stringova
char lozinka[20] = "tajno123";
char unos[20] = "tajno123";
if (strcmp(lozinka, unos) == 0) {
printf("Lozinka tocna!\n");
} else {
printf("Pogresna lozinka.\n");
}
return 0;
}
#include <string.h> — biblioteka s funkcijama za rad sa stringovima.
Bez nje ne možemo koristiti strcpy, strcat,
strlen, strcmp.
char ime[50] = "Ana"; — niz od 50 znakova. String "Ana" zauzima
samo 4 mjesta: 'A', 'n', 'a', '\0'. Ostatak niza je slobodan.
Veličina mora biti dovoljno velika za sadržaj + null terminator.
strcpy(odrediste, izvor) — kopira string iz izvora u odredište.
Ne možeš koristiti = za kopiranje stringova
(to bi kopiralo pointer, ne sadržaj). Uvijek koristi strcpy.
strcat(odrediste, dodatak) — concatenate, dodaje string na kraj
drugog stringa. Modifikacija je in-place — niz puno_ime se
direktno mijenja.
strcmp(s1, s2) — uspoređuje dva stringa. Vraća
0 ako su jednaki, negativan broj ako je s1 "manji",
pozitivan ako je s1 "veći" (leksikografski). Nikad ne koristi ==
za usporedbu stringova — to uspoređuje adrese, ne sadržaj!
Pokazivači (Pointers)
Pokazivač je varijabla koja čuva memorijsku adresu neke druge varijable, umjesto same vrijednosti. Ovo je tema koja zbunjuje mnoge početnike — ali je temelj C-a. Operativni sustavi, driveri i sve visoko-performantne strukture podataka ovise o pokazivačima.
Vizualni prikaz memorije
Varijabla ptr čuva adresu varijable broj, ne njenu vrijednost.
#include <stdio.h>
int main() {
int broj = 42;
int *ptr = &broj; // ptr cuva adresu varijable broj
printf("Vrijednost broja: %d\n", broj);
printf("Adresa broja: %p\n", (void*)&broj);
printf("Vrijednost ptr-a: %p\n", (void*)ptr);
printf("Dereferenciranje ptr: %d\n", *ptr);
// Promjena vrijednosti PUTEM pokazivaca
*ptr = 100;
printf("\nNakon *ptr = 100:\n");
printf("broj = %d\n", broj); // ispisuje 100!
return 0;
}
int *ptr = &broj;& = address-of operator — daj mi adresu varijable brojint * = tip "pokazivač na int" — zvjezdica je dio tipaptr sada sadrži memorijsku adresu gdje živi varijabla broj
*ptr = dereferenciranje — "idi na adresu koju ptr čuva
i pročitaj vrijednost tamo". Zvjezdica ovdje znači nešto drugo nego pri deklaraciji —
pri deklaraciji definira tip, pri korištenju pristupa vrijednosti na adresi.
*ptr = 100 — piši 100 na adresu gdje ptr pokazuje.
Budući da ptr pokazuje na broj, ovo mijenja broj.
Linija 15 ispisuje 100 iako smo mijenjali putem pokazivača, ne direktno.
NULL: int *ptr = NULL;. Dereferenciranje NULL pokazivača
uzrokuje crash programa (Segmentation fault). Provjeri: if (ptr != NULL) { ... }
Čitanje unosa — scanf
Do sada su svi naši programi imali unaprijed zadane podatke u kodu.
U stvarnosti, program mora čitati unos od korisnika. Funkcija scanf
čita podatke s tipkovnice — i upravo tu se po prvi put direktno susrećemo
s pokazivačima u praksi.
#include <stdio.h>
int main() {
int a, b;
printf("Unesi prvi broj: ");
scanf("%d", &a);
printf("Unesi drugi broj: ");
scanf("%d", &b);
printf("\nZbroj: %d\n", a + b);
printf("Razlika: %d\n", a - b);
printf("Produkt: %d\n", a * b);
if (b != 0) {
printf("Kolicnik: %.2f\n", (float)a / b);
} else {
printf("Dijeljenje s nulom nije moguce.\n");
}
return 0;
}
scanf("%d", &a) — čita cijeli broj s tipkovnice i sprema
ga u varijablu a. Zašto & (ampersand)?
Zato što scanf mora znati gdje u memoriji da spremi podatak.
Dajemo mu adresu varijable a putem &a.
Bez & program se ruši!
if (b != 0) je sigurnosna provjera.
Dijeljenje s nulom je undefined behavior u C-u i može
srušiti program ili dati nepredvidiv rezultat.
#include <stdio.h>
int main() {
float a, b;
char operacija;
printf("=== Mini kalkulator ===\n");
printf("Unesi racun (npr. 5 + 3): ");
scanf("%f %c %f", &a, &operacija, &b);
printf("%.2f %c %.2f = ", a, operacija, b);
switch (operacija) {
case '+': printf("%.2f\n", a + b); break;
case '-': printf("%.2f\n", a - b); break;
case '*': printf("%.2f\n", a * b); break;
case '/':
if (b != 0) printf("%.2f\n", a / b);
else printf("Greska: dijeljenje s nulom\n");
break;
default:
printf("Nepoznata operacija.\n");
}
return 0;
}
scanf("%f %c %f", &a, &operacija, &b) — čita
tri vrijednosti odjednom: float, char i float. Format stringa definira
što se očekuje. Razmaci u formatu preskaču whitespace u unosu.
switch — elegantnija alternativa dugom if-else if
lancu kada uspoređujemo jednu varijablu s više mogućih vrijednosti.
Svaki case je jedna mogućnost, break izlazi
iz switch bloka, default hvata sve ostalo (kao else).
Sada znaš: varijable, tipove, operatore, uvjete, petlje (for/while/do-while), funkcije, nizove, stringove, pokazivače i unos s tipkovnice. To su temelji C-a — sve ostalo (strukture, dinamička alokacija, datoteke) gradi se na ovome.