Le Funzioni

Lo scopo della lezione di oggi e' prendere confidenza con la definizione e l'utilizzo delle funzioni in C.

 



Cosa e' una funzione in C

I comandi che compongono un programma possono essere visti come azioni elementari organizzate per assolvere un determinato compito. Spesso capita di dover riscrivere le stesse sequenze di istruzioni in più punti dello stesso programma, o di doverle utilizzare in programmi diversi. Inoltre tali sequenze realizzano spesso un preciso compito logico, anche complesso, che costituisce un vero e proprio sottoprogramma (esempio la stampa della cartella nell'esercizio della tombola).

Abbiamo del resto visto che, quando bisogna scrivere un programma per risolvere un problema complesso, e' buon uso scomporre il problema in sottoproblemi piu' semplici. In linea di principio, i sottoproblemi possono essere risolti da appositi sottoprogrammi, che vengono poi combinati fra loro per risolvere il problema complesso all'interno del programma principale (main).

In C i sottoprogrammi vengono chiamati funzioni e, da un punto di vista logico, si comportano esattamente come le funzioni matematica: dati uno o piu'  argomenti, una funzione C restituisce valore di ritorno (eventualmente vuoto).

Le librerie che abbiamo usato nelle lezioni precedenti non sono altro che collezioni di funzioni (esempio sqrt, abs, printf, etc.). Anche main è una funzione, che e' associata al programma principale.

Per poter essere utilizzata, una funzione deve essere:

- dichiarata fornendo il cosiddetto prototipo di funzione (prima del main)
- definita (dopo il
main)


Vediamo un esempio di programma che sfrutta una funzione per leggere valori interi compresi in un certo intervallo:


/*
Legge N valori compresi fra MIN e MAX e li memorizza in un array
*/


#include <stdio.h>
#include <stdlib.h>

#define N 10
#define MIN 1
#define MAX 6

// dichiarazione del prototipo della funzione
int leggi(int,int);

// funzione principale
int main () {

int a[N],i;

for ( i=0 ; i<N ; i++)
a[i] = leggi(MIN,MAX); // invoca la funzione ausiliaria

return EXIT_SUCCESS;
}

// definizione della funzione
int leggi(int x, int y) { // prende due argomenti x e y
int t; // usa una variabile locale t

printf("Immetti un valore tra %d e %d:\n",x,y);
scanf("%d",&t);

while ( t<x || t>y ) {
printf("Ho detto tra %d e %d! Riprova\n",x,y);
scanf("%d",&t);
}

return t; // restituisce il valore letto al chiamante
}



Nota Bene: In C, sia gli argomenti passati alla funzione che il risultato dell'esecuzione di una funzione devono essere di un certo tipo di dato. I tipi di dato sia degli argomenti che del risultato sono dichiarati nel prototipo della funzione.


Nota Bene: Se una funzione non deve restituire nessun risultato, deve essere dichiarata di tipo
void.

Nell'esempio sopra riportato, la funzione leggi ha due argomenti di tipo int, e restituisce un risultato di tipo int.

Nella definizione della funzione (dopo il main), assegno un nome ai parametri (x e y), nome che viene utilizzato all'interno della definizione di funzione per elaborare il risultato della funzione stessa.




Funzioni Iterative e Ricorsive

Nelle esercitazioni precedenti abbiamo usato i cicli (costrutti while, do_while e for) per ripetere più volte l'esecuzione di un certo gruppo di comandi, magari sfruttando alcuni indici che ad ogni iterazione assumevano valori diversi in modo da ripetere l'esecuzione dei comandi su porzioni differenti dei dati (ad esempio, le celle di un array).

Le funzioni basate sull'uso di questi costrutti vengono chiamate funzioni iterative.

Le funzioni iterative sono potenti, largamente applicabili, spesso efficienti ma possono risultare artificiose, poco leggibili e quindi difficili da comprendere.

Per molte classi di problemi, una valida alternativa alla definizione di funzioni iterative è offerta dalla natura ricorsiva del problema stesso. In questi casi possiamo rimpiazzare le funzioni iterative con funzioni ricorsive,

Cosa e' una funzione ricorsiva? Una funzione ricorsiva e' una funzione che, all'interno della sua definizione, invoca se stessa (con argomenti diversi).

Le funzioni ricorsive sono solitamente eleganti, sintetiche, di immediata comprensione, ma bisogna stare attenti a non abusarne. Infatti, sono meno efficienti delle funzioni iterative, perche' la stessa funzione viene invocata molte volte, ed ogni invocazione di una funzione "costa".

Inoltre, bisogna stare attenti a definire correttamente le funzioni ricorsive, altrimenti rischio di generare un ciclo infinito nell'esecuzione del programma!!!!

L'esempio classico per illustrare la differenza tra funzioni iterative e ricorsive è quello del fattoriale di un numero.


Calcolo del Fattoriale di un numero

Definizione iterativa: Il fattoriale n! di un intero n maggiore o uguale a 2 è definito come:

n! = n * (n-1) * (n-2) * ... * 2 * 1
Inoltre assumiamo che:
1! = 1
0! = 1

Definizione ricorsiva: Alternativamente il fattoriale può invece essere espresso come

n! = n * (n-1)!

ovvero il fattoriale di n si ottiene a partire dal fattoriale di n-1, moltiplicando per n.


Adesso vediamo le realizzazioni iterativa e ricorsiva della funzione fattoriale in C.

Il prototipo della funzione è indipendente dalla realizzazione: il fattoriale deve ricevere un intero come argomento e deve restituire un intero:

int fat(int);
Analogamente il main si limiterà ad usare (invocare) la funzione, senza preoccuparsi di come venga implementata:

int main () {

int numero;

printf("Immetti un numero maggiore o uguale a 0: ");
scanf("%d",&numero);

printf("%d! = %d\n",numero,fat(numero));

return EXIT_SUCCESS;

}

La definizione della funzione fat sarà invece diversa a seconda della scelta realizzativa:


// versione iterativa
int fat(int n) {

int i,risultato=1; // variabili locali

if ( n<0 )
risultato = 0; // parametro errato
else
for ( i=1 ; i<=n ; i++ ) // iterazione
risultato *= i;

return risultato;
}



// versione ricorsiva
int fat(int n) {

int risultato; // variabile locale

if ( n<0 )
risultato = 0; // parametro errato
else if ( n==0 )
risultato = 1; // caso base
else
risultato = n*fat(n-1); // ricorsione

return risultato;
}



Attenzione:
Le due versioni non possono essere inserite assieme nello stesso file perché definiscono funzioni con lo stesso nome.

Nota: Il fattoriale è una funzione che cresce molto rapidamente. Può essere conveniente definire il valore di ritorno come float oppure double. Come dovremmo modificare i programmi visti sopra?



Esercitazione non guidata

Da fare subito!!

Esercizio:

Dato un valore n maggiore o uguale a 2, il corrispondente numero di Fibonacci Fib(n) è definito ricorsivamente come:

Fib(n) = Fib(n-1) + Fib(n-2)
Inoltre assumiamo che:
Fib(1) = 1
Fib(0) = 0


Scrivere un programma che, dato un numero intero positivo
n, calcola l'n-esimo numero di Fibonacci Fib(n).

Il programma deve utilizzare una funzione ausiliaria calcola_fib per calcolare l'n-esimo numero di Fibonacci. Si richiedono due diverse versioni della funzione ausiliaria calcola_fib, una ricorsiva e l'altra iterativa. 



Torna alla HomePage del corso