07 1 funzioni

88
1 Fondamenti di informatica 1 Sottoprogrammi (Funzioni)

description

 

Transcript of 07 1 funzioni

Page 1: 07 1 funzioni

1

Fondamenti di informatica 1Sottoprogrammi (Funzioni)

Page 2: 07 1 funzioni

Problema

• Abbiamo visto sequenze di istruzioni che risolvono particolari “sotto-problemi”: controllo della primalità di un numero, confronto di stringhe, etc.

• È possibile riutilizzare queste sequenze?• È possibile consentire ad altri di riutilizzare queste

sequenze?• È possibile dare a queste sequenze un nome che

ne indichi la funzionalità?• È possibile “svincolare” lo sviluppo di soluzioni a

sotto-problemi di questo tipo dallo sviluppo di una soluzione “complessa”?

Page 3: 07 1 funzioni

Se fosse possibile…

• …fare queste cose, i vantaggi sarebbero molti:

• Potendo riutilizzare facilmente le sequenze, aumenterebbe la produttività (potremmo scrivere più codice in meno tempo)

• Potremmo consentire ad altri di aumentare la loro produttività

• Potremmo rendere il codice più leggibile• Potremmo “spezzare” la risoluzione di un

problema in sotto-problemi

Page 4: 07 1 funzioni

4

Le funzioni

• I principali linguaggi di programmazione offrono, in risposta a queste esigenze, lo strumento delle procedure o funzioni

• In C e C++, una funzione è una sequenza di comandi che:– ha un nome– può essere invocata (cioè può esserne richiesta l’esecuzione)– può ricevere dei valori di parametri che ne influenzano

l’esecuzione– può produrre un valore risultato

Page 5: 07 1 funzioni

Motivazioni• Modularità nello sviluppo del codice

– Affrontare il problema per raffinamenti successivi• Riusabilità

– Scrivere una sola volta il codice e usarlo più volte– Esempi:

• un algoritmo di ordinamento• Le operazioni del calcolo matriciale• La ricerca di una parola in un testo

• Astrazione– Esprimere in modo sintetico operazioni complesse– Definire operazioni specifiche dei tipi di dato definiti dal programmatore – Esempi:

• Calcolo “totale + iva” di un ordine• Lettura scrittura transazioni di vendita dei libri• Operazioni con entità quali punti, segmenti, poligoni, numeri complessi

Page 6: 07 1 funzioni

Esempio: gestione del dialogo con l'utente

int main() {

char scelta;

int valore, risultato;

while (true) { // menu, loop infinito

cout << "Premere A per inserire un num tra 0 e 10 e calcolarne il cubo" << endl;

cout << "Premere B per inserire un num tra 11 e 20 e calcolarne il quadrato" << endl;

cout << "Premere C per inserire un num tra 21 e 30 e calcolarne il doppio" << endl;

cout << "Premere Q per uscire" << endl;

cin >> scelta;

switch (scelta) {

case 'a': case 'A':

cout << "Inserisci valore" << endl;

cin >> valore;

risultato = valore * valore * valore;

cout << "risultato = " << risultato << endl;

break;

6

Page 7: 07 1 funzioni

… continua case 'b': case 'B':

cout << "Inserisci valore" << endl;

cin >> valore;

risultato = valore * valore;

cout << "risultato = " << risultato << endl;

break;

case 'c': case 'C':

cout << "Inserisci valore" << endl;

cin >> valore;

risultato = valore * 2;

cout << "risultato = " << risultato << endl;

break;

case 'q': case 'Q':

cout << "GRAZIE E ARRIVEDERCI";

return 0; // termina il ciclo infinito

}

}

}

7

Page 8: 07 1 funzioni

Fattorizzazione del dialogochar menu() {

char ch;

cout << "Premere A per inserire un num tra 0 e 10 e calcolarne il cubo" << endl;

cout << "Premere B per inserire un num tra 11 e 20 e calcolarne il quadrato" << endl;

cout << "Premere C per inserire un num tra 21 e 30 e calcolarne il doppio" << endl;

cout << "Premere Q per uscire" << endl;

cin >> ch;

return ch;

}

int leggi() {

int v;

cout << "Inserisci valore" << endl;

cin >> v;

return v;

}

8

Page 9: 07 1 funzioni

Programma principaleint main() {

char scelta;

int valore, risultato;

while (true) { // menu, loop infinito

scelta = menu();

switch (scelta) {

case 'a': case 'A':

valore = leggi();

risultato = valore * valore * valore;

cout << "risultato = " << risultato << endl;

break;

case 'b': case 'B':

valore = leggi();

risultato = valore * valore;

cout << "risultato = " << risultato << endl;

break;

case 'c': case 'C':

valore = leggi();

risultato = valore * 2;

cout << "risultato = " << risultato << endl;

break;

case 'q': case 'Q':

cout << "GRAZIE E ARRIVEDERCI";

return 0;

}

}

}

9

Page 10: 07 1 funzioni

Osservazioni

• La funzione main() invoca le funzioni menu() e leggi()– main(): funzione chiamante– menu() e leggi(): funzioni chiamate

• La comunicazione chiamante-chiamato nel caso visto avviene tramite il valore di ritornochar menu() {.. return ch; }int main () {… .. scelta = menu();

• Il chiamante copia in una propria variabile locale il valore prodotto dall'invocazione della funzione

10

Page 11: 07 1 funzioni

Definizione delle funzioni<tipo restituito> <nome della funzione> (<lista parametri>) { }

•Tipo restituito: il tipo del valore di ritorno della funzione

– Non può essere un tipo array•Nome: un identificatore•Lista parametri: una lista di coppie <tipo-nomeParametro> separate da virgole

– Si possono usare 0 o più parametri•{ . . . }: corpo della funzione

Page 12: 07 1 funzioni

Definizione di una funzione

int leggi(int min, int max) {

int v;

bool ok = false;

while (!ok) {

cout << "Inserisci valore" << endl;

cin >> v;

if (v >= min && v <= max)

ok = true;

}

return v;

}

12

Page 13: 07 1 funzioni

Chiamata di una funzioneint main() {

char scelta;int valore, risultato;while (true) { // menu, loop infinito

scelta = menu();switch (scelta) {case 'a':case 'A':

valore = leggi(0,10);risultato = valore * valore * valore;cout << "risultato = " << risultato << endl;break;

•Leggi(0,10) è un'espressione di chiamata a funzione•0 e 10 sono gli argomenti della chiamata•Valore dell'espressione è il valore restituito dalla funzione•Il tipo dell'espressione è il tipo di ritorno delle funzione

13

Page 14: 07 1 funzioni

Esempio

• Definiamo il fattoriale come una funzioneint fatt(int val){ int ris = 1; while (val > 1) ris *= val--; //assegna ris * val a ris e decrementa val return ris; }

int main() {int numero;cout << "Inserire un numero positivo" << endl;cin >> numero;cout << "Il fattoriale di " << numero << " vale "

<< fatt(numero) << endl; return 0;}

Page 15: 07 1 funzioni

Sintassi dell’invocazione• L'invocazione di una funzione è un’espressione

– es: possiamo immaginare una funzione somma(a,b) equivalente ad a+b

• Quando, nell’esecuzione del codice, è necessario ‘valutare’ un’espressione-funzione, la funzione viene invocata

• La sintassi per la formulazione di una espressione-funzione è semplicemente:<nome funzione>(<lista argomenti>) – dove <lista argomenti> è una lista di espressioni di tipo

corrispondente ai parametri della definizione della funzione• Corrispondenza posizionale

– al primo parametro corrisponde il primo argomento, al secondo il secondo, e così via ...

Page 16: 07 1 funzioni

Semantica dell’invocazione

• Quando una funzione viene invocata:– Le espressioni corrispondenti agli argomenti vengono

valutate, e il valore reso disponibile ("passato") alla funzione

– I valori passati alla funzione sono utilizzati per inizializzare i parametri della funzione

– Il controllo passa da chiamante alla funzione chiamata,– Questa viene eseguita, fino a quando non viene incontrato

un comando return (o il termine del blocco principale })– Il “valore” dell’espressione-funzione corrispondente alla

chiamata è il valore dell'espressione che appare nell'istruzione return che ha causato la terminazione

• NB: oppure nessun valore se la funzione ha void come tipo di ritorno

Page 17: 07 1 funzioni

Comunicazione chiamante-chiamato

• L'uso del valore di ritorno non è sufficiente• Può essere necessario:

– Che il chiamante comunichi dei valori al chiamato– Che il chiamato restituisca più di un valore al

chiamante– Che il chiamato modifichi direttamente un valore

del chiamante

• Ad esempio, la funzione leggi() deve ricevere dal chiamante i limiti inferiore e superiore del valore da chiedere all'utente

17

Page 18: 07 1 funzioni

Modalità di passaggio degli argomenti

• Chiamante e chiamato devono comunicare• Ci sono diverse esigenze a cui corrispondono diversi

meccanismi di comunicazione– Il chiamante passa informazione al chiamato

• Il chiamante passa argomenti al chiamato• Il chiamato opera su una variabile globale visibile al chiamante

– Il chiamante riceve informazione dal chiamato• Il chiamato restituisce un valore• Il chiamato modifica una variabile del chiamante• Il chiamato opera su una variabile globale visibile al chiamante

• Ulteriore esigenza: – Evitare di copiare argomenti di grandi dimensioni nei

parametri del chiamato, se non necessario

18

Page 19: 07 1 funzioni

Argomenti e parametri

• Gli argomenti fungono da inizializzatori dei parametri di una funzione

• Notazione posizionale: – Argomento1 parametro1, argomento2

parametro2– Tutti gli argomenti richiesti devono essere forniti– Il tipo di un argomento deve corrispondere a quello

del parametro, secondo le stesse regole di inizializzazione delle variabili

• Nomenclatura alternativa: – Parametri Parametri formali– Argomenti Parametri attuali

19

Page 20: 07 1 funzioni

Passaggio per valore

• Call by value

• Funziona come nell'inizializzazione di una variabile– Il valore dell'argomento (inizializer) viene

copiato nel parametro– Modifiche del parametro NON cambiano

l'argomentovoid callByValue(int i) {

i = i * 2; // NON modifica l'argomento passato

}

20

Page 21: 07 1 funzioni

Passaggio per riferimento

• Call by reference• I parametri sono di ripo reference (&)

– Il valore dell'argomento (inizializer) è un sinonimo del parametro

– Modifiche del parametro CAMBIANO l'argomento del chiamante

– L'argomento deve essere una variabile, non una costante

void callByRef(int &i) { i = i * 2; // modifica l'argomento passato del chiamante}

21

Page 22: 07 1 funzioni

Passaggio per riferimento costante

• Call by const reference• I parametri sono di ripo const reference (const &)

– Il valore dell'argomento (inizializer) è un sinonimo del parametro

– Il costruttore const IMPEDISCE modifiche del parametro, come nel passaggio per copia

– Come nel passaggio per riferimento si evita di copiare il valore del parametro

– L'argomento deve essere una variabile, non una costante

void callByConstRef(const int &i) { i = i * 2; // errore: non si può modifica l'argomento}

22

Page 23: 07 1 funzioni

Esempiovoid callByValue(int i) {

i = i * 2;}

void callByRef(int &i) {i = i * 2;

}

int main() { int j=1; callByValue(j); cout << "After call by value j's value is " << j << endl; callByRef(j); cout << "After call by reference j's value is " << j << endl;return 0;}

23

Page 24: 07 1 funzioni

Altro esempiovoid swapByValue(int p, int q) {

int temp;

temp = p;

p = q;

q = temp;

}

void swapByRef(int &p, int &q) {

int temp;

temp = p;

p = q;

q = temp;

}

int main() {

int a = 1, b = 2;

cout << "Valori iniziali: " << a << ' ' << b << endl;

swapByValue(a, b);

cout << "Valori dopo swapByValue: " << a << ' ' << b << endl;

swapByRef(a, b);

cout << "Valori dopo swapByRef: " << a << ' ' << b << endl;

return 0;

}

24

Page 25: 07 1 funzioni

25

• Esistono sottoprogrammi che restituiscono valori (funzioni) e sottoprogrammi che non lo fanno (procedure)

• In C++ e C è possibile scrivere un sottoprogramma che non restituisce un valore, ma produce solo un effetto sullo stato del programma

• Tale fatto è segnalato dal tipo di ritorno void void error_line (int line) {

cout << "Errore alla linea" << line << endl;}

• Il sottoprogramma viene chiamato come segue:error_line (line_number);

• La terminazione della funzione e il ritorno del controllo al chiamante avvengono a fine blocco }

Funzioni vs procedure

Page 26: 07 1 funzioni

Intercambiabilità di procedure e funzioni

• Funzione:int f (int par1) {

/* calcola valore di variabile “risultato” */

return (risultato);

}

• chiamata:y = f(x);

• Procedura:void f (int par1, int & par2)

{ /* calcola valore di variabile “risultato” */ par2 = risultato;}

• chiamata:f (x, y);

• y del chiamante riceve il valore di par2 del chiamato

Page 27: 07 1 funzioni

27

Raccomandazioni di stile• Per le funzioni:

– passare i parametri per copia

– usare passaggio per riferimento per parametri di grandi dimensione, per evitare la copia

– evitare l'abuso di variabili non locali, che rendono difficile la comprensione dei rapporti tra chiamante e chiamato

• Una funzione che abbia parametri passatiper reference e ne modifichi il valore, si dice avere un effetto collaterale (side effect)

• Benché ammissibili, gli effetti collaterali si devono usare con prudenza, e in generale sono sconsigliabili quando non strettamente necessari

Page 28: 07 1 funzioni

28

Modi dipassaggio

Caratteristiche

 per valore

("per copia")  per riferimento

Tempo e spazionecessari a trasferire (copiare) i dati

grandi(per parametri di grandi dimensioni)

piccoli(si copia un indirizzo, la cui dimensione non dipende dai dati)

C'è rischio di effetti collaterali indesiderati?

No(i parametri attuale e formale sono distinti)

Sì(i parametri attuale e formale "di fatto" coincidono)

Permette la restituzione di valori al chiamante?

No Sì

Parametri: pro e contro

 per riferimento costante

piccoli(si evita la copia, si definisce un sinonimo dell'argomento)

Sì(i parametri attuale e formale sono sinonimi)

Page 29: 07 1 funzioni

Esempio

• Funzione per ricercare un carattere in una stringa e restituire la prima posizione in cui appare e il numero di occorrenze

• Progettazione della comunicazione– Input:

• La stringa: per riferimento costante• Il carattere da cercare: per copia

– Output :• La posizione: valore restituito (o parametro per

riferimento)• Numero di occorrenze: parametro per riferimento

29

Page 30: 07 1 funzioni

Funzionestring::size_type find_char(const string &s, char c, string::size_type &occurs) {

auto ret = s.size(); // position of the first occurrence, if any

occurs = 0; // set the occurrence count parameter

for (decltype(ret) i = 0; i != s.size(); ++i) {

if (s[i] == c) {

if (ret == s.size())

ret = i; // remember first occurrence of c

++occurs; // increment the occurrence count

}

}

return ret; // count is returned implicitly in occurs

}

30

Page 31: 07 1 funzioni

Main#include <iostream>

#include <string>

using namespace std;

int main() {

string someString;

string::size_type count;

cout << "Insert the string to search" << endl;

getline(cin, someString);

char c;

cout << "Insert the char to search" << endl;

cin >> c;

auto index = find_char(someString, c, count);

if (index < someString.size()) {

cout << "Character " << c << " first occurs at pos: " << index << endl;

cout << "Number of occurrences is: " << count << endl;

} else

cout << "Character not found" << endl;

return 0;

} 31

Page 33: 07 1 funzioni

Visibilità e durata• Tutte le variabili di un programma hanno una visibilità (scope)

e una durata (lifetime)– Scope: porzione del programma in cui il nome è visibile– Lifetime: tempo durante l'esecuzione in cui la variabile esiste

• Il corpo della funzione definisce un ambito di visibilità (scope) per cui– Le variabili dichiarate nella funzione e i parametri sono locali alla

funzione– Variabili locali e parametri della stessa funzione devono avere

nomi diversi– Variabili locali e parametri di funzioni diverse possono avere

nomi coincidenti– I nomi locali mascherano eventuali nomi uguali definiti in scope

più esterni (nomi di variabili globali)

33

Page 34: 07 1 funzioni

Il blocco

• E' un delimitatore esplicito sia di scope sia di lifetime

• Può comparire ovunque la sintassi preveda un'istruzione

• Si compone di due parti racchiuse tra { }– una sequenza di istruzioni– può anche contenere dichiarazioni

• Due blocchi possono essere:– annidati (uno è interno all’altro) – paralleli (entrambi interni a un terzo blocco, ma

non annidati tra loro)

Page 35: 07 1 funzioni

int g1, g2;char g3;int f1(int par1, int par2); // Prototipo di f1 int f2(int par3, int par1); // Prototipo di f2

main () { int a, b; { char a, c; ... {float a;

... } // Fine blocco2 } // Fine blocco1 } // Fine main

int f1(int par1, int par2) { intd; { int e;

... } // Fine blocco3 { int d;

... } // Fine blocco4} // Fine f1

int f2(int par3, int par4) {int f;

...} // Fine f2

modello “a contorni”

par1, par2,

par3, par4,

Page 36: 07 1 funzioni

Mascheramento della visibilità

• La dichiarazione di un elemento in una funzione o in un blocco maschera le eventuali entità omonime più “esterne”

livello globale

main

f1

g1,g2,g3

a,b

a,c

a,d

d

blocco1

blocco2

blocco3

È possibile (ma sconsigliato) riusare nomi di variabili

Page 37: 07 1 funzioni

Denominare le variabili locali alle funzioni

#include <iostream>

using namespace std;

double potenza (double base, int esp) {

double ris = 1.0;

for (int i = 0; i < esp; ++i)

ris = ris * base;

return ris;

}

int main() {

float base;

cout << "Inserire un numero reale come base" << endl;

cin >> base;

int esp;

cout << "Inserire un numero reale come base" << endl;

cin >> esp;

cout << endl << base << " elevato a " << esp << " vale " << potenza(base, esp) << endl;

return 0;

}

Le variabili locali e i parametri di funzioni diverse appartengono a "scope" distinti.Per cui possono anche avere lo stesso nome, senza pericolo di confusione. Meglio però evitare tale pratica

Page 38: 07 1 funzioni

Durata delle variabili (lifetime)

• Va dalla creazione (allocazione della memoria) alla distruzione (rilascio della memoria allocata)

• Due classi di variabili– Statiche:

• Allocate una volta per tutte• Distrutte solo al termine dell’esecuzione del programma• Sono tutte le variabili globali e quelle locali al main()

– Anche le variabili di funzione o blocco dichiarabili static

• Possono fungere da ulteriore canale di comunicazione tra funzioni

– Automatiche:• Sono quelle dichiarate nelle funzioni (inclusi i parametri) e nei blocchi• Sono create quando il flusso di esecuzione "entra" nel loro ambito di visibilità• Sono distrutte all’uscita da tale ambito• Sono allocate ad ogni esecusione in celle di memoria differenti• Non conservano i valori prodotti da precedenti esecuzioni della funzione o

del blocco

Page 39: 07 1 funzioni

Oggetti locali statici• Esigenza: memorizzare informazione locale a una funzione ( o a un

blocco) che permane per tutta l'esecuzione del programma (e non solo per una singola chiamata)

• Meccanismo: variabili locali static

int conta_chiamate(){ static int ctr = 0; // persiste dopo la chiamata return ++ctr;}int main(){ for (int i = 0; i != 10; ++i) cout << conta_chiamate() << endl; return 0;}

• Stampa numeri da 1 a 10 incluso

39

Page 40: 07 1 funzioni

40

• In seguito a una chiamata a sottoprogramma,il programma in corso viene sospeso e il controllo passa al sottoprogramma

• Al livello dell'ambiente di esecuzione: – Salvataggio dell'indirizzo della prossima istruzione da

eseguire (program counter, PC) e del contesto(valori di variabili) del programma chiamante

– Assegnazione al PC dell’indirizzo del sottoprogramma– Esecuzione del sottoprogramma– Ritorno al programma chiamante con ripristino

del suo contesto

Modello di esecuzione

Page 41: 07 1 funzioni

41

Record di attivazione

• Ogni sottoprogramma (incluso il main) ha associato un record di attivazione. Contiene:– tutti i dati relativi all’ambiente locale del

sottoprogramma– l’indirizzo di ritorno nel programma chiamante (serve

per poter eseguire l'istruzione del chiamante immediatamente successiva alla invocazione del sotto-programma)

• Per ogni attivazione di sottoprogramma si crea un nuovo record di attivazione

Page 42: 07 1 funzioni

42

• In generale, una funzione può essere chiamata un numero imprecisato di volte

• Ogni chiamata a procedura richiede allocazione di spazio di memoria per le sue variabili locali– Il compilatore potrebbe “preparare” un ambiente per ogni

funzione definita? In generale no...

• Vi sono procedure che richiamano se stesse (vedremo: ricorsione)– Possono esistere più “istanze” di una funzione,

“addormentate” in attesa della terminazione di una “gemella” per riprendere l’esecuzione

• In questo ultimo caso il compilatore non può sapere quanto spazio allocare per le variabili del programma (nei vari ambienti)

Perché è necessario?

Page 43: 07 1 funzioni

43

• Una porzione della memoria di lavoro, chiamata stack (pila): modalità LIFO (Last in First Out) permette al sistema operativo di gestire i processi e di eseguire le chiamate a sottoprogramma

• Lo Stack Pointer (puntat. alla pila) è un registro che contiene l’indirizzo della parola di memoria da leggere nello stack

Stack pointer = 312312 312

311

310

303

...• Operazione di inserimento: - incremento SP - scrittura nella parola indirizzata da SP• Operazione di estrazione: - lettura da parola indirizzata da SP - decremento SP

La pila (stack) di sistema

SP

Page 44: 07 1 funzioni

44

main()

f1() f2()

Chiama f1

variabili globali

r.d.a. di main()

r.d.a. di f1(1°)

r.d.a. di f2(1°)

r.d.a. di f1(2°)

r.d.a. di f2(2°)

...

Chiama f2

Chiama f1

ambiente globale

Esempio

r.d.a. di f1(3°)

crescita verso l'alto

Page 45: 07 1 funzioni

Esempio di esecuzioneint potenza(int a,int b);int moltiplica(int x,int y);

int main(){ int i=2,j=3,k; k=moltiplica(i,j); k=potenza(i,j);}

int potenza(int a,int b){ int i, p=1; for(i=1;i<=b;i++) p=moltiplica(p,a); return p;

}

int moltiplica(int x,int y){ return x*y;}

i=2 j=3 k

Page 46: 07 1 funzioni

Stack

x=2 y=3

i=2 j=3 kint potenza(int a,int b);int moltiplica(int x,int y);

int main(){ int i=2,j=3,k; k=moltiplica(i,j); k=potenza(i,j);}

int potenza(int a,int b){ int i, p=1; for(i=1;i<=b;i++) p=moltiplica(p,a); return p;

}

int moltiplica(int x,int y){ return x*y;}

Page 47: 07 1 funzioni

47

Stack

i=2 j=3 k=6int potenza(int a,int b);int moltiplica(int x,int y);

int main(){ int i=2,j=3,k; k=moltiplica(i,j); k=potenza(i,j);}

int potenza(int a,int b){ int i, p=1; for(i=1;i<=b;i++) p=moltiplica(p,a); return p;

}

int moltiplica(int x,int y){ return x*y;}

Page 48: 07 1 funzioni

48

Stack

a=2 b=3 i p=1

i=2 j=3 k=6int potenza(int a,int b);int moltiplica(int x,int y);

int main(){ int i=2,j=3,k; k=moltiplica(i,j); k=potenza(i,j);}

int potenza(int a,int b){ int i, p=1; for(i=1;i<=b;i++) p=moltiplica(p,a); return p;

}

int moltiplica(int x,int y){ return x*y;}

Page 49: 07 1 funzioni

49

Stack

a=2 b=3 i=1 p=1

i=2 j=3 k=6int potenza(int a,int b);int moltiplica(int x,int y);

int main(){ int i=2,j=3,k; k=moltiplica(i,j); k=potenza(i,j);}

int potenza(int a,int b){ int i, p=1; for(i=1;i<=b;i++) p=moltiplica(p,a); return p;

}

int moltiplica(int x,int y){ return x*y;}

Page 50: 07 1 funzioni

50

Stack

a=2 b=3 i=1 p=1

i=2 j=3 k=6

x=1 y=2

int potenza(int a,int b);int moltiplica(int x,int y);

int main(){ int i=2,j=3,k; k=moltiplica(i,j); k=potenza(i,j);}

int potenza(int a,int b){ int i, p=1; for(i=1;i<=b;i++) p=moltiplica(p,a); return p;

}

int moltiplica(int x,int y){ return x*y;}

Page 51: 07 1 funzioni

51

Stack

a=2 b=3 i=1 p=2

i=2 j=3 k=6int potenza(int a,int b);int moltiplica(int x,int y);

int main(){ int i=2,j=3,k; k=moltiplica(i,j); k=potenza(i,j);}

int potenza(int a,int b){ int i, p=1; for(i=1;i<=b;i++) p=moltiplica(p,a); return p;

}

int moltiplica(int x,int y){ return x*y;}

Page 52: 07 1 funzioni

52

Stack

a=2 b=3 i=2 p=2

i=2 j=3 k=6int potenza(int a,int b);int moltiplica(int x,int y);

int main(){ int i=2,j=3,k; k=moltiplica(i,j); k=potenza(i,j);}

int potenza(int a,int b){ int i, p=1; for(i=1;i<=b;i++) p=moltiplica(p,a); return p;

}

int moltiplica(int x,int y){ return x*y;}

Page 53: 07 1 funzioni

53

Stack

a=2 b=3 i=2 p=2

i=2 j=3 k=6

x=2 y=2

int potenza(int a,int b);int moltiplica(int x,int y);

int main(){ int i=2,j=3,k; k=moltiplica(i,j); k=potenza(i,j);}

int potenza(int a,int b){ int i, p=1; for(i=1;i<=b;i++) p=moltiplica(p,a); return p;

}

int moltiplica(int x,int y){ return x*y;}

Page 54: 07 1 funzioni

54

Stack

a=2 b=3 i=2 p=4

i=2 j=3 k=6int potenza(int a,int b);int moltiplica(int x,int y);

int main(){ int i=2,j=3,k; k=moltiplica(i,j); k=potenza(i,j);}

int potenza(int a,int b){ int i, p=1; for(i=1;i<=b;i++) p=moltiplica(p,a); return p;

}

int moltiplica(int x,int y){ return x*y;}

Page 55: 07 1 funzioni

55

Stack

a=2 b=3 i=3 p=4

i=2 j=3 k=6

x=4 y=2

int potenza(int a,int b);int moltiplica(int x,int y);

int main(){ int i=2,j=3,k; k=moltiplica(i,j); k=potenza(i,j);}

int potenza(int a,int b){ int i, p=1; for(i=1;i<=b;i++) p=moltiplica(p,a); return p;

}

int moltiplica(int x,int y){ return x*y;}

Page 56: 07 1 funzioni

56

Stack

a=2 b=3 i=3 p=8

i=2 j=3 k=6int potenza(int a,int b);int moltiplica(int x,int y);

int main(){ int i=2,j=3,k; k=moltiplica(i,j); k=potenza(i,j);}

int potenza(int a,int b){ int i, p=1; for(i=1;i<=b;i++) p=moltiplica(p,a); return p;

}

int moltiplica(int x,int y){ return x*y;}

Page 57: 07 1 funzioni

57

Stack

i=2 j=3 k=8int potenza(int a,int b);int moltiplica(int x,int y);

int main(){ int i=2,j=3,k; k=moltiplica(i,j); k=potenza(i,j);}

int potenza(int a,int b){ int i, p=1; for(i=1;i<=b;i++) p=moltiplica(p,a); return p;

}

int moltiplica(int x,int y){ return x*y;}

Page 58: 07 1 funzioni

Call stack in Eclipse

58

Activation record del main Stato delle variabili

Page 59: 07 1 funzioni

Chiamata di funzione

59

Call stack con funzione chiamataStato del chiamato, parametri passati per copia

Page 60: 07 1 funzioni

Passaggio per riferimento

60

Parametri passato per riferimento

Page 61: 07 1 funzioni

61

Struttura di un programma C++inclusione librerie / per poter richiamare funzioni utili (i/o, ...) /

dichiarazione di variabili e tipi globali e funzioni

int main ( ) {

dichiarazione di variabili locali

istruzione 1; / tutti i tipi di operazioni, e cioè: /

istruzione 2; / istr. di assegnamento /

istruzione 3; / istr. di input / output /

istruzione 4; / istr. di controllo (condizionali, cicli) /

/ dich. altre variabili locali /

istruzione N;

}

parte esecutiva

parte dichiarativa locale

parte dichiarativa globale

Page 62: 07 1 funzioni

Esempio mono-file#include <iostream>using namespace std;

double potenza (double base, int esp) {double ris = 1.0;

for (int i = 0; i < esp; ++i) ris = ris * base;

return ris;}

int main() {float numero;cout << "Inserire un numero reale come base" << endl;cin >> numero;int esponente;cout << "Inserire un numero reale come base" << endl;cin >> esponente;cout << endl << numero << " elevato a " << esponente

<< " vale " << potenza(numero, esponente) << endl;return 0;

}

Definizione di variabililocali alla funzione

definizionedi funzione

definizionedi funzione

chiamatadi funzione

Page 63: 07 1 funzioni

Esempio mono-file#include <iostream>using namespace std;

double potenza (double base, int esp);

int main() {float numero;cout << "Inserire un numero reale come base" << endl;cin >> numero;int esponente;cout << "Inserire un numero intero come exp" << endl;cin >> esponente;cout << endl << numero << " elevato a " << esponente

<< " vale " << potenza(numero, esponente) << endl;return 0;

}

double potenza (double base, int esp) {double ris = 1.0;

for (int i = 0; i < esp; ++i) ris = ris * base;

return ris;}

dichiarazionedi funzione

definizionedi funzione

Page 64: 07 1 funzioni

Un errore comune

64

Page 65: 07 1 funzioni

Segnatura (definizione) di una funzione

• Come per le variabili le funzioni – devono essere dichiarate e definite prima di essere usate– possono essere definite una volta e dichiarate più volte– Una funzione può essere dichiarata in un file di header (.h)

per poi venir inclusa da un altro programma

• La dichiarazione avviene specificando la segnatura (o interfaccia, prototipo) della funzione (function header), cioè:– tipo del risultato – identificatore del sottoprogramma– elenco delle dichiarazioni dei parametri formali

double potenza (double base, int esp);

Page 66: 07 1 funzioni

Esempio di programma multifile

/* File contenente funzioni di utilità sui cerchi

* dichiarazione e definizione di una costante

* riusabile : pigreco.cpp */

extern const float pigreco = 3.14159265;

float area(float r){

return r*r*pigreco;

}

float perimeter(float r){

return 2*r*pigreco;

}

Page 67: 07 1 funzioni

File di intestazione della libreria

/* File contenente solo le dichiarazioni delle

* funzioni di utilità sui cerchi

* e della costante riusabile: pigreco.h

* Usato dal compilatore per controllare la

* correttezza del programma */

extern const float pigreco;

float area(float r);

float perimeter(float r);

Page 68: 07 1 funzioni

Programma principale#include <iostream>

#include "pigreco.h"

using namespace std;

int main() {

float radius;

cout << "Insert the value of the radius: " << endl;

cin >> radius;

cout << "Value of pigreco: " << pigreco << endl;

cout << "Perimeter of circle: " << perimeter(radius) << endl;

cout << "Area of the circle: " << area(radius) << endl;

return 0;

}

Page 69: 07 1 funzioni

Esempio

• Ristrutturiamo (rifattorizziamo) il programma di evidenziazione del testo

• Spostiamo in una funzione:– Le lettura del file– La stampa del testo– La stampa degli indici di riga delle occorrenze– L'evidenziazione del testo

69

Page 70: 07 1 funzioni

Progettazione della comunicazione

70

Mainvector<string>

vector<int>

leggitesto stampatesto stamparighe evidenzia

vector<string>

vector<string> vector<int>

vector<string>vector<int>crea

modifica, crea

usausa

usastring

Page 71: 07 1 funzioni

Passaggio argomenti

• Per non copiare nel parametro del chiamato argomenti di grandi dimensioni (testo, righe) usiamo passaggio per riferimento

• Prototipi delle funzioni:void leggitesto(vector<string> &t);

void stampatesto(vector<string> &t);

void stamparighe(vector<int> &rr);

void evidenzia(vector<string> &t, string &parola, vector<int> &righe)

71

Page 72: 07 1 funzioni

Passaggio per riferimento costante

• Laddove– il chiamato non modifica l'argomento ricevuto dal

chiamante, ma– L'argomento è di dimensioni non trascurabili

• Meglio usare const & void stampatesto(const vector<string> &t);void stamparighe(const vector<int> &rr);void evidenzia(vector<string> &t, const string &parola,

vector<int> &righe)

• Le funzioni così non possono modificare l'argomento ricevuto dal chiamante

72

Page 73: 07 1 funzioni

Programma principale#include <iostream>#include <vector>#include <string>#include <fstream>using namespace std;

int main() { vector<string> testo; // il testo vettorializzato, inizialm. vuoto vector<int> righe; // l'indice delle righe, inizialmente vuoto string par; // la parola da cercare leggitesto(testo); // crea tutte le righe all'inizio cout << "Inserisci parola da ricercare" << endl; cin >> par; evidenzia(testo, par, righe); stampatesto(testo); stamparighe(righe);return 0;}

73

Page 74: 07 1 funzioni

74

Parametri array

• Gli array hanno una importante differenza rispetto agli altri tipi primitivi o costruiti: non è definita la copia né all'inizializzazione ne all'assegnamento

• Pertanto non si possono passare come argomenti per copia a una funzione

• Tuttavia si possono lo stesso definire parametri e argomenti per copia, ma bisogna capire che cosa succede

Page 75: 07 1 funzioni

Parametri di tipo array

void stampa(char[10]);•Apparentemente la funzione riceve in ingresso un array di 10 caratteri

•In realtà riceve l'indirizzo (puntatore) al primo elemento di un array di lunghezza ignota!

•E' equivalente a void stampa(char[]);

void stampa(char *); // vedremo in seguito

75

Page 76: 07 1 funzioni

Cautele

• Se il parametro denota l'indirizzo del primo elemento allora di solito NON va modificato

• Meglio allora renderlo constvoid stampa(const char[10]);

void stampa(const char[]);

void stampa(const char *);

76

Page 77: 07 1 funzioni

Passaggio di argomenti di tipo array

void stampaArray(const char regione[6]){cout << regione << endl;

}

int main() {char array1[10]="Lombardia";char array2[6]="Lazio";stampaArray(array1);stampaArray(array2);return 0;

}

•Le dimensioni del parametro vengono ignorate•Pericoloso: se manca il terminatore dell'array il comportamento è indefinito•Usato spesso comunque, perché apparentemente "flessibile"

77

Page 78: 07 1 funzioni

Typedef e parametri array

• Usare typdef non cambia

typedef char arraycorto[6];typedef char arraylungo[10];

void stampaArray(arraycorto regione) {cout << regione << endl;

}

int main() {arraylungo array1 = "Lombardia";arraycorto array2 = "Lazio";stampaArray(array1);stampaArray(array2);return 0;

}

78

Page 79: 07 1 funzioni

Passaggio esplicito delle dimensioni

• Per ovviare al pericolo di fuoriuscita, si può aggiungere un parametro esplicito che rappresenta le dimensioni– E' l'unica soluzione per array non terminati da '\0'

• Difetto: i due parametri sono indipendenti e il valore delle dimensioni deve essere gestito con cura dal programmatore– Se la dimensione passata è maggiore di quella

reale, il comportamento del chiamato è indefinito

79

Page 80: 07 1 funzioni

Stampare un array di intericonst int corto = 2;const int lungo = 4;

void stampaArray(const int array[], int dim) { for (int i = 0; i < dim; ++i) cout << array[i] << endl;}

int main() {int arraylungo[lungo]={1,2,3,4};int arraycorto[corto]={5,6};stampaArray(arraylungo, lungo);stampaArray(arraycorto, corto);return 0;

}

80

Page 81: 07 1 funzioni

Warning

• Non si stampa con cout << un array che non sia una stringa terminata da '\0'

const int lungo = 4;

int main() {

int arraylungo[lungo]={1,2,3,4};

cout << arraylungo; // errato !!

return 0;

}

81

Page 82: 07 1 funzioni

Modificabilità degli array

• Gli array non si possono passare per copia

• Il chiamato riceve l'indirizzo del primo elemento dell'array passato come argomento

• Pertanto il chiamato modifica direttamente i valori dell'array del chiamante

• Da considerare con cautela

82

Page 83: 07 1 funzioni

Modifica di un parametro arrayconst int size = 4;

void modificaInt(int i) {

i = i + 100;

}

void modificaArray(int vett[], int dim) {

for (int i = 0; i < dim; i++)

vett[i] += 100;

}

int main() {

int a = 0, v[size] = { 0, 1, 2, 3 };

cout << "Valori prima" << endl;

cout << "a = " << a << endl;

for (int i = 0; i < size; ++i)

cout << "v[" << i << "] = " << v[i] << endl;

cout << "Chiamo le funzioni..." << endl;

modificaInt(a);

modificaArray(v, 4);

cout << "Valori dopo" << endl;

cout << " a = " << a << endl;

for (int i = 0; i < size; ++i)

cout << "v[" << i << "] = " << v[i] << endl;

return 0;

}

83

Page 84: 07 1 funzioni

Passaggio di array multidimensionali

• Ricordando che una matrice è in realtà un array di arrayf(int [][maxcol], int r, int c);

• Dichiara una funzione che riceve in input una matrice, cioè maxcol array di interi

• Tutte le dimensioni successive alla prima fanno parte del tipo e devono essere fissate

84

Page 85: 07 1 funzioni

Esempiovoid stampaMatrice(int mat[][2], int r, int c) {

for (int i = 0; i < r; ++i) {for (int j = 0; j < c; ++j) // c è cmque fissato a 2

cout << mat[i][j] << " ";cout << '\n';

}}// il secondo indice DEVE valere 2int main() {

int matrice[2][2] = { 1, 2, 3, 4 };int matricebis[3][2] = {5,6,7,8,9,10};stampaMatrice(matrice, 2, 2);cout << endl;stampaMatrice(matricebis, 3, 2);return 0;

}

85

Page 86: 07 1 funzioni

Perché la prima dimensione è libera?

• Matrice = Array di array di interi

• Ogni valore dell'array, tranne quelli dell'array "più esterno" deve avere una dimensione fissa e nota– 3 celle nel caso presente

86

a[0,2] int a [][3]

a[0]

a[0,0] a[0,1] a[1,2]

a[1]

a[1,0] a[1,1] a[3,2]

a[3]

a[3,0] a[3,1]

Page 87: 07 1 funzioni

Forme sintattiche equivalenti

• Sono definite 3 forme sintattiche diverse per rappresentare una matrice e quindi anche un parametro di tipo matrice

• Si capirà meglio dopo aver studiato il costruttore di tipo puntatore (*)

• http://stackoverflow.com/questions/8767166/passing-2d-array-to-function

87

Page 88: 07 1 funzioni

88

Parametri di tipo array e struct• Un parametro di tipo struct si può passare sia

per riferimento sia per valore (anche se la struct contiene campi di tipo array!)

• Analogamente, una funzione può restituire una struct (anche se la struct contiene degli array)