Funzioni Moreno Marzolla Dipartimento di Informatica—Scienza e Ingegneria (DISI) Università di Bologna http://www.moreno.marzolla.name/
Copyright © 2008, Stefano Mizzaro http://users.dimi.uniud.it/~stefano.mizzaro/dida/Prog0708/ Copyright © 2017, Moreno Marzolla http://www.moreno.marzolla.name/teaching/FINFA/ This work is licensed under the Creative Commons Attribution-Non Commercial 2.0 International (CC BY-NC 2.0) License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/2.0/ or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA. Funzioni
Ringraziamenti prof. Stefano Mizzaro, Università di Udine http://users.dimi.uniud.it/~stefano.mizzaro/ Funzioni
Funzioni /* demo-funzioni.c */ #include <stdio.h> double somma(double a, double b) { return (a + b); } void stampa(double v) printf("v=%f\n", v); return; /* opzionale */ void saluta( void ) printf("Ciao\n"); int main( void ) saluta(); stampa(somma(3.0, 4.0/2.0)); return 0; Una funzione in C può essere intuitivamente definita come “un blocco di istruzioni con un nome” Una funzione può accettare un certo numero di parametri (anche nessuno), ciascuno con un tipo Una funzione può restituire un risultato di un certo tipo, oppure nessun risultato Funzioni
Perché le funzioni sono utili Sintesi Stesse operazioni da ripetere in più punti del programma Evito ripetizione codice → evito errori Modularità Scompongo problema (programma) in sottoproblemi (sottoprogrammi) Funzioni
Dichiarazione vs Definizione La dichiarazione di una funzione indica la sua segnatura Nome della funzione; tipo del risultato; tipo dei parametri Il corpo della funzione può venire omesso Una funzione può essere usata dopo che è stata dichiarata La definizione di una funzione indica cosa fa Il corpo della funzione deve essere specificato La definizione funge anche da dichiarazione Funzioni
Dichiarazione vs Definizione /* dich.c */ #include <stdio.h> int fattoriale(int); int fat5( void ) { return fattoriale(5); } int fattoriale(int n) { int f = 1; while (n > 1) { f = f*n; n--; } return f; int main( void ) printf("%d\n", fat5() ); return 0; Dichiarazione di una funzione “fattoriale” che accetta un parametro intero e restituisce un intero. La funzione fattoriale() può essere usata dopo che è stata dichiarata Definizione della funzione fattoriale Funzioni
Definizione delle funzioni Al di fuori del main e di altre funzioni Intestazione (prima riga) Tipo del risultato restituito (void se non restituisce un risultato) Nome della funzione Parametri formali e loro tipi (separati da “,”) Corpo (fra graffe { } obbligatorie) Istruzioni return termina esecuzione della funzione Il “return” è obbligatorio solo per funzioni che restituiscono un valore Per quelle che non restituiscono un valore si può usare “return” senza argomento, oppure ometterlo per terminare la funzione alla fine del blocco Funzioni
Esecuzione di programma con funzioni L’esecuzione comincia da main Anche il main è una funzione Invocazione → l’esecuzione passa alla funzione Associazione parametri Esecuzione istruzioni della funzione … fino al return o alla fine della funzione Poi l’esecuzione ritorna al chiamante, all’istruzione successiva Funzioni
Flusso di esecuzione main() fat5() fattoriale() /* dich.c */ #include <stdio.h> int fattoriale(int); int fat5( void ) { return fattoriale(5); } int fattoriale(int n) { int f = 1; while (n > 1) { f = f*n; n--; } return f; int main( void ) printf("%d\n", fat5() ); return 0; main() fat5() fattoriale() Funzioni
Parametri formali/attuali /* cotangente.c: Calcolo cotangente. Compilare con: gcc cotangente.c -o cotangente -lm */ #include <stdio.h> #include <math.h> double tangente(double x) { return (sin(x) / cos(x)); } double cotangente (double x) { return (1.0 / tangente(x)); int main( void ) { double angolo; printf("Inserisci angolo "); scanf("%lf", &angolo); printf("La cotangente di %f e' %f\n", angolo, cotangente(angolo)); return 0; Parametro formale: usato nella definizione della funzione cotangente usa (chiama, invoca) tangente Parametro attuale: specifica su quali parametri la funzione deve operare Funzioni
Associazione fra parametri attuali e parametri formali All’invocazione il valore del parametro attuale viene assegnato al parametro formale Come con un assegnamento I tipi devono essere compatibili, come per un assegnamento Se ci sono più parametri, associazione posizionale (basata sulla posizione) f(x,y) = 2x2 + 4y + 9xy f(4,7) Funzioni
Cosa succede? /* parametri.c */ #include <stdio.h> void inc(int x) { x = x + 1; } int main( void ) int y = 0; inc(y); printf("%d\n", y); return 0; > gcc parametri.c -o parametri > ./parametri Funzioni
Perché Il passaggio di parametri avviene per valore Il valore del parametro attuale viene copiato nel parametro formale Ma sono 2 variabili diverse Quindi ogni modifica al parametro formale non si riflette sul parametro attuale Vedremo più avanti il meccanismo da usare perché una funzione possa “modificare” i parametri attuali Funzioni
Esempio Scrivere una funzione che determina se un numero n positivo è un numero primo oppure no La funzione riceve come parametro un intero n e restituisce 1 se n è primo, 0 altrimenti 1 non è un numero primo 2 è un numero primo Scrivere anche un main() che invoca la funzione in modo appropriato Funzioni
Idea Guardo tutti i numeri i minori di n Escluso 1 Basta arrivare a √n Controllo per ogni i se è un divisore di n Se trovo un divisore di n, allora n non è primo Ne basta uno; mi fermo subito Se non ne trovo, n è primo Devo guardarli tutti; esco solo alla fine Perché mi posso fermare a sqrt(n) ? Supponiamo che n sia un valore composto, quindi n = s * t. Supponiamo che uno dei due fattori, s, sia maggiore di sqrt(n). Quindi n = s * t => n/t = s > sqrt(n) Ma quindi sqrt(n) * sqrt(n) / t > sqrt(n) semplificando sqrt(n) / t > 1 quindi t < sqrt(n) Ergo, in un numero composto, almeno uno dei fattori è sempre minore di sqrt(n) Funzioni
SBAGLIATO Questo programma non funziona per n = 1 #include <stdio.h> /* Dato un intero n maggiore di zero, restituisce 1 se n e' primo, 0 altrimenti */ int primo(int n) { int i; for (i = 2; i*i <= n; i++) { if (n % i == 0) { return 0; } else { return 1; } int main( void ) int v; printf("Inserisci un intero "); scanf("%d", &v); if ( primo(v) ) { printf(“%d e' un numero primo\n”, v); printf(“%d NON e' un numero primo\n”, v); SBAGLIATO Questo programma non funziona per n = 1 Funzioni
SBAGLIATO Questo programma non funziona per n = 1 #include <stdio.h> /* Dato un intero n maggiore di zero, restituisce 1 se n e' primo, 0 altrimenti */ int primo(int n) { int i; for (i = 2; i*i <= n; i++) { if (n % i == 0) { return 0; } else { return 1; } int main( void ) int v; printf("Inserisci un intero "); scanf("%d", &v); if ( primo(v) ) { printf(“%d e' un numero primo\n”, v); printf(“%d NON e' un numero primo\n”, v); SBAGLIATO Questo programma non funziona per n = 1 Funzioni
Compilazione con -Wall > gcc -Wall primo.c -o primo primo.c: In function ‘primo’: primo.c:14:1: warning: control reaches end of non-void function [- Wreturn-type] } ^ Disegnando il flowchart della funzione primo() si vede che c'è un cammino che porta al termine della funzione senza alcun return; peraltro, questo cammino viene seguito dando in input n=3 Funzioni
Meglio, ma sempre sbagliato #include <stdio.h> /* Dato un intero n maggiore di zero, restituisce 1 se n e' primo, 0 altrimenti */ int primo(int n) { int i; for (i = 2; i*i <= n; i++) { if (n % i == 0) { return 0; /* trovato un divisore; n non primo */ } return 1; /* nessun divisore; n e' primo */ int main( void ) int v; printf("Inserisci un intero "); scanf("%d", &v); if ( primo(v) ) { printf("%d e' un numero primo\n", v); } else { printf("%d NON e' un numero primo\n", v); return 0; Meglio, ma sempre sbagliato Questo programma non funziona per n = 1 Funzioni
Attenzione... Verifichiamo empiricamente la correttezza della funzione primo(n), testando i “casi limite” n = 1, n = 2 int primo(int n) { int i; for (i = 2; i*i <= n; i++) { if (n % i == 0) { return 0; /* n non e' primo */ } return 1; /* n e' primo */ SBAGLIATO Quando n = 1 il ciclo “for” non viene eseguito; quindi la funzione ritorna “true”, il che significherebbe che 1 viene considerato un numero primo, il che non è corretto Funzioni
Versione corretta Mettiamoci “una pezza” OK /* primo.c */ int primo(int n) { int i; if ( n == 1 ) { return 0; } else { for (i = 2; i*i <= n; i++) { if (n % i == 0) { } return 1; OK Quando n = 1 il ciclo “for” non viene eseguito; quindi la funzione ritorna “true”, il che significherebbe che 1 viene considerato un numero primo, il che non è corretto Funzioni
Il meccanismo di invocazione delle funzioni
Esempio Stack #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; Funzioni
Esempio Stack main() a = 3 b = 2 #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 Funzioni
Esempio Stack main() a = 3 b = 2 #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 Funzioni
Esempio Stack main() a = 3 b = 2 ret #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 ret Funzioni
Esempio Stack main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = ?? #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = ?? sqy = ?? Funzioni
Esempio Stack main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = ?? #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = ?? sqy = ?? Funzioni
Esempio Stack main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = ?? #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = ?? sqy = ?? ret Funzioni
Esempio Stack main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = ?? #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = ?? sqy = ?? ret sq() x = 3 Funzioni
Esempio Stack main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = ?? #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = ?? sqy = ?? 9 Funzioni
Esempio Stack main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = 9 #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = 9 sqy = ?? Funzioni
Esempio Stack main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = 9 #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = 9 sqy = ?? Funzioni
Esempio Stack main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = 9 #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = 9 sqy = ?? ret Funzioni
Esempio Stack main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = 9 #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = 9 sqy = ?? ret sq() x = 2 Funzioni
Esempio Stack main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = 9 #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = 9 sqy = ?? 4 Funzioni
Esempio Stack main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = 9 #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = 9 sqy = 4 Funzioni
Esempio Stack main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = 9 #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 ret sumsq() x = 3 y = 2 sqx = 9 sqy = 4 Funzioni
Esempio Stack main() a = 3 b = 2 13 #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 13 Funzioni
Esempio Stack main() a = 3 b = 2 #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; main() a = 3 b = 2 Funzioni
Esempio Stack #include <stdio.h> int sq(int x) { return x * x; } int sumsq(int x, int y) int sqx, sqy; sqx = sq(x); sqy = sq(y); return sqx + sqy; int main( void ) int a = 3, b = 2; printf("%d\n", sumsq(a, b)); return 0; Funzioni
Domanda Chi "consuma" il valore di ritorno della funzione main()? Risposta: il Sistema Operativo Vedremo... Funzioni
Visibilità delle variabili Funzioni
Regola di visibilità delle variabili Una variabile è visibile unicamente all’interno del blocco in cui è dichiarata I parametri formali funzionano come le variabili (sono visibili solo nel corpo della funzione cui si riferiscono) void f(int i) { /* Qui i e' visibile, j no */ } void g(int j) { /* Qui j e' visibile, i no */ int main( void ) { /* Qui non sono visibili ne' i ne' j */ return 0; Funzioni
Blocco Da “{” a “}” #include <stdio.h> int main( void ) { int a = 3; if ( a > 0 ) { int b = a+1; } else { int b = a-1; } printf("%d\n", a); printf("%d\n", b); /* errore: b non e' visibile qui */ return 0; Funzioni
Variabili all’esterno delle funzioni Variabili globali x e y sono variabili globali, quindi visibili ovunque, anche all'interno di f(), g() e main() #include <stdio.h> int x, y; void f(int i) { x = i; /* ok */ } int g(int j) { y = j; /* ok */ return y + 1; int main( void ) { f(4); g(5); return 0; Funzioni
Modello a contorni Utile per capire dove una variabile è visibile Un “contorno” (rettangolo) intorno a ogni blocco All'interno di un blocco sono visibili Tutte le variabili definite all'inizio di quel blocco Tutte le variabili definite nei blocchi che contengono quel blocco Funzioni
Modello a contorni: esempio int va; void f(int vb) { ... for (...) { int vc; if (...) { int vd; } int main( void ) { int ve; va vb vc vd Un contorno intorno a ogni blocco ve Funzioni
Blocchi annidati E' possibile (ma fortemente sconsigliato) definire variabili che hanno lo stesso nome di variabili già definite in un blocco più esterno Le variabili definite nel blocco interno “nascondono” quelle con lo stesso nome definite esternamente Vedi esempio che segue Funzioni
Esempio /* nascoste.c */ #include <stdio.h> int x, y; /* variabili globali */ void f( ) { int x; x = 1; /* locale */ y = 1; /* globale */ printf(“f(): x=%d\n”, x); printf(“f(): y=%d\n”, y); } int main( void ) x = 0; /* globale */ y = 0; /* globale */ f(); printf(“main(): x=%d\n”, x); printf(“main(): y=%d\n”, y); return 0; x, y x In f() la x locale nasconde la x globale > gcc nascoste.c -o nascoste > nascoste f(): x=1 f(): y=1 main(): x=0 main(): y=1 Funzioni
Variabili “globali” vs. passaggio parametri Quando possibile, usare parametri e non variabili globali È più semplice capire cosa fa una funzione Basta leggere la definizione della funzione; non serve far riferimento a tutto il resto del programma Funzioni
Alcune funzioni matematiche dichiarate in <math.h> double sqrt(double) Radice quadrata double exp(double x), double exp2(double x), double exp10(double x) e (base dei logaritmi naturali) elevato alla x, 2 elevato alla x, 10 elevato alla x double log(double x), double log2(double x), double log10(double x) logaritmo in base e, in base 2, in base 10 di x double sin(double x), double cos(double x), double tan(double x) seno, coseno, tangente di x double pow(double x, double y) x elevato alla y double fabs(double x) valore assoluto di x double floor(double x), double ceil(double x) arrotonda x all'intero inferiore o superiore Funzioni
Uso di <math.h> Le funzioni di cui al lucido precedente (e molte altre) sono solo dichiarate in math.h La definizione risiede di una libreria che va inclusa nel programma con l'opzione -lm /* esempio-math.c : compilare con gcc esempio-math.c -o esempio-math -lm */ #include <stdio.h> #include <math.h> int main( void ) { double v; for (v=0.0; v < 1.0; v += 0.1) { printf("%f %f %f %f\n", v, sin(v), cos(v), tan(v)); } return 0; Provare a compilare il programma d'esempio senza l'opzione -lm per vedere l'errore che da Funzioni
Uso di <math.h> Cosa succede se dimentico di includere math.h ? > gcc esempio-math.c -o esempio.math -lm esempio-math.c: In function ‘main’: esempio-math.c:9:30: warning: incompatible implicit declaration of built-in function ‘sin’ [enabled by default] printf("%f %f %f %f\n", v, sin(v), cos(v), tan(v)); ^ esempio-math.c:9:38: warning: incompatible implicit declaration of built-in function ‘cos’ [enabled by default] esempio-math.c:9:46: warning: incompatible implicit declaration of built-in function ‘tan’ [enabled by default] Funzioni
Uso di <math.h> Cosa succede se dimentico di compilare con -lm ? > gcc esempio-math.c -o esempio.math /tmp/ccEOzRqN.o: In function `main': esempio-math.c:(.text+0x27): undefined reference to `tan' esempio-math.c:(.text+0x42): undefined reference to `cos' esempio-math.c:(.text+0x5d): undefined reference to `sin' collect2: error: ld returned 1 exit status Funzioni
Esercizio Scrivere un programma che stampa i numeri primi compresi fra 2 e n, con n letto in input durante l’esecuzione Suggerimento: se lo si ritiene utile, si può usare la funzione primo() definita in questi lucidi. Funzioni
Esercizio Scrivere una funzione int sqrtint(int n) che accetta come parametro un intero n ≥ 0, e restituisce la radice quadrata (approssimata per difetto) di n In altre parole, la funzione restituisce il massimo intero q per cui si abbia q*q ≤ n Es. sqrtint(0) ritorna 0; sqrtint(1) ritorna 1; sqrtint(2) ritorna 1; sqrtint(13) ritorna 3 Predisporre una funzione main() che invoca la funzione precedente Funzioni
Esercizio Scrivere una funzione int primofattore(int n) che, dato in input un intero n > 0, restituisce il più piccolo fattore primo di i; se n è un numero primo, la funzione restituisce n Es.: primofattore(1) restituisce 1; primofattore(2) restituisce 2; primofattore(4) restituisce 2; primofattore(9) restituisce 3; primofattore(21) restituisce 3 Suggerimento bastano alcune semplici modifiche alla funzione primo() vista a lezione Scrivere una nuova funzione void fattorizzazione(int n) che dato in input un intero n > 0 stampa la fattorizzazione di n Es.: fattorizzazione(3) stampa 3; fattorizzazione(21) stampa 3 7; fattorizzazione(16) stampa 2 2 2 2; fattorizzazione(27) stampa 2 3 3 Suggerimento: sfruttare la funzione primofattore() definita sopra. Funzioni
Esercizio Scrivere una funzione void lanci(int n) che accetta in input un intero n ≥ 1, e simula n lanci di una moneta; la funzione deve stampare il numero di teste e croci uttenute Per simulare il lancio di una moneta è possibile usare la funzione int rand(void) definita nel file stdlib.h che va incluso all'inizio del programma (rand() % 2) è una espressione che vale 0 oppure 1 con uguale probabilità ogni volta che l'espressione viene valutata si potrebbe ottenere un valore diverso #include <stdio.h> #include <stdlib.h> void lanci(int n) { int m; … m = (rand() % 2); /* m vale 0 oppure 1 */ } Funzioni