13
PROGRAMIRANJE 2 LABORATORIJSKE VJEŽBE LV9 Rekurzije i pokazivači na funkcije Elektrotehnički fakultet Osijek Kneza Trpimira 2b www.etfos.unios.hr

LV9 - Rekurzije i Pokazivaci Na Funkcije

Embed Size (px)

DESCRIPTION

rekurzije

Citation preview

Page 1: LV9 - Rekurzije i Pokazivaci Na Funkcije

PROGRAMIRANJE 2

LABORATORIJSKE VJEŽBE

LV9 Rekurzije i pokazivači na funkcije

Elektrotehnički fakultet Osijek

Kneza Trpimira 2b

www.etfos.unios.hr

Page 2: LV9 - Rekurzije i Pokazivaci Na Funkcije

1 UVOD

Do sada smo obradili razne mogućnosti prilikom definiranja funkcija, objasnili smo razne

tipove varijabli koje mogu biti predani kao parametri funkcije te isto tako različite tipove

varijabli koji mogu biti postavljeni za povratnu vrijednost funkcije.

Danas ćemo se pozabaviti rekurzijama te ćemo pogledati kako izgledaju funkcije koje za

parametar imaju poziv druge funkcije.

Page 3: LV9 - Rekurzije i Pokazivaci Na Funkcije

2 REKURZIJE

Kada funkcija unutar svoje definicije poziva samu sebe, takva se funkcija naziva rekurzija.

Pogledajmo vrlo jednostavan primjer :

#include <stdio.h>

void func(void){

printf("\n Ovo je rekurzija \n");

func(); //funkcija poziva samu sebe

return;

}

int main(void){

func();

return 0;

}

U gornjem kodu, možemo vidjeti da funkcija func(), u svojoj definiciji, poziva samu sebe.

Upravo iz tog razloga je ova funkcija rekurzija. Nakon što bismo pokrenuli ovaj program,

dobili bismo sljedeći ispis na ekran:

Ovo je rekurzija

Ovo je rekurzija

.... ....

U gornjem primjeru vidimo da se funkcija vrti beskonačno, odnosno dok se program ne

sruši. Razlog tome je što se za svaki novi poziv funkcije kreira novi funkcijski stog, a kako se

funkcija konstantno poziva, tako se i konstantno kreiraju funkcijski stogovi. Na kraju se

prepuni stog (eng. stack overflow) i program se sruši.

Pozivom funkcije formiraju se unutar nje lokalne varijable koje su nedohvatljive izvan same

funkcije. Neovisnost lokalnih varijabli od okolnog programa omogućava da funkcija poziva

samu sebe - ponovnim pozivom se stvaraju nove lokalne varijable potpuno neovisne o

varijablama u pozivajućoj funkciji. Razmotrimo rekurziju na sljedećem jednostavnom

primjeru funkcije za računanje faktorijela:

int faktorijel(int n) {

return (n > 1) ? (n * faktorijel (n - 1)> : 1;

}

Pozovemo li tu funkciju iz main funkcije naredbom

int nf = faktorijel(3) ;

funkcija faktorijel() će pozivati samu sebe u tri "razine":

1. Prilikom prvog poziva formalni argument će biti inicijaliziran na vrijednost n = 3. U

uvjetnom izrazu će funkcija faktorijel() biti pozvana po drugi put, ali sada sa

stvarnim argumentom 2.

Page 4: LV9 - Rekurzije i Pokazivaci Na Funkcije

2. U drugom pozivu formalni argument se inicijalizira na n = 2. Argument pozivajuće

funkcije (n = 3) još uvijek postoji, ali je on nedohvatljiv i potpuno neovisan od

argumenta drugopozvane funkcije. U uvjetnom izrazu će funkcija faktorijel() biti

treći puta pozvana, ali ovaj puta sa stvarnim argumentom 1.

3. U trećem pozivu funkcije faktorijel() formalni argument se inicijalizira na

vrijednost 1 (u pozivajućim funkcijama formalni argumenti još uvijek postoje s

neizmijenjenim vrijednostima). Budući da nije zadovoljen uvjet u uvjetnom izrazu,

funkcija faktorijel() se više ne poziva, već se kao povratna vrijednost pozivnoj

funkciji šalje broj 1.

Slijedi postepeni povratak do početnog poziva funkcije:

1. Vrijednost 1 koju je vratila trećepozvana funkcija množi se s vrijednošću formalnog

argumenta u drugopozvanoj funkciji (n = 2), te se taj umnožak vraća kao rezultat

prvopozvanoj funkciji.

2. Vrijednost 2 koju je vratila drugopozvana funkcija množi se s vrijednošću formalnog

argumenta u početnoj funkciji (n = 3), te se taj umnožak vraća kao rezultat kodu

koji je prvi pozvao funkciju faktorijel() s argumentom 3.

Izvođenje koda može se simbolički prikazati slikom 1.

Slika 1. Primjer rekurzije

Mnoge matematičke formule se mogu implementirati primjenom rekurzije. Međutim, kod

primjene rekurzija valja biti krajnje oprezan: lokalne varijable unutar funkcija i adrese s

kojih se obavlja poziv funkcije se pohranjuju na stog, pa preveliki broj uzastopnih poziva

funkcije unutar funkcije može vrlo brzo prepuniti stog i onemogućiti daljnje izvođenje

programa. Daleko je sigurnije (i nerijetko efikasnije) takve formule implementirati

jednostavnim petljama. Rekurzije mogu biti vrlo efikasno sredstvo za rukovanje posebnim

strukturama podataka.

Na primjer, računanje faktorijela se može riješiti i bez rekurzije na sljedeći način, što je

efikasnije jer trebamo samo jedan poziv funkcije:

long faktorijeli(long n) {

long f=1;

for(;n>1;n--) f*=n;

return f;

}

0. p

ozi

v

faktorijel(3);

1. p

ozi

v

n = 3;

faktorijel(n-1);

2. p

ozi

v

n = 2;

faktorijel(n-1);

3. p

ozi

v

n = 1;

return 1;

nf=faktorijel(3); return

n*faktorijel(n-1);

return

n*faktorijel(n-1);

Page 5: LV9 - Rekurzije i Pokazivaci Na Funkcije

Pogledajmo još jedan primjer rekurzivne funkcije:

#include <stdio.h>

void unos(void);

int main(void){

printf("\n Unesite niz znakova: ");

unos();

return 0;

}

void unos(void){

char znak;

if((znak=getchar())!=’\n’) unos();

putchar(znak);

}

Funkcija učitava znakove sa standardnog ulaza sve dok ne naiđe do prijelaza u novu liniju i

ispisuje učitane znakove. Ako izvršimo program

Unesite niz znakova: Bok!

!koB

Prvo je ispisan znak za prijelaz u novi red, a zatim niz Bok! u obrnutom redoslijedu.

Svaki poziv funkcije unos učita jedan znak sa standardnog ulaza i spremi ga u lokalnu

varijablu znak. Ukoliko učitani znak nije znak prijelaza u novi red, ponovo se poziva funkcija

unos.

Svaka varijabla definirana unutar funkcije vidljiva je samo u toj funkciji. Ona se kreira kod

poziva funkcije i nestaje nakon izlaza iz funkcije. Stoga pri svakom novom pozivu funkcije

unos kreira se nova varijabla s imenom znak. Budući da varijabla definirana u funkciji

(unutarnja varijabla) nije vidljiva izvan te funkcije, prisutnost više različitih varijabli s

imenom znak je dozvoljeno. Dakle, pri svakom novom pozivu funkcije unos bit će pročitan i

pohranjen jedan znak sve dok ne naiđemo do znaka za prijelaz u novi red. Tada pozvana

funkcija ispiše znak za prijelaz u novi red i izađe. Funkcija koja ju je pozvala ispisat će znak

koji je pročitala (znak ’!’) i predaje kontrolu funkciji koja ju je pozvala. Na taj se način svi

upisani znakovi ispisuju u obrnutom redoslijedu.

U ovom jednostavnom primjeru vidi se drugi nedostatak rekurzivnog programa. Pri svakom

novom rekurzivnom pozivu funkcije kreiraju se ponovo sve lokalne varijable. U gornjem

primjeru koristili smo onoliko varijabli koliko je bilo upisanih znakova, mada bi jedna bila

sasvim dovoljna.

Rekurzija se najčešće koristi za povezane liste, binarna stabla, ispitivanje mogućih scenarija

u igrama poput šaha. Naučili smo da se rekurzija uvijek sastoji od dva dijela: uvjet završetka

Page 6: LV9 - Rekurzije i Pokazivaci Na Funkcije

rekurzije i dio u kojem funkcija poziva samu sebe pri čemu parametri koji se predaju vode

ka završetku rekurzije.

3 POKAZIVAČI NA FUNKCIJE

Do sada smo naučili koristiti pokazivače na razne tipove podataka, preostalo nam je naučiti

kako pokazivači mogu biti usmjereni na funkcije. Funkcija također ima svoju početnu

memorijsku adresu gdje počinje njena izvedba te ćemo tu adresu predati pokazivaču. No,

ako će se funkcija pozivat preko pokazivača, moraju joj biti dostupne informacije o broju i

tipu argumenata koji se trebaju predati te tipu povrate vrijednosti koji se očekuje. Baš radi

ovoga, pokazivač na funkciju je malo kompleksnije deklarirati od ostalih pokazivača.

3.1 DEKLARACIJA POKAZIVAČA NA FUNKCIJU

Pogledajmo deklaraciju jednostavne funkcije:

int (*pfunction) (int);

Na ovaj način smo deklarirali pokazivač imena pfunction koji pokazuje na funkciju koja

prima jedan parametar tipa int i vraća vrijednost tipa int pozivajućem programu. Ovaj

pokazivač još ne pokazuje na neku funkciju, no kada ga „usmjerimo“ na konkretnu funkciju,

ta funkcija će morati imati upravo jedan argument tipa int i povratnu vrijednost isto tipa

int. Ako se izostave oble zagrade (prioritet operatora), imali bismo deklaraciju na funkciju

pfunction koja vraća vrijednost koja je pokazivač na tip int.

3.2 POZIVANJE FUNKCIJE PREKO POKAZIVAČA NA FUNKCIJU

Pretpostavimo funkciju koja ima sljedeći prototip:

int sum(int a, int b); /* racuna a+b */

Ova funkcija ima dva parametra tipa int te vraća rezultat koji je također tipa int. Možemo

deklarirati pokazivač na funkciju kojem ćemo predati adresu funkcije sum na sljedeći način:

int (*pfunction) (int);

pokazivač može

biti dodijeljen

samo

funkcijama koje

imaju ovaj

povratni tip

podatka

ime pokazivača

na funkciju

pokazivač može

biti dodijeljen

samo funkcijama

koje imaju ove

tipove

argumenata

Page 7: LV9 - Rekurzije i Pokazivaci Na Funkcije

int (*pfun)(int, int) = sum;

Sada smo deklarirali pokazivač na funkciju sa imenom pfun kojem smo predali adresu

funkcije sum() koja ima dva parametra tipa int i vraća vrijednost int.

Sada možemo pozvati funkciju sum() preko pokazivača:

int result = pfun(45, 55);

U gornjoj naredbi smo pozvali funkciju sum() preko pokazivača pfun sa vrijednostima

argumenata 45 i 55. Vrijednost koju će vratiti funkcija sum() će se spremiti u varijablu

result.

Možemo primijetiti da smo funkciju preko pokazivača pozvali na isti način kao kod

direktnog poziva funkcije, nije bilo potrebno koristiti operator dereferenciranja.

Pretpostavimo da imamo još jednu funkciju sljedećeg prototipa:

int product(int a, int b); /* racuna a*b */

Pokazivač možemo sada usmjeriti na funkciju product jer ima jednaki broj argumenata istog

tipa kao i funkcija sum(), te je i povratni tip isto int:

pfun = product;

3.3 POLJA POKAZIVAČA NA FUNKCIJE

Pokazivač na funkciju je varijabla kao bilo koja druga te stoga možemo kreirati i polje

pokazivača na funkcije. Na sljedećem primjeru smo pokazali kako možemo deklarirati polje

pokazivača na funkcije:

int (*pfunctions[10]) (int, int);

Dakle, deklarirali smo polje, pfunctions, od 10 elemenata gdje svaki element može spremiti

adresu funkcija koja ima povratni tip int te prima dva parametra tipa int.

#include <stdio.h>

/* Prototip funkcija */

int sum(int, int);

int product(int, int);

int difference(int, int);

int main(void){

int a = 10;

int b = 5;

int result = 0;

int (*pfun[3])(int, int);

/* Inicijalizacija pokazivaca */

pfun[0] = sum;

pfun[1] = product;

pfun[2] = difference;

/* Izvrsi svaku funkciju */

for(int i = 0 ; i < 3 ; i++){

Page 8: LV9 - Rekurzije i Pokazivaci Na Funkcije

result = pfun[i](a, b); /* poziv funkcije preko pokazivaca */

printf("\nresult = %d", result);

}

/* poziv sve tri funkcije preko pokazivaca */

result = pfun[1](pfun[0](a, b), pfun[2](a, b));

printf("\n\nProdukt sume i razlike = %d\n",result);

return 0;

}

int sum(int x, int y){

return x + y;

}

int product(int x, int y){

return x * y;

}

int difference(int x, int y){

return x - y;

}

3.4 POKAZIVAČI NA FUNKCIJE KAO ARGUMENTI FUNKCIJE

Moguće je predati pokazivač na funkciju kao argument neke funkcije. To nam omogućava

poziv raznih funkcija, ovisno o adresi koja se preda pokazivaču na funkciju. Primjer je dan

niže.

#include <stdio.h>

int sum(int,int);

int product(int,int);

int difference(int,int);

int any_function(int(*pfun)(int, int), int x, int y);

int main(void){

int a = 10;

int b = 5;

int result = 0;

int (*pf)(int, int) = sum;

result = any_function(pf, a, b);

printf("\nresult = %d", result );

result = any_function(product,a, b);

printf("\nresult = %d", result );

printf("\nresult = %d\n", any_function(difference, a, b));

return 0;

}

int any_function(int(*pfun)(int, int), int x, int y){

return pfun(x, y);

}

int sum(int x, int y){

return x + y;

}

int product(int x, int y){

return x * y;

}

int difference(int x, int y){

return x - y;

Page 9: LV9 - Rekurzije i Pokazivaci Na Funkcije

}

Page 10: LV9 - Rekurzije i Pokazivaci Na Funkcije

4 RIJEŠENI PRIMJERI

Primjer 1.

Napisati C program koji će omogućiti zbrajanje brojeva na sljedeći način:

0 + 1 = 1

1 + 2 = 3

3 + 3 = 6

6 + 4 = 10

10 + 5 = 15

15 + 6 = 21

21 + 7 =28

28 + 8 = 36

36 + 9 = 45

Početne vrijednosti za zbrajanje su 0 i 1, potrebno je ispisati konačnu sumu.

#include <stdio.h>

int count = 1;

void func(int sum){

sum = sum + count;

count ++;

if(count <= 9){

func(sum);

}

else{

printf("\nSuma je [%d] \n", sum);

}

return;

}

int main(void)

{

int sum = 0;

func(sum);

return 0;

}

Kratko obrazloženje [Primjer 1]: Pozivamo funkciju func kojoj predajemo početnu

vrijednost 0. Funkcija func će pozivati samu sebe dokle god je varijabla count manja ili

jednaka 9 (početna vrijednost varijable count je 1) i zbrajati u varijablu sum sa uvećanom

vrijednošću varijable count. Kada se varijabla count poveća na 10, ispisuje se konačni

rezultat koji je spremljen u varijabli sum i iznosi 45.

Page 11: LV9 - Rekurzije i Pokazivaci Na Funkcije

Primjer 2.

Napisati C program koji će u funkciji pomoću rekurzivnog zbrajanja izvesti operaciju množenja

dva broja.

#include <stdio.h>

unsigned int multiply(unsigned int x, unsigned int y){

if (x == 1){

/* Kraj rekurzije */

return y;

}

else if (x > 1){

/* Korak rekurzije */

return y + multiply(x-1, y);

}

/* Ukoliko je x = 0 */

return 0;

}

int main() {

printf("3 puta 5 je %d", multiply(3, 5));

return 0;

}

Kratko obrazloženje [Primjer 2]: Funkcija multiply ima 3 uvjeta:

ako je prvi predani argument funkcije, x, jednak 1, funkcija vraća drugi argument y

(npr. 1*3=3)

ako je prvi argument veći od 1, funkcija poziva samu sebe te umanjuje prvi

argument za 1 uz sumiranje sa drugim argumentom (npr. 2*3 -> 3 + multiply(1,3)= 3)

ako je prvi argument funkcije 0, povratna vrijednost je 0

Page 12: LV9 - Rekurzije i Pokazivaci Na Funkcije

Primjer 3.

Napisati C program koji će za uneseni broj ispisati Fibonacci niz. Za ispis Fibonacci niza koristiti

rekurziju.

#include<stdio.h>

void printFibonacci(int);

int main(){

int k,n;

long int i=0,j=1,f;

printf("Unesite raspon Fibonacci reda: ");

scanf("%d",&n);

printf("Fibonacci red: ");

printf("%d %d ",0,1);

printFibonacci(n);

return 0;

}

void printFibonacci(int n){

static long int first=0,second=1,sum;

if(n>0){

sum = first + second;

first = second;

second = sum;

printf("%ld ",sum);

printFibonacci(n-1);

}

}

Kratko obrazloženje [Primjer 3]: Kod Fibonaccijevog reda svaki broj je zbroj dvaju

prethodnih brojeva. U funkciji printFibonacci, varijabla first se postavlja na 0, a second na 1,

što su početne vrijednosti F. reda. Nakon toga se radi suma prva dva broja te se ta suma

predaje varijabli second, dok se varijabla first postavi na vrijednost second. Funkcija poziva

samu sebe sa argumentom umanjenim za 1 sve dok je argument veći od 0.

Page 13: LV9 - Rekurzije i Pokazivaci Na Funkcije

5 ZADACI

Pri rješavanju zadataka potrebno je ispisati rezultate u točno određenom formatu. Prvi

zadatak je potrebno predati preko aktivnosti na Loomenu u koju će Vas uputiti nastavnici

na vježbama, a drugi trebate pokazati nastavniku na satu.

1. Rekurzija prima jednu cjelobrojnu vrijednost koja je visina piramide. U datoteku

piramida, koju ćete kreirati iz programa, „nacrtati“ piramidu od znakova '*' koja

može imati maksimalnu visinu 30 redova. U main() dijelu učitati visinu piramide te

pozvati rekurziju. Primjer piramide je prikazan na slici niže.

2. Napisati C program koji će učitati cijeli broj te u datoteku naziva suma.txt upisati

sumu njegovih znamenaka. Za traženje sume znamenaka broja korijjstiti rekurziju.

NAPOMENA: U ovom slučaju nije potrebno ispisivati na ekran rezultate, VPL aktivnost

direktno provjerava zapisane datoteke.