Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf ·...

21
Le funzioni Ver. 2.4 © 2010 - Claudio Fornaro - Corso di programmazione in C 2 Struttura modulare Per semplificare la struttura di un programma complesso è possibile suddividerlo in moduli Un modulo è un blocco di codice che assolve ad un compito preciso (ad es. calcola la radice quadrata) e a cui è stato dato un nome Un programma consta di un modulo principale (il main) ed eventuali altri moduli di supporto Quando un modulo richiama un altro modulo, il chiamante viene sospeso finché il chiamato non ha terminato la sua esecuzione In C i moduli sono chiamati funzioni Ogni funzione può richiamare (far eseguire) qualsiasi altra funzione (anche se stessa) 3 Struttura modulare Le due chiamate del modulo StampaCiao fanno eseguire ogni volta le istruzioni che lo costituiscono (sospendendo il chiamante) ... scanf... StampaCiao() ... for ... switch ... printf... ... StampaCiao() if (x==2) then ... printf("### # # ###\n"); printf("# # # # #\n"); printf("# # # # # #\n"); printf("# # ### # #\n"); printf("### # # # ###\n"); Modulo chiamato (StampaCiao) Modulo chiamante 4 Struttura modulare Vantaggi della programmazione modulare: poiché i moduli “nascondono” al loro interno i dettagli di come una certa funzionalità venga realizzata, il programma complessivo ha un livello di astrazione maggiore: il modulo viene visto come un insieme di macro-istruzioni (“black-box”) il codice per ottenere una certa funzionalità viene scritto una volta sola e viene richiamato ogni volta che è necessario (ma la chiamata richiede tempo) il codice complessivo è più corto essendo più piccoli, i moduli sono più semplici da realizzare e da verificare il codice di un modulo correttamente funzionante può essere riutilizzato in altri programmi

Transcript of Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf ·...

Page 1: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

Le funzioni

Ver. 2.4

© 2010 - Claudio Fornaro - Corso di programmazione in C

2

Struttura modulare Per semplificare la struttura di un programma

complesso è possibile suddividerlo in moduli Un modulo è un blocco di codice che assolve

ad un compito preciso (ad es. calcola la radice quadrata) e a cui è stato dato un nome

Un programma consta di un modulo principale (il main) ed eventuali altri moduli di supporto

Quando un modulo richiama un altro modulo, il chiamante viene sospeso finché il chiamatonon ha terminato la sua esecuzione

In C i moduli sono chiamati funzioni Ogni funzione può richiamare (far eseguire)

qualsiasi altra funzione (anche se stessa)

3

Struttura modulare Le due chiamate del modulo StampaCiao

fanno eseguire ogni volta le istruzioni che lo costituiscono (sospendendo il chiamante)

...scanf...StampaCiao()...for ...

switch ...printf......StampaCiao()if (x==2) then...

...scanf...StampaCiao()...for ...

switch ...printf......StampaCiao()if (x==2) then...

printf("### # # ###\n"); printf("# # # # #\n"); printf("# # # # # #\n"); printf("# # ### # #\n"); printf("### # # # ###\n");

printf("### # # ###\n"); printf("# # # # #\n"); printf("# # # # # #\n"); printf("# # ### # #\n"); printf("### # # # ###\n");

Modulo chiamato (StampaCiao)

Modulo chiamante

4

Struttura modulare Vantaggi della programmazione modulare:

poiché i moduli “nascondono” al loro interno i dettagli di come una certa funzionalità venga realizzata, il programma complessivo ha un livello di astrazione maggiore: il modulo viene visto come un insieme di macro-istruzioni (“black-box”)

il codice per ottenere una certa funzionalità viene scritto una volta sola e viene richiamato ogni volta che è necessario (ma la chiamata richiede tempo)

il codice complessivo è più corto essendo più piccoli, i moduli sono più semplici da

realizzare e da verificare il codice di un modulo correttamente funzionante

può essere riutilizzato in altri programmi

Page 2: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

5

Variabili locali Ogni funzione è un piccolo programma a sé

stante, isolato dalle altre funzioni All’interno di una funzione possono essere

definite delle variabili locali (cioè hanno scopelocale): le altre funzioni non le “vedono”

Variabili con lo stesso nome in funzioni diverse sono quindi completamente scorrelate(possono dunque anche essere di tipo diverso)

Vengono create ogni volta che si entra nella funzione e distrutte (perdendo il valore) quando si esce

Le inizializzazioni avvengono ad ogni chiamata, senza inizializzazione il contenuto è indefinito

6

Parametri e valore restituito Essendo le variabili interne locali, private, per

passare ad una funzione i dati da elaborare è necessario utilizzare variabili speciali dette parametri

La funzione comunica al modulo chiamante il risultato della sua elaborazione producendo un unico valore detto valore restituito o valore di ritorno

main funzione

parametri

risultato

7

Definizione di funzionitipo nomeFunzione(parametri){

definizione_variabili_locali

istruzionieventuale return

} tipo indica il tipo del valore restituito

(es. sqrt restituisce un double) Se la funzione non restituisce valori (ad

esempio StampaCiao visualizza soltanto) bisogna indicare il tipo void:void StampaCiao(.....)Se non si mette nulla viene supposto int

corpodellafunzione

8

Definizione di funzioni Se la funzione non ha parametri (ad esempio StampaCiao), si indica void tra le parentesi:void StampaCiao(void)

Se non si indica nulla, il compilatore C non attua alcun controllo sui parametri (invece per un compilatore C++ è come se l’argomento fosse esplicitamente void)

Page 3: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

9

Chiamata di funzione Si chiama una funzione indicandone il nome

seguito da una coppia di parentesi contenenti i valori da elaborare (separati da virgole)eleva(y,2);

Se la funzione non richiede parametri, le parentesi sono vuote, ma devono esserci:StampaCiao();

Il valore restituito può essere assegnato ad una variabile o utilizzato in un’espressione, altrimenti viene semplicemente scartato:x = eleva(y,2);y = 3*eleva(2,k)-4*k;eleva(3,5);

10

Ritorno da una funzione La funzione termina (cioè l’esecuzione torna al

modulo chiamante) quando viene eseguita l’istruzionereturn risultato;

Una funzione può avere più istruzioni return risultato è il valore restituito dalla funzione al

chiamante, è un’espressione qualsiasi return x*2;

Se il tipo restituito dalla funzione è void, non si deve indicare risultato, inoltre la returnche precede la graffa di chiusura (solo questa) può essere omessa

I valori delle variabili locali vengono persi

11

Ritorno da una funzione Nel main la return termina il programma Per terminare un programma dall’interno di

una funzione (e passare lo status al Sistema Operativo) si utilizza la funzione exit(status)dichiarata in <stdlib.h>:exit(EXIT_SUCCESS);

12

Esempio di funzione#include <stdio.h>int eleva(int b, int e){

int k=1;while (e-- > 0)

k *= b;return k;

}Continua (stesso file)...

Page 4: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

13

Esempio di funzioneContinuazione (stesso file)main() {int x, y;printf("Introduci numero: ");scanf("%d", &x);y = eleva(x, 2);printf("%d^%d = %d\n", x,2,y);return 0;

} Alla chiamata, la x del main viene copiata

nella b di eleva, mentre il 2 del main viene copiato nella e di eleva

14

Tipo di una funzione Il tipo di una funzione è determinato dal

tipo del valore restituito e dal tipo, numero e ordine dei suoi parametriint eleva(int b, int e)eleva è una funzione che ha un primo paramero int, un secondo parametro int e restituisce un int

15

Scope di una funzione Lo scope di una funzione (nome e tipo) si

estende dal punto in cui viene definita fino a fine file: la funzione può essere utilizzata solo dalle funzioni che nello stesso file seguono la sua definizione (vale anche per il main)f1() {...}f2(){...}main(){...}f1 non “vede” e non può quindi usare f2, f2vede f1, il main vede f1 e f2

16

Scope di una funzione Il compilatore verifica che le chiamate a

funzione siano coerenti con le corrispondenti definizioni (cioè abbiano lo stesso tipo)

E’ necessario che la chiamata a funzione sia nello scope della funzione stessa

Se il compilatore trova una funzione di cui non conosce il tipo (non è in scope, ad esempio f1che chiama f2), allora presuppone che essa sia definita altrove e quindi: non fa controlli sugli argomenti presuppone che restituisca un int segnala il possibile problema con un Warning

Page 5: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

17

Prototipo di una funzione Il prototipo di una funzione è una

dichiarazione che estende lo scope della funzione (nome e tipo)

Il corpo della funzione (la sua definizione) può quindi essere collocato: in un punto successivo a dove viene chiamata

(nell’esempio seguente, eleva è definita dopo il main dove viene utilizzata)

in un altro file di codice sorgente C in una libreria (compilata)

Lo scopo primario degli header file è quello di fornire al compilatore i prototipi delle funzioni delle librerie del C (ad es. stdio.h contiene i prototipi di scanf, printf, getchar, etc.)

18

Esempio di funzione#include <stdio.h>int eleva(int b, int e); prototipomain() {int x, y;printf("Introduci numero: ");scanf("%d", &x);y = eleva(x, 2);printf("%d^%d = %d\n", x,2,y);return 0;

}Continua (stesso file)...

19

Esempio di funzioneContinuazione (stesso file)int eleva(int b, int e){

int k=1;while (e-- > 0)

k *= b;return k;

}

20

Prototipo di una funzione I prototipi possono essere collocati:

prima del main (come nell’esempio eleva) tra una funzione e l’altra insieme alle definizioni delle variabili locali di una

funzione

Il prototipo estende lo scope della funzione (nome e tipo) dal punto dove è indicato: fino a fine file se esso è collocato esternamente alle

funzioni (prima del main o tra due funzioni) fino a fine funzione se è interno ad una funzione

(collocato con le variabili locali di una funzione)

Page 6: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

21

Prototipo di una funzione Il prototipo di una funzione è simile alla

definizione della funzione, salvo che: manca il corpo i nomi dei parametri possono essere omessi (ma i

tipi devono essere presenti!) ha un punto e virgola alla fineint eleva(int, int);

I nomi dei parametri dei prototipi: se non sono omessi, possono essere diversi da

quelli usati nella definizione della funzione sono scorrelati dagli altri identificatori (nomi uguali

si riferiscono comunque a identificatori diversi sono utili per descrivere il significato dei parametri:int eleva(int base, int esponente);

22

Parametri attuali e formali Parametri attuali (o argomenti):

sono i valori (variabili, costanti o espressioni) indicati tra le parentesi alla chiamata di una funzione eleva(x,2)

Parametri formali:sono le variabili indicate tra le parentesi nella definizione della funzione int eleva(int b, int e)

23

Parametri attuali e formali Nella chiamata ad una funzione bisogna

indicare un argomento per ciascuno dei parametri formali

I parametri attuali e quelli formali corrispondono in base alla posizione (il primo attuale al primo formale, etc.)

I nomi dei parametri formali sono scorrelati(e quindi tipicamente diversi) dai nomi di eventuali variabili usate come argomenti (inoltre gli argomenti possono essere valori costanti o il risultato di espressioni)

I parametri formali hanno lo stesso scope delle variabili locali della funzione

24

Passaggio dei parametri I dati possono essere passati ad una funzione

esclusivamente per valore (by value):alla chiamata della funzione vengono create nuove variabili con i nomi di ciascuno dei parametri formali e in esse viene copiato il valore del corrispondente parametro attuale

main()main() eleva()eleva()x

2 be

Page 7: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

25

Passaggio dei parametri Come per le assegnazioni, se il parametro

attuale e il corrispondente formale sono di tipo diverso c’è una conversione automatica al tipo del parametro formale (se è di tipo meno capiente può essere generato un Warning)

Poiché in memoria i parametri formali e quelli attuali sono completamente distinti e indipendenti, cambiare il valore di un parametro formale non modifica il parametro attuale corrispondente, neppure se questo è una semplice variabile (è ovviamente impossibile modificare una costante o il risultato di un’espressione): nell’esempio visto la modifica di b non si ripercuote su x

26

Variabili locali static Le variabili locali hanno classe di allocazione

automatica: vengono create ogni volta che si esegue la funzione ed eliminate ogni volta che questa termina (perdendone il valore)

Le variabili locali di classe di allocazione statica invece non vengono mai rimosse dalla memoria per cui non perdono il loro valore quando la funzione termina (resta quello che aveva al termine della chiamata precedente)

Le variabili statiche non richiedono la ri-allocazione della memoria ad ogni chiamata della funzione, quindi il programma può essere più veloce

27

Variabili locali static Si richiede una classe di allocazione statica e

non automatica mediante lo specificatore di classe di allocazione static:static int cont = 0;

Se non inizializzate esplicitamente, vengono inizializzate automaticamente a 0 (che nel caso dei puntatori viene automaticamente convertito in NULL)

28

Variabili locali static L’inizializzazione delle variabili locali static

avviene idealmente solo la prima volta che si esegue la funzione (in realtà i valori vengono inizializzati dal compilatore)

Possono essere inizializzate dal compilatore solo con espressioni costanti: numeri e #define valori enum indirizzi di memoria di variabili statiche

Non possono essere inizializzate con: valori const variabili e risultati di funzioni indirizzi di memoria di variabili automatiche

Page 8: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

29

Variabili locali static int conta(void){

static int cont = 0; return ++cont;

}Ogni volta che conta viene chiamata, essa incrementa il contatore cont e ne restituisce il valore, se non fosse statica ma automatica restituirebbe sempre il valore 1 perché contverrebbe ri-inizializzata ogni volta a 0

30

Variabili locali static char *nomeMese(int n){

static char *nome[] = {"inesistente", "gennaio","febbraio", ecc... };

if (n<1 || n>12)return nome[0];

elsereturn nome[n];

}La stringa di cui viene restituito il puntatore può essere utilizzata dal chiamante in quanto essendo statica non viene rimossa dalla memoria

31

Passaggio dei parametri Il passaggio di parametri nella modalità

per riferimento (by reference) prevede che la modifica del parametro formale si ripercuota identica sul corrispondente parametro attuale (deve essere una variabile)

In C non esiste il passaggio per riferimento, ma questo può essere simulato passando per valore alla funzione il puntatore al dato da passare (che deve essere una variabile, non può essere il risultato di un calcolo)

Nella scanf le variabili scalari sono precedute da & perché devono essere modificate dalla funzione e quindi se ne passa l’indirizzo

32

Passaggio dei parametri#include <stdio.h>void fz(int *); prototipomain(){

int x=2;fz(&x);printf("%d\n", x);return EXIT_SUCCESS;

}void fz(int *p){

*p = 12;}

Page 9: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

33

Passaggio dei parametri Nell’esempio:

il main alloca x, gli assegna il valore 2 e chiama fz passandole l’indirizzo di x (&x) presente in una variabile temporanea (“senza nome”)

alla chiamata di fz, l’indirizzo di x (che è BF32F0) viene copiato by-value in p

fz accede a x come *p, modificandola in 12 quando fz termina, x vale 12

main()

fz(&x);x 12

main()

fz(&x);x 12

fz(int*p)

*p=12;

fz(int*p)

*p=12;

BF32F0&x2 BF32F0 px

34

Passaggio dei parametrivoid swap(int *, int *); prototipomain(){

int x=12, y=24;swap(&x, &y);

}void swap(int *a, int *b){

int temp;temp = *a;*a = *b;*b = temp;

}

35

Passaggio di vettori Per passare un vettore come argomento, si

indica il suo nome senza parentesix = media(vettore);

Il parametro formale corrispondente definirà un vettore dello stesso tipo (in genere senza dimensione in quanto ininfluente)float media(int v[]);

La funzione deve conoscere in qualche modo la dimensione del vettore (indicarlo tra le parentesi quadre non serve a nulla): viene passato come argomentofloat media(int v[], int lung);

è noto a priori (es. una #define) si usano variabili esterne (vedere più avanti)

36

Passaggio di vettori Possono essere passati vettori con dimensioni

diverse, ma dello stesso tipo T Quando si passa un vettore-di-T, poiché si

indica il nome del vettore, in realtà si passa l’indirizzo di memoria del suo primo elemento (e questa non dà alcuna informazione né restrizione sulla lunghezza del vettore)

Il parametro formale è quindi in realtà un puntatore-a-T, la forma v[] viene convertita automaticamente in *v, si possono usare le due definzioni indifferentemente float media(int *v);

Page 10: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

37

Passaggio di matrici Per passare una matrice come argomento, si

indica il suo nome senza parentesix = media(matrice);

Il parametro formale corrispondente dichiara una matrice dello stesso tipo (in genere senza la prima dimensione in quanto ininfluente)void funz(int matrice[][10])

La funzione deve conoscere in qualche modo le dimensioni della matrice (indicarle tra le parentesi quadre non serve a questo scopo)

38

Passaggio di matrici Possono essere passate matrici con diverso

numero di righe, ma devono avere lo stesso numero di colonne e gli elementi devono essere dello stesso tipo T

Poiché una matrice è un vettore-di-vettori, quando essa viene passata ad una funzione, viene in realtà passato l’indirizzo del primo elemento del vettore-di-vettori

Il tipo del parametro formale corrispondente è quindi un puntatore-a-vettore e NON un puntatore-a-puntatore (il decadimento da vettore a puntatore avviene una volta sola)

39

Passaggio di matrici Nel caso dell’esempio, il parametro formale è

un puntatore-a-vettore-di-10-int:int (*matrice)[10])

La forma matrice[][10] viene convertita automaticamente in (*matrice)[10], si possono usare le due definzioni indifferentem. void funz(int (*matrice)[10])

E’ quindi un errore scrivere:void funz(int **matrice)oltre all’errore di tipo, si perde la dimensione delle colonne e quindi non si può determinare la posizione degli elementi della matrice

40

Passaggio di matrici In memoria l’elemento Mx[i][j] viene

determinato con il calcolo indirizzo_di_Mx+NC*i+jdove NC è il numero delle colonne,come si vede il numero delle righe non serve

Per passare ad una funzione una matrice con qualsiasi numero di righe e qualsiasi numero di colonne vi sono diverse soluzioni, si rimanda alle slide relative all’allocazione dinamica

Page 11: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

41

Passaggio di vettori multidim. Quanto visto per le matrici può essere esteso

ai vettori multidimensionali In particolare:

nel parametro formale si può tralasciare la dimensione del solo primo parametro

il parametro formale è un puntatore ad un vettore di X vettori di Y vettori di Z vettori di .... di tipo T

la funzione deve conoscere i valori di tutte le dimensioni

possono essere passate matrici con la sola prima dimensione diversa

42

Parametri const Il modificatore const applicato ai parametri

formali impedisce che all’interno della funzione si possa modificarne il valoreint funzione(const int v)

Permette di proteggere i parametri da una successiva incauta modifica (per prevenire errori di programmazione)

Ad esempio, questo richiede al compilatore di segnalare se un puntatore-a-dato-costanteviene assegnato ad un puntatore-a-dato-variabile (ad esempio passandolo come parametro), cosa che by-passerebbe la restrizione

43

Parametri puntatori const1. Puntatore variabile a dati variabiliint f(int *p){*p = 12; OK, dato variabilep++; } OK, puntatore variabile

Si può passare un int* Non si può passare un const int*

(non c’è conversione automatica di tipo perché dentro la funzione nulla vieterebbe di poter cambiare il valore alla variabile puntata):int x=12;const int *y=&x;f(y); ERRORE

44

Parametri puntatori const2. Puntatore variabile a dati costantiint f(const int *p) /* int const */{*p = 12; NO, dato costantep++;} OK, puntatore variabile

Si può passare un int* (c’è conversione di tipo automatica in quanto si passa ad un tipo più restrittivo)

Si può passare un const int* Note:

Tipicamente utilizzato per passare un puntatore ad una struct o ad un vettore impedendo che possano essere modificati

Non si può passare senza cast un Tipo** dove è richiesto un const Tipo**

Page 12: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

45

Parametri puntatori const3. Puntatore costante a dati variabiliint f(int * const p){*p = 12; OK, dato variabilep++;} NO, puntatore costante

Si può passare un int*

4. Puntatore costante a dati costantiint f(const int * const p){*p = 12; NO, dato costantep++;} NO, puntatore costante

Si può passare un int*

46

Variabili esterne Vengono definite (riservando memoria)

esternamente al corpo delle funzioni: in testa al file, tipicamente dopo le direttive #include e #define

oppure tra una funzione e un’altra

Sono visibili e condivisibili da tutte le funzioni che nello stesso file seguono la definizione

Possono essere utilizzate come metodo alternativo per comunicare dati ad una funzione e per riceverne

A questo scopo si usino con parsimonia: rendono poco evidente il flusso dei dati all’interno del programma

47

Variabili esterne Hanno classe di allocazione statica: non

vengono mai rimosse dalla memoria e, salvo inizializzazione esplicita, vengono inizializzate automaticamente a 0 (i puntatori a NULL)

Una variabile locale (interna ad una funzione) con lo stesso nome di una esterna copre la visibilità di quella esterna alla quale quindi non può accedere con quel nome (non è buona pratica di programmazione)

48

Variabili esterne#include<...>int uno;main(){uno = 12;

}long due;void fun1(){uno = 21; due=55;

}int tre;int fun2(){return uno + due + tre;

}tre

due

uno

Page 13: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

49

extern Lo specificatore di classe di allocazioneextern permette di estendere la visibilità delle variabili esterne

La clausola extern viene premessa ad una definizione di variabile per trasformarla in dichiarazione (non riserva memoria)extern int x;

Indica al compilatore che la variabile è definita (con allocazione di memoria, senza extern) altrove (più avanti nello stesso file o in un altro file)

In seguito il linker ricondurrà tutte le dichiarazioni all’unica definizione

50

extern La dichiarazione di una variabile extern può

essere sia interna ad una funzione, sia esterna#include<...>extern int due; dichiarazionefunz4() esterna a funz{extern long uno; dichiarazioneint tre; interna a funztre = uno + due;

}long uno = 0, due = 0; definizioni

di var esterne

51

extern All’interno di una funzione, la clausola extern associata ad una variabile la identifica come variabile esterna definita dopo la funzione stessa o in altro file

Nelle funzioni che, nello stesso file, seguonola definizione di una variabile esterna, tutta la dichiarazione (con extern) è opzionalefun3(){ extern long abc; dichiarazioneabc = 12;} utilizzo

long abc; definizionefun4(){ extern long abc; dich. opzionaleabc = 21;} utilizzo

52

Documentazione delle funzioni E’ utile scrivere sempre la documentazione

relativa allo scopo e all’uso di una funzione come commento iniziale contenente: Scopo: a cosa serve la funzione Parametri: tipo e descrizione di ciascuno Valore restituito: tipo e descrizionePossibilmente anche: Pre-condizioni: requisiti particolari sui parametri che

devono essere soddisfatti da chi invoca la funzione (es. param > 29)

Post-condizioni: garanzie date dalla funzione sul valore restituito o sullo stato del programma, purché le precondizioni siano state soddisfatte (es. risultato >= 23 && risultato <= 32)

Page 14: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

53

Chiamata di funzione - dettagli Il programma compilato è costituito da due

parti distinte: code segment: codice eseguibile data segment: costanti e variabili

note alla compilazione (statiche ed esterne)

Quando il programma viene eseguito, il Sistema Operativo alloca spazio di memoria per: il code segment (CS) il data segment (DS) lo stack e lo heap (condiviso)

Stack e heap

DATA

CODE

STACK

HEAP

54

Chiamata di funzione - dettagli Lo stack contiene inizialmente le variabili

locali della funzione main() Lo heap inizialmente è vuoto

e serve per contenere i blocchi di memoria allocati dinamicamente con funzioni malloc() (trattatein altre slide)

Stack e heap crescono nell’area condivisa nel sensoindicato dalle frecce

STACK

Stack e heap

HEAP

DATI

CODICE

55

Chiamata di funzione - dettagli Quando viene chiamata una funzione, sullo

stack vengono prima copiati i valori dei suoi argomenti e poi vi viene allocato unActivation Record(o stack frame) per contenere tutte le variabili locali (e altro)

Quando la funzionetermina, gli argomenti el’AR vengono rimossidallo stack che quindi ritorna nello statoprecedente la chiamata

Stack e heap

HEAP

DATI

CODICE

STACK

56

Chiamata di funzione - dettagli Nell’Activation Record viene anche

memorizzato l’indirizzo di ritorno dalla funzione: la locazione di memoria che contiene l’istruzione del chiamante da cui continuare dopo che la funzione è terminata

Queste operazioni di allocazione e deallocazione di spazio sullo stack e in generale il meccanismo di chiamata e ritorno da una funzione richiedono tempo

In casi estremi di necessità di elevate performance si può cercare di limitare il numero delle chiamate a funzione, a costo di ricopiare lo stesso codice in più punti (eventualm. utilizzando macro con argomenti )

Page 15: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

57

Esercizi1. Si scriva un programma che per 10 volte

chieda all’utente un valore e ne calcoli il logaritmo in base 2.Per il calcolo del logaritmo si scriva una funzione con prototipo:double log2(double a);che calcoli il valore utilizzando la formula:

2logloglog2

e

e xx

58

Esercizi2. Si scriva un programma che chieda all’utente

10 valori e di questi calcoli la radice quadrata. Per il calcolo della radice quadrata si scriva una funzione con prototipo:double radice(double a, double prec);che calcoli il valore approssimato della radice quadrata di a con il metodo di Newton:

xi sono approssimazioni successive della radice quadrata di a. Si assuma x0= a e si iteri fintanto che xi – xi+1 > prec.

iii x

axx21

1

59

Esercizi3. Si scriva una funzione con prototipo:double media(double v[], int len);che calcoli e restituisca la media di un vettore di double passato come argomento.Si scriva un programma che riempia due vettori di lunghezza differente (es. a[8] e b[10], li passi a media e visualizzi il risultato per ciascuno di essi. La funzione non esegua operazioni di input/output.

60

Esercizi4. Si scriva una funzione con prototipo:void rovescia(char s[]);che rovesci la stringa passata come argomento (modifica del parametro). Si scriva un programma che chieda una stringa, la passi a rovescia e la visualizzi.

5. Si scriva una funzione con prototipo:int contastr(char a[], char x[]);che conti quante volte la stringa x sia contenuta in a. N.B. “bb” in “bbb”: 2 volte

6. Si scriva una funzione undup che modifichi una stringa eliminandone i caratteri duplicati: esempio: “ciao come va?” “ciao mev?”

Page 16: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

61

Esercizi7. Si scriva una funzione con prototipo:void ordina(int v[], int len,

int ord);che ordini in senso crescente e decrescente il vettore di int passato come argomento. Il senso dell’ordinamento venga indicato dal parametro ord (decrescente=0, crescente=1). Si scriva un main di test.

62

Esercizi8. Si scriva la funzione sommaVett che calcoli la

somma di due vettori. Si scriva un main che chieda la dimensione dei vettori (max 100), ne chieda i valori, li passi alla funzione e visualizzi il vettore dei risultati. Non si alteri il contenuto dei due vettori da sommare. La funzione non faccia input/output.Per non usare variabili esterne o variabili locali static, alla funzione bisogna passare anche il vettore dei risultati (il contenuto iniziale non è rilevante).

63

Esercizi9. Un file di testo denominato Parole.txt

contiene una lista di parole, una per riga. Non è noto a priori di quante righe sia composto. Si scriva un programma che chieda all’utente di introdurre una parola e visualizzi tutte le parole presenti nel file che anagrammate danno la parola introdotta. Si scrivano due funzioni: una che riordina alfabeticamente le lettere di una stringa (“telefono” “eeflnoot”) e un’altra che modifichi una stringa trasformandone tutti i caratteri in minuscolo.

64

Esercizi10. Si scriva una funzione con prototipo:double media(double M[][10],

int righe, int colonne);che calcoli e restituisca la media di una generica matrice di 10 colonne passata come argomento. Si scriva un programma che riempia due matrici di dimensioni richieste all’utente (massime 8x10 e 12x10, il massimo di 10 colonne ciascuna è per coerenza con il prototipo), le passi a media e visualizzi il risultato per ciascuna di esse.La funzione non faccia input/output.

Page 17: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

65

Esercizi11. Scrivere una funzione con prototipoint parseToIntVect(char *stringa,

int vett[], int n);che analizzi la stringa data estraendo da essa i primi n valori numerici interi e li inserisca nel vettore vett[]. Se vi sono più di n valori, i successivi vengano ignorati. La funzione restituisca il numero di elementi letti (che possono quindi essere meno di n). Si supponga che il file non contenga caratteri diversi da cifre e whitespace. Si scriva un main di test.

66

tvett[cont]

EserciziSoluzione a stati

ws=white spacet=stringa per contenere i caratteri da trasf. in numerocont=contatore valoric=carattere i-esimo della stringa

tvett[cont]

cont++ct[0]

no-op

ct[i]

FUORI DENTRO

!ws !ws

ws ws

67

Esercizi12. Si scriva un programma che permetta di

calcolare il prodotto di 2 numeri interi senza segno introdotti dall’utente e composti ciascuno da un massimo di 1000 cifre. Conviene modularizzare il codice utilizzando opportune funzioni di supporto (ad esempio per moltiplicare un numero per una cifra, per fare lo shift di n posizioni a sinistra di un numero e per sommare due numeri).

68

Progetti multi-file E’ possibile suddividere le funzioni che

costituiscono un eseguibile su più file (detti translation unit )

Ciascun file viene compilato separatamente e il linker li assembla per costituire un unico file eseguibile

Uno solo dei file deve contenere la definizione della funzione principale main

L’insieme dei file sorgenti viene spesso chiamato progetto

In ciascun file si collocano funzioni che insieme forniscono una certa funzionalità (ad es. la gestione di uno stack)

Page 18: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

69

Progetti multi-file Ciascuno dei file si comporta come una

libreria di funzioni, salvo che queste vengono compilate (e non solo linkate) con il programmma principale

Un file con funzioni specifiche per fornire una determinata funzionalità può essere facilmente riutilizzato in altri programmi: basta includerlo nel progetto

70

Progetti multi-file Ciascun file ha bisogno delle sole direttive #include e #define necessarie al codice di quel file

Per usare in un file una funzione dichiarata in un altro file (non può essere static), si deve indicarne il prototipo (extern opzionale)

Spesso si raggruppano tutte le #define, le variabili esterne e i prototipi di tutte le funzioni (non può essere static) di un progetto in un unico file .h e i file .c del progetto che ne abbisognano lo includono (con virgolette): #include "mioheader.h"

71

Progetti multi-file Le variabili esterne usate in tutti i file devono

essere definite solo in uno dei file, mentre gli altri devono avere la dichiarazione externcorrispondente (vedere le slide sulla compilazione condizionale)

Non si può usare extern con variabili esterne con specificatore di classe di allocazione static in un altro file (sono utilizzabili solo dalle funzioni di quel file)

72

Progetti multi-file – extern La clausola extern permette di usare una

variabile esterna anche in altri file, purché faccia parte dello stesso progetto

È compito del linker: controllare che le varie dichiarazioni siano conformi

alla definizione controllare che la definizione sia unica associare definizione e dichiarazioni alla stessa

zona di memoria

Page 19: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

73

Progetti multi-file – static Le variabili esterne con specificatore di classe

di memorizzazione static hanno scope limitato alle funzioni definite in quel file (solo a quelle successive alla definizione): sono “private” ad uso esclusivo delle funzioni di quel filestatic int funzione(int x)

La keyword static si riferisce alla funzione, non al valore restituito

74

Linkage di variabili e funzioni Il linkage esprime la corrispondenza di

identificatori (variabili o costanti) omonimi presenti in più blocchi e/o in più translation unit diverse (linkate insieme) e/o in librerie

Un identificatore con linkage esterno è visibile in più translation unit (es. variabili e funzioni esterne non static)

Un identificatore con linkage interno è visibile solo nella translation unit dove è definito (es. variabili e funzioni static)

Un identificatore non ha linkage se è locale al blocco dove è definito (es. variabili locali, parametri di funzioni, tag, membri, etc.)

75

Linkage di variabili e funzioni Un identificatore con linkage esterno è

significativo almeno nei primi 6 caratteri, maiuscole e minuscole rappresentano caratteri uguali

Un identificatore con linkage interno è significativo almeno nei primi 31 caratteri, maiuscole e minuscole rappresentano caratteri diversi

Il linkage di un identificatore preceduto dalla clausola extern è quello stabilito dalla sua precedente dichiarazione nella stessa translation unit (ad es. una dichiarazione con extern relativa ad una variabile definita static più sopra nel file ha linkage interno)

76

Lo stack Uno stack (o pila) è una struttura dati di tipo

LIFO (Last In First Out) in cui i valori vengono prelevati nell’ordine inverso a quello di introduzione

Introduzione: push Prelievo: pop

ABA

CBA

BA A

Page 20: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

77

Esercizi13. Scrivere in un file separato la realizzazione di

uno stack basato su un vettore di interi. Si realizzino le funzioni push e pop (con opportuni parametri ) che restituiscano 1 in caso di errore (stack pieno o vuoto) e 0 altrimenti (non devono fare I/O). Si scriva un main a menu in grado di verificarne il funzionamento. I prototipi siano in un file .h.Il vettore deve essere static perché solo push e pop ne abbiano accesso. Si utilizzi un puntatore p che punti alla prima locazione libera utilizzando le seguenti espressioni:per push: *p++ = val;per pop: val = *--p;

78

La coda Una coda è una struttura dati di tipo FIFO

(First In First Out) in cui i valori vengono prelevati nello stesso ordine di introduzione

Introduzione: enqueue Prelievo: dequeue

A B A C B A

C B C

coda testa

79

Esercizi14. Scrivere in un file separato la realizzazione di

una coda basata su un vettore di interi. Si scrivano le funzioni enqueue e dequeue (con opportuni parametri ) che restituiscano 1 in caso di errore (coda piena o vuota) e 0 altrimenti (non devono fare I/O). Si scriva un main a menu in grado di verificarne il funzionamento. I prototipi siano in un file .h. Si utilizzino due puntatori: testa punta alla cella con il prossimo valore da prelevare, coda punta alla prossima cella da riempire. Il vettore sia static.Si può notare che la enqueue è identica alla push.

80

Il buffer circolare (coda) Testa e coda non sono più fisse, ma si

rincorrono E’ necessario un contatore delle posizioni

libere per discriminare la condizione “pieno” da “vuoto” in cui testa e coda coincidono

5 4 3 2 1 0

testacoda

C B A

1

2

3

4

5

0

A

BC

testa

coda

Page 21: Struttura modulare - staff.polito.itstaff.polito.it/claudio.fornaro/CorsoC/14-Funzioni.pdf · quello che aveva al termine della chiamata precedente) Le variabili statiche non richiedono

81

Il buffer circolare (coda) Aggiunta (in coda)

if ( non pieno ) aggiungi in codafa avanzare codaposti liberi – 1

Prelievo (dalla testa)if ( non vuoto )

preleva valorefa avanzare la testaposti liberi +1

82

Esercizio15. Scrivere in un file separato la realizzazione di

una coda come buffer circolare basato su un vettore di interi. Si scrivano le funzioni enqueue e dequeue con identico tipo di quelle dell’esercizio precedente così da utilizzare lo stesso identico main.Si noti che è necessario utilizzare un contatore delle posizioni libere (o occupate) per verificare se è possibile inserire/togliere un valore, non c’è modo di stabilirlo confrontando semplicemente i puntatori testae coda.