Linguaggio C - Funzioni Funzioni: Il linguaggio C è di tipo procedurale; ogni programma viene suddiviso in sottoprogrammi, ognuno dei quali svolge un determinato compito. I sottoprogrammi si usano anche per evitare di replicare porzioni di codice sorgente. Questo favorisce anche la riusabilità di codice esistente, agevolando il compito del programmatore. In C i sottoprogrammi si chiamano funzioni. Ogni funzione prende in ingresso un insieme di parametri e restituisce un valore; possiamo vederla come una scatola nera (black-box) a determinati valori in input fa corrispondere un valore di output. Basti pensare alle funzioni di libreria; es. abs, strlen, ecc. double cubo(double valore); // Dichiarazione prototipo della funzione int main () { double a, b; printf("inserisci un numero: "); scanf("%lf", &a); b = cubo(a); printf("il cubo di a = %f è: %f\n", a, b); double cubo(double valore) { // Definizione della funzione double temp = valore * valore * valore; return temp;
Si noti che come per le variabili, anche le funzioni devono essere dichiarate prima dell uso. Dichiarazione delle funzioni: Con la dichiarazione definiamo il prototipo della funzione: il nome con cui verrà chiamata, i parametri d ingresso e il valore di ritorno. Sintassi: equiv.: tipo_ritorno nome_funzione (tipopar_1 nome_1,, tipopar_n nome_n); tipo_ritorno nome_funzione (tipopar_1,, tipopar_n); Le due notazione sono equivalenti, visto che a questo livello stabiliamo soltanto l interfaccia verso l esterno; quello che conta è conoscere il numero e il tipo dei parametri. Si noti che la definizione della funzione è inserita dopo il main, ma potrebbe stare anche in altri file sorgenti. Nel caso specifico, se la funzione viene definita prima del main, la dichiarazione non è necessaria. Definizione delle funzioni (implementazione): In questa fase si specifica che cosa fa la funzione e in che modo. Sintassi generica: tipo_ritorno nome_funzione(tipopar_1 par_1, tipopar_n par_n) { dichiarazione di variabili locali sequenza istruzioni return valore; A questo livello, dobbiamo obbligatoriamente specificare il tipo e il nome di ogni parametro d ingresso; questi sono detti : parametri formali.
Nota: In C non esiste il polimorfismo; ovvero non è possibile definire due funzioni con lo stesso nome anche se dovessero avere parametri diversi. Es.: double cubo(float) double cubo (int) // errore! Non è possibile definire una funzione all interno di un altra funzione. return: Ad ogni funzione è associato un tipo (fondamentale o derivato) che caratterizza il valore di ritorno; restituito al programma chiamante attraverso l istruzione return. Sintassi: return espressione; o return (espressione); In una funzione possiamo avere più return; termina quando se ne esegue uno: Esistono funzione che non necessitano di valore di ritorno: in tal caso utilizzare void come tipo_ritorno. int pari (int a) { if (a%2 == 0) return 1; else Ad esempio si usa per funzioni che stampano a video messaggi di errore. Es.: void print_error(int n); // se n=0 print warning. ; n=1 print errore ; ecc Esercizi: - Funzione che restituisca il massimo tra due numeri che gli vengono passati. - Funzione che stampi a video la codifica binaria di un numero n (parametro input). - Funzione chiamata da un altra funzione (passaggio dei parametri): es. conversione in binario dei numeri da 0 a n.
Chiamata di una funzione: La chiamata di una funzione si ottiene semplicemente invocando il nome della funzione seguito dalla lista di parametri da passare, rispettando il numero, il tipo e l ordine. float calc_area(float, float, char); int main () { float a, b, h; char t; do { printf("\n"); printf("tipo di poligono: T-triangolo R-rettangolo: "); scanf(" %c", &t); while ((t!= 't') && (t!= 'T') && (t!= 'r') && (t!= 'R')); printf("inserire base e altezza: "); scanf("%f %f", &b, &h); a = calc_area(b,h,t); // chiamata della funzione. printf( L area del poligono è : %f", a); // system("pause"); float calc_area(float base, float altezza, char tipo) { switch (tipo) { case 't': case 'T': return (base*altezza/2); case 'r': case 'R': return (base*altezza); default: printf( errore!!! "); return -1;
I parametri che vengono forniti al momento della chiamata sono detti : parametri attuali; poiché contengono i valori d ingresso in quella specifica chiamata di funzione. In questa fase ogni parametro attuale viene assegnato al corrispondente parametro formale. I parametri attuali possono essere variabili, costanti o espressioni purchè rispettino il tipo. Nell esempio precedente: a= calc_area(23.2*2, 45./4, T ); errore: a= calc_area (b, t, h); a=calc_area(b, 12, h, t); Esercizi: - Funzione che trovi i numeri primi tra 1 e n. - Funzione che converta la temperatura da gradi centigradi a Fahrenheit. - Implementare un programma in C, dove il sorgente è presente su più file.c ( es. 1. main, 2. funzione )
Passaggio di parametri: Si ricordi che al momento della chiamata di una funzione, i parametri attuali vengono assegnati ai parametri formali. Che cosa accade ai valori dei parametri attuali? Dipende dal tipo di passaggio. In generale i parametri possono essere passati in due modi: Per valore Per riferimento Nel primo caso, i valori dei parametri attuali vengono semplicemente copiati nei parametri formali; tutte le eventuali modifiche apportate dalla funzione non si ripercuotono nei parametri attuali. Nel secondo caso invece, si instaura un collegamento tra i parametri attuali e quelli formali; cosicchè tutte le eventuali modifiche apportate dalla funzione si riflettono nei parametri attuali. Chiamante Chiamante int a Funzione par1 int a Funzione par1 Chiamata di funzione Chiamata di funzione Valore di ritorno Valore di ritorno
Nel linguaggio C i parametri vengono passati per valore (o copia). Vedremo più avanti che con l utilizzo dei puntatori si simulerà il passaggio per riferimento. /* Esempio: parametri attuali restano invariati */ void calcola(int); int main() { int a = 5; printf("valore iniziale di a: %d\n", a); calcola(a); printf("nuovo valore a: %d\n", a); void calcola(int a) { a*=5 ; printf("valore di a nella funzione : %d\n",a ); // return; Nota: eseguendo il programma si noterà che il valore della variabile a definita nel main non viene modificato dalla funzione calcola.
Per esercizio tenteremo di scrivere una funzione che scambi il contenuto di due variabili: void scambio(int, int); // dichiarazione prototipo della funzione int main () { int a, b; printf("inserisci due numeri interi: "); scanf("%d %d", &a,&b); scambio(a,b); printf("nel main a= %d b= %d", a, b); // risultano non scambiati // system("pause"); void scambio(int a, int b) { // definizione della funzione int appoggio; appoggio=a; a=b; b=appoggio; printf("nella funzione scambio: a= %d b= %d", a, b); // sono scambiati return; Dagli esempi precedenti si nota che ogni identificatore (variabili, funzioni, ecc) ha una determinata visibilità all interno del programma.
Visibilità degli identificatori La visibilità (campo di azione) di un identificatore indica quella parte di programma in cui tale identificatore può essere menzionato. In relazione alle variabili abbiamo 3 livelli di visibilità: Ambiente globale Ambiente locale a una funzione Ambiente di blocco Variabili globali: sono visibili nell intero programma. Esempio di visibilità locale, sono le variabili definite all interno di una funzione; in questa categoria rientrano anche i parametri formali. Mentre una variabile definita in un blocco { è menzionabile a partire dalla dichiarazione fino alla fine del blocco, compresi eventuali blocchi interni. Organizzazione tipica di un programma in C: Direttive al preprocessore Dichiarazione di funzioni utente Dichiarazioni variabili globali main() { // istruzioni Definizione di funzioni utente
Esempio: variabile definita fuori da tutte le funzioni, main compreso: void stampa_msg(void); int a = 3; // variabile globale int main(){ a+=5; printf("valore iniziale di a: %d\n", a); stampa_msg(); printf("valore finale di a: %d\n", a); // system("pause"); void stampa_msg(void) { a=100; int b=0; // variabile locale printf("funzione di stampa: \n"); return; Domanda: cosa accadrebbe se la dichiarazione di a fosse nel main? Errore in compilazione. Nota : Le variabili globali, costituiscono un modo per passare dei parametri alle funzioni (Fare attenzione!).
Mascheramento: Un variabile ridefinita in un blocco interno, nasconde il significato di quella precedente (esterna). E possibile definire variabili con lo stesso nome, ma devono avere un diverso scope void stampa_msg(void); int a = 3; // variabile globale int main() { a+=5; printf("valore iniziale di a: %d\n", a); stampa_msg(); printf("valore finale di a: %d\n", a); // system("pause"); void stampa_msg(void) { int a=100; // ridefinizione di a; // maschera la variabile a definita globalmente printf("valore di a nella funzione: %d\n", a); return;
Funzioni: Passaggio di array come parametro: dich.: valore_ritorno nome_funzione( tipo array[] ); L identificatore di un array non è altro che un puntatore che punta a un area di memoria; esso non rappresenta l intero array, ma costituisce un link al suo indirizzo iniziale. Nota: se v è un vettore di n elementi, v coincide con &v[0]. Pertanto, se la dimensione non è nota a livello globale, dobbiamo passare anche la dimensione dell array, altrimenti la funzione non sarebbe in grado di risalire al numero degli elementi che costituiscono l array. Es.: valore_ritorno nome_funzione(tipo array[], int dim_max); Nota: nel caso di array come parametro, il passaggio è per riferimento ( Fare attenzione! ). Passaggio di array multidimensionali: valore_ritorno nome_funzione(tipo array[][dim2_max] [DIMn_MAX]); Nota: dobbiamo specificare le dimensioni successive alla prima! Comunque, il passaggio di array multidimensionali, non è molto agevole, vedremo che è molto più semplice lavorare con i puntatori.
/* Funzione: trova max in un array è lo moltiplica per 2 */ void stampa_vettore(int v[], int len); int trova_max(int v[], int len); int main() { int imax=0, x[] = {1,2,3,6,4; stampa_vettore(x, 5); imax = trova_max(x, 5); stampa_vettore(x, 5); int trova_max(int v[], int len) { int i, max=v[0], imax=0; for(i = 0; i < len; i++) if (v[i] > max) {max = v[i]; imax = i; v[imax]*=2; return imax; void stampa_vettore(int v[], int len) { int i; for(i = 0; i < len ; i++) printf("%d ", v[i]); printf("\n"); Esercizio: funzione che riceve un valore ed un vettore; ritorna il numero di occorrenze del valore nel vettore.
Classi di memoria La classe di memoria di un identificatore determina la sua permanenze in memoria, il suo campo di azione e il suo collegamento. Il linguaggio C fornisce gli specificatori della classe di memoria. Alcuni esempi: auto, extern e static ; (già accennati in una precedente lezione.) In relazione alla permanenza, possiamo avere una permanenza in memoria automatica oppure una permanenza in memoria statica. Esempio del primo tipo sono le variabili definite in un blocco, esistono finchè il blocco è attivo e vengono distrutte appena si è fuori dal blocco. Anche auto viene impiegato a tale scopo; di default le variabili sono automatiche quindi questo specificatore si usa raramente. Gli identificatori con permanenza in memoria statica, sono quelli che vengono definiti una sola volta (in genere all inizio) ed esistono per tutta la durata del programma. Esempi di questo tipo sono, le variabili globali, i nomi delle funzioni, e le variabili locali dichiarate con lo specificatore static. es.: static int n; All interno di una funzione, una variabile static, mantiene il valore anche dopo l uscita; ovvero alle chiamate successive ricorda il valore assunto alla precedente chiamata.
Esempio: void func(); int main() { int i; for (i=0; i<10; ++i) func(); void func() { int local_var=0; // variabile locale static int static_var=0; printf("local_var=%d, static_var=%d \n", local_var, static_var); ++local_var; ++static_var; Output: local_var=0, static_var=0 local_var=0, static_var=1 local_var=0, static_var=2..
Nota: una variabile globale, se definita static, la rendiamo privata al programma corrente; ovvero non è visibile da altri file sorgente anche se dovessero usare lo specificatore extern. Il collegamento di un identificatore determina, in un progetto che si compone di diversi file sorgente, la sua visibilità; ovvero se è noto al solo file corrente oppure a tutti gli altri sorgenti. A tale scopo si usa extern. myprog.c void funzione(void); int n; int main() { printf("inserire valore di n: "); scanf ("%d", &n); funzione(); printf("valore finale di n= %d", n); modulo.c extern int n; void funzione(void) { int i = 10; n+=i; // altre istruzioni. Esercizi: - Funzione di conversione caratteri di stringhe: maiuscole in minuscole e viceversa. - Programma calcolatrice: menu di scelta operazione ( +,-,*,/); ogni operazione una funzione.