Elementi di InformaticaA. A. 2016/2017
Ing. Nicola Amatucci
Università degli studi di Napoli Federico II
Scuola Politecnica e Delle Scienze di Base
Programmazione C++Parte 3
Da dove studiare?
• «Che C Serve», Capitolo 6• Da 6.1 a 6.7
• Riferimento: Appendice B
Moduli (1/2)
• Un programma descrive un algoritmo che risolve un problema
• Per risolvere un problema possiamo utilizzare vari approcci• Bottom-up, Top-down, …
• Il problema viene quindi scomposto in sotto-problemi più piccoli che sonorisolvibili tramite un algoritmo tipicamente più semplice o ben noto• Si cerca di ridurre la complessità del sistema
• Si suddivide un algoritmo in moduli• Componenti capaci di svolgere specifiche funzioni• Autonomo e ben identificato• Riusabile
Moduli (2/2)
• Ogni modulo implementa un algoritmo o una sua parte• Ci interessa sapere cosa fa e come deve essere utilizzato• Non ci interessa (necessariamente) sapere come è fatto dentro• Molti moduli sono "chiusi", ovvero non è possibile vedere cosa fanno al loro interno
(ad es. moduli a pagamento)
• Ogni modulo è identificato da un nome con cui può essere chiamato (oattivato)• È come se diventasse una nuova istruzione del linguaggio in uso• È fatto da istruzioni• Ha degli ingressi e delle uscite• Chi lo ha chiamato prende il nome di chiamante
Moduli e Funzioni
• Le funzioni in C/C++ sono sottoprogrammi• Contenitori di frammenti di codice che implementano un algoritmo
• Richiamabili tramite un nome
• Una chiamata a funzione provoca l'esecuzione del codice in essa contenuto• Alla funzione possono essere dati dei parametri di ingresso
• Una funzione può avere parametri di uscita• Tali parametri sono ritornati al chiamante tramite l'istruzione return
• Se non li ha prende il nome di procedura
Vantaggi
• Sinteticità• Il codice non viene duplicato• I programmi diventano più brevi e di dimensioni "gestibili"
• Leggibilità• Un algoritmo diviso in funzioni (chiamate in modo consono) diventa facilmente leggibile
• Scomposizione del problema• La divisione in funzione permette di realizzare il programma in modo modulare, suddividendo
il lavoro tra più persone
• Riuso• È possibile usare funzioni scritte da altri, raccolte in librerie• Non ci interessano i dettagli dell'algoritmo che implementano
Dichiarare una Funzione
• Per utilizzare una funzione ci servono• Il suo nome
• Il tipo e l'ordine dei parametri in ingresso
• Il tipo di ritorno (se previsto)
• L'insieme di queste informazioni si definisce "firma" (o intestazione)della funzione
tipo_restituito nome_funzione (tipo_ingresso1 nome_variabile_ingresso1,tipo_ingresso1 nome_variabile_ingresso2, …)
Tipo Restituito
• Può essere un tipo di dato semplice o strutturato• int, long, …• char[], struct Contatto, …
• In caso la funzione non debba ritornare nulla, si dichiara che l'insiemedi ritorno è "void" (ovvero vuoto)
• Il valore viene ritornato al chiamante tramite l'istruzione return• Va specificato il valore o la variabile da ritornare• In caso sia "void", basta la sola istruzione return
Nome
• Il nome di una funzione rispetta le convenzioni di cui abbiamodiscusso per le variabili• essere formati da lettere minuscole o maiuscole, numeri e underscore (_)
• cominciare con una lettera o un underscore
• non essere parole chiave del C++
• Come per le variabili, maiuscole e minuscole sono differenti in C++• Ad es. «funzione1» è un nome diverso da «Funzione1»
Parametri
• Una funzione può avere degli input, ovvero i dati in ingresso delproblema• Tali input prendono il nome di parametri della funzione
• Distinguiamo tra parametri formali ed effettivi• Formali
• Sono i parametri che la funzione utilizza al suo interno• Sono variabili che esistono e possono essere utilizzati solo all'interno della funzione
• Effettivi• Sono i parametri passati alla funzione dal chiamante• Sono i valori su cui effettivamente opera l'algoritmo, copiato nelle variabili relative ai
parametri formali
Parametri Formali ed Effettivi
• Non c'è bisogno che parametri effettivi e formali abbiano lo stesso nome• Viene considerata la posizione del parametro all'interno della firma della funzione
• All'atto della chiamata• Viene copiato il valore di ogni parametro effettivo nella rispettiva posizione di
memoria relativa al parametro formale nella stessa posizione• Prima dell'esecuzione, il compilatore controlla che i tipi di parametri formali ed effettivi
coincidano
• Viene eseguito il codice della funzione
• Viene reso disponibile in memoria l'output della funzione (se previsto)• Tipicamente lo si assegna ad una variabile o lo si usa in un'operazione
Esempi di firme di funzioni
int potenza(int p, int esp)
float somma(float a, float b)
void stampa_somma(float a, float b)
int leggi_intero()
int main(int argc, char** argv)
• Anche main, in cui siamo abituati a scrivere il nostro codice è unafunzione
• È l'entry point del programma• È la funzione da cui comincia l'esecuzione del programma
• Viene invocata dal sistema operativo direttamente
• Il valore ritornato prende il nome di "error code" e viene interpretato dalsistema operativo• 0, tutto ok
Esempio – Distanza tra due punti
• Calcolo della distanza tra due punti nel piano cartesiano (ipotenusa diun triangolo rettangolo)
http://www.youmath.it/formulari/formulari-di-geometria-analitica/426-distanza-tra-due-punti-nel-piano.html
Esempio – Distanza tra due punti
• Vogliamo calcolare
• Per realizzare la funzione d(P1,P2)• Dividiamo in sotto-problemi
• double radice_quadrata(double x)• double quadrato(double x1, double x2)
• Avremo• q1 = quadrato(x1, x2)• q2 = quadrato(y1, y2)• d = radice_quadrata( q1 + q2 )
Le librerie di funzioni
• Sono raccolte di funzioni che implementano algoritmi noti per larisoluzione di sotto-problemi comuni• Ad es. calcolo della radice quadrata
• Alcune raccolte sono messe a disposizione con il compilatore• Sono inclusi in file che vanno inclusi nel nostro programma con la direttiva #include
• Ad es. iostream
• Includendo la libreria "math.h" si ha la possibilità di utilizzare alcunefunzioni che risolvono semplici problemi matematici• Vedi "Appendice B" del libro "Che C serve?"• In particolare, a noi serve la funzione con la firma
• double sqrt(double x)
Esempio – Distanza tra due punti
double quadrato(double x1, double x2) {
double q = (x1*x1) + (x2*x2);
return q;
}
double distanza(double x1, double x2, double y1, double y2) {
double c;
c = sqrt( quadrato(x1, x2) , quadrato(y1,y2) );
return c;
}
int main() {
cout << distanza (3,3,4,4);
}
Esercizio 1
• Scrivere il prototipo di una funzione che accetti in ingresso un numerointero e restituisca un valore che indichi se il numero in ingresso è unnumero primo
• Si implementi poi tale funzione
• Si realizzi infine un programma chiamante che verifichi il correttocomportamento della funzione implementata
• Definizione di numero primo:• un numero intero positivo è numero primo se è divisibile solo per 1 e per sé stesso
Esercizio 2
• Utilizzando la funzione per la rivelazione dei numeri primi• si realizzi una seconda funzione che, dato un numero intero dall’utente,
stampi tutti i numeri primi minori o uguali ad esso
Esercizio 3
• Scrivere il prototipo di una funzione che accetti in ingresso tre interi e,interpretandoli come giorno, mese ed anno, restituisca un valore cheindichi se la data specificata è una data valida.
• Si implementi poi la funzione.
• Si realizzi infine un programma chiamante (main) che verifichi ilcorretto comportamento della funzione
Il passaggio dei parametri
• I parametri effettivi in C++ sono nella maggior parte dei casi copiati all'internodelle variabili utilizzate all'interno della funzione• Il parametro effettivo del chiamante non viene modificato all'interno della funzione
• Ad es. consideriamo la funzione
void incrementa(int i) {i++;
}
• Data la variabile "int numero = 1", se richiamiamo "incrementa(numero)",• Il valore di i, inizialmente 1, diventerà invece 2• il valore numero rimarrà invariato• È stato passato alla funzione solo il valore della variabile
Passaggio per valore
• Il passaggio dei parametri in C++, in assenza di esplicite direttive, avvienesecondo la modalità detta per valore
• Questo significa che, all’atto della chiamata di una funzione, il compilatorerealizza una copia dei parametri effettivi e la associa ai parametri formali
• La funzione lavora dunque su delle copie dei parametri effettivi localizzatein aree di memoria completamente diverse, e non sui parametri effettiviveri e propri
• Le copie vengono distrutte al termine della funzione: del loro valore,eventualmente alterato all’interno della funzione, non resta traccia
Passaggio per valore
• Vantaggi:• dal punto di vista del programma chiamante:
• il chiamante di una funzione può essere certo che i parametri ad essa passati non potrannoessere alterati in seguito alla chiamata
• dal punto di vista della funzione:• la funzione, se lo crede opportuno, può modificare i parametri a suo piacimento con la
certezza che le modifiche non saranno visibili all’esterno di essa
• Svantaggi:• l’occupazione di memoria risulta doppia rispetto al necessario• il tempo per effettuare la copia dei parametri, specialmente nel caso in cui questi
siano appartenenti a tipi strutturati di grosse dimensioni, può degradare leprestazioni di un programma
Passaggio per riferimento
• Non viene più passato il valore, ma il riferimento alla posizione dimemoria della variabile (il suo indirizzo di memoria)
• Per richiedere questo tipo di passaggio, bisogna aggiungere ilcarattere ‘&’ tra il tipo ed il nome del parametro in questione
void incrementa(int &i) {
i++;
}
Passaggio per riferimento
• Vantaggi:• il passaggio è più efficiente dal momento che, a prescindere dalle dimensioni
del dato, quello che deve essere passato è sempre e solo un indirizzo dimemoria
• Svantaggi:• si perde il meccanismo di protezione garantito dal passaggio dei parametri
per valore
Valore o riferimento?
• Parametri di ingresso• parametri di ingresso, se ai fini della corretta esecuzione della funzione è sufficiente, per la funzione stessa,
esclusivamente leggere il loro valore;• Passati per valore• Ad es. bool numero_pari(int n)
• Parametri di ingresso/uscita• parametri di ingresso-uscita, se il loro valore all’ingresso della funzione è significativo ai fini della elaborazione
che essa realizza ma vengono anche alterati per convogliare informazioni verso il chiamante;• Passati per riferiemento• Ad es. void incrementa(int &n)
• Parametri di uscita• parametri di uscita, se essi rappresentano esclusivamente un supporto per convogliare informazioni verso
l’esterno della funzione;• Passati per riferimento• Ad es. void giorno_oggi(int &giorno)
Il passaggio di parametri const
• Problema• È necessario passare un dato di grosse dimensioni conservando l’efficienza e
proteggendolo comunque da modifiche indesiderate
• Soluzione• Durante lo scambio dei parametri, se si fa anticipare al tipo del parametro la
keyword const, si impedisce del tutto alla funzione di modificare il parametroall’interno di essa
• Nel caso di passaggio per riferimento, la clausola const risolve il problema dimodifiche indesiderate ai parametri, consentendo contemporaneamente disfruttare l’efficienza intrinseca di questa modalità
Passaggio di Array
• Dato un array "int vettore[10]", se stampiamo la variabile "vettore", notiamo un valore insolito• Viene stampata la posizione del primo elemento in memoria
• Quando passiamo un array ad una funzione, viene copiato questo indirizzo per valore• Ma questo valore non è altro che un riferimento ad un array!
• La funzione lavorerà sull'area di memoria dell'array originale• L'array originale non sarà protetto dalle modifiche• Anteporre "const" ad un parametro di tipo array permette di evitare modifiche
• void stampa_vettore(const int vettore[10])
Passaggio di Array
• Un array monodimensionale può essere passato specificando o meno la sua dimensione• void stampa_vettore(const int vettore[10])
• void stampa_vettore(const int vettore[])
• In entrambi i casi, conviene passare esplicitamente anche il numero di elementi che effettivamente lo compongono, oppure usare un carattere terminatore (ad es. nelle stringhe)• void stampa_vettore(const int vettore[], const int numero_elementi)
Passaggio di una Matrice
• Le matrici sono Array• Valgono le considerazioni fatte per gli array
• Mentre per un array monodimensionale possiamo omettere ladimensione, per una matrice deve essere specificata la secondadimensione (ovvero le colonne)• void stampa(int m[][10])
• Questo dipende dal fatto che il C++ deve necessariamente conoscerequante "celle di memoria" occupa una riga
Riepilogo
Nel prototipo Modalità di passaggio Quando si usa Note
int n Passaggio per valore Parametro in ingresso Le modifiche non sonopropagate all'esterno
const int n Passaggio const per valore Parametro in ingresso Le modifiche non sonopropagate all'esterno
int &n Passaggio per riferimento Parametro di uscita o diingresso/uscita
Le modifiche sonopropagate all'esterno
const struct Contatto c Passaggio per riferimento Parametro di ingresso digrosse dimensioni
Non può esseremodificato
int v[] Passaggio per riferimentodi un vettore
Vettore di uscita oingresso/uscita
Viene copiato il puntatoreal vettore e non il vettore.Le modifiche sipropagano.
const int v[] Passaggio const perriferimento di un vettore
Vettore di ingresso Il vettore non può esseremodificato
Esercizi
• Scrivere il prototipo di una funzione che realizzi il prodotto scalare di due vettori
• Scrivere il prototipo di una funzione che calcoli la media dei componenti di unvettore
• Scrivere il prototipo di una funzione che calcoli il minimo di un vettore
• Scrivere il prototipo di una funzione che realizzi il concatenamento di due vettori
• Implementare le funzioni summenzionate ed i rispettivi main per il testing
Esercizi
• Implementare le seguenti funzioni ed i rispettivi programmi di testing:• void calcolaModulo(float PReale, float PImmag, float& Modulo);
• float calcolaModulo(float PReale, float Pimmag);
• float calcolaModulo( const NumeroComplesso& n);• Dove “NumeroComplesso” è un tipo strutturato composto dai campi “P_reale” e
“P_immaginaria”
• int quantiAnniHa(const Persona& p); e void stampaNome(const Persona& p);• Dove “Persona” è un tipo strutturato composto dai campi “Nome” ed “Età”