SlideShare a Scribd company logo
INFORMATICA Sottoprogrammi
Sottoprogrammi Quando si scrive un programma succede spesso che si debba eseguire numerose   volte una stessa sequenza di istruzioni, sugli stessi dati o su dati diversi. Supponiamo di dover realizzare un  programma per il gioco degli scacchi: ogni volta che si esegue una mossa i pezzi devono essere ridisegnati o nella stessa posizione o nella nuova posizione conseguente alla mossa. Per evitare di riscrivere più volte queste sequenze di istruzioni e per   limitare le dimensioni dei programmi, il linguaggio C dispone di una particolare struttura chiamata funzione
Sottoprogrammi Una  funzione  è un modulo di programma la cui esecuzione può essere invocata più volte nel corso del programma principale. La funzione può calcolare uno o più valori, che saranno comunicati al programma   chiamante al termine della sua esecuzione, oppure può eseguire azioni generiche (come, ad esempio, l’aggiornamento della base dati). Il nome  funzione  deriva dall'evidente analogia con le funzioni intese in senso matematico. Poiché la  funzione  fornisce un valore, ad essa è associato un  tipo .
Sottoprogrammi Altra giustificazione all’esistenza dei sottoprogrammi deriva dal fatto che un programma può consistere di decine di migliaia di istruzioni. Sebbene fattibile, una soluzione “monolitica” del problema è inefficiente in quanto complicata da “ debuggare  ” e difficile da leggere. Per questo si tende a organizzare il programma complessivo in “ moduli  ” ognuno dei quali tratta e risolve solo una parte del problema. In altre parole si adotta, per la soluzione del problema, il cosiddetto “approccio  top-down   ”
Approccio top-down Il problema è decomposto in una sequenza di sottoproblemi più semplici. Quest’azione è ripetibile su più livelli, ovvero ogni sottoproblema può a sua volta essere decomposto in una sequenza di sottoproblemi. La decomposizione prosegue sino a che si ritiene che la sequenza è composta ormai solamente da “ sottoproblemi terminali  ”, ovvero da sottoproblemi risolvibili in modo “semplice”.
Approccio top-down Esempio: pulizia di una casa
Approccio top-down I linguaggi di programmazione permettono di suddividere le operazioni in modo simile tramite sottoprogrammi. La sequenza dei sottoproblemi terminali si traduce in una sequenza di sottoprogrammi. Così pure, poiché una  funzione  può invocare un’altra  funzione , la gerarchia delle operazioni si traduce in una gerarchia di sottoprogrammi. Genericamente si chiamano  procedure   i   sottoprogrammi che NON ritornano un risultato,  funzioni  i sottoprogrammi che ritornano un risultato (di qualche tipo primitivo o non).
Funzioni La forma generale è: tipo nome_funzione (parametro_1,  .........  , parametro_n) { /*  definizione delle costanti e delle variabili usate nella funzione */ /*  attenzione! Saranno visibili solo nella funzione!  */   /*  istruzioni della funzione  */ return ( valore ) } L'istruzione  return  provoca la restituzione del controllo al programma   chiamante nel quale  valore   viene associato al nome della funzione che pertanto appare come una normale variabile.
Funzioni La struttura è del tutto analoga a quella del programma principale: lo stesso  main  è una funzione, anzi è l'unica che può avere quel nome!  Quando si manda in esecuzione un programma, è come richiedere la valutazione della funzione  main  la quale, a sua volta, può richiamare altre funzioni. Se non si specifica il tipo, il C assume che la funzione sia di tipo   intero ( int ). Capita talvolta che una  funzione  non debba restituire alcun risultato: per questi casi il C prevede un tipo speciale,  void  (“ vacante  ”):  void  nome_funzione  è una funzione che non restituisce nulla.
Funzioni Anche i parametri possono mancare: in questo caso si può usare   il tipo  void , oppure niente (ma le parentesi che delimitano la lista   dei parametri devono essere sempre presenti). Un modo per definire il programma principale è quindi int main (void) { /*  corpo del programma  */ }   return  può essere presente una o più volte o anche nessuna: se manca, la funzione termina dopo l'ultima istruzione.
Funzioni Nel corso del programma principale, per far eseguire la funzione, è sufficiente “ nominarla  ” con gli eventuali parametri. Esempio: programma per disegnare un rettangolo di asterischi come il seguente: ************ *  *  *  *  *  *  ************  La prima e l'ultima riga contengono una sequenza di 12  asterischi , mentre le 3 righe centrali sono formate da un  asterisco   seguito da 10 caratteri di  spazio  e da un altro  asterisco .
Esempio Possiamo organizzare il programma in modo tale da avere una funzione, che chiameremo  testacoda , per le righe estreme ed un'altra funzione,  corporett , per le righe intermedie. #include <stdio.h>   void testacoda(void)   {  printf (&quot;************\n&quot;);  /*  visualizza 12 asterischi  */ return; } void corporett(void)  { printf (&quot;*  *\n&quot;);  /* visualizza *, poi 10 spazi, poi *  */ return; }
Esempio main() { testacoda(); corporett(); corporett(); corporett(); testacoda(); } L'esecuzione del programma partirà dalla prima istruzione   del programma principale che è un'invocazione alla procedura   testacoda .  Il controllo è poi trasferito alla prima istruzione della  funzione , al termine il controllo ritorna al programma principale all'istruzione successiva alla chiamata, ecc.
Esempio Esempio:   funzione che legge un numero intero da tastiera e lo restituisce al programma chiamante. #include <stdio.h> int lettura (void)  { int dato_letto; printf (“\nIntroduci un intero:  &quot;); scanf (&quot;%d&quot;, &dato_letto); return (dato_letto); } main()  { int dato; dato = lettura(); printf (&quot;\nIl dato e': %d\n&quot;, dato); }
Funzioni con parametri Affinché le funzioni possano essere utilizzate più volte nello stesso programma o in programmi differenti è necessario che possano operare su valori diversi dei dati ad ogni chiamata.  Il C prevede la trasmissione di parametri da un modulo ad   un altro: il programma che utilizza una funzione dichiara nella chiamata il valore dei dati. Nell'esempio precedente (rettangolo), si potrebbero generalizzare le funzioni consentendo all'utente di specificare le dimensioni del rettangolo, la posizione dello stesso rispetto ai bordi dello schermo, il carattere da utilizzare per la stampa al posto di  ‘*’ , ecc.
Passaggio dei parametri “ by value” Affinché una funzione possa “ricevere” dati dal programma chiamante, occorre che essi siano indicati  tra parentesi  nella dichiarazione di funzione, ognuno con il proprio tipo, così: tipo_parametro  identificatore  e separati da “ , ” (virgola) tra di loro. Il programma richiama una funzione parametrizzata facendo seguire al   nome della funzione stessa la lista degli identificatori dei dati racchiusi tra parentesi. Generalmente gli identificatori usati nel programma chiamante sono diversi da   quelli usati nella funzione:  la corrispondenza è basata solo sull'ordine   nella lista .
Passaggio dei parametri “ by value” Esempio: funzione che calcola la media tra due numeri interi. #include <stdio.h> int media (int val1,  int val2)   { int risul; risul = (val1 + val2) / 2;  /*  divisione tra interi!  */ return (risul); }   int main(void)  { int primo, secondo, finale; printf (“\nIntroduci due numeri interi: “); scanf (“%d%d”, &primo, &secondo); finale = media (primo, secondo); printf (“\nLa media tra %d e %d è: %d”, primo, secondo, finale); }
Passaggio dei parametri “ by value” Le variabili che compaiono nella dichiarazione di funzione (nell'esempio  val1  e  val2 ), sono detti  parametri formali. Le variabili che compaiono nella chiamata, (nell’esempio  primo  e  secondo ), sono detti  parametri effettivi. Quando, in seguito al richiamo di funzione, il controllo del programma passa   a quest'ultima, prima dell'esecuzione delle istruzioni viene attivato un meccanismo per cui i valori contenuti nei  parametri effettivi  sono  copiati nei  parametri formali   corrispondenti interni alla funzione.
Passaggio dei parametri “ by value” Osservazioni: Il numero e il tipo dei  parametri formali   deve essere uguale  al numero e al   tipo dei  parametri effettivi , perché l'azione di copiatura viene eseguita in modo meccanico durante l'esecuzione. La modifica di un  parametro formale   in una funzione non provoca alcun effetto sul corrispondente  parametro effettivo  del programma   chiamante. Si dice che i parametri sono trasferiti  per valore  ( by value ). Questo meccanismo restringe il campo di propagazione degli errori: non è possibile che in una funzione,   per errore, si alterino variabili non appartenenti alla funzione stessa. Si dice che si ha la protezione dagli  effetti collaterali   ( side effects ).
Passaggio dei parametri “ by reference” Può succedere che una funzione debba restituire al programma chiamante  più di un risultato  o comunque effettuare  alterazioni ai dati  definiti nel programma   chiamante. Il meccanismo di passaggio “ by value   ” funziona solo nella direzione programma chiamante-funzione e non viceversa! Per restituire dati si deve ricorrere ad un nuovo tipo di dati, il tipo  puntatore , e agli operatori ad esso connessi. Si consideri l'assegnazione: dato1 = dato2; il cui significato è: “copia il valore contenuto nel contenitore  dato2  e scrivilo nel contenitore  dato1 ”.
Passaggio dei parametri “ by reference” In linguaggio macchina gli identificatori  dato1  e  dato2  non sono altro che gli indirizzi delle celle di memoria che costituiscono il contenitore.  A differenza di altri linguaggi di alto livello, in C è possibile manipolare anche gli indirizzi; l'assegnazione: ind_dato =  & dato2; significa: assegna a  ind_dato  non il contenuto di  dato2 , bensì l' indirizzo  del contenitore (cioè della variabile)  dato2  (si dice che  ind_dato  “ punta  ” a  dato2 ).
Passaggio dei parametri “ by reference” In questo caso quindi  &  è un  operatore d'indirizzo . Affinché l'assegnazione  ind_dato = &dato2; sia lecita,  ind_dato  deve essere definito come “ puntatore allo stesso tipo di   dato2 ”: per realizzare questa operazione occorre semplicemente anteporre l'operatore “ * ” all'identificatore, così: int dato2;  /*  Definizione di un intero  */ int  * ind_dato;  /*  Definizione di un puntatore a un intero  */
ind_dato  quindi potrà contenere solo l’ indirizzo  di una variabile intera e non un valore come le altre variabili. Occorre ancora rilevare come l’operatore  *  anteposto ad un puntatore abbia il significato di: “ scrivi, non nella variabile puntatore, ma nell’indirizzo da questa indicato”. Viene quindi realizzato il meccanismo di “ indirizzamento indiretto”   ben noto in tutti i linguaggi di basso livello. Come possiamo utilizzare questo nuovo tipo di dato? Passaggio dei parametri “ by reference”
Passaggio dei parametri “ by reference” Si consideri il seguente programma: int dato2; int *ind_dato;   main() { ind_dato = &dato2;  /* assegna l'indirizzo  di dato2 */ dato2 = 5; *ind_dato = 5; } Le ultime due assegnazioni sono del tutto equivalenti: assegna 5 alla variabile  dato2 .
È possibile quindi usare questo tipo di dato per aggirare la protezione messa in atto dal meccanismo di passaggio dei parametri a una funzione. Infatti, trasmettendo alla funzione non il valore di un dato ma il suo  indirizzo  (cioè anteponendo l’operatore  &  al nome della variabile), si potrà, all’interno della funzione, scrivere direttamente  in quell’indirizzo  (che però si trova nel programma chiamante!) trasmettendone pertanto implicitamente il valore al programma chiamante. Pertanto nella parentesi tonda, insieme ai parametri passati  by value , ci possono essere altri parametri (indirizzi dei dati dichiarati nel modulo chiamante) utili per trasmettere indietro quanti risultati si desidera. Passaggio dei parametri “ by reference”
Passaggio dei parametri “ by reference” Ovvero, se si utilizzano i puntatori come parametri, ogni variazione del  parametro formale  effettuato nella funzione si ripercuote direttamente sul  parametro effettivo , quindi  parametro formale  e  parametro effettivo in questo caso  sono la stessa cosa! E’ possibile quindi “ restituire  ” un risultato al programma chiamante: questo metodo di passaggio dei parametri è detto  per riferimento   ( by reference ) . Poiché  parametro formale  e  parametro effettivo  sono la stessa cosa è evidente che viene meno la protezione dagli  effetti collaterali   ( side effects ) .
Passaggio dei parametri “ by reference” Esempio - Programma per leggere da terminale un numero reale e calcolare il quadrato e il cubo, visualizzando i risultati. Soluzione - Si realizza una funzione che, dato un numero, ne calcola il quadrato e il cubo. Il numero è un parametro che passa dal programma chiamante alla funzione  by value , mentre i risultati (quadrato e cubo) devono essere restituiti al programma chiamante  by reference .
Passaggio dei parametri “ by reference” #include <stdio.h> void calcola (double valore, double *quad_val, double *cub_val) { /*  corpo della funzione  */ *quad_val = valore * valore; *cub_val = *quad_val * valore; return; }   int main(void) { double un_dato, quadrato, cubo; printf (“\nIntroduci un dato reale: &quot;); scanf (&quot;%lf&quot;, &un_dato); calcola (un_dato, &quadrato, &cubo); printf (“\nIl quadrato di %lf è: %lf,&quot;, un_dato, quadrato); printf (&quot; il cubo è: %f &quot;, cubo); }
Passaggio dei parametri “ by reference” Osservazioni : Nella funzione  calcola , l'operatore  *  davanti a  quad_val  e  cub_val  indica che questi parametri sono passati per indirizzo. Nel corpo della funzione, l'istruzione *quad_val = valore * valore; significa “calcola il prodotto e assegnalo alla variabile il cui indirizzo è  quad_val ”. Il programma chiamante passa gli indirizzi delle variabili  quadrato  e  cubo  anteponendo agli identificatori l'operatore  & .
Definizione di una funzione Non è lecito inserire una funzione all'interno di un’altra, quindi le funzioni non possono essere “ annidate  ”. Una funzione può però essere richiamata da altre funzioni purché siano soddisfatte alcune condizioni e può richiamare sempre se stessa  ( recursione ) . Non è indispensabile che la definizione delle funzioni preceda sempre il  main , tuttavia la condizione affinché si possa richiamare una funzione è che sia già stata  definita  oppure  dichiarata .
Prototipo di una funzione La  dichiarazione  di una funzione è costituita dalla sua intestazione, cioé dal tipo, nome e lista dei parametri formali che è anche detta  prototipo  della funzione.  Il compilatore, disponendo del  prototipo , può effettuare i controlli di congruenza sia sul tipo della funzione che sul tipo e sul numero dei parametri. Pertanto il  prototipo  può apparire più volte all'interno del programma. La definizione invece deve essere unica.
Esempio #include <stdio.h> float media (int primo, int secondo);  /* prototipo  della funzione */ main()   { int dato1, dato2; printf (“\nIntroduci due interi: &quot;); scanf (&quot;%d%d&quot;, &dato1, &dato2); printf (“\nMedia tra %d e %d: %f&quot;,dato1,dato2, media(dato1,dato2)); } float media (int primo, int secondo)  { float somma; somma = primo + secondo;  return (somma / 2.0); }
Osservazioni sull'uso dei parametri   Nelle funzioni si potrebbero omettere i parametri e utilizzare variabili globali: questo comportamento è sconsigliabile perchè in questo modo si deve tener conto ovunque dell'utilizzo che ne vien fatto   e possono prodursi “ effetti collaterali  ”. Il passaggio dei parametri “ per valore  ” è sempre da preferire perchè permette la massima elasticità senza  effetti collaterali . Il passaggio dei parametri “ per riferimento  ” è indispensabile quando si devono restituire dei risultati al programma chiamante: si mantiene la versatilità della parametrizzazione ma si possono avere  effetti collaterali  poiché, nel momento della chiamata, si crea una “identità” tra  parametri formali  ed  effettivi .  Per riferimento  si possono passare solo variabili: non sono accettabili costanti od espressioni.
Osservazioni sull'uso dei parametri E’ buona norma dichiarare  globali  solo le variabili che rappresentano la base dati di un programma e sono manipolate da tutti i moduli.  Conviene che le variabili  globali  compaiano come parametri nella chiamata   di una funzione solo se ciò migliora la leggibilità del programma, rendendo   evidente su quali variabili opera quella funzione. Sono invece da dichiarare  locali  tutti quegli identificatori a cui si fa riferimento solo all'interno della singola funzione. Per una migliore leggibilità del programma, è meglio usare nomi diversi per variabili  locali   e  globali  anche se non è affatto necessario.
Vettori come parametri di una funzione Nella definizione di una funzione in cui compare come parametro un vettore non è indispensabile che di questo sia definita la dimensione: è sufficiente indicare il carattere di vettore del parametro (cioè  [] ): int nome (int vett[], int lung,...) Infatti è nel programma chiamante che deve essere riservata l’area fisica   di memoria che ospita il vettore, nella funzione il compilatore deve gestire solamente gli indirizzi dei singoli elementi. Nel programma chiamante il vettore è passato semplicemente indicandone il nome (senza  [] ).
Vettori come parametri di una funzione Nelle chiamate di funzioni, per i vettori,   il compilatore C  forza automaticamente il passaggio “ per riferimento  ”. La ragione del passaggio “ by reference  ” è conseguenza del fatto che il nome del vettore indica l’indirizzo del primo elemento. Nella funzione si usa il vettore con le stesse modalità con cui lo si usa nel  main , al contrario di quanto succede per le variabili scalari, che devono essere gestite in funzione del modo col quale è stato passato il parametro. Pertanto g li elementi di un vettore passato come argomento possono essere modificati nella funzione e non c’è protezione dagli “ effetti collaterali  ”!
Vettori come parametri di una funzione Poiché come sempre in C non ci sono controlli sugli indici, è buona norma passare a una funzione  insieme al vettore anche la sua dimensione . Il programmatore può così controllare che all’interno della funzione non si trasbordi fuori dall’area di memoria dedicata al vettore. Esempio: scrivere una funzione  nonnull   che ritorni il numero di elementi non nulli di un vettore di interi passato come parametro.
  int nonnull (int vett[], int dim) { int ind, n=0; for (ind = 0; ind < dim;  i++) {   if (vett[ind] != 0)    n++;     } return (n); } Esercizio Per risolvere il problema è necessario conoscere la dimensione del vettore Se modificassi vett[ ] dentro la funzione, il valore sarebbe  modificato anche nel programma chiamante
Esercizio Programma che legge da tastiera 20 interi e li salva in un vettore. Ne elimina i doppioni e visualizza i dati del vettore prima e dopo l'eliminazione dei doppi. #include <stdio.h> #define NUMDATI 20 /*  prototipi  */ void compatta (int vett[], int inizio, int *n_dati); void visualizza (int vett[], int n_dati); main() { int vett_dati[NUMDATI]; int ind, ind_aux; int attuali;
Esercizio /* legge i dati da tastiera */ printf (“\nIntroduci %d dati:\n&quot;,NUMDATI); for (ind = 0; ind < NUMDATI; ind++) { printf (“\nDato n. %2d = &quot;, (ind+1)); /* la numerazione parte da 1 */ scanf (&quot;%d&quot;, &vett_dati[ind]); } attuali = NUMDATI;  printf (“\nDati introdotti:\n&quot;); visualizza (vett_dati, attuali);
Esercizio for (ind = 0; ind < (attuali-1); ind++) { for (ind_aux=(ind + 1);  ind_aux<attuali; ind_aux++) { if (vett_dati[ind] == vett_dati[ind_aux]) { compatta (vett_dati, ind_aux, &attuali); ind_aux--; } } } printf (“\nDati rimasti dopo l’eliminazione dei dati uguali:\n&quot;); visualizza (vett_dati,attuali); }  /*  fine programma main  */
Esercizio /* funzione che elimina l’elemento di indice inizio dal vettore vett  */ /* spostando in avanti di una posizione tutti gli elementi successivi  */ /* aggiorna infine il numero di dati che compongono il vettore  */ /* decrementandolo di una unità  */ void compatta(int vett[],  int inizio, int *n_dati) { int posiz; for (posiz = inizio;  posiz < (*n_dati - 1); posiz++) vett [posiz] = vett [posiz + 1];  (*n_dati)--;  /*  decrementa n_dati  */ return; }  /*  fine funzione compatta  */
Esercizio /*  Funzione che visualizza sul monitor il valore degli elementi  del  */ /*  vettore vett composto da n_dati elementi.  */ /*  I dati sono scritti uno per riga nel formato:  */ /*  Vettore[xx] = valore  */ void visualizza(int vett[], int n_dati) { int ind; for (ind = 0; ind < n_dati; ind++) printf (&quot;Vettore[%2d] = %d\n&quot;,   (ind + 1), vett [ind]); return; }  /*  fine funzione visualizza  */
Funzioni di libreria Ogni linguaggio prevede che alcune funzioni siano già predefinite (quindi note al compilatore e comprese nelle sue  librerie ). In C il loro numero è grandissimo (centinaia) e sono suddivise in files a seconda della categoria di appartenenza: ci sono funzioni  matematiche , di  gestione   dell'I/O , di  conversione , ecc. Un certo numero è esplicitamente previsto dall'ANSI C  (funzioni standard) , ma i realizzatori dei compilatori sono soliti aggiungerne molte altre: l'uso di funzioni non standard è di ostacolo alla trasportabilità dei  programmi e quindi deve essere evitato.
Funzioni di libreria L'elenco completo delle funzioni definite sono reperibili nel manuale   ( Reference Manual )  del compilatore usato e a   volte sono rese disponibili anche nell'ambiente di sviluppo dei programmi (ad esempio nell’ help  del  TURBO-C  della  Borland ) Per ciascuna funzione viene di solito specificato se appartiene o meno allo  standard  e quale file di libreria la contiene. Questo file deve essere incluso nel programma mediante la direttiva  #include . In questi file sono contenuti anche i prototipi delle funzioni, che pertanto non devono essere specificati altrove.
Funzioni matematiche Utilizzabili con la direttiva:  #include <math.h> calcola la radice quadrata di  x  con  x  positivo;  x  e il risultato sono   double ; sqrt(x) calcola il valore assoluto di  x ; sia  x  che il risultato sono numeri  floating-point  ; fabs(x) restituisce un numero con parte decimale nulla e parte intera arrotondata al valore intero successivo;  x  e risultato sono  double ; ceil(x) restituisce un numero con parte decimale nulla e parte intera troncata al valore intero;  x  e il risultato sono  double ; floor(x) calcola il valore assoluto di  x ; sia  x  che il risultato sono  long   int ; labs(x) calcola il valore assoluto di  x ; sia  x  che il risultato sono  int ; abs(x)
Funzioni matematiche Utilizzabili con la direttiva:  #include <math.h> calcola il coseno di  x ;  x  è l'angolo espresso in   radianti;  x  e risultato sono  double ; cos(x) calcola  e x ;  x  e il risultato sono  double ; exp(x) calcola  x y : se  x  è negativo  y  deve avere parte decimale nulla;  x ,  y  e risultato sono tutti  double ; pow(x, y) calcola il seno di  x ;  x  è l'angolo espresso in   radianti;  x  e il risultato sono  double ; sin(x) calcola il logaritmo in base 10 di  x  con  x  positivo;  x  e il risultato sono   double ; log10(x) calcola la tangente di  x ;  x  è l'angolo espresso in radianti;  x  e il risultato sono  double ; tan(x) calcola il logaritmo naturale di  x  con  x  positivo;  x  e il risultato sono   double ; log(x)
Funzioni matematiche Utilizzabili con la direttiva:  #include <math.h> calcola il coseno iperbolico di  x ;  x  e risultato sono  double ; cosh(x) calcola l’arcotangente di  x  (tra – π  /2 e + π  /2);  x  e risultato sono  double ; atan(x) arcotangente di  y  /  x  (tra - π  e + π );  x ,  y  e risultato sono  double ; atan2(y, x) calcola il seno iperbolico di  x ;  x  e risultato sono  double ; sinh(x) calcola l’arcocoseno di  x  (tra 0 e + π );  x  e risultato sono  double ; acos(x) calcola la tangente iperbolica di  x ;  x  e il risultato sono  double ; tanh(x) calcola l’arcoseno di  x  (tra – π  /2 e + π   /2);  x  e risultato sono  double ; asin(x)
Funzioni di classificazione dei caratteri Utilizzabili con la direttiva:  #include <ctype.h> restituice  vero  (1) se  c  è un carattere stampabile;  c  è  char ; isprint(c) restituice  vero  (1) se  c  è una cifra;  c  è  char ; isdigit(c) restituice  vero  (1) se  c  è  delete   o un carattere di controllo;  c  è  char ; iscntrl(c) restituice  vero  (1) se  c  è un carattere ASCII valido;  c  è  char ; isascii(c) restituice  vero  (1) se  c  è un carattere alfabetico;  c  è  char ; isalpha(c) restituice  vero  (1) se  c  è un carattere stampabile escluso lo  spazio ;  c  è  char ; isgraph(c) restituice  vero  (1) se  c  è un carattere alfabetico o una cifra;  c  è  char ; isalnum(c)
Funzioni di classificazione dei caratteri Utilizzabili con la direttiva:  #include <ctype.h> restituice  vero  (1) se  c  è un carattere di punteggiatura;  c  è  char ; ispunct(c) restituice  vero  (1) se  c  è  spazio, tab, carriage return, new line, vert. tab, form feed ;  c  è  char ; isspace(c) restituice  vero  (1) se  c  è una cifra esadecimale;  c  è  char ; isxdigit(c) restituice  vero  (1) se  c  è un carattere maiuscolo;  c  è  char ; isupper(c) restituice  vero  (1) se  c  è un carattere minuscolo;  c  è  char ; islower(c)
Funzione per le stringhe Utilizzabili con la direttiva:  #include <string.h> copia  s2  in  s1 ; strcpy(s1, s2) come  strcmp , ma  effettua il confronto per i primi  n  caratteri.   strncmp(s1, s2, n) concatena  s1  con  s2 , copiando i caratteri di  s2  in coda a quelli di  s1  compreso il carattere  NULL  (quello di  s1  viene sovrascritto).  Il valore restituito dalla funzione è un puntatore a  s1 .   strcat(s1, s2) copia i primi  n   caratteri di  s2  in  s1 .   strncpy(s1, s2, n) confronta due stringhe e restituisce   un  valore intero :  negativo  se  s1  precede  s2 ,  zero  se sono uguali,  positivo  se  s1  succede a   s2  nell'ordinamento alfabetico; strcmp(s1, s2) restituice la lunghezza (numero di caratteri) della stringa  s ; il risultato è intero; strlen(s)
Funzione per le stringhe Utilizzabili con la direttiva:  #include <string.h> cerca in  s1  la prima occorrenza di uno dei caratteri presenti in  s2 . Mentre  strchr  cerca un unico carattere questa opera su un gruppo di caratteri: è utile, ad esempio, per cercare i caratteri d'interpunzione in un testo. Il valore restituito è un puntatore alla prima occorrenza di uno dei caratteri in  s1 ,  oppure  NULL  se nessun carattere di  s2  è presente in  s1 .   strpbrk(s1, s2) verifica se  s2  è contenuta in  s1 . Il valore restituito dalla funzione è un puntatore al punto di  s1   dove inizia  s2 , oppure  NULL  se  s2  non è presente; strstr(s1, s2) cerca se il carattere  car  è presente in  str .   Il valore restituito dalla funzione è un puntatore alla prima occorrenza  di  car  in  str , oppure  NULL  se il carattere è assente ; strchr(str, car)
Funzioni di conversione di stringhe Utilizzabili con la direttiva:  #include <stdlib.h> Queste funzioni operano sulle stringhe con le stesse modalità della  scanf : gli spazi neutri iniziali vengono ignorati,   viene cercata una sequenza di caratteri compatibile con il tipo di dato, infine   viene effettuata la conversione.  converte  stringa  in un numero intero lungo;   restituisce il valore convertito in un tipo  long   int ;   atol (stringa) converte  stringa  in un numero intero;   restituisce il valore convertito in un tipo  int ;   atoi (stringa) converte  stringa  in un numero reale in doppia   precisione: restituisce il valore convertito in un tipo  double ; atof (stringa)
Osservazioni sulle funzioni per le stringhe Data la loro natura di tipo “aggregato”, le stringhe non possono essere usate come variabili qualunque. E’ già stato osservato, ad esempio, che non è lecito assegnare una stringa a un altra o “aggiungere” una stringa ad un altra. char s1[20], s2[10], s3[50]; ... s1 = “abcdefg”; s2 = “hijklmno”; s3 = s2; s3 = s1 + s2; Per questi scopi si devono usare le apposite funzioni di libreria. NO!
Osservazioni sulle funzioni per le stringhe NOTE: Non è possibile usare vettori come valori di ritorno delle funzioni di libreria. Esempio:  char finale[40], stringa1[10], stringa2[10]; ... finale = strcat (stringa1, stringa2);   Alcune funzioni possono essere usate “senza risultato”. Esempio: ........ strcpy   (finale, stringa1); strcat (finale, stringa2); NO!

