edutecnica

Tecnica Top-Down

       

Quando un problema si presenta immediatamente complesso una tecnica per trovarne la soluzione consiste nel scomporre il problema in sottoproblemi più semplici, tenendo presente le loro relazioni. In questo modo è possibile realizzare un algoritmo di risoluzione complessivo ottenuto da una serie di algoritmi più semplici, opportunamente assemblati.

Questo "modus operandi" di tipo gerarchico viene chiamato metodo top-down, ossia "dall'alto verso il basso", dove "alto" e basso" si riferiscono al livello di dettaglio o astrazione. Il livello pù alto (top) è quello che descrive la procedura generale di risoluzione del problema, o problema principale, utilizzando descrizioni sommarie di quelli che saranno i suoi passi fondamentali, o sottoproblemi.

Vengono definite, dunque, le parti fondamentali di cui si compone il programma (top) si procede poi allo sviluppo dettagliata di ogni singola parte (down). Per scomporre un problema in sottoproblemi si evidenzia cosa debba essere fatto piuttosto che come realizzarlo.

Questa metodologia per affrontare il processo risolutivo, viene solitamente applicata dall'analista, poi, al momento di procedere all'elaborazione del codice relativo (e risolutivo) sarà il programmatore a decidere se implementare singolarmente i vari sottoproblemi oppure accorparne alcuni.

L'approccio informatico al metodo top-down è quello di utilizzare dei sottoprogrammi da definire in un secondo momento ed è questo il classico approccio della programmazione procedurale. Ci sono due tipi di sottoprogrammi : le procedure e le funzioni.

La procedura è un sottoprogramma contenente le istruzioni che risolvono uno specifico problema. Al momento dell'esecuzione di una procedura viene attivata da una apposita istruzione chiamata invocazione. Le procedure non possono far parte di espressioni e non restituiscono alcun valore al programma chiamante.

La funzione è un sottoprogramma contenente le istruzioni che risolvono uno specifico problema, queste restituiscono al programma chiamante un valore, assegnabile, eventualmente, ad una variabile. Le funzioni vengono usate principalmente a destra dell'operatore di assegnazione (=) e possono essere impiegate all'interno di espressioni.


Dichiarazione di procedure

       

Una procedura rappresenta una parte di programma che per comodità, viene isolata ed eseguita all'occorrenza . La comodità consiste nella possibilità di riusare la stessa procedura in differenti punti del programma senza la necessità di ricopiarla.
Per dichiarare una procedura si usa la sintassi seguente:

void nomeProcedura(elencoDeiParametri){
     istruzioni;
}

La parola void viene usata per indicare che la procedura non deve ritornare alcun valore al programma chiamante.
nomeProcedura rappresenta, invece, il nome assegnato alla procedura (identificatore) e segue le stesse regole sintattiche dei nomi da assegnare alle variabili. Le parentesi tonde racchiudono l'elenco dei parametri, eventualmente, da passare alla procedura e le parentesi graffe delimitano il blocco che contiene le istruzioni.

Per poter essere eseguita, la procedura necessita di una chiamata da parte della funzione main() oppure di qualsiasi altra funzione. (compresa se stessa nel caso di funzioni ricorsive).

La dichiarazione della procedura o della funzione deve essere posizionata all'esterno del main() e di qualsiasi altra funzione o procedura precedente la sua chiamata. In altre parole, va scritta prima del main().

#include<iostream>
using namespace std;
void stampa(int s){
    cout<<"somma:"<<s;
}
main(){
    int a=3,b=2,s;
    s=a+b;
stampa(s);//invocazione della procedura
}//__________fine main

In linguaggio Visual Basic le procedure prendono il nome di subroutine mentre in linguaggio Pascal le procedure si chiamano semplicemente procedure. In C/C++ le procedure sono semplicemente delle funzioni dichiarate di tipo void.


Dichiarazione di funzioni

       

Una funzione differisce da una procedura perchè ritorna un valore al programma chiamante. Per dichiarare una funzione si usa la sintassi seguente:

tipoRestituito nomeFunzione(elencoParametri){
   tiporestituito risultato;
   ..istruzioni;
   return risultato;
}

Qui, assume particolare importanza l'istruzione return, usata per restituire un valore al programma chiamante. La sua sintassi prevede di specificare dopo la parola chiave return, un valore costante o una variabile compatibili col tipo di dato restituito dalla funzione.

#include<iostream>
using namespace std;
float sup(float b, float h){
   float a; a=b*h;
   return a;
}
main(){
int base=5,altezza=3; float area;
//espressione contenente la chiamata alla funzione
   area=sup(base,altezza);
   cout<<"superficie:"<<area<<" m";
}//__________fine main

Poiché la funzione restituisce un valore occorre specificare prima del nome che identifica la funzione il tipo di dato del valore restituito.

Dopo il nome della funzione sono obbligatorie le parentesi tonde che contengono l'elenco degli argomenti passati alla funzione, detti parametri formali.
I parametri attuali, sono quelli nominati nel punto dell'invocazione della funzione.

Le istruzioni eseguite dalla funzione sono contenute all'interno delle parentesi graffe. Il corpo della funzione contiene come ultima istruzione la parola return seguita dal valore della variabile contenente il risultato restituito dalla funzione.

L'istruzione return provoca il ritorno del controllo alla funzione principale (funzione chiamante) che può essere anche il main().
Se il tipo restituito dalla funzione non è specificato, si assume, che il tipo sia int.


Variabili locali e variabili globali

       

In un programma le variabili globali sono dichiarate all'inizio, dopo le direttive d'inclusione(librerie) al di fuori dal main e dal corpo di qualsiasi altra funzione.

esse sono chiamate variabili globali in contrapposizione alle variabili locali dichiarate all'inizio di ogni funzione. Le variabili globali possono essere usate da qualsiasi punto del programma invece quelle locali possono essere usate soltanto all'interno della funzione nella quale sono state dichiarate.

I parametri di una funzione sono a tutti gli effetti delle variabili locali alla funzione a cui si riferiscono:
esse sono come variabili implicitamente dichiarate.

L'idea di organizzare programmi in moduli funzionalmente indipendenti porta con se la necessità di definire all'interno della funzione tutte le variabili necessarie al suo funzionamento:in realtà spesso in un programma una funzione può condividere variabili con altre funzioni. In un programma si possono distinguere: variabili globali che vengono dichiarate all'inizio del programma e che possono essere usate dal programma principale e da tutte le funzioni. Le variabili locali che vengono dichiarate all'interno della funzione che le utilizza e che non sono visibili alle altre funzioni.

E' da ricordare che anche le variabili dichiarate nel main sono di tipo locale, dato che il main è una funzione a sua volta e quindi le variabili dichiarate nel main non sono visibili nelle altre funzioni.

Un programma ben strutturato avrà poche variabili globali e molte variabili locali, per consentire una facile manutenzione e una maggior possibilità di riusare i moduli funzionali in altri programmi.

Alle variabili locali vengono riservate posizioni nella memoria centrale(RAM) solo al momento dell'esecuzione della funzione. Lo spazio viene creato all'inizio e rilasciato al termine della funzione, liberando quindi spazio nella memoria utente: una ragione in più per usare molte variabili locali e molte variabili globali.

In base a queste regole di visibilità, possiamo dire che ogni ambiente (identificato da una coppia di parentesi graffe) vede:

1 le proprie risorse locali,
2 quelle definite all'esterno e prima dell'ambiente stesso.

In sostanza, una funzione compreso il main può usare tutte le variabili locali e quelle globali che hanno nomi diversi da quelle locali.


Funzioni con parametri

       

L'operazione con la quale il main (o un'altra funzione chiamante) invia i valori ad una funzione, assegnandoli ai parametri si chiama passaggio di parametri. Come abbiamo già visto, le variabili indicate nell'intestazione della funzione si chiamano parametri formali, le variabili che forniscono i valori ai parametri si chiamano parametri attuali, come si vede in questo programma per il calcolo dell'area di un cerchio.

#include<iostream>
using namespace std;

float sup(int ray){ //ray parametro formale
float superficie; //variabile locale a sup
superficie=3.14*ray*ray;
return superficie;
}

main ( ){
int R=4;
float AREA; //invocazione della funzione sup
AREA=sup(R); //R parametro attuale
cout << AREA;
}


Passaggio di parametri

       

L'operazione a cui sono associate le due liste: dei parametri attuali e dei parametri formali, è detta passaggio dei parametri, questa può avvenire in due modi diversi.

Si ha il passaggio di parametri per valore: quando i valori delle variabili della funzione principale vengono ricopiati nei parametri della funzione; i cambiamenti effettuati sui parametri formali durante l'esecuzione della funzione non influenzano i valori delle variabili nella funzione chiamante.

#include<iostream>
using namespace std;
void fun(int a,int b){
a=a+b;
cout << "fun:" << a<< " " << b << "\n";//fun:5 3
} //fine fun
main (){
int x=2,y=3;
fun (x,y);
cout<< "main:" << x << " " << y;//main:2 3
}//fine main

Nel codice soprascritto si vede come le modifiche apportate alla variabile a dalla funzione non abbiano influenza sul valore di a che nel main() rimane immutato anche dopo la chiamata alla funzione.
Anche nel listato seguente si nota questo comportamento e si evideenzia, inoltre, come parametri formali e parametri attuali possano anche avere nomi (identificatori) differenti.

#include<iostream>
using namespace std;
void fun(int a){
a++;
cout << "fun:" << a << "\n";//fun:3
} //fine fun
main (){
int x=2;
fun (x);
cout << "main:" << x;//main:2
} //fine main

Si ha il passaggio di parametri per indirizzo (per referenza): quando i parametri attuali e formali fanno riferimento alla stessa cella di memoria, cioè allo stesso indirizzo di memoria; questo è il caso in cui il programmatore vuole permettere che i cambiamenti di valore ai parametri durante l'esecuzione della funzione possano riguardare anche i valori delle variabili corrispondenti nella funzione chiamante.

#include<iostream>
using namespace std;
void fun(int &a,int &b){
a=a+b;
cout << "fun:" << a << " " << b << "\n";//fun:5 3
} //fine fun
main (){
int x=2,y=3;
fun (x,y);
cout << "main:" << x << " " << y;//main:5 3
} //fine main

Qui si vede come i parametri passati alla funzione abbiano il completo controllo delle corrispondenti variabili presenti nel main().


Prototipi di funzione

       

Le funzioni possono comparire in qualsiasi ordine nel file sorgente; esse però devono essere definite prima di essere utilizzate dal main o da altre funzioni.
L'ordine in cui le funzioni sono definite è quindi importante: infatti la funzione non può richiamare un'altra se quest'ultima non è stata ancora definita.
Per questa ragione e per una migliore organizzazione del programma, è opportuno inserire le funzioni nel codice sorgente attraverso due fasi distinte:

A Dichiarazione della funzione per mezzo del suo prototipo.
B Definizione (o implementazione) della funzione.

La dichiarazione della funzione che si intende usare nel programma, di norma va imposta in testa al programma subito dopo le direttive di inclusione ed assieme alle variabili globali. Tramite la dichiarazione, il programmatore si limita ad comunicare al compilatore la sua intenzione di usare una funzione che restituisca un dato di un certo tipo e che utilizza determinati parametri, riservandosi di specificare il codice in seguito, nella fase di definizione. Questa dichiarazione si chiama prototipo della funzione. La sintassi per la dichiarazione di un prototipo è:

tipo nomeFunzione(elencoParametri);

La definizione(o implementazione) della funzione, con le istruzioni che la compongono viene normalmente posta dopo la funzione main.

All'inizio del programma le funzioni vengono soltanto dichiarate specificandone per ciascuna il tipo restituito, il nome e i parametri richiesti. In questo modo si ottiene una semplice elencazione dei prototipi che facilita la comprensione del programma.