La dichiarazione di una funzione (prototipo) ha la sintassi:
tipo nome (argomenti);
Dove:
Esempio:
char MiaFunz(int dato, float valore);
char MiaFunz(int, float);
Se in un file di codice sorgente una funzione é chiamata prima di essere definita, bisogna dichiararla prima di chiamarla.
Nello sviluppo di librerie di funzioni è molto utile l’uso delle dichiarazioni di funzioni
Gli argomenti di una funzione (o procedura) possono essere:
Problema: Calcolare la somma di due frazioni n1/d1, n2/d2 e ridurla ai minimi termini.
Primo raffinamento:
leggi(n1,d1,n2,d2)
calcola il numeratore, num, ed il denominatore, den, della somma
riduci num e den ai minimi termini
stampa(num e den)
I quattro numeri in input non possono essere quattro interi qualsiasi perché un denominatore non può mai essere zero.
Quindi precondizioni: d1≠0 e d2≠0
Per ridurre num e den ai minimi termini dobbiamo prima trovare il massimo comun divisore k, e successivamente effettuare le operazioni num←num/k, den←den/k.
Nella procedura leggi i quattro parametri della procedura sono tutti di output.
Nella procedura calcolasomma gli stessi sono invece parametri di input mentre num e den sono parametri di output.
Nella procedura riduci num e den sono parametri di input-output.
Nella procedura stampa sono parametri di input.
Nella funzione mcd le variabili a,b si riferiscono al loro valore (right value
), mentre nella funzione riduci le variabili num e den al loro indirizzo (left value
).
Interfacciamento: l’insieme di operazioni che occorre fare per rendere possibili le due fasi dell’invocazione di funzione
1. chiamata della funzione: passaggio di parametri dalla funzione chiamante alla funzione chiamata (come suoi argomenti)
controllo dalla funzione chiamante alla funzione chiamata
2. ritorno della funzione: passaggio di valore di ritorno della funzione chiamata alla funzione chiamante
controllo dalla funzione chiamata (al suo termine) alla funzione chiamante
Record di Attivazione
La memoria effettivamente usata dal programma a runtime è organizzata come una pila o stack.
Inizialmente una certa area di memoria è riservata al main.
Una chiamata ad una function corrisponde alla allocazione di una opportuna area di memoria detta record di attivazione della function.
Nel record di attivazione è assegnato uno spazio opportuno, dipendente dal tipo, ad ognuno dei parametri della function e ad ogni variabile locale (cioè definita all’interno della function).
Il C++ usa una struttura di memoria detta stack per gestire l’interfacciamento tra funzione chiamante e funzione chiamata
Lo stack è formato da una pila di aree di memoria detti record di attivazione, uno per ogni chiamata di funzione, contenente:
Quando il controllo deve tornare dalla funzione chiamata alla chiamante, il programma fa riferimento all’ultimo record dello stack per:
conoscere l’indirizzo di rientro nella funzione chiamante
eseguita tale operazione, rimuove lo stesso record dallo stack (cancellando di conseguenza anche le variabili automatiche)
passaggio dei parametri per valore: come argomento viene assegnato una copia del valore del parametro
passaggio dei parametri per riferimento (per indirizzo): come parametro della funzione viene passato l’indirizzo di una variabile
Suggerimento:
Regola
E’ errato dichiarare due parametri dello stesso tipo nella forma double x, y anziché nella forma double x, double y. Si commette un errore di sintassi. Ogni parametro deve essere specificato con il tipo di dato a cui appartiene.
riduci(int &n,int &d)
Viene segnalato un errore in corrispondenza della chiamata:
riduci(num+1,den);
Infatti num+1 è un’espressione cui dovrebbe corrispondere nella intestazione della function un parametro per passaggio di valore mentre il primo argomento di riduci è un riferimento.
Una soluzione può essere:
num++;
riduci(num,den);
E’ sempre possibile trasformare una funzione in una procedura (e viceversa):
Esempio:
int calcola(int p,... ,float q,int &r,...,float &s)
void calcola(int p,... ,float q,int &r,...,float &s,int &t)
Suggerimento:
Scrivere una funzione che ritorna un valore solo se tutti suoi parametri devono essere passati per valore. In tutti gli altri casi scrivere una procedura.
leggi:
precondizioni: le quattro variabili devono essere di tipo intero;
postcondizioni: le quattro variabili hanno ricevuto i valori che l’utente ha inserito e d1 e
d2 devono essere dei numeri diversi da zero.
calcolasomma:
precondizione: n1,d1,n2,d2 devono avere i valori inseriti dall’utente;
postcondizione: num e den devono rappresentare il numeratore ed il denominatore della somma.
riduci:
precondizione: num e den rappresentano numeratore e denominatore di una frazione;
postcondizione: num e den rappresentano il numeratore ed il denominatore della stessa
frazione, ma ridotta ai minimi termini.
stampa:
precondizione: num e den sono i valori della frazione ridotta ai minimi termini;
postcondizione: che questi siano stampati a video.
Per le prime tre procedure dopo la loro chiamata lo stato del sistema cambia mentre per l’ultima l’insieme delle variabili del sistema non subisce alcun cambiamento.
3. Verifica e raffinamento: ogni macroazione va raffinata
3. Verifica e raffinamento: ogni macroazione va raffinata
3. Verifica e raffinamento: grazie al metodo dei raffinamenti successivi (e dell’astrazione procedurale) possiamo
l’astrazione procedurale consente di evitare inutili duplicazioni di codice (semplicità e leggibilità del codice)
l’astrazione procedurale consente di separare il programma in moduli indipendenti (modularità) fornendo la possibilità
l’astrazione procedurale consente di separare il programma in moduli indipendenti (modularità) fornendo la possibilità
Problema
Assegnata una quantità di centesimi di euro, suddividerla nel numero minimo di monete da 50, 20, 10, 5, 1
1. Analisi del problema:
Chiamiamo X la quantità in centesimi da convertire (input utente)
M1=50, M2=20, M3=10, M4=5, M5=1
npezzi2 = X / M2, resto = X % M2
2. Individuare un algoritmo (G), ossia una successione finita di azioni che, in prima approssimazione, risolve il problema
Costruiamo una procedura che accetti in ingresso i centesimi e il valore della moneta e ci restituisca in uscita sia il numero di pezzi di tale tipo di moneta che il numero di centesimi rimasti (dato in input 74 centesimi e 20 come valore della moneta, ci fornisca in uscita 3 ed il numero di centesimi rimasti 14).
Parametri?
4. Implementazione: scriviamo il programma principale e i prototipi delle funzioni
Mostra codice4. Implementazione: scriviamo il codice per il corpo delle funzioni di cui abbiamo già il prototipo
Mostra codiceOsservazioni:
Calcolapezzi potrebbe essere una funzione ma la presenza di centesimi che è un parametro di input-output impone di scrivere una procedura.
Abbiamo visto un altro uso molto comune dei sottoprogrammi. Codificando direttamente l’algoritmo iniziale avremmo dovuto scrivere più volte del codice sostanzialmente simile. L’utilizzo della procedura CalcolaPezzi ce lo ha evitato.
Si sarebbero potuto anche fondere in una unica procedura CalcolaPezzi e Stampa, la quale, dopo aver calcolato Npezzi, provvedesse anche a stamparlo. Tuttavia così come è scritta CalcolaPezzi risulta più facilmente riutilizzabile in un altro programma.
Non abbiamo ancora scritto il corpo delle due function. Ma questo programma incompleto può essere compilato. Inoltre introducendo nel corpo delle function un opportuno messaggio di stampa si può verificare se il flusso del programma è quello voluto. Ad esempio si potrebbe inserire in stampa:
cout<<''sono in stampa''
ed in CalcolaPezzi:
cout<<Tpezzo
4. Implementazione: scriviamo il codice per il corpo delle funzioni di cui abbiamo già il prototipo.
Se gli argomenti di una funzione sono nomi di array il passaggio di parametri avviene sempre per riferimento
Non occorre anteporre l’operatore & nella dichiarazione degli argomenti
Nella definizione di funzione la variabile (identificatore) dell’array è preceduta dal tipo e seguita dalla coppia di parentesi quadre
Esempio:
void sommavet(const int A[], const int B[], int k, int C[]) {
for (int i=0; i < k; i++)
C[i] = A[i] + B[i];
}
non serve specificare la dimensione perché é già stata dichiarata nel programma chiamante
se l’array é multidimensionale l’unico indice che si può omettere é quello a sinistra
Lo specificatore const serve a indicare che il parametro è solo in lettura (ogni tentativo di modica nel corpo della funzione è un errore del compilatore)
Nella dichiarazione di funzione (o prototipo) ogni argomento è dichiarato solo con il tipo affiancato dalle parentesi quadre
Esempio:
void sommavet(const int[], const int[], int, int[]);
Per trasmettere un intero array a una funzione bisogna inserire nella chiamata il nome dell’array (senza parentesi quadre):
Esempio:
int vet1[100], vet2[100], vet3[100]
sommavet(vet1, vet2, 100, vet3)
Nel corpo della funzione tutte le modifiche fatte ai singoli elementi dell’array vengono riprodotte sull’array del programma chiamante
Nel caso di passaggio di parametri come singoli elementi di un array non ci sono eccezioni alla regola generale.
Esempio:
1. Prime nozioni di Programmazione
2. C++ elementi di un programma
3. Le istruzioni di I/O standard
5. C++ funzioni matematiche ed espressioni booleane
6. Le strutture di controllo - parte seconda
8. Array di caratteri e tipi astratti
9. Astrazione procedurale: Procedure e Funzioni
10. Astrazione procedurale: Procedure e Funzioni -parte seconda
11. Astrazione procedurale: Procedure e Funzioni - parte terza
12. Librerie
13. Le strutture di controllo - parte terza
14. Algoritmi
16. I File di testo
17. La classe string