More Related Content

PDF
Batch File Programming
PPT
Bergson
PPT
Spagnolo
PPTX
I sepolcri
PDF
Presentazione corretta algoritmi
PDF
Le donne e il lavoro - altro spread da eliminare
PDF
La rivoluzione francese
Batch File Programming
Bergson
Spagnolo
I sepolcri
Presentazione corretta algoritmi
Le donne e il lavoro - altro spread da eliminare
La rivoluzione francese

What's hot (20)

PDF
Come leggere la poesia
PDF
Programmazione in C (corso 12BHD Informatica)
PPTX
Parameter passing to_functions_in_c
PPTX
AGENDA 2030 n.7.pptx
DOCX
Gabriele d'Annunzio
PPT
La Parabola
PDF
Embedded C - Lecture 3
PPT
Il termovalorizzatore
PPT
Napoleone Bonaparte - Età napoleonica
PDF
Codage de l'information
PPT
L'italia fascista
PPT
Europa alla vigilia della grande guerra
PPT
Memory allocation in c
PDF
TD sur les fonctions en Python
PPTX
Lezione d'Annunzio.pptx
PPT
decadentismo-e-simbolismo.ppt
PPTX
Rivoluzione francese
PPT
Dal latino alle lingue volgari
PPSX
Files in c++
PPT
Nietzsche 2
Come leggere la poesia
Programmazione in C (corso 12BHD Informatica)
Parameter passing to_functions_in_c
AGENDA 2030 n.7.pptx
Gabriele d'Annunzio
La Parabola
Embedded C - Lecture 3
Il termovalorizzatore
Napoleone Bonaparte - Età napoleonica
Codage de l'information
L'italia fascista
Europa alla vigilia della grande guerra
Memory allocation in c
TD sur les fonctions en Python
Lezione d'Annunzio.pptx
decadentismo-e-simbolismo.ppt
Rivoluzione francese
Dal latino alle lingue volgari
Files in c++
Nietzsche 2
Ad

Similar to 7 Sottoprogrammi (20)

PPT
07 1 funzioni
PDF
Lezione 12 (28 marzo 2012)
PDF
Lezione 12 (28 marzo 2012)
PPTX
Ripasso funzioni
PDF
05 - Programmazione: Funzioni
PDF
Lezione 5 (7 marzo 2012)
PPTX
Funzioni
PDF
Esercitazione 1 (27 febbraio 2012)
ODP
Programmazione Top Down in C++
PDF
08 - Programmazione: Passaggio valori tra funzioni per riferimenti
PDF
Lezione 10 (21 marzo 2012)2
PDF
Esercitazione 3 (14 marzo 2012)
PDF
Lezione 16 (2 aprile 2012)
PPTX
La scomposizione in sotto programmi in C++.pptx
PPTX
La metodologia Top - Down - applicazione al C++
PPSX
Informatica di base
PDF
Esercitazione 4 (19 marzo 2012)
PPT
3 Linguaggioc
PDF
Lezione 12 (28 marzo 2012) funzioni memoria - puntatori
PDF
Sistemi Operativi: Il kernel linux - Lezione 06
07 1 funzioni
Lezione 12 (28 marzo 2012)
Lezione 12 (28 marzo 2012)
Ripasso funzioni
05 - Programmazione: Funzioni
Lezione 5 (7 marzo 2012)
Funzioni
Esercitazione 1 (27 febbraio 2012)
Programmazione Top Down in C++
08 - Programmazione: Passaggio valori tra funzioni per riferimenti
Lezione 10 (21 marzo 2012)2
Esercitazione 3 (14 marzo 2012)
Lezione 16 (2 aprile 2012)
La scomposizione in sotto programmi in C++.pptx
La metodologia Top - Down - applicazione al C++
Informatica di base
Esercitazione 4 (19 marzo 2012)
3 Linguaggioc
Lezione 12 (28 marzo 2012) funzioni memoria - puntatori
Sistemi Operativi: Il kernel linux - Lezione 06
Ad

More from guest60e9511 (12)

PPT
2 Rappresentazione Dei Dati
PDF
Codifica
PPT
13 Puntatori E Memoria Dinamica
PPT
12 Struct
PPT
11 I File
PPT
10 Typedef Enum
PPT
9 Altre Istruzioni Di I O
PPT
8 Algoritmi
PPT
6 Vettori E Matrici
PPT
5 Strutture Iterative
PPT
4 Strutture Condizionali
PPT
4 Algebra Di Boole
2 Rappresentazione Dei Dati
Codifica
13 Puntatori E Memoria Dinamica
12 Struct
11 I File
10 Typedef Enum
9 Altre Istruzioni Di I O
8 Algoritmi
6 Vettori E Matrici
5 Strutture Iterative
4 Strutture Condizionali
4 Algebra Di Boole

7 Sottoprogrammi

  • 2. Sottoprogrammi Quando si scrive un programma succede spesso che si debba eseguire numerose volte una stessa sequenza di istruzioni, sugli stessi dati o su dati diversi. Supponiamo di dover realizzare un programma per il gioco degli scacchi: ogni volta che si esegue una mossa i pezzi devono essere ridisegnati o nella stessa posizione o nella nuova posizione conseguente alla mossa. Per evitare di riscrivere più volte queste sequenze di istruzioni e per limitare le dimensioni dei programmi, il linguaggio C dispone di una particolare struttura chiamata funzione
  • 3. Sottoprogrammi Una funzione è un modulo di programma la cui esecuzione può essere invocata più volte nel corso del programma principale. La funzione può calcolare uno o più valori, che saranno comunicati al programma chiamante al termine della sua esecuzione, oppure può eseguire azioni generiche (come, ad esempio, l’aggiornamento della base dati). Il nome funzione deriva dall'evidente analogia con le funzioni intese in senso matematico. Poiché la funzione fornisce un valore, ad essa è associato un tipo .
  • 4. Sottoprogrammi Altra giustificazione all’esistenza dei sottoprogrammi deriva dal fatto che un programma può consistere di decine di migliaia di istruzioni. Sebbene fattibile, una soluzione “monolitica” del problema è inefficiente in quanto complicata da “ debuggare ” e difficile da leggere. Per questo si tende a organizzare il programma complessivo in “ moduli ” ognuno dei quali tratta e risolve solo una parte del problema. In altre parole si adotta, per la soluzione del problema, il cosiddetto “approccio top-down ”
  • 5. Approccio top-down Il problema è decomposto in una sequenza di sottoproblemi più semplici. Quest’azione è ripetibile su più livelli, ovvero ogni sottoproblema può a sua volta essere decomposto in una sequenza di sottoproblemi. La decomposizione prosegue sino a che si ritiene che la sequenza è composta ormai solamente da “ sottoproblemi terminali ”, ovvero da sottoproblemi risolvibili in modo “semplice”.
  • 6. Approccio top-down Esempio: pulizia di una casa
  • 7. Approccio top-down I linguaggi di programmazione permettono di suddividere le operazioni in modo simile tramite sottoprogrammi. La sequenza dei sottoproblemi terminali si traduce in una sequenza di sottoprogrammi. Così pure, poiché una funzione può invocare un’altra funzione , la gerarchia delle operazioni si traduce in una gerarchia di sottoprogrammi. Genericamente si chiamano procedure i sottoprogrammi che NON ritornano un risultato, funzioni i sottoprogrammi che ritornano un risultato (di qualche tipo primitivo o non).
  • 8. Funzioni La forma generale è: tipo nome_funzione (parametro_1, ......... , parametro_n) { /* definizione delle costanti e delle variabili usate nella funzione */ /* attenzione! Saranno visibili solo nella funzione! */   /* istruzioni della funzione */ return ( valore ) } L'istruzione return provoca la restituzione del controllo al programma chiamante nel quale valore viene associato al nome della funzione che pertanto appare come una normale variabile.
  • 9. Funzioni La struttura è del tutto analoga a quella del programma principale: lo stesso main è una funzione, anzi è l'unica che può avere quel nome! Quando si manda in esecuzione un programma, è come richiedere la valutazione della funzione main la quale, a sua volta, può richiamare altre funzioni. Se non si specifica il tipo, il C assume che la funzione sia di tipo intero ( int ). Capita talvolta che una funzione non debba restituire alcun risultato: per questi casi il C prevede un tipo speciale, void (“ vacante ”): void nome_funzione è una funzione che non restituisce nulla.
  • 10. Funzioni Anche i parametri possono mancare: in questo caso si può usare il tipo void , oppure niente (ma le parentesi che delimitano la lista dei parametri devono essere sempre presenti). Un modo per definire il programma principale è quindi int main (void) { /* corpo del programma */ }   return può essere presente una o più volte o anche nessuna: se manca, la funzione termina dopo l'ultima istruzione.
  • 11. Funzioni Nel corso del programma principale, per far eseguire la funzione, è sufficiente “ nominarla ” con gli eventuali parametri. Esempio: programma per disegnare un rettangolo di asterischi come il seguente: ************ * * * * * * ************ La prima e l'ultima riga contengono una sequenza di 12 asterischi , mentre le 3 righe centrali sono formate da un asterisco seguito da 10 caratteri di spazio e da un altro asterisco .
  • 12. Esempio Possiamo organizzare il programma in modo tale da avere una funzione, che chiameremo testacoda , per le righe estreme ed un'altra funzione, corporett , per le righe intermedie. #include <stdio.h>   void testacoda(void) { printf (&quot;************\n&quot;); /* visualizza 12 asterischi */ return; } void corporett(void) { printf (&quot;* *\n&quot;); /* visualizza *, poi 10 spazi, poi * */ return; }
  • 13. Esempio main() { testacoda(); corporett(); corporett(); corporett(); testacoda(); } L'esecuzione del programma partirà dalla prima istruzione del programma principale che è un'invocazione alla procedura testacoda . Il controllo è poi trasferito alla prima istruzione della funzione , al termine il controllo ritorna al programma principale all'istruzione successiva alla chiamata, ecc.
  • 14. Esempio Esempio: funzione che legge un numero intero da tastiera e lo restituisce al programma chiamante. #include <stdio.h> int lettura (void) { int dato_letto; printf (“\nIntroduci un intero: &quot;); scanf (&quot;%d&quot;, &dato_letto); return (dato_letto); } main() { int dato; dato = lettura(); printf (&quot;\nIl dato e': %d\n&quot;, dato); }
  • 15. Funzioni con parametri Affinché le funzioni possano essere utilizzate più volte nello stesso programma o in programmi differenti è necessario che possano operare su valori diversi dei dati ad ogni chiamata. Il C prevede la trasmissione di parametri da un modulo ad un altro: il programma che utilizza una funzione dichiara nella chiamata il valore dei dati. Nell'esempio precedente (rettangolo), si potrebbero generalizzare le funzioni consentendo all'utente di specificare le dimensioni del rettangolo, la posizione dello stesso rispetto ai bordi dello schermo, il carattere da utilizzare per la stampa al posto di ‘*’ , ecc.
  • 16. Passaggio dei parametri “ by value” Affinché una funzione possa “ricevere” dati dal programma chiamante, occorre che essi siano indicati tra parentesi nella dichiarazione di funzione, ognuno con il proprio tipo, così: tipo_parametro identificatore e separati da “ , ” (virgola) tra di loro. Il programma richiama una funzione parametrizzata facendo seguire al nome della funzione stessa la lista degli identificatori dei dati racchiusi tra parentesi. Generalmente gli identificatori usati nel programma chiamante sono diversi da quelli usati nella funzione: la corrispondenza è basata solo sull'ordine nella lista .
  • 17. Passaggio dei parametri “ by value” Esempio: funzione che calcola la media tra due numeri interi. #include <stdio.h> int media (int val1, int val2) { int risul; risul = (val1 + val2) / 2; /* divisione tra interi! */ return (risul); }   int main(void) { int primo, secondo, finale; printf (“\nIntroduci due numeri interi: “); scanf (“%d%d”, &primo, &secondo); finale = media (primo, secondo); printf (“\nLa media tra %d e %d è: %d”, primo, secondo, finale); }
  • 18. Passaggio dei parametri “ by value” Le variabili che compaiono nella dichiarazione di funzione (nell'esempio val1 e val2 ), sono detti parametri formali. Le variabili che compaiono nella chiamata, (nell’esempio primo e secondo ), sono detti parametri effettivi. Quando, in seguito al richiamo di funzione, il controllo del programma passa a quest'ultima, prima dell'esecuzione delle istruzioni viene attivato un meccanismo per cui i valori contenuti nei parametri effettivi sono copiati nei parametri formali corrispondenti interni alla funzione.
  • 19. Passaggio dei parametri “ by value” Osservazioni: Il numero e il tipo dei parametri formali deve essere uguale al numero e al tipo dei parametri effettivi , perché l'azione di copiatura viene eseguita in modo meccanico durante l'esecuzione. La modifica di un parametro formale in una funzione non provoca alcun effetto sul corrispondente parametro effettivo del programma chiamante. Si dice che i parametri sono trasferiti per valore ( by value ). Questo meccanismo restringe il campo di propagazione degli errori: non è possibile che in una funzione, per errore, si alterino variabili non appartenenti alla funzione stessa. Si dice che si ha la protezione dagli effetti collaterali ( side effects ).
  • 20. Passaggio dei parametri “ by reference” Può succedere che una funzione debba restituire al programma chiamante più di un risultato o comunque effettuare alterazioni ai dati definiti nel programma chiamante. Il meccanismo di passaggio “ by value ” funziona solo nella direzione programma chiamante-funzione e non viceversa! Per restituire dati si deve ricorrere ad un nuovo tipo di dati, il tipo puntatore , e agli operatori ad esso connessi. Si consideri l'assegnazione: dato1 = dato2; il cui significato è: “copia il valore contenuto nel contenitore dato2 e scrivilo nel contenitore dato1 ”.
  • 21. Passaggio dei parametri “ by reference” In linguaggio macchina gli identificatori dato1 e dato2 non sono altro che gli indirizzi delle celle di memoria che costituiscono il contenitore. A differenza di altri linguaggi di alto livello, in C è possibile manipolare anche gli indirizzi; l'assegnazione: ind_dato = & dato2; significa: assegna a ind_dato non il contenuto di dato2 , bensì l' indirizzo del contenitore (cioè della variabile) dato2 (si dice che ind_dato “ punta ” a dato2 ).
  • 22. Passaggio dei parametri “ by reference” In questo caso quindi & è un operatore d'indirizzo . Affinché l'assegnazione ind_dato = &dato2; sia lecita, ind_dato deve essere definito come “ puntatore allo stesso tipo di dato2 ”: per realizzare questa operazione occorre semplicemente anteporre l'operatore “ * ” all'identificatore, così: int dato2; /* Definizione di un intero */ int * ind_dato; /* Definizione di un puntatore a un intero */
  • 23. ind_dato quindi potrà contenere solo l’ indirizzo di una variabile intera e non un valore come le altre variabili. Occorre ancora rilevare come l’operatore * anteposto ad un puntatore abbia il significato di: “ scrivi, non nella variabile puntatore, ma nell’indirizzo da questa indicato”. Viene quindi realizzato il meccanismo di “ indirizzamento indiretto” ben noto in tutti i linguaggi di basso livello. Come possiamo utilizzare questo nuovo tipo di dato? Passaggio dei parametri “ by reference”
  • 24. Passaggio dei parametri “ by reference” Si consideri il seguente programma: int dato2; int *ind_dato;   main() { ind_dato = &dato2; /* assegna l'indirizzo di dato2 */ dato2 = 5; *ind_dato = 5; } Le ultime due assegnazioni sono del tutto equivalenti: assegna 5 alla variabile dato2 .
  • 25. È possibile quindi usare questo tipo di dato per aggirare la protezione messa in atto dal meccanismo di passaggio dei parametri a una funzione. Infatti, trasmettendo alla funzione non il valore di un dato ma il suo indirizzo (cioè anteponendo l’operatore & al nome della variabile), si potrà, all’interno della funzione, scrivere direttamente in quell’indirizzo (che però si trova nel programma chiamante!) trasmettendone pertanto implicitamente il valore al programma chiamante. Pertanto nella parentesi tonda, insieme ai parametri passati by value , ci possono essere altri parametri (indirizzi dei dati dichiarati nel modulo chiamante) utili per trasmettere indietro quanti risultati si desidera. Passaggio dei parametri “ by reference”
  • 26. Passaggio dei parametri “ by reference” Ovvero, se si utilizzano i puntatori come parametri, ogni variazione del parametro formale effettuato nella funzione si ripercuote direttamente sul parametro effettivo , quindi parametro formale e parametro effettivo in questo caso sono la stessa cosa! E’ possibile quindi “ restituire ” un risultato al programma chiamante: questo metodo di passaggio dei parametri è detto per riferimento ( by reference ) . Poiché parametro formale e parametro effettivo sono la stessa cosa è evidente che viene meno la protezione dagli effetti collaterali ( side effects ) .
  • 27. Passaggio dei parametri “ by reference” Esempio - Programma per leggere da terminale un numero reale e calcolare il quadrato e il cubo, visualizzando i risultati. Soluzione - Si realizza una funzione che, dato un numero, ne calcola il quadrato e il cubo. Il numero è un parametro che passa dal programma chiamante alla funzione by value , mentre i risultati (quadrato e cubo) devono essere restituiti al programma chiamante by reference .
  • 28. Passaggio dei parametri “ by reference” #include <stdio.h> void calcola (double valore, double *quad_val, double *cub_val) { /* corpo della funzione */ *quad_val = valore * valore; *cub_val = *quad_val * valore; return; }   int main(void) { double un_dato, quadrato, cubo; printf (“\nIntroduci un dato reale: &quot;); scanf (&quot;%lf&quot;, &un_dato); calcola (un_dato, &quadrato, &cubo); printf (“\nIl quadrato di %lf è: %lf,&quot;, un_dato, quadrato); printf (&quot; il cubo è: %f &quot;, cubo); }
  • 29. Passaggio dei parametri “ by reference” Osservazioni : Nella funzione calcola , l'operatore * davanti a quad_val e cub_val indica che questi parametri sono passati per indirizzo. Nel corpo della funzione, l'istruzione *quad_val = valore * valore; significa “calcola il prodotto e assegnalo alla variabile il cui indirizzo è quad_val ”. Il programma chiamante passa gli indirizzi delle variabili quadrato e cubo anteponendo agli identificatori l'operatore & .
  • 30. Definizione di una funzione Non è lecito inserire una funzione all'interno di un’altra, quindi le funzioni non possono essere “ annidate ”. Una funzione può però essere richiamata da altre funzioni purché siano soddisfatte alcune condizioni e può richiamare sempre se stessa ( recursione ) . Non è indispensabile che la definizione delle funzioni preceda sempre il main , tuttavia la condizione affinché si possa richiamare una funzione è che sia già stata definita oppure dichiarata .
  • 31. Prototipo di una funzione La dichiarazione di una funzione è costituita dalla sua intestazione, cioé dal tipo, nome e lista dei parametri formali che è anche detta prototipo della funzione. Il compilatore, disponendo del prototipo , può effettuare i controlli di congruenza sia sul tipo della funzione che sul tipo e sul numero dei parametri. Pertanto il prototipo può apparire più volte all'interno del programma. La definizione invece deve essere unica.
  • 32. Esempio #include <stdio.h> float media (int primo, int secondo); /* prototipo della funzione */ main() { int dato1, dato2; printf (“\nIntroduci due interi: &quot;); scanf (&quot;%d%d&quot;, &dato1, &dato2); printf (“\nMedia tra %d e %d: %f&quot;,dato1,dato2, media(dato1,dato2)); } float media (int primo, int secondo) { float somma; somma = primo + secondo; return (somma / 2.0); }
  • 33. Osservazioni sull'uso dei parametri   Nelle funzioni si potrebbero omettere i parametri e utilizzare variabili globali: questo comportamento è sconsigliabile perchè in questo modo si deve tener conto ovunque dell'utilizzo che ne vien fatto e possono prodursi “ effetti collaterali ”. Il passaggio dei parametri “ per valore ” è sempre da preferire perchè permette la massima elasticità senza effetti collaterali . Il passaggio dei parametri “ per riferimento ” è indispensabile quando si devono restituire dei risultati al programma chiamante: si mantiene la versatilità della parametrizzazione ma si possono avere effetti collaterali poiché, nel momento della chiamata, si crea una “identità” tra parametri formali ed effettivi . Per riferimento si possono passare solo variabili: non sono accettabili costanti od espressioni.
  • 34. Osservazioni sull'uso dei parametri E’ buona norma dichiarare globali solo le variabili che rappresentano la base dati di un programma e sono manipolate da tutti i moduli. Conviene che le variabili globali compaiano come parametri nella chiamata di una funzione solo se ciò migliora la leggibilità del programma, rendendo evidente su quali variabili opera quella funzione. Sono invece da dichiarare locali tutti quegli identificatori a cui si fa riferimento solo all'interno della singola funzione. Per una migliore leggibilità del programma, è meglio usare nomi diversi per variabili locali e globali anche se non è affatto necessario.
  • 35. Vettori come parametri di una funzione Nella definizione di una funzione in cui compare come parametro un vettore non è indispensabile che di questo sia definita la dimensione: è sufficiente indicare il carattere di vettore del parametro (cioè [] ): int nome (int vett[], int lung,...) Infatti è nel programma chiamante che deve essere riservata l’area fisica di memoria che ospita il vettore, nella funzione il compilatore deve gestire solamente gli indirizzi dei singoli elementi. Nel programma chiamante il vettore è passato semplicemente indicandone il nome (senza [] ).
  • 36. Vettori come parametri di una funzione Nelle chiamate di funzioni, per i vettori, il compilatore C forza automaticamente il passaggio “ per riferimento ”. La ragione del passaggio “ by reference ” è conseguenza del fatto che il nome del vettore indica l’indirizzo del primo elemento. Nella funzione si usa il vettore con le stesse modalità con cui lo si usa nel main , al contrario di quanto succede per le variabili scalari, che devono essere gestite in funzione del modo col quale è stato passato il parametro. Pertanto g li elementi di un vettore passato come argomento possono essere modificati nella funzione e non c’è protezione dagli “ effetti collaterali ”!
  • 37. Vettori come parametri di una funzione Poiché come sempre in C non ci sono controlli sugli indici, è buona norma passare a una funzione insieme al vettore anche la sua dimensione . Il programmatore può così controllare che all’interno della funzione non si trasbordi fuori dall’area di memoria dedicata al vettore. Esempio: scrivere una funzione nonnull che ritorni il numero di elementi non nulli di un vettore di interi passato come parametro.
  • 38. int nonnull (int vett[], int dim) { int ind, n=0; for (ind = 0; ind < dim; i++) { if (vett[ind] != 0) n++; } return (n); } Esercizio Per risolvere il problema è necessario conoscere la dimensione del vettore Se modificassi vett[ ] dentro la funzione, il valore sarebbe modificato anche nel programma chiamante
  • 39. Esercizio Programma che legge da tastiera 20 interi e li salva in un vettore. Ne elimina i doppioni e visualizza i dati del vettore prima e dopo l'eliminazione dei doppi. #include <stdio.h> #define NUMDATI 20 /* prototipi */ void compatta (int vett[], int inizio, int *n_dati); void visualizza (int vett[], int n_dati); main() { int vett_dati[NUMDATI]; int ind, ind_aux; int attuali;
  • 40. Esercizio /* legge i dati da tastiera */ printf (“\nIntroduci %d dati:\n&quot;,NUMDATI); for (ind = 0; ind < NUMDATI; ind++) { printf (“\nDato n. %2d = &quot;, (ind+1)); /* la numerazione parte da 1 */ scanf (&quot;%d&quot;, &vett_dati[ind]); } attuali = NUMDATI; printf (“\nDati introdotti:\n&quot;); visualizza (vett_dati, attuali);
  • 41. Esercizio for (ind = 0; ind < (attuali-1); ind++) { for (ind_aux=(ind + 1); ind_aux<attuali; ind_aux++) { if (vett_dati[ind] == vett_dati[ind_aux]) { compatta (vett_dati, ind_aux, &attuali); ind_aux--; } } } printf (“\nDati rimasti dopo l’eliminazione dei dati uguali:\n&quot;); visualizza (vett_dati,attuali); } /* fine programma main */
  • 42. Esercizio /* funzione che elimina l’elemento di indice inizio dal vettore vett */ /* spostando in avanti di una posizione tutti gli elementi successivi */ /* aggiorna infine il numero di dati che compongono il vettore */ /* decrementandolo di una unità */ void compatta(int vett[], int inizio, int *n_dati) { int posiz; for (posiz = inizio; posiz < (*n_dati - 1); posiz++) vett [posiz] = vett [posiz + 1]; (*n_dati)--; /* decrementa n_dati */ return; } /* fine funzione compatta */
  • 43. Esercizio /* Funzione che visualizza sul monitor il valore degli elementi del */ /* vettore vett composto da n_dati elementi. */ /* I dati sono scritti uno per riga nel formato: */ /* Vettore[xx] = valore */ void visualizza(int vett[], int n_dati) { int ind; for (ind = 0; ind < n_dati; ind++) printf (&quot;Vettore[%2d] = %d\n&quot;, (ind + 1), vett [ind]); return; } /* fine funzione visualizza */
  • 44. Funzioni di libreria Ogni linguaggio prevede che alcune funzioni siano già predefinite (quindi note al compilatore e comprese nelle sue librerie ). In C il loro numero è grandissimo (centinaia) e sono suddivise in files a seconda della categoria di appartenenza: ci sono funzioni matematiche , di gestione dell'I/O , di conversione , ecc. Un certo numero è esplicitamente previsto dall'ANSI C (funzioni standard) , ma i realizzatori dei compilatori sono soliti aggiungerne molte altre: l'uso di funzioni non standard è di ostacolo alla trasportabilità dei programmi e quindi deve essere evitato.
  • 45. Funzioni di libreria L'elenco completo delle funzioni definite sono reperibili nel manuale ( Reference Manual ) del compilatore usato e a volte sono rese disponibili anche nell'ambiente di sviluppo dei programmi (ad esempio nell’ help del TURBO-C della Borland ) Per ciascuna funzione viene di solito specificato se appartiene o meno allo standard e quale file di libreria la contiene. Questo file deve essere incluso nel programma mediante la direttiva #include . In questi file sono contenuti anche i prototipi delle funzioni, che pertanto non devono essere specificati altrove.
  • 46. Funzioni matematiche Utilizzabili con la direttiva: #include <math.h> calcola la radice quadrata di x con x positivo; x e il risultato sono double ; sqrt(x) calcola il valore assoluto di x ; sia x che il risultato sono numeri floating-point ; fabs(x) restituisce un numero con parte decimale nulla e parte intera arrotondata al valore intero successivo; x e risultato sono double ; ceil(x) restituisce un numero con parte decimale nulla e parte intera troncata al valore intero; x e il risultato sono double ; floor(x) calcola il valore assoluto di x ; sia x che il risultato sono long int ; labs(x) calcola il valore assoluto di x ; sia x che il risultato sono int ; abs(x)
  • 47. Funzioni matematiche Utilizzabili con la direttiva: #include <math.h> calcola il coseno di x ; x è l'angolo espresso in radianti; x e risultato sono double ; cos(x) calcola e x ; x e il risultato sono double ; exp(x) calcola x y : se x è negativo y deve avere parte decimale nulla; x , y e risultato sono tutti double ; pow(x, y) calcola il seno di x ; x è l'angolo espresso in radianti; x e il risultato sono double ; sin(x) calcola il logaritmo in base 10 di x con x positivo; x e il risultato sono double ; log10(x) calcola la tangente di x ; x è l'angolo espresso in radianti; x e il risultato sono double ; tan(x) calcola il logaritmo naturale di x con x positivo; x e il risultato sono double ; log(x)
  • 48. Funzioni matematiche Utilizzabili con la direttiva: #include <math.h> calcola il coseno iperbolico di x ; x e risultato sono double ; cosh(x) calcola l’arcotangente di x (tra – π /2 e + π /2); x e risultato sono double ; atan(x) arcotangente di y / x (tra - π e + π ); x , y e risultato sono double ; atan2(y, x) calcola il seno iperbolico di x ; x e risultato sono double ; sinh(x) calcola l’arcocoseno di x (tra 0 e + π ); x e risultato sono double ; acos(x) calcola la tangente iperbolica di x ; x e il risultato sono double ; tanh(x) calcola l’arcoseno di x (tra – π /2 e + π /2); x e risultato sono double ; asin(x)
  • 49. Funzioni di classificazione dei caratteri Utilizzabili con la direttiva: #include <ctype.h> restituice vero (1) se c è un carattere stampabile; c è char ; isprint(c) restituice vero (1) se c è una cifra; c è char ; isdigit(c) restituice vero (1) se c è delete o un carattere di controllo; c è char ; iscntrl(c) restituice vero (1) se c è un carattere ASCII valido; c è char ; isascii(c) restituice vero (1) se c è un carattere alfabetico; c è char ; isalpha(c) restituice vero (1) se c è un carattere stampabile escluso lo spazio ; c è char ; isgraph(c) restituice vero (1) se c è un carattere alfabetico o una cifra; c è char ; isalnum(c)
  • 50. Funzioni di classificazione dei caratteri Utilizzabili con la direttiva: #include <ctype.h> restituice vero (1) se c è un carattere di punteggiatura; c è char ; ispunct(c) restituice vero (1) se c è spazio, tab, carriage return, new line, vert. tab, form feed ; c è char ; isspace(c) restituice vero (1) se c è una cifra esadecimale; c è char ; isxdigit(c) restituice vero (1) se c è un carattere maiuscolo; c è char ; isupper(c) restituice vero (1) se c è un carattere minuscolo; c è char ; islower(c)
  • 51. Funzione per le stringhe Utilizzabili con la direttiva: #include <string.h> copia s2 in s1 ; strcpy(s1, s2) come strcmp , ma effettua il confronto per i primi n caratteri. strncmp(s1, s2, n) concatena s1 con s2 , copiando i caratteri di s2 in coda a quelli di s1 compreso il carattere NULL (quello di s1 viene sovrascritto). Il valore restituito dalla funzione è un puntatore a s1 . strcat(s1, s2) copia i primi n caratteri di s2 in s1 . strncpy(s1, s2, n) confronta due stringhe e restituisce un valore intero : negativo se s1 precede s2 , zero se sono uguali, positivo se s1 succede a s2 nell'ordinamento alfabetico; strcmp(s1, s2) restituice la lunghezza (numero di caratteri) della stringa s ; il risultato è intero; strlen(s)
  • 52. Funzione per le stringhe Utilizzabili con la direttiva: #include <string.h> cerca in s1 la prima occorrenza di uno dei caratteri presenti in s2 . Mentre strchr cerca un unico carattere questa opera su un gruppo di caratteri: è utile, ad esempio, per cercare i caratteri d'interpunzione in un testo. Il valore restituito è un puntatore alla prima occorrenza di uno dei caratteri in s1 , oppure NULL se nessun carattere di s2 è presente in s1 . strpbrk(s1, s2) verifica se s2 è contenuta in s1 . Il valore restituito dalla funzione è un puntatore al punto di s1 dove inizia s2 , oppure NULL se s2 non è presente; strstr(s1, s2) cerca se il carattere car è presente in str . Il valore restituito dalla funzione è un puntatore alla prima occorrenza di car in str , oppure NULL se il carattere è assente ; strchr(str, car)
  • 53. Funzioni di conversione di stringhe Utilizzabili con la direttiva: #include <stdlib.h> Queste funzioni operano sulle stringhe con le stesse modalità della scanf : gli spazi neutri iniziali vengono ignorati, viene cercata una sequenza di caratteri compatibile con il tipo di dato, infine viene effettuata la conversione. converte stringa in un numero intero lungo; restituisce il valore convertito in un tipo long int ; atol (stringa) converte stringa in un numero intero; restituisce il valore convertito in un tipo int ; atoi (stringa) converte stringa in un numero reale in doppia precisione: restituisce il valore convertito in un tipo double ; atof (stringa)
  • 54. Osservazioni sulle funzioni per le stringhe Data la loro natura di tipo “aggregato”, le stringhe non possono essere usate come variabili qualunque. E’ già stato osservato, ad esempio, che non è lecito assegnare una stringa a un altra o “aggiungere” una stringa ad un altra. char s1[20], s2[10], s3[50]; ... s1 = “abcdefg”; s2 = “hijklmno”; s3 = s2; s3 = s1 + s2; Per questi scopi si devono usare le apposite funzioni di libreria. NO!
  • 55. Osservazioni sulle funzioni per le stringhe NOTE: Non è possibile usare vettori come valori di ritorno delle funzioni di libreria. Esempio: char finale[40], stringa1[10], stringa2[10]; ... finale = strcat (stringa1, stringa2); Alcune funzioni possono essere usate “senza risultato”. Esempio: ........ strcpy (finale, stringa1); strcat (finale, stringa2); NO!