User Tools

Site Tools


appunti3s:programmazione_procedurale

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
appunti3s:programmazione_procedurale [2018/04/25 07:55]
127.0.0.1 external edit
appunti3s:programmazione_procedurale [2020/06/08 22:19] (current)
Line 1: Line 1:
 +====== Programmazione procedurale ======
 +Fino a questo momento ogni problema è stato affrontato individuando prima un algoritmo risolutore e poi traducendolo con un programma.
 +{{ :appunti3s:cpp_procedura.png |immagine che descrive con una freccia il passaggio dal problema verso la sua soluzione tramite un programma}}
 +Quando il problema da risolvere è abbastanza grande e complicato, il programma diventa più lungo e difficile da scrivere. In questi casi conviene dividere il problema in problemi più piccoli e semplici da risolvere. Per ogni sottoproblema si ottiene un sottoprogramma (o funzione) che lo risolve. Il programma finale desiderato (che risolve il problema iniziale) si può ottenere rimettendo insieme i diversi sottoprogrammi ottenuti.
 +
 +{{ :appunti3s:cpp_procedure.png |immagine che descrive la scomposizione di un problema in problemi parziali, di cui si trovano soluzioni parziali, da riunire insieme}}
 +
 +Una volta che delle funzioni sono state scritte per risolvere una parte di un problema, queste possono anche essere riutilizzate per risolvere altri problemi simili. La maggior parte dei programmatori riutilizza infatti delle funzioni scritte da altri programmatori, senza preoccuparsi di scrivere nulla di nuovo, perché quello che serve spesso è già stato scritto da qualcun altro.
 +
 +===== Procedure e funzioni =====
 +I termini //procedura// e //funzione// sono quasi sinonimi, anche se vi sono piccole differenze di significato. Nel linguaggio C++ si usa esclusivamente il termine funzione quindi non si parla di procedure. Per capire cosa sia una funzione in un programma basta pensare ad una funzione matematica, dove c'è la variabile indipendente //x// e quella dipendente //y//. Quest'ultima variabile di solito è il risultato che si trova elaborando la prima variabile, come //y=f(x)//. Ad esempio, la funzione //y=x<sup>2</sup>// calcola il quadrato dei valori forniti tramite //x//.
 +
 +Nella programmazione procedurale, ad ogni funzione viene affidata l'esecuzione di un compito specifico e ogni volta che si deve svolgere quel compito si chiama in esecuzione quella funzione.
 +
 +Come accade nelle funzioni matematiche, anche le funzioni del programma possono contenere tra parentesi gli argomenti da utilizzare. Alcune funzioni, come //main()//, possono essere prive di argomenti, ma le parentesi si devono scrivere sempre.
 +
 +Sarebbe utile eseguire i seguenti programmi in modalità [[appunti3s:gdb|debug]] e visualizzare il contenuto delle variabili in runtime per capire meglio cosa accade durante l'esecuzione delle funzioni. D'ora in avanti gli identificatori delle funzioni e delle variabili costituiti da più parole si riconosceranno dall'iniziale minuscola ed eventuali altre parole maiuscole tutte attaccate (regola CamelCase).
 +
 +<file cpp 12.cpp>
 +// Questo programma è stato scritto da Fabio.
 +// Serve a calcolare il triplo di un numero
 +// scrivendo tutto il codice nella funzione principale.
 +#include <iostream>
 +
 +int main ()
 +{
 +  int mioNumero;
 +
 +  std::cout << "Per favore scrivi un numero intero: ";
 +  std::cin >> mioNumero;
 +
 +  std::cout << "il triplo di " << mioNumero << " vale " 
 +            << 3*mioNumero << endl;
 +  return 0;
 +}
 +</file>
 +
 +==== Divisione in sottoproblemi ====
 +Il precedente programma era stato scritto usando solo una funzione (main). 
 +
 +{{:appunti3s:cppmoduli.png |immagine che descrive il legame reciproco tra le due funzioni (chiamate visualizzazione e calcolo)}} Ora verrà riscritto dividendo il programma in due parti che si occupano di due aspetti diversi del problema, usando quindi due funzioni. La prima parte si occuperà della visualizzazione dei dati mentre l'altra del calcolo. Per poter far funzionare questo sistema si deve far comunicare tra loro le due parti. Ovviamente l'esempio è così semplice da risolvere che non sarebbe necessario dividere il programma in due parti, ma inizialmente è necessario farlo su casi semplici per capire come si può dividere il codice di un programma.
 +
 +===== Passaggio per valore =====
 +La prima parte verrà svolta (come prima) dalla funzione main() mentre per la seconda parte verrà creata una nuova funzione: calcolaTriplo().
 +Leggendo il nuovo codice sorgente si noterà che è diviso in due parti:
 +  * in basso vi è la solita definizione di //main()//, la funzione principale, che come al solito restituisce un valore intero al sistema operativo. 
 +  * in alto vi è la definizione di //calcolaTriplo()//, che è una nuova funzione. Anche essa restituisce un valore intero, alla funzione main().
 +Le due parti sono collegate tra di loro, infatti si può notare la presenza, dentro main(), della //chiamata// di calcolaTriplo(). La prima funzione che viene eseguita è sempre main(). Ogni volta che si incontra una chiamata di una funzione, l'esecuzione delle istruzioni si interrompe per eseguire le istruzioni della funzione che è stata chiamata. Al termine della funzione, si riprende da dove si era interrotto.
 +<file cpp 13.cpp>
 +// Questo programma è stato scritto da Fabio.
 +// Come nel programma precedente calcola il triplo di un numero, ma
 +// ora usando una semplice funzione chiamata calcolaTriplo().
 +// Questo modo di passare il valore crea una nuova variabile locale (int x)
 +// questa funzione restituisce un int (return)
 +
 +#include <iostream>
 +
 +// definizione della funzione calcolaTriplo() con parametro formale 'x'
 +int calcolaTriplo(int x)
 +{
 +  return 3*x;
 +}
 +
 +//definizione della solita funzione main()
 +int main()
 +{
 +  int mioNumero,mioTriplo;
 +
 +  std::cout << "Per favore scrivi un numero intero: ";
 +  std::cin >> mioNumero;
 +  
 +  mioTriplo = calcolaTriplo(mioNumero);  // chiamata della funzione con il 
 +                                         // parametro effettivo 'mioNumero'
 +  
 +  std::cout << "il triplo di " << mioNumero << " vale "
 +            << mioTriplo << std::endl;
 +  return 0;
 +}
 +</file>
 +{{:appunti3s:cppmaincalcolatriplo.png |immagine che descrive il legame tra due moduli software chiamati main e calcolatriplo}} Come avviene la "comunicazione" tra le due funzioni? 
 +  - Dentro main() c'è una chiamata di calcolaTriplo() e il valore da passare viene scritto tra parentesi.
 +  - Dentro calcolaTriplo() c'è l'istruzione "//return//" che restituisce un valore al main().
 +Dentro la //definizione// della funzione //calcolaTriplo()// tra parentesi c'è scritto //int x//: questo permette di passare alla funzione il valore contenuto in una variabile (non si passa la variabile), e questo meccanismo si chiama appunto //passaggio per valore//.
 +
 +{{ :appunti3s:pervalore.png |immagine che descrive come viene copiato il valore di una variabile locale da main a calcolatriplo}}
 +
 +Dentro la funzione calcolaTriplo() la variabile x non sembra essere mai inizializzata, ma grazie al passaggio del valore è come se ci fosse scritto il seguente codice:
 +<code>
 + int x = mioNumero;         // definizione di una nuova variabile locale
 +                            // la sua inizializzazione tramite valore passato tra parentesi
 +</code>
 +Questo codice serve solo per capire come avviene l'inizializzazione di x dentro la funzione, e non va preso alla lettera, cioè non vanno copiate quelle istruzioni...
 +
 +Nella //definizione// della funzione //calcolaTriplo()// c'è scritto che le si può passare un valore intero. Non è consentito quindi passare più di un valore, oppure un valore non corrispondente al tipo intero.
 +
 +==== Valore restituito ====
 +
 +Al termine della funzione //calcolaTriplo()// c'è l'istruzione return, che restituisce il risultato a chi ha chiamato questa funzione. Anche il tipo del risultato restituito deve corrispondere al tipo che è dichiarato davanti al nome della funzione (int). Questo tipo di definizione assomiglia alla definizione di una variabile intera. 
 +Infatti, leggendo il codice del main(), si può immaginare di sostituire, al posto della chiamata della funzione //calcolaTriplo(mioNumero)// una variabile intera dentro cui sarà __copiato__ il valore restituito con return.
 +
 +NOTA: questa nota deve essere letta solo da chi conosce il linguaggio C. A volte i programmatori C usano il valore restituito da una funzione per stabilire se questa è stata eseguita senza errori (restituendo 0) oppure se ha prodotto un errore (restituendo ad esempio 5). In C++, invece, il valore restituito non dovrebbe essere usato per questo scopo, perché, per stabilire se ci sono stati errori, si dovrebbero usare le //exception// (vedere gestione delle [[appunti3s:eccezioni]]).
 +
 +===== Visibilità delle variabili=====
 +
 +Ecco una tabella che mostra la traccia dei valori contenuti nelle aree di memoria utilizzate durante l'esecuzione dell'ultimo esempio:
 +^ mioNumero ^ mioTriplo ^ x ^
 +|  9  |  -  |    |
 +|  9  |  -  |  9  |
 +|  9  |  27  |  - |
 +
 +  * Le variabili dentro la chiamata di una funzione (mioNumero) sono dette parametri effettivi (actual parameters)
 +  * Le variabili dentro la definizione di una funzione (x) sono dette parametri formali (formal parameters)
 +Questo tipo di tabella che tiene la traccia dei valori assunti dalle variabili durante l'esecuzione si può paragonare allo strumento di [[appunti3s:programmazione_procedurale#debugger|debug]]. Come si può notare l'esistenza della variabile locale della funzione //calcolaTriplo()// (il parametro formale //x//) ha una durata è più breve, rispetto alle variabili della funzione //main()// (il parametro effettivo //mioNumero//). 
 +Da questo di deduce che ognuna delle funzioni viene eseguita utilizzando aree di memoria separate, che non sono reciprocamente visibili e per questo motivo tali variabili sono dette //variabili locali//. La comunicazione tra le due funzioni avviene solo grazie al passaggio della copia del valore (dentro //x//) e grazie al comando //return//.
 +
 +Facendo un paragone, la funzione main() è come un datore di lavoro che chiama la funzione calcolaTriplo() ad eseguire un certo lavoro. Quando la chiama le fornisce anche una copia del dato su cui dovrà lavorare. Al termine, la funzione calcolaTriplo() restituisce al suo datore il risultato del suo lavoro.
 +
 +==== Debugger ====
 +Prima di andare avanti nella lettura è necessario leggere questa [[appunti3s:eccezioni#premessa]]
 +
 +Per l'analisi e l'individuazione dei bug di un programma o per osservare il contenuto della memoria durante la sua esecuzione esistono degli appositi strumenti software utili anche per visualizzare i valori dei parametri attuali e formali durante l'esecuzione di un programma.
 +Tali software di debug permettono quindi di capire cosa accade durante l'esecuzione di un programma.
 +
 +I software di debug esistono sia con interfaccia grafica sia con interfaccia testuale:
 +  * Si può usare un IDE (come //codelite// o //ddd// o {{:olimpiadi:03-ide.pdf|c++dev}}) 
 +  * Si può usare la linea di comando di //gdb//, come in questo esempio:
 +    - compilare il sorgente.cpp in debug mode <code>g++ -g file.cpp -o file.exe</code>
 +    - avviare il debugger, specificando anche l'eseguibile da aprire, digitando: <code>gdb file.exe</code>
 +    - inserire un breackpoint alla riga 5 digitando: <code>break file.cpp:5</code> 
 +    - avviare esecuzione, che si interromperà al break, digitando:<code>run</code>
 +    - al prompt creare un //automatic display// digitando: <code>display nomevariabile</code>
 +    - procedere con passo 1, digitando: <code>step</code>
 +    - basta premere invio per continuare con passo 1...
 +    - Per uscire, digitare: <code>quit</code>
 +
 +
 +=== Esempi con errori ===
 +
 +Questo programma contiene un'istruzione che __non__ rispetta la visibilità delle variabili locali di ogni funzione. Provare a compilare il seguente programma:
 +
 +<file cpp 13error1.cpp>
 +// Questo programma è stato scritto da Fabio.
 +// Serve a dimostrare l'ambito di visibilità delle variabili locali
 +// usando due funzioni, ma contiene un ERRORE....
 +
 +#include <iostream>
 +
 +int calcolaTriplo() 
 +{
 +  return 3*mioNumero;  
 +}
 +
 +int main()
 +{
 +  int mioNumero,mioTriplo;
 +
 +  std::cout << "Per favore scrivi un numero intero: ";
 +  std::cin >> mioNumero;
 +  
 +  mioTriplo = calcolaTriplo(); // chiamata della funzione
 +  
 +  std::cout << "il triplo di " << mioNumero << " vale "
 +            << mioTriplo << std::endl;
 +  return 0;
 +}
 +</file>
 +dovrebbe produrre il seguente errore:
 +<code> In function ‘int calcolaTriplo()’:
 + error: ‘mioNumero’ was not declared in this scope</code> 
 +
 +<file cpp 13error2.cpp>
 +// Questo programma è stato scritto da Fabio.
 +// e' simile al precedente, ma contiene un ERRORE...
 +
 +#include <iostream>
 +
 +int calcolaTriplo(int x) 
 +{
 +  return 3*x;
 +}
 +
 +int main()
 +{
 +  int mioNumero,mioTriplo;
 +
 +  std::cout << "Per favore scrivi un numero intero: ";
 +  std::cin >> mioNumero;
 +  
 +  mioTriplo = calcolaTriplo(mioNumero); // chiamata della funzione
 +  
 +  std::cout << "il triplo di " << mioNumero << " vale "
 +            << x << std::endl;
 +  return 0;
 +}
 +</file>
 +
 +Si dovrebbe ottenere questo errore 
 +<code>
 +   In function ‘int main()’:
 +        : error: ‘x’ was not declared in this scope 
 +</code>
 +
 +
 +===== Introduzione alle direttive =====
 +Questo argomento viene ripreso e approfondito nel prossimo capitolo, nel paragrafo [[appunti3s:programmazione multifile#direttive]] direttive al compilatore.
 +
 +Che cosa significa la parola "iostream" che si trova quasi sempre all'inizio di ogni programma?
 +Si tratta di un file che si può trovare su ogni sistema operativo dove è installato un compilatore.
 +Se non si conosce la sua collocazione si può fare una ricerca... 
 +iostream è un file (senza estensione) che contiene solo semplice testo. Aprendolo si troveranno dei commenti e delle dichiarazioni di classi, operatori, variabili, oggetti e funzioni che sarebbe necessario scrivere prima del main() quando si vogliono usare certe funzioni.
 +
 +Questo tipo di file sono detti "header file" o //file di intestazione//.
 +
 +Usando la direttiva #include si evita di scrivere tutte le cose che contengono. 
 +
 +Esistono numerosi altri file di intestazione, tra cui "string".
 +
 +==== Esempio con string ====
 +
 +In C++ esiste un tipo di dato chiamato //%%std::string%%//.
 +
 +Una variabile di tipo std%%::%%string può contenere una frase che usa qualsiasi simbolo della tastiera, ma per poterlo utilizzare è necessario aggiungere
 +<code>#include <string></code>
 +Come già detto, però, l'operatore Extractor %%>>%% interrompe la lettura quando incontra un invio, uno spazio bianco o una tabulazione. 
 +<file cpp 14.cpp>
 +// questo programma è stato scritto da Fabio
 +// mostra che l'operatore Extractor (>>) lascia i caratteri non letti in input 
 +// e che essi verranno letti al successivo utilizzo di Extractor
 +
 +#include <string>
 +#include <iostream>
 +
 +int main()
 +{
 +    std::string parola;
 +
 +    std::cout << "primo input: scrivi due parole con lo spazio in mezzo" << std::endl;
 +    std::cin >> parola;
 +    std::cout << parola << std::endl; // non ha letto la seconda parola
 +
 +
 +return 0;
 +}
 +</file>
 +Ogni volta che si inserisce uno spazio si deve effettuare una nuova lettura per non perdere i dati in input. Quindi bisognerebbe aggiungere queste istruzioni
 +<code>
 +    std::cin >> parola;          // ora ha letto la seconda parola
 +    std::cout << parola << std::endl; 
 +</code>
 +Questo esempio mostra l'uso di una funzione che legge anche più parole separate da uno spazio.
 +<file cpp 14bis.cpp>
 +// questo programma è stato scritto da Fabio
 +// mostra che l'operatore Extractor (>>) lascia in sospeso l'ultimo newline 
 +// digitato dall'utente
 +// mostra il funzionamento delle funzioni cin.ignore() e getline();
 +
 +#include <string>
 +#include <iostream>
 +
 +using namespace std;
 +
 +int main()
 +{
 +    string parola;
 +    
 +    cout << "scrivi due parole con lo spazio in mezzo" << endl;
 +    getline(cin,parola);    // questa funzione legge anche gli spazi
 +    cout << parola << endl; 
 +    
 +    cout << "scrivi una sola parola, senza spazi" << endl;
 +    cin >> parola;
 +    cout << parola << endl; // rimane uno "newline" non letto.
 +                            // Se si usasse di nuovo getline()
 +                            // verrebbe letto solo quest'ultimo "newline"
 +                            
 +    cin.ignore();             // permette di buttare via l'ultimo carattere non letto
 +                              // (newline) che l'utente ha premuto
 +                              // Quindi tra un cin e un getline() e' necessario usare un ignore()
 +                              
 +    cout << "scrivi due parole con lo spazio in mezzo" << endl;
 +    getline(cin,parola);      
 +    cout << parola << endl; 
 +
 +return 0;
 +}
 +</file>
 +
 +Un'altro esempio di un programma che contiene una nuova funzione
 +
 +<file cpp 15.cpp>
 +// Questo programma e` stato scritto da Fabio.
 +// Dimostra l'utilita` delle funzioni per riutilizzare il codice
 +// effettuando più chiamate della stessa funzione. 
 +// 
 +// Serve a calcolare la media di 4 numeri.
 +
 +#include <iostream>
 +
 +int calcolaMedia(int x, int y)
 +{
 +  return (x+y)/2;
 +}
 +
 +int main()
 +{
 +  int numero1,numero2,numero3,numero4;
 +
 +  std::cout << "Per favore scrivi due numeri interi separati da uno spazio: " 
 +            << std::endl;
 +  std::cin >> numero1 >> numero2 ;
 +  int media1 = calcolaMedia(numero1,numero2);
 +  
 +  std::cout << "Per favore scrivi due numeri interi separati da uno spazio: " 
 +            << std::endl;
 +  std::cin >> numero3 >> numero4;
 +  int media2 = calcolaMedia(numero3,numero4);
 +
 +  std::cout <<  "La media vale " << calcolaMedia(media1,media2) << endl;
 +
 +  return 0;
 +
 +}
 +</file>
 +
 +===== Dichiarazioni =====
 +Ora che è chiara l'importanza delle funzioni per il riutilizzo del codice, si può anche capire che il compilatore ha bisogno di controllare la correttezza delle chiamate delle funzioni, che cioè vengano chiamate con il giusto numero e il giusto tipo di parametri. Nei precedenti esempi questo controllo era reso possibile dal fatto che le nuove funzioni erano //definite// all'inizio del file, prima di essere usate nel main(). L'ordine era il seguente:
 +  - definizione di calcolaTriplo()
 +  - definizione di main()
 +
 +Provando ad invertire l'ordine delle definizione delle due precedenti funzioni, si otterrebbe un errore durante la compilazione. Anche se la ragione per cui si vuole spostare la definizione della nuova funzione diverrà chiara solo più avanti, esiste comunque un modo per superare questo problema.
 +
 +La funzione calcolaTriplo() può essere //definita// anche dopo main(), ma bisogna comunque //dichiarare// o "annunciare" la sua forma, cioè, bisogna comunque dire prima al compilatore quali parametri sono richiesti da tale funzione e che tipo essa restituisce. Vedere il seguente esempio,
 +dove si possono individuare tre parti:
 +  - dichiarazione di calcolaTriplo()
 +  - definizione di main()
 +  - definizione di calcolaTriplo()
 +
 +L'argomento dichiarazione/definizione verrà approfondito nel paragrafo [[appunti3s:programmazione_procedurale#dichiarazione e definizione]]. Per adesso, potrebbe essere sufficiente notare che la //dichiarazione// di una funzione corrisponde alla prima riga della //definizione//, terminata dal punto e virgola (;), mentre la definizione prosegue con le parentesi graffe, senza nessun punto e virgola finale.
 +
 +<file cpp 16.cpp>
 +// Questo programma è stato scritto da Fabio.
 +// Come nel programma precedente calcola il triplo di un numero, ma
 +// serve a dimostrare la differenza tra dichiarazione e definizione di una funzione
 + 
 +#include <iostream>
 + 
 +// dichiarazione della funzione calcolaTriplo() 
 +int calcolaTriplo(int x);
 + 
 +//definizione della solita funzione main()
 +int main()
 +{
 +  int mioNumero,mioTriplo;
 + 
 +  std::cout << "Per favore scrivi un numero intero: ";
 +  std::cin >> mioNumero;
 + 
 +  mioTriplo = calcolaTriplo(mioNumero);  // chiamata della funzione con il 
 +                                         // parametro effettivo 'mioNumero'
 + 
 +  std::cout << "il triplo di " << mioNumero << " vale "
 +            << mioTriplo << std::endl;
 +  return 0;
 +}
 +
 +// definizione di calcolaTriplo() (che deve rispettare la dichiarazione)
 +int calcolaTriplo(int x);
 +{
 +  return 3*x;
 +}
 +</file>
 +Un'altro esempio in cui la dichiarazione di una nuova funzione calcolaMedia() deve precedere main()
 +
 +
 +
 +
 +<file cpp 17.cpp>
 +// questo programma e` stato scritto da Fabio
 +// Serve a calcolare la media (valore intero) tra due interi
 +
 +#include <iostream>
 +
 +int media(int x, int y); // dichiarazione 
 +
 +int main()
 +{
 +  int numero1,numero2;
 +
 +  std::cout << "Per favore scrivi due numeri interi separati da uno spazio: " 
 +            << std::endl;
 +  std::cin >> numero1 >> numero2 ;
 +  
 +  std::cout <<  "La media vale" << media(numero1,numero2) << endl;
 +  return 0;
 +}
 +
 +int media(int x, int y) // definizione
 +{
 +  return (x+y)/2;
 +}
 +</file>
 +
 +
 +===== Classificazione delle funzioni=====
 +
 +In base al valore restituito, esistono due diversi tipi di funzioni: 
 +  * quelle che restituiscono un risultato (es: un valore) a chi le chiama;
 +    * <code>int fun(int x);              // la sua definizione terminerà con un return</code>
 +  * quelle che non restituiscono nessun risultato a chi le chiama (tipo mancante: //void//).
 +    * <code>void gun(float z);           // la sua definizione terminerà SENZA return</code>
 +
 +Il primo tipo di funzioni può essere chiamato in uno di questi due semplici modi:
 +  * <code>y = fun(n);</code>
 +  * <code>std::cout << fun(n);</code>
 +
 +Il secondo tipo può essere chiamato in questo modo:
 +  * <code>gun(y);</code>
 +
 +Il primo tipo consente anche di essere utilizzato con chiamate più articolate, come:
 +  * <code>y = fun(fun(n));</code>
 +  * <code>if (y == fun(n)) .......</code>
 +
 +==== L'effetto sulla memoria ====
 +Per quanto riguarda il primo tipo, si tratta di funzioni che sono progettate per fare una cosa:
 +  - restituire un risultato (es: un valore)
 +Per quanto riguarda il secondo tipo, si può facilmente immaginare che una funzione che __non__ restituisce un risultato dovrà avere sicuramente un qualche altro tipo di **effetto** nella memoria o nell'output. Ad esempio, potrebbe modificare una variabile o visualizzare un messaggio...
 +
 +Questo effetto potrebbe produrlo anche una funzione del primo tipo. Questo è solo un caso particolare del primo tipo di funzioni, che, quindi, producono due effetti: 
 +  - restituiscono un risultato
 +  - producono un effetto sulla memoria
 +Quando interessa esclusivamente il secondo effetto, queste funzioni (del primo tipo) possono essere usate ignorando completamente il valore che restituiscono e chiamate come se fossero del secondo tipo.
 +
 +==== Esempio ====
 +
 +Si può riscrivere il precedente esempio in modo che la funzione sia di tipo void?
 +
 +Se il solito esempio venisse scomposto in due problemi di tipo diverso, si potrebbe realizzare una funzione che si occupa di leggere i dati in input e un'altra funzione che si occupa di fare il calcolo e visualizzare l'output. In tal caso la seconda funzione non dovrebbe restituire nulla con return.
 +Si suggerisce di confrontare il vecchio esempio con il seguente e di annotare cosa cambia nella definizione della funzione e cosa cambia nella chiamata della funzione.
 +
 +{{:appunti3s:cppmoduli2.png|immagine che descrive il legame tra due nuove funzioni, una per l'input l'altra per l'output dei dati}}
 +
 +<file cpp 18.cpp>
 +    // Questo programma è stato scritto da Fabio.
 +    // Come nel programma precedente calcola il triplo di un numero, ma
 +    // la funzione calcolaTriplo() è di tipo void.
 +    // Questa funzione non restituisce nessun valore (non c'e' return)
 +     
 +    #include <iostream>
 +     
 +    void calcolaTriplo(int x) // definizione della funzione con parametro formale 'x'
 +    {
 +      std::cout << 3*x << std::endl;
 +    }
 +     
 +    int main()
 +    {
 +      int mioNumero,mioTriplo;
 +     
 +      std::cout << "Per favore scrivi un numero intero: ";
 +      std::cin >> mioNumero;
 +          
 +      std::cout << "il triplo di " << mioNumero << " vale "
 +                << mioTriplo << endl;
 +
 +      calcolaTriplo(mioNumero); // chiamata della funzione
 +
 +      return 0;
 +    }
 +</file>
 +
 +Quest'ultimo esempio serve per introdurre il prossimo paragrafo
 +
 +<file cpp 18error.cpp>
 +// Questo programma e` stato scritto da Fabio.
 +// Contiene una funzione di "tipo" void ovvero che non restituisce nulla 
 +// infatti (manca return)
 +// ed inoltre mostra un ERRORE dovuto all'effetto del passaggio per valore.
 +
 +#include <iostream>
 +
 +void calcolaTriplo(int x)
 +{
 +  x= 3*x; // Ha effetto sul parametro reale?
 +}
 +
 +int main()
 +{
 +  int mioNumero;
 +
 +  std::cout << "Per favore scrivi un numero intero: ";
 +  std::cin >> mioNumero;
 +  std::cout << "il triplo di " << mioNumero << " vale ";
 +
 +  calcolaTriplo(mioNumero);  // chiamata della funzione
 +  std::cout  << mioNumero << std::endl;
 +  return 0;
 +}
 +</file>
 +
 +Provare sostituire la definizione della funzione con la seguente definizione
 +<code>
 +void calcolaTriplo(int& x)
 +// eccetera...
 +</code>
 +==== Ispettori e modificatori ====
 +
 +Un altro tipo di classificazione delle funzioni le divide in funzioni ispettrici (inspector) e modificatrici (mutator) (o metodi //ispettori// e //modificatori//).
 +
 +=== Ispettori ===
 +
 +Queste non modificano il contenuto delle variabili passate, quindi di solito, per avere un effetto al loro esterno, devono restituire un valore (quindi non possono essere di tipo void); di solito sono usate per __prelevare un valore__ (senza apportare modifiche) perciò in passato venivano chiamati anche metodi //getter//
 +
 +Si possono riconoscere facilmente in quanto è buona abitudine terminare la loro dichiarazione con //const//. In questo modo infatti il compilatore aiuta a trovare errori di programmazione.
 +<code>
 +int fun(int x) const;            // funzione costante con passaggio per valore
 +int fun(const NomeClasse& o) const;  // funzione costante con passaggio per riferimento (costante!!!)
 +const NomeClasse& fun() const;     // funzione costante che restituisce un riferimento (costante!!!)
 +</code>
 +
 +=== Mofidicatori ===
 +
 +Queste possono modificare il contenuto delle variabili passate, quindi possono produrre un effetto al loro esterno anche se sono di tipo void; di solito sono usate per __impostare un valore__ perciò in passato venivano chiamati anche metodi //setter//; 
 +
 +Ovviamente queste si riconoscono dall'assenza di const. 
 +
 +<code>
 +void fun(Classe& o);  // funzione con passaggio per riferimento
 +</code>
 +Per completare la discussione su ispettori e modificatori si devono conoscere i [[appunti3s:references]] (o riferimenti).
 +Si preferisce rimandare questa discussione, intanto sono presentati alcuni esempi da leggere.
 +
 +===== Esempi =====
 +
 +Nel programma 13.cpp la variabile mioTriplo NON era veramente necessaria. Si può riscrivere il programma scrivendo la chiamata della funzione calcolaTriplo() come se fosse una variabile intera, che viene visualizzata su %%std::cout%%.
 +  
 +<file cpp 19.cpp>
 +// Questo programma e` stato scritto da Fabio.
 +// Dimostra l'uso di una funzione dentro l'operatore "Insertion" <<
 +
 +#include <iostream>
 +
 +int calcolaTriplo(int x)
 +{
 +  return 3*x;
 +}
 +
 +int main()
 +{
 +  int mioNumero;
 +
 +  std::cout << "Per favore scrivi un numero intero: ";
 +  std::cin >> mioNumero;
 +
 +// la chiamata della funzione (triplicare) dentro Insertion 
 +  std::cout << "il triplo di "<< mioNumero << " vale "
 +            << calcolaTriplo(mioNumero) << std::endl;
 +  return 0;
 +}
 +</file>
 +
 +
 +
 +<file cpp 20.cpp>
 +// Questo programma è stato scritto da Fabio.
 +// Dimostra la differenza tra concetto di dichiarazione e di definizione 
 +
 +#include <iostream>
 +
 +// dichiarazione della funzione
 +int calcolaMedia(int x, int y); 
 +
 +int main()
 +{
 +  int numero1,numero2,numero3,numero4,media;
 +
 +  std::cout << "Per favore scrivi due numeri interi separati da uno spazio: "
 +            << std::endl;
 +  std::cin >> numero1 >> numero2;
 +  std::cout << "Per favore scrivi due numeri interi separati da uno spazio: "
 +            << std::endl;
 +  std::cin >> numero3 >> numero4;
 +
 +  media = calcolaMedia(calcolaMedia(numero1,numero2),calcolaMedia(numero3,numero4));
 +  std::cout <<  "La media vale " << media << endl;
 +
 +  return 0;
 +
 +}
 +// definizione della funzione
 +int calcolaMedia(int x, int y)
 +{
 +  return (x+y)/2;
 +}
 +</file>
 +==== Dichiarazione e definizione ====
 +La //dichiarazione// è un "annuncio" che viene fatto al compilatore dell'uso di nuovo ente (variabile, oggetto, funzione, ecc.). Questo annuncio contiene alcune informazioni sull'ente, ma non lo definisce in modo completo. Per poterlo usare è necessaria quindi anche la sua //definizione//.
 +In breve, tutto quello che è viene semplicemente "dichiarato" DEVE essere poi anche "definito" .
 +Una classe può essere usata solo dopo aver effettuato (oltre alla sua definizione) anche la definizione delle funzioni che essa contiene.
 +
 +=== Dichiarazione ===
 +Annuncia (al compilatore) l'utilizzo di un nuovo oggetto (variabile) o una nuova funzione (o f. membro). 
 +La //dichiarazione// può essere fatta più volte in posti diversi ogni volta che è necessario dare informazioni al compilatore per lavorare su un nuovo oggetto o funzione.
 +Vedere [[appunti3s:possibili approfondimenti#extern|extern]]
 +  * La //dichiarazione// delle funzioni viene fatta nell'header file.
 +  * La //dichiarazione// di una funzione, se necessario, può essere fatta anche più di una volta.
 +  * La //dichiarazione// delle funzioni membro viene fatta dentro le //definizioni// delle classi, sempre nell'header file.
 +  * La //dichiarazione// di una funzione (f.membro) è detta anche //firma//. es: //int funzione(float a);//
 +  * La //dichiarazione// di un oggetto (variabile) può essere fatta, in un unico colpo, attraverso la sua //definizione// (all'interno di una classe o di una funzione (o f.membro).
 +  * La dichiarazione di una classe contiene solo il nome della classe, e anch'essa termina sempre col punto e virgola (;)
 +  * nota: per approfondire le dichiarazioni, studiare i namespace....
 +
 +=== Definizione ===
 +
 +{{:appunti3s:cpp_definizione.png?160 |immagine che descrive come il concetto di dichiarazione sia contenuto nel concetto di definizione}}
 +
 +La //definizione// contiene in sé anche una //dichiarazione//, ma può essere fatta solo una volta, perché la //definizione//, oltre ad effettuare anche una //dichiarazione// (l'annuncio), alloca (riserva) anche la necessaria area di memoria per contenere un'ente. L'area può essere immediatamente inizializzata.
 +
 +  * La //definizione// di una funzione (f. membro), oltre ad annunciarne l'utilizzo, specifica anche il blocco delle istruzioni che la costituiscono, detto anche il //corpo//. Viene detta anche //implementazione// della funzione.
 +  * Per i precedenti motivi la definizione è unica (non si può ripetere).
 +  * La //definizione// di una classe, invece, non alloca memoria né inizializza! 
 +  * La //definizione// di una classe è l'unica ad avere bisogno delle parentesi graffe, e termina anch'essa col punto e virgola (;)
 +  * La //definizione// di una classe (come detto prima) non contiene le //definizioni//, ma solo le //dichiarazioni// delle sue funzioni membro. 
 +  * La //definizione// di una classe deve, per questo motivo, essere seguita dalla //definizione// (o implementazione) di tutte le sue funzioni membro .
 +
 +
 +==== Variabili ====
 +
 +Per quanto riguarda le variabili non si distingue tra //dichiarazione// e //definizione// perché quando una variabile viene dichiarata viene anche definita.
 +
 +<code>int x; </code>
 +è sia dichiarazione che definizione.
 +Dopo aver visto alcuni esempi, si può stabilire una regola per le dichiarazioni di variabili:
 +
 +<code>const float tassoMutuo = 0.064;</code>
 +
 +specificatore + tipo + operatore + nome + operatore + inizializzazione
 +
 +==== Funzioni ====
 +esempio di //dichiarazione// (nell'header)
 +
 +<code>void fun(std::string parola);
 +</code>
 +
 +esempio di //definizione//
 +
 +<code>void fun(std::string parola)
 +{
 +  // codice...
 +}
 +</code>
 +
 +>> Le funzioni membro definite dentro la def. della classe sono **implicitamente** "[[appunti3s:possibili approfondimenti|inline]]"
 +==== Classi ====
 +esempio di //dichiarazione// (nell'header)
 +<code>
 +Class Cosa; // esempio di forward declaration        
 +</code>
 +
 +esempio di //definizione// (anch'essa nell'header)
 +<code>
 +Class Cosa
 +{
 +public: 
 +  Cosa();   // dichiarazione di funzione (da definire)
 +private:
 +  int x;
 +  void fun(std::string parola); // dichiarazione di funzione (da definire)
 +};
 +</code>
 +L'//implementazione// della classe viene fatta nel file sorgente (.cpp) e contiene le funzioni e i dati membro della classe.
 +Il codice che usa oggetti del tipo appartenente ad una determinata classe (detto anche codice client o client code) deve includere l'header di tale classe.
 +===== Verifica =====
 +  - In cosa consiste la fase di compilazione e cosa produce?
 +  - Che differenza c'è tra un errore di compilazione e un bug? Fare un esempio.
 +  - Che cosa significa l'istruzione return dentro il main()?
 +  - Che cosa significa l'istruzione return dentro un'altra funzione?
 +  - Quale delle due istruzioni è obbligatoria?
 +  - Come si riconosce il nome di una funzione() dal nome di una variabile?
 +  - Come si può migliorare la funzione int calcolaMedia(); per non troncare il risultato?
 +
 +  - Quanti tipi di funzioni esistono in base al tipo di dato restituito?
 +  - Cosa sono i parametri formali e i parametri effettivi?
 +  - Cosa significa passaggio per valore dei parametri?
 +
 +
 +Esercizi: data la dichiarazione di una funzione, scrivere la chiamata
 +  - double areaRettangolo(double base, double altezza);
 +  - double radiceQuadrata(double numero);
 +  - bool segnoPositivo(double numero);
 +  - float distanza(float x, float y);
 +  - void saluto();
 +  - void numeroCasuale();
 +
 +Esercizi: usare le seguenti funzioni <cmath>
 +  - float pow(float a, float b);   %%//%% restituisce la potenza
 +  - float sqrt(float a);         %%//%% restituisce la radice q.
 +  - float fmax(float a, float b);  %%//%% restituisce il massimo
 +  - trovare il massimo di tre numeri usando fmax()...
 +
 +Esercizi: usare le seguenti funzioni <string>  %%//%% richiedono dot notation
 +  - int find( char c, size_t pos = 0 ) const; 
 +  - int size() const; 
 +
 +Programmi procedurali da scrivere:
 +  - scrivere una funzione che calcola l'area del rettangolo
 +  - verificare pre-condizioni sui dati in ingresso prima di usarli (raggio positivo)
 +  - verificare post-condizioni sui dati in uscita (area positiva)
  
appunti3s/programmazione_procedurale.txt · Last modified: 2020/06/08 22:19 (external edit)