Funzioni (Informatica) (superiori)
Metodologia Top-Down
modificaPer risolvere dei problemi complessi possiamo utilizzare la metodologia Top-Down, che prevede di risolvere un problema complesso scomponendolo in sottoproblemi, scomponibili a loro volta in ulteriori sottoproblemi, e così via , a cascata.Una volta arrivati a sottoproblemi "semplici" da risolverle, li risolviamo, e via via che li risolviamo troviamo la soluzione del problema iniziale complesso. Ad esempio se il problema complesso da risolvere e la costruzione di una casa, scomponiamo il problema nei seguenti sottoproblemi: costruzione fondazioni, costruzione muri, costruzione tetto, costruzione impianto riscaldamento, costruzione impianto sanitario, ... .Il sottoproblema costruzioni muri viene scomposto in costruisci muro nord , muro sud, muro ovest e muro est. Ora pensando che i sottoproblemi siano diventati sufficientemente semplici da essere risolti chiamiamo le squadre per risolverli, la squadra di muratori deve costruire il muro e viene impiegata 4 volte, prima per il muro nord, poi per quello sud e così via, le squadre . Nella programmazione le squadre di lavoratori che servono per risolvere i sottoproblemi "semplici" sono dette funzioni.
Funzioni
modificaUn programma in c è costituito da un insieme di funzioni di cui una si chiama main , la funzione main ha la particolarità di essere l'unica funzione ad essere eseguita automaticamente quando il programma parte. Le funzioni sono dei pezzi di codice autonomo che servono per risolvere dei problemi parametrici. L'autonomia del codice è garantita dall' uso delle variabili locali, ogni variabile dichiarata all'interno di una funzione viene detta locale perché ha validità solo all'interno di quella funzione, se ci sono due funzioni f1 e f2 che hanno entrambe dichiarato una variabile a di tipo intero , esiste una variabile a per la funzione f1 e una per la funzione f2, e anche se si chiamano nello stesso modo non hanno niente in comune. All'opposto, una variabile dichiarata al di fuori da tutte le funzioni prende il titolo di variabile globale e questa risulta accessibile da tutte le funzioni , se esiste una variabile globale di nome a e una variabile a locale alla funzione f1 , quella locale all'interno della funzione f1 oscura quella globale. Una funzione che lavora sulle sue variabili locali garantisce l'indipendenza di una funzione dall'altra, visto che ogni funzione serve per risolvere un particolare tipo di problemi ( ordinare un vettore, ricercare un elemento in un vettore, calcolare l'area di un rettangolo, risolvere una equazione di 2 grado etc) quando c'e un errore o una modifica da fare nel codice della soluzione di una equazione di 2 grado possiamo limitare l'analisi e le modifiche alla sola funzione che svolge quel compito, con la sicurezza che data la località delle variabili utilizzate tutto è limitato al solo codice della funzione. La frase una funzione risolve uno specifico problema parametrico non deve disturbare, un problema parametrico è un problema in cui i dati vengono indicati simbolicamente tramite il nome di alcune variabili , quindi se scrivo arearettangolo=base*altezza base e altezza sono i due parametri d'ingresso per poter risolvere il problema, naturalmente prima di poter fare i calcoli a questi parametri dovranno essere assegnati dei valori, mentre in fase di scrittura del codice della funzione potremmo scrivere la soluzione del problema utilizzando i nomi della variabili parametriche (senza preoccuparci del valore che sarà assegnato successivamente). Le funzioni servono per condividere le conoscenze, Marco che conosce le equazioni di secondo condivide le sue conoscenze scrivendo il codice della funzione per risolvere un'equazione di secondo grado in modo parametrico, Luca potrà utilizzare questa funzione senza dover saper il codice scritto nella funzione, potrà richiamarla passandogli i valori per inizializzare i parametri d'ingresso e l'esecuzione della funzione gli permette di ottenere le soluzioni. Le funzioni vengono scritte una volta sola ma poi possono essere richiamate più volte, questo semplifica la scrittura del programma e concentra all'interno della funzione la logica risolutiva di quello specifico problema parametrico. Cerchiamo di non usare variabili globali perché queste minano l'indipendenza risolutiva delle diverse funzioni permettendo alle funzioni di interagire fra loro al di fuori del più esplicito passaggio dei parametri in fase di chiamata di una funzione.
Dopo molte parole passiamo alla pratica. Dopo using namespace std e prima di int main ( ....), scriviamo una funzione per risolvere il calcolo dell'area del rettangolo. Si scrive:
float calcoloarearetangolo( float base, float altezza) { }
questa è la struttura di una funzione , calcoloarearettangolo è il nome della funzione, fra parentesi i parametri d'ingresso per poter risolvere il problema : base e altezza. I parametri scritti quando si dichiara una funzione fra parentesi rotonde sono detti parametri formali. Poi fra le { } bisogna scrivere il codice per risolvere il problema.
float calcoloarearetangolo( float base, float altezza) { float area; area=base*altezza; return area; }
Analizziamo quello che è stato scritto ,
float area;
serve per dichiarare una variabile locale , visibile solo internamente a questa funzione, che si va ad aggiungere a base e altezza che essendo i parametri d'ingresso riceveranno un valore in fase di chiamata alla funzione (fase che vedremo fra poco) tramite i parametri attuali. dopo la dichiarazione della variabile area, c'è la fase di soluzione del problema in forma parametrica area=base*altezza; finiti i calcoli siamo in grado di restituire il risultato e lo facciamo con il comando return return area; restituisce il valore dell'espressione scritta dopo la parola return, il valore viene restituito al posto della chiamata alla funzione (che vedremo fra poco).
Ora finito di scrivere la funzione calcoloarearettangolo possiamo vedere come eseguirla , possiamo fare questa operazione (chiamata della funzione) da una qualsiasi funzione semplicemente scrivendone il nome e indicando la sequenza di valori da usarsi per inizializzare i parametri d'ingresso della funzione, ad esempio nella funzione main possiamo scrivere il codice
calcoloarearettangolo(5,7);
5 e 7 sono detti parametri attuali e vengono fatti corrispondere per posizione ai parametri formali, quindi 5 viene caricato nella variabile base e 7 nella variabile altezza, quando viene eseguito il comando calcoloarearettangolo(5,7); dopo aver usato i parametri attuali per inizializzare i corrispondenti parametri formali, viene eseguito il codice fra { } della funzione calcoloarearettangolo, il valore restituito dal comando return prende il posto della chiamata alla funzione, cioè potete pensare che al posto di calcoloarearettangolo ora ci sia il numero 35, per non perdere il valore scriviamo
c=calcoloarearettangolo(5,7);
ora c vale 35.
Scritta una funzione possiamo riutilizzarla più volte , nel main vogliamo calcolare l'area di un secondo rettangolo , aggiungiamo il codice
float a,b,soluzione;
cout<<"calcolo area secondo rettangolo"<<endl;
cout<<"inserisci la base del rettangolo";
cin>>a;
cout<<"inserisci l'altezza del rettangolo"
cin>>b;
soluzione=calcoloarearettangolo(a,b);
cout<<"l'area del 2^ rettangolo vale"<<soluzione;
in questo caso a e b quando abbiamo scritto la chiamata alla funzione calcoloarearettangolo(a,b) sono i parametri attuali che vengono fatti corrispondere (per posizione, il primo con il primo, il secondo con il secondo) con i corrisdpondenti parametri formali.Il valore di a viene usato per inizializzare la base e il valore di b viene usato per inizializzare l'altezza. Facciamo un terzo calcolo per l'area del rettangolo; potevo riutilizzare le variabili a,b,soluzione ma complichiamo le cose per evidenziare la località delle variabili dichiarate in una funzione.
nel main aggiungiamo
float base,altezza,area;
cout<<"calcolo area secondo rettangolo"<<endl;
cout<<"inserisci la base del rettangolo";
cin>>base;
cout<<"inserisci l'altezza del rettangolo"
cin>>altezza;
area=calcoloarearettangolo(base,altezza);
cout<<"l'area del 2^ rettangolo vale"<<sol3;
esiste una variabile base e una altezza e una di nome area nella funzione main e una variabile base e altezza e area nella funzione calcoloarearettangolo , ma queste non hanno nulla in comune fra loro, esiste una variabile di nome base nel main e una variabile di nome base nella funzione calcolarearettangolo, se nella funzione calcolaarearettangolo scrivo base=100; la variabile base del main non si modifica e mantiene il suo vecchio valore. Con la chiamata alla funzione calcoloarearettangolo(base,altezza); i parametri attuali vengono fatti corrispondere ai corrispondenti (per posizione ) parametri formali.
passiamo a un secondo esempio , vogliamo scrivere una funzione per il calcolo delle soluzioni di una equazione di 2 grado. Marco che è il solo ad averle studiate scrive la funzione, noi che invece che siamo andati al cinema la richiameremo solamente.
In questo caso dobbiamo restituire più valori le 2 soluzioni e l'informazione se il calcolo è stato possibile (nel campo dei reali non esistono soluzioni se il discriminante è negativo , delta<0), quando si vogliono restituire più di un risultato non è possibile usare il comando return , allora decidiamo di usare 6 parametri d'ingresso, i coefficienti a,b,c dell'equazione di 2^ grado e gli indirizzi delle celle di memoria dove caricare la prima soluzione x1, la seconda soluzione x2 e dove indicare se il calcolo è stato possibile o meno. Scriviamo allora void eq2grado( flaot a, float b, float c, float *x1, float *x2, bool *esistesoluzione) { }
cioè per sapere dove scrivere i 3 risultati usiamo 3 puntatori che riceveranno l'indirizzo della cella dove scrivere i risultati.
il void indica che la funzione non usa return per restituire il risultato.
La funzione eq2grado viene scritta dopo la dichiarazione della funzione calcoloarearettangolo e prima della funzione main.
void eq2grado( flaot a, float b, float c, float *x1, float *x2, bool *esistesoluzione) { float delta;
delta=b*b-4*a*c; if(delta<0) *esistesoluzione=false; else { *x1=(-b-sqrt(delta)/(2*a); *x2=(-b+sqrt(delta)/(2*a); *esistesoluzione=true; }
}
Il comando *x1=(-b-sqrt(delta)/(2*a); permette di scrivere il valore della prima radice nella cella puntata da x1 (che è un puntatore a float)
In questo caso nel main quando faccio la chiamata alla funzione eq2grado devo fornire gli indirizzi delle celle dove voglio vengano scritti i risultati, comunicando l'indirizzo di celle di memoria locali della funzione main tramite il passaggio dei parametri (attuali con formali) permetto alla funzione eq2grado di leggere e modificare tramite i puntatori la cella puntata.
nel main allora scriviamo
float a1,b1,c1, sol1,sol2;
bool calcolato;
cout<<" data una eq di secondo grado della forma ax^2+bx+c=0"<<endl;
cout<<"scrivi il coefficiente a";
cin>>a1;
cout<<"scrivi il coefficiente b";
cin>>b1;
cout<<"scrivi termine noto c";
cin>>c1;
eq2grado( a1,b1,c1, &sol1, &sol2, &calcolato;
if(calcolato)
cout<<"le soluzioni sono"<< sol1<< " e "<< sol2<<endl;
else
cout<<"non ci sono soluzioni reali al problema";
tutti i parametri attuali vengono passati per inizializzare i corrispondenti parametri formali, ma i primi tre sono dei valori e i secondi tre sono degli indirizzi, si parla allora di passaggio per valore e passaggio per indirizzo. Ecco il programma completo
#include <iostream>
#include <math.h>
using namespace std;
//dichiarazione funzione
float arearettangolo( float base, float altezza)//base altezza parametri formali
{ float area;
area=base*altezza;
return area;
}
void eq2grado( float a, float b, float c, float *x1, float *x2, bool *calcolato)
{ float delta;
delta=b*b-4*a*c;
if(delta<0)
*calcolato=false;
else
{*x1=(-b-sqrt(delta))/(2*a);
*x2=(-b+sqrt(delta))/(2*a);
*calcolato=true;
}
}
int main(int argc, char *argv[])
{ float a,b,c;
cout<<"primo rettangolo"<<endl;
cout<<"base1=?";
cin>>a;
cout<<"altezza1=?";
cin>>b;
c=arearettangolo(a,b);// a,b parametri attuali, chiamata alla funzione
cout<<"l'area del 1 rettangolo vale"<<c<<endl;
cout<<"secondo rettangolo"<<endl;
cout<<"base2=?";
cin>>a;
cout<<"altezza2=?";
cin>>b;
c=arearettangolo(a,b);// a,b parametri attuali, chiamata alla funzione
cout<<"l'area del 2 rettangolo vale"<<c<<endl;
cout<<"terzo rettangolo"<<endl;
c=arearettangolo(7,12);// a,b parametri attuali, chiamata alla funzione
cout<<"l'area del 3 rettangolo vale"<<c<<endl;
float sol1,sol2;
bool trovato;
cout<<"soluz 2 grado"<<endl;
eq2grado(4,6,-40,&sol1,&sol2,&trovato);
if(trovato)
cout<<" le soluzioni sono"<<sol1<<" "<<sol2<<endl;
else
cout<<"non ci sono soluzioni";
return 0;
}