290 likes | 497 Views
Elementi izrade programa. Normalan slijed; Bezuvjetni skok. Normalan programski slijed naredba_1 naredba_2 naredba_3 ... Bezuvjetni skok Pseudokod: C: idi na oznaka_naredbe goto oznaka_naredbe ;. Grananje. S: Oznaka za jednu ili više naredbi, odnosno programski odsječak
E N D
Normalan slijed; Bezuvjetni skok • Normalan programski slijed naredba_1 naredba_2 naredba_3 ... • Bezuvjetni skok Pseudokod: C: idi naoznaka_naredbegotooznaka_naredbe;
Grananje • S: Oznaka za jednu ili više naredbi, odnosno programski odsječak • Uvjetno obavljanje naredbi (jednostrana selekcija) Pseudokod: C: ako je (logički_izraz) ondaif (logički_izraz){ | S S; } • Grananje (dvostrana selekcija) Pseudokod: C: ako je (logički_izraz) onda if (logički_izraz){ | S_1 S_1; inače } else { | S_2 S_2; }
Grananje • Višestruko grananje (višestrana selekcija) Pseudokod: C: ako je (logički_izraz_1) onda if (logički_izraz_1) { | S_1S_1; inače ako je (logički_izraz_2) onda } else if (logički_izraz_2) { | S_2 S_2; inače ako je (logički_izraz_3) onda } else if (logički_izraz_3) { | S_3 S_3; ... ... inače } else { | S_0 S_0; }
Grananje • Skretnica Pseudokod: C: skretnica (vrijednost) switch(cjelobrojna vrijednost){ slučaj C1 case C1: | S_1S_1; break; slučaj C2 case C2: | S_2S_2; break; ... ... slučaj Cn case Cn: | S_nS_n; break; inačedefault: | S_n+1S_nplus1; }
Grananje • Primjer za vježbu: Ostvariti skretnicu naredbom if - else. if (vr == C1) { S1; } else if (vr == C2) { S2; } ... } else if (vr == Cn) { Sn; } else { S_nplus1; } • Ako se iz skretnice izbace naredbe break, obavljaju se slijedno sve naredbe iza one gdje je prvi put zadovoljeno: vr == Ci
Grananje • Skretnica bez break korištenjem naredbe if. nadjen = 0; if (vr == C1) { S1; nadjen = 1; } if (vr == C2 || nadjen) { S2; nadjen = 1; } ... if (vr == Cn || nadjen) { Sn; } S_nplus1;
Programska petlja • Petlja s ispitivanjem uvjeta ponavljanja na početku Pseudokod: C: dok je (logički_izraz)činitiwhile (logički_izraz) { | SS; } • Petlja s ispitivanjem uvjeta ponavljanja na kraju • U nekim programskim jezicima postoji oblik: REPEAT...UNTIL Pseudokod: C: ponavljatido { | SS; do (logički_izraz) } while(!logički_izraz) • Standardna petlja u C-u: Pseudokod: C: ponavljatido { | S S; dok je (logički_izraz)}while (logički_izraz);
Programska petlja • Petlja s poznatim brojem ponavljanja Pseudokod: C: zai := pocdokraj (korakk) činitifor(i=poc;i<=kraj;i=i+k){ | S S; } • Primjer: Realizacija istog odsječka petljom while: i = poc; while (i <= kraj) { S; // niz naredbi koje ne mijenjaju vrijednost za i i += k; } ili općenitije: i = poc; while ((i - kraj) * k <= 0) { S; // niz naredbi koje ne mijenjaju vrijednost za i i += k; } // U čemu je razlika? Pokus: poc = 5; kraj = -20; k = -7;
Programska petlja • Skok iz petlje Pseudokod: C: izađi iz petljebreak; skoči na kraj petljecontinue; • Beskonačna petlja Pseudokod: C: ponavljajwhile(1) { S S; zauvijek} • U algoritmima ovakva petlja nije dopuštena jer je u suprotnosti sa zahtjevom da postupak bude konačan.
Programska petlja • U tijelu petlje mora postojati barem jednom ispitivanje uvjeta za izlazak iz petlje: while(1) { S1; if (logički_izraz) break; S2; ... } ili while(1) { S1; if (logički_izraz) goto oznaka_naredbe; S2; ... } • Naredbu gototreba izbjegavati ako je ikako moguće. Smanjuje se mogućnost pogreške i olakšava testiranje programa ako svaka programska cjelina (npr. petlja) ima samo jedan ulaz i jedan mogući izlaz.
Procedure • Programi se sastoje od procedura. Prva pozvana procedura je glavni program. Kad glavni program završi, slijedi povratak u operacijski sustav. Inače, povratak iz procedure je uvijek na onu proceduru koja ju je pozvala. Uobičajena je podjela na funkcije (function) koje imaju od nula do više ulaznih argumenata i vraćaju jedan rezultat, te na općenite potprograme (subroutine) koje rezultat predaju argumentima. • U jeziku C sve procedure su funkcije koje daju rezultat nekog od tipova podataka, ali mogu mijenjati i vrijednost argumenata. • Posebni slučajevi: • Glavni program: main • Potprogram koji u imenu ne vraća vrijednost:void
Razmjena podataka između funkcija • globalne varijable • argumenti navedeni u zagradi, prenose se vrijednosti (call by value) • za prijenos vrijednosti u pozivajuću funkciju koriste se kod poziva funkcije kao argumenti adrese, a u definiciji funkcije argumenti su pokazivači (call by reference). • Ako funkcija mora predati rezultat preko argumenata, nužno se koristi call by reference. • Ulazno-izlazne operacije: • Za slijedno čitanje/pisanje preko standardnih ulazno-izlaznih jedinica koristit će se odgovarajuće C funkcije ili naredbe pseudokoda: • ulaz (lista adresa argumenata) • izlaz (lista argumenata) • Kod čitanja je dakle nužan call by reference, dok kod ispisa može poslužiti i call by value.
1 2 3 4 5 6 7 8 9 10 11 12 Dvodimenzionalna i višedimenzionalna polja kao argumenti funkcije • Polje u funkciji treba prihvatiti kao jednodimenzionalno polje ili pokazivač. • Primjer: int polje[3][4] = { { 1, 2, 3, 4}, { 5, 6, 7, 8}, { 9, 10, 11, 12} }; u memoriji računala spremljeno je kao tj. jednako kao jednodimenzionalno polje od 12 elemenata. • Razlikuju se fizičke dimenzije polja od logičkih, koje mogu biti i manje. Za pozicioniranje je potrebno znati fizički broj stupaca (MAX_broj_stupaca). • Za dohvat elementa izi-tog retka treba preskočitii-1 punih redaka. • Kako prvi redak ima indeks 0, drugi 1, treći 2 itd., za dohvat elemenata retka s indeksomitreba preskočitii*MAX_broj_stupacačlanova polja. • Općenito: dvodim_polje[i][j] jednodim_polje[i * MAX_broj_stupaca + j]
Pisanje strukturiranih programa • Osnovni naputci • specifikacija ulaznih i izlaznih varijabli ( u C obvezatno!) • definicija lokalnih varijabli • tok programa prema dolje, osim petlji i neizbježne goto naredbe • poravnati naredbe iste razine za povećanje čitkosti (indentation) • dokumentacija kratka ali smislena • koristiti potprograme gdje je to primjereno • Izbor vrste petlje • Primjer: Čitati članove nekog skupa nenegativnih brojeva sve dok njihova suma ne premaši neki predviđeni iznos n. y = 0; do { ulaz(&x); y += x; } while (y <= n); Programi nisu funkcionalno identični jer u prvom slučaju se uvijek obavlja barem jedno čitanje, pa program ispravno radi samo za n0. U drugom slučaju, ako je n<0, smatra se da je bez čitanja ijednog podatka postupak završen. y = 0; while (y <= n){ ulaz(&x); y += x; }
Pisanje strukturiranih programa • Primjer: Pročitatin vrijednosti i obraditi ih procedurom PROCES. Korištenjem petlje while: i = 0; while (i < n) { ulaz(&x); PROCES(x); i++; } S obzirom da je unaprijed poznat broj ponavljanja, primjerenije je koristiti petlju s poznatim brojem ponavljanja: for (i = 0; i < n; i++) { ulaz(&x); PROCES(x); } Ne radi se samo o manjem broju naredbi, nego se smanjuje i mogućnost pogreške u pisanju programa.
Pisanje strukturiranih programa • Ako ima dva kriterija za završetak petlje: kraj podataka ili da rezultat procedurePROCES, pohranjen u varijabliy bude nula: do { ulaz(&x); if (x == EOF) break; PROCES(x, &y); } while (y != 0); if (x != EOF) { ... } else { ... } Na izlasku iz programskog segmenta ne zna se zbog čega je petlja završila pa se to mora ispitivati. • Korištenje naredbecase • Uvijek se može uprabiti elementarnija naredbaif-else. Naredba case je u prednosti kad ima mnogo ravnopravnih grananja.
Višeobličje; Logičke operacije • Razlika između opisa algoritma i realizacije - višeobličje • Deklarirat će se tip varijable tamo gdje je to jednoznačno za algoritam. C ne podržava višeobličje (polimorfizam). Umjesto nekog od elementarnih tipova može se (zahvaljujući naredbi typedef), koristiti općenititip. • Obavljanje logičkih operacija • U analizi algoritama pretpostavljat će se obavljanje logičkih operacija na najkraći način (reducirano ispitivanje, short-circuit evalation). Npr. logički izraz (logički_izraz1 logički_izraz2) daje odmah rezultat true ako je logički_izraz1 = true, bez vrednovanja za logički_izraz2. Analogno (logički_izraz1 logički_izraz2) daje odmah rezultat false ako je logički_izraz1 = false.
Operacije u programskom jeziku C • Operacija izjednačavanja: = • Aritmetičke operacije : +, -, *, /, % • Operatori za skraćeno pisanje nekih aritmetičkih izjednačavanja: i++; ili ++i ; odgovara i = i + 1; i--; ili --i; odgovara i = i – 1; x = a * b++ odgovara x = a + b ; b = b + 1; x = --i * (a * b) odgovara i = i – 1 ; x = i * (a + b); i += 10; odgovara i = i + 10; i -= 10; odgovara i = i – 10; i*= 10; odgovara i = i * 10; i /= 10; odgovara i = i / 10; Operator pretvorbe tipa (cast ) : primjer type1 i; type2 j; j = sqrt ( (type2) i); Uspoređivanje: ==, >, <, >=, <=, != • Logički operatori: &&, ||, !
Definiranje novih tipova podataka u C-u pomoću ključne riječi struct • Već znamo da u C-u možemo definirati varijable koje mogu biti različitih tipova. Npr, kada negdje u programu napišemo int i; float f; char c; int p[10]; • Time smo definirali četiri varijable: varijabla i je tipa int, f je tipa float, c je tipa char, a p je polje tipa int koje ima 10 elemenata. Tipove poput int, float i char zovemo osnovni ili ugrađeni tipovi, jer su oni ugrađeni u svaki standardni C kompajler. • No, pored definiranja varijabli čiji je tip neki od ugrađenih tipova, jezik C nam omogućuje još nešto: da, koristeći se osnovnim tipovima kao temeljnim gradivim jedinicama, sami definiramo svoje (složenije) tipove i onda ih koristimo ravnopravno, baš kao i ugrađene tipove. To se radi pomoću ključne riječi struct. • Npr., pogledajmo ovu programsku konstrukciju: struct complex { float Re; float Im; }; • Gornjom definicijom definirali smo novi, složeni tip podataka, čije je ime struct complex i koji je sastavljen od dva člana: realnog broja Re i realnog broja Im. Kada u programu želimo koristiti naš novodefinirani tip, napisat ćemo nešto poput ovoga: struct complex z1; struct complex z2; • Time smo definirali dvije varijable, z1 i z2, koje su tipa struct complex. Da ne bismo svaki put pri deklaraciji varijabli morali pisati dvije riječi (struct complex) možemo se poslužiti ključnom riječi typedef i napisati ovo: typedef struct complex Complex;
Ključna riječ typedef, suprotno svom imenu, ne služi za definiranje novih tipova, nego samo za preimenovanje već postojećih tipova. Koristi se u obliku: typedef stari_tip novo_ime_za_stari_tip; • Dakle, pomoću naredbe typedef struct complex Complex; rekli smo ustvari kompajleru da je ime Complex (uočite veliko početno slovo C) od sada pa nadalje novo ime za stari (već prije definirani) tip struct complex. Nakon toga možemo, primjerice, deklarirati neki kompleksni broj z na dva načina: Ovako: struct complex z; Ili ovako: Complex z; • Naravno da je drugi način čitljiviji od prvoga, što je sasvim dobar razlog za korištenje typedef-a. • Kako deklarirati pokazivač na kompleksni tip? Znamo da pokazivač pok na varijablu tipa int deklariramo sa int * pok; • Slično tome, pokazivač cpok na tip Complex deklariramo ovako: Complex * cpok; • Varijable z1, z2 i z su nakon gornjih deklaracija spremne za korištenje i možemo dalje napisati npr. ovo: z1.Re = 1; z1.Re = 3; z2.Re = 2; z2.Im = 5; cpok = &z1; cpok->Re = 7; • Primjetite upotrebu operatora . (točka) na mjestima na kojima pristupamo članovima Re i Im od kojih su sastavljene varijable z1 i z2 tipa Complex. Primjetimo također upotrebu operatora -> pomoću kojeg pristupamo članovima Re i Im preko pokazivača cpok. • Dakle: kada imamo varijablu tipa Complex, njenim članovima pristupamo pomoću operatora točka (.). No, kada imamo pokazivač koji je tipa Complex, tada članovima pristupamo pomoću operatora ->.
Pogledajmo sada potpuni primjer upotrebe novog tipa struct complex : #include <stdio.h> struct complex { float Re; float Im; }; typedef struct complex Complex; Complex Add(Complex u, Complex v) { Complex rez; rez.Re = u.Re + v.Re; rez.Im = u.Im + v.Im; return rez; } int main() { Complex z1 = {1, 3}; /* z1 = 1 + 3i */ Complex z2; Complex r; Complex * rpok; z2.Re = 2; z2.Im = 5; /* z2 = 2 + 5i */ r = Add(z1, z2); /* r = 3 + 8i */ rpok = &r; /* sada rpok pokazuje na r */ printf("Rezultat zbrajanja: %f + %fi\n", r.Re, r.Im); rpok->Re = rpok->Re + 1; /* indirektno mijenjamo r, preko pointera */ printf("Nakon uvecavanja Re dijela za 1 : %f + %fi\n", r.Re, r.Im); return 0; }
Razvoj algoritma na primjeru množenja kompleksnih brojeva • Za izradu efikasnog i brzog algoritma potrebno je dobro razumijevanje problema koji se rješava i metoda koje se koriste u rješavanju • Jednostavan primjer množenja kompleksnih brojeva u kojem je potrebno znanje matematike: 1) Algoritam zasnovan na znanju množenja realnih brojeva: množiti svaki element sa svakim, zatim sakupiti zajedno brojeve koji nemaju i imaju “i”. Algoritam za to ima mnogo koraka i ispitivanja, spor i kompliciran, a rezultat je jednostavan izraz 2) Algoritam koji direktno koristi definiciju množenja kompleksnih brojeva iz matematike: 4 množenja, 1 zbrajanje, 1 oduzimanje (a+ib)*(c+id) = (ac –bd) +i(ad +bc) na nekim mašinama množenje je mnogo sporiji proces od zbrajanja, pa će se krajnji rezultat dobiti brže upotrebom izraza (a+ib)*(c+id) = (ac - bd) + i [(a+b)(c+d) – ac -bd] gdje se obavlja 3 množenja, 2 zbrajanja i 3 oduzimanja
Maksimalna granična vrijednost: primjer s kompleksnim brojevima • Međurezultati množenja u nekim izrazima mogu biti veći od maksimalne granične vrijednosti za neki tip podataka pa dolazi do prelijevanja (overflow) znamenki, iako je konačni rezultat unutar granica koje se mogu predstaviti tim tipom podataka • Raniji primjer množenja kompleksnih brojeva: do prelijevanja će doći samo kada je i krajnji rezultat blizu maksimalne granične vrijednosti • Dobar primjer je algoritam za određivanje modula kompleksnog broja dan izrazom: U rješavanju gornjeg izraza često će međurezultat (kvadrat) biti veći od graničnog. Ispravan način na koji ovaj račun ostaje unutar granica je:
Isti problem se javlja i kod dijeljenja kompleksnih brojeva: Ispravan postupak računanja je:
Rješenje kvadratne jednadžbe #include <stdio.h> #include <math.h> int main() { float a, b, c; float D; float x1, x2; printf("Unesi a: "); scanf("%f", &a); printf("Unesi b: "); scanf("%f", &b); printf("Unesi c: "); scanf("%f", &c); D = b * b - 4 * a * c; if(D >= 0) { x1 = (-b - sqrt(D)) / (2 * a); x2 = (-b + sqrt(D)) / (2 * a); printf("x1 = %f\n", x1); printf("x2 = %f\n", x2); } else printf("Rjesenja su kompleksni brojevi!"); return 0; }
Funkcija ne mijenja svoju ulaznu varijablu #include <stdio.h> void func(int); int main() { int a = 1; printf("Prije poziva funkcije func: a = %d\n", a); func(a); printf("Nakon poziva funkcije func: a = %d\n", a); return 0; } void func(int a) { a = a + 100; } Funkcija mijenja elemente polja #include <stdio.h> void func(int[]); int main() { int i; int a[3] = {0, 1, 2}; printf("Prije poziva funkcije func:\n"); for(i = 0; i < 3; i++) printf("%d ", a[i]); printf("\n\n"); func(a); printf("Nakon poziva funkcije func:\n"); for(i = 0; i < 3; i++) printf("%d ", a[i]); printf("\n"); return 0; } void func(int a[]) { a[0] = a[0] + 100; } Promjena ulazne varijable u funkciji
Primjer s pokazivačima int main() { int a,b,*p; a=10; b=5; p=&a; printf("a= %d\n",a); printf("a= %d\n",*p); a++; printf("a= %d\n",a); printf("a= %d\n",*p); *p=*p+b; printf("a= %d\n",a); printf("a= %d\n",*p); return 0; }