Le funzioni Fondamenti di programmazione
Moduli I programmi sono spesso abbastanza complessi da dover essere scomposti in pezzi più maneggevoli Un modulo consiste di istruzioni per svolgere un certo compito raggruppate insieme in un unità a cui è dato un nome Vantaggi: il nome può essere usato come sostituto dell intero insieme di istruzioni risparmio di scrittura, organizzazione, riutilizzo
Programmazione modulare Le funzioni in C permettono di realizzare la scomposizione di un programma in sottoprogrammi (o moduli) main Funzioni di I/O scanf,printf Funzioni matematiche sqrt,pow Funzioni scritte dal programmatore
Black box result=sqrt(x); Funzione sqrt Calcolo della x vale 25 Radice quadrata result vale 5.0 y=4.7+sqrt(x); //y vale 9.7 z=sqrt(12.+4.); //z vale 4.0
Caso di studio Programma di conversione da gradi Fahrenheit a Celsius: #include<stdio.h> #include<stdlib.h> #include<stdio.h> #include<stdlib.h> main() { main() { double tc, tf, offset, conv; double tc, tf, offset, conv; offset = 32.; offset = 32.; conv = 5./ 9.; conv = 5./ 9.; /*Acquisizione dati*/ /*Acquisizione dati*/ printf("valore in gradi Fahrenheit = "); /*conversione*/ scanf("%lf", &tf); /*conversione*/ /*stampa risultati*/ tc = converter(tf,offset,conv); /*stampa risultati*/ } printf("valore in gradi celsius = %f\n", tc); }
Dichiarazione (il prototipo) Una funzione deve essere dichiarata prima di poter essere utilizzata tipo_prog nome_funzione(parametri,...) Il prototipo specifica: Il tipo della funzione (ovvero il tipo ritornato) Il nome della funzione Il numero e il tipo dei parametri formali della funzione
Dichiarazione Dove si inserisce? Prima della funzione main() dopo le #define Includendo la libreria standard o utente che contiene il prototipo della funzione Consente al compilatore di tradurre correttamente la chiamata alla funzione in termini di trasferimento del controllo di flusso
Prototipi int main(void) Il tipo può essere omesso (se l'istruzione return è omessa, main restituisce 0) int scanf(const char *format,...); void printf(const char *format,...); double converter(double,double double)
} Dichiarazione #include<stdio.h> #include<stdlib.h> double converter(double,double,double); main() { double tc, tf, offset, conv; offset = 32.; conv = 5./ 9.; printf("valore in gradi Fahrenheit = "); scanf("%lf", &tf); tc = converter(tf,offset,conv); printf("valore in gradi celsius = %f\n", tc);
Definizione Intestazione della funzione (simile al prototipo ma specifica i nomi dei parametri formali e non termina con ; ) Contiene la specifica delle istruzioni della funzione
Definizione tipo nome della funzione parametri formali double converter(double temp_f,double offset, double fat_conv) { return((temp_f-offset)*fat_conv); } Istruzione di ritorno del valore del tipo specificato
Esempi Scrivere una funzione che calcola la somma dei quadrati di due numeri double squaresum(double a, double b) { } return (a * a + b * b);
Esempi Scrivere una funzione che calcola il fattoriale di un intero n unsigned long long int factorial(int n ) { } int i; unsigned long long int p = 1; for (i = 1; i <= n; i++) { p *= i;} return p;
Posizionamento delle funzioni La visibilità di una funzione dipende dalla dichiarazione o dal posizionamento L'ordine in cui vengono definite le funzioni non influisce sull'ordine con cui vengono eseguiti L'ordine di esecuzione dipende dall'ordine delle chiamate
#include<stdio.h> #include<stdlib.h> Ordine di chiamata = ordine di esecuzione main() { double tc, tf, offset, conv; offset = 32.; conv = 5./ 9.; /*Acquisizione dati*/ printf("valore in gradi Fahrenheit = "); scanf("%lf", &tf); /*conversione*/ tc = converter(tf,offset,conv); /*stampa risultati*/ printf("valore in gradi celsius = %f\n", tc); }
Chiamata a funzione #include<stdio.h> #include<stdlib.h> main() { double tc, tf, offset, conv; offset = 32.; conv = 5./ 9.; /*Acquisizione dati*/ printf("valore in gradi Fahrenheit = "); scanf("%lf", &tf); /*conversione*/ tc = converter(tf,offset,conv); /*stampa risultati*/ printf("valore in gradi celsius = %f\n", tc); } double converter(double temp_f,double offset, double fat_conv) { return((temp_f-offset)*fat_conv); }
Parametri formali e attuali L'input per la funzione chiamata viene trasmesso dalla funzione chiamante attraverso i parametri Parametri attuali (funzione chiamante) e parametri formali (funzione chiamata) si devono accordare nel numero e nel tipo ordinatamente Non c'è un legame tra i nomi
Esecuzione della chiamata Il prototipo consente al compilatore di tradurre correttamente la chiamata alla funzione in termini di trasferimento del controllo di flusso dal chiamante al chiamato Al termine della funzione chiamata viene aggiunta una istruzione (nel programma oggetto) che causa il trasferimento di controllo di flusso al chiamante
Esecuzione della chiamata #include<stdio.h> #include<stdlib.h> main() { double tc, tf, offset, conv; offset = 32.; conv = 5./ 9.; /*Acquisizione dati*/ printf("valore in gradi Fahrenheit = "); scanf("%lf", &tf); /*conversione*/ tc = converter(tf,offset,conv); /*stampa risultati*/ printf("valore in gradi celsius = %f\n", tc); } double converter(double temp_f,double offset, double fat_conv) { return((temp_f-offset)*fat_conv); }
Passaggio di dati per valore La funzione chiamata tramite il parametro formale riceve l'attuale valore del dato passato tramite il parametro attuale (ma non ha accesso alla variabile usata come parametro attuale) Per riferimento: la funzione chiamata riceve l'indirizzo della posizione di memoria che contiene il dato
L'esecuzione e la memoria Quando viene eseguito un programma la prima istruzione eseguita è la prima istruzione del main() Quando viene chiamata una funzione, il controllo di flusso viene trasferito alla funzione e in memoria (nello stack) vengono allocate le variabili dichiarate in essa e i parametri Al termine dell'esecuzione della funzione, il controllo viene trasferito e la memoria liberata Il valore restituito (se presente) viene scambiato usando un registro del processore come variabile condivisa Il programma termina dopo aver eseguito l'ultima istruzione del main()
L'esecuzione e la memoria Prima della chiamata: Area di memoria main Area di memoria converter conv 5./9 offset 32. tc? tf 59.
L'esecuzione e la memoria Passaggio di dati per valore: Area di memoria main Area di memoria converter conv offset temp_f offset 5./9 32. 59. 32. tc tf fat_conv? 59. 5./9
L'esecuzione e la memoria Al termine dell'esecuzione di converter: conv Area di memoria main offset Area di memoria converter liberata 5./9 32. tc 15. tf 59.
Parametri del main main(int argc, char *argv[]) Il primo parametro indica la lunghezza +1 di argv Il secondo parametro indica le stringhe in input al programma (argomanti a riga di comando) Se ad es. un programma backup potrebbe richiedere il nome del file di cui eseguire il backup e il nome del file di backup >backup 3 old.txt new.txt
Come posso modificare la variabile in input in una funzione? Come posso ritornare più di un valore? Come posso passare usare un array come parametro di una funzione?
Problema: scrivere una funzione che scambia il contenuto di due variabili
Esempio sbagliato main() { double x, y; x = 3.14; y = 2.72; printf(" prima dello scambio, x = %f, y = %f\n", x, y); wrongswap(x, y); printf(" dopo lo scambio, x = %f, y = %f\n", x, y); } void wrongswap(double a, double b) { double temp; temp = a; a = b; b = temp; }
Area di memoria main Area di memoria wrongswap x y a b 3.14 2.72 3.14 2.72 temp?
Area di memoria main Area di memoria wrongswap x y a b 3.14 2.72 2.72 3.14 temp 3.14
Esempio corretto main() { double x, y; x = 3.14; y = 2.72; printf(" prima dello scambio, x = %f, y = %f\n", x, y); swap(&x, &y); printf(" dopo lo scambio, x = %f, y = %f\n", x, y); system("pause"); } Parametri in ingresso e in uscita void swap(double *a, double *b) { double temp; temp = *a; *a = *b; *b = temp; }
Area di memoria main x 3.14 Area di memoria swap a y b 2.72 temp?
Nota bene: l'inizializzazione dei puntatori come parametri formali avviene attraverso il passaggio di parametri per valore con la chiamata della funzione
Area di memoria main x 2.72 Area di memoria swap a y b 3.14 temp 3.14
main() { double x[n], y[n],z[n]; int n; /*Acquisizione dati per n(=3), e per gli array x e y*/... add_arrays(x,y,z,3);... } array in ingresso array in uscita void add_arrays(const double a[],const double b[],double sum[],int n) { int i; for(i=0;i<n;i++){ sum[i]=a[i]+b[i]; }
Area di memoria main Area di memoria add_arrays x 1.5 2.72 3.2 2.5 a n b 3 y 2. 1.3 2.72 3.2 sum z? 2.72??
Area di memoria main Area di memoria add_arrays x 1.5 2.72 3.2 2.5 a n b 3 y 2. 1.3 2.72 3.2 sum z 3.5 4.5 2.72 5.7
Tempo di vita e visibilità Intervallo temporale durante il quale una variabile è mantenuta in memoria Visibilità: sezione di codice entro il quale il nome della dichiarazione può essere usato Globale: visibile da tutto il programma con durata per tutta l'esecuzione del programma Locale: nel corpo di una funzione, nei parametri formali o in un blocco (nascondono variabili con stesso nome)
Tempo di vita, visibilità e memoria La memoria allocata dal compilatore prima dell'esecuzione del programma può essere gestita staticamente Visibilità globale Tempo di vita: esecuzione del programma La memoria allocata durante l'esecuzione del programma viene gestita dinamicamente (stack) Variabili locali, blocchi di istruzioni, funzioni
Stack e tempo di vita della variabili Stack di sistema: area di memoria che supporta i meccanismi di trasferimento del controllo e la comunicazione tra funzioni Al momento della chiamata di una funzione sullo stack sono memorizzati: L'indirizzo dell'ultima istruzione eseguita nel chiamante Valori attuali dei parametri Variabili locali della funzione Al termine dell'esecuzione della funzione la memoria è liberata
Chiamate successive di una funzione Come posso modificare la variabile in input in una funzione? Come posso ritornare più di un valore? Come posso mantenere il valore di una variabile tra chiamate successive di una funzione? Problema: scrivere un programma che realizza una pila (funzioni push e pop)
Stack Push: Se la pila non è piena (TOS<N) Inserisci l'elemento in TOS Incrementa TOS Pop: Se la pila non è vuota (TOS>0) Decrementa TOS Elimina l'elemento in TOS
Stack Attenzione! L'operazione di pop rende disponibile (al chiamante) l'elemento eliminato dalla pila Se l'elemento è un intero, il parametro corrispondente deve essere un parametro in uscita, quindi un puntatore ad intero
Stack Attenzione! TOS deve mantenere il suo valore tra chiamate successive della funzione Come posso mantenere il valore di una variabile tra chiamate successive di una funzione? Il parametro corrispondente a TOS deve essere un parametro in ingresso e in uscita della funzione
Parametri in ingresso e in uscita I parametri formali di una funzione che permettono la comunicazione dalla funzione chiamante alla funzione chiamata sono parametri in ingresso I parametri formali di una funzione che permettono la comunicazione dalla funzione chiamata alla funzione chiamante sono parametri in uscita I parametri formali di una funzione che permettono la comunicazione dalla funzione chiamante alla funzione chiamata e viceversa sono parametri in ingresso e in uscita
Coda Enqueue: Se la coda non è piena (tail+1<n) Inserisci l'elemento in tail Incrementa tail Dequeue: Se la coda non è vuota (head!=tail) Elimina l'elemento in head Incrementa head
Coda (anello) Enqueue: Se la coda non è piena (tail+1!=head) Dequeue: Inserisci l'elemento in tail Incrementa tail (l'elemento successivo di N-1 è 0) Se la coda non è vuota (head!=tail) Elimina l'elemento in head Incrementa head (l'elemento successivo di N-1 è 0)
Coda (anello) Attenzione! tail e head sono parametri in ingresso e in uscita value è un parametro in ingresso in enqueue value corrisponde ad un parametro in uscita in dequeue
Parametri in ingresso e in uscita Come posso passare usare un array come parametro di una funzione?
Passaggio di parametri: array Problema: Scrivere un programma che somma gli elementi corrispondenti di due array a e b e memorizza i risultati in un terzo array sum void add_arr(int a[],int b[],int sum[],int n);
Array multidimensionali e puntatori int a[3][4]; rappresenta una matrice di 3 righe e 4 colonne L'indice di riga è più lento Possiamo pensarlo come 3 sottoarray di 4 elementi a[0][0]a[0][1]a[0][2]a[0][3] a[1][0]...a[1][3] a[2][0]...a[2][3] a[0] a[1] a[2] a Se si dichiara a[n1][n2] a[i][j] equiv *(*(a+i)+j) equiv *(&a[0][0]+n2*i+j)
Passaggio di parametri La prima dimensione è l'unica che può essere omessa (le altre sono necessarie per l'accesso agli elementi) initialize(int a[][n], int nrows) Il valore memorizzato nel parametro formale di un array è l'indirizzo del primo elemento del parametro attuale es. somma di due matrici in una terza