Il ciclo for



Per impiegare il costrutto for() è necessario utilizzare tre diverse espressioni:

  1. una che viene valutata SEMPRE, prima di valutare le altre due ed il corpo del for() (espr1)
  2. una che viene valutata PRIMA di eseguire il corpo del for(); se il suo valore logico è VERO allora il corpo del for() viene eseguito, altrimenti no (espr2)
  3. una che viene valutata DOPO aver eseguito le istruzioni contenute nel corpo del for() (espr3)



for(espr1;espr2;espr3)
   {corpo_del_ciclo;}


Il ciclo for() viene abitualmente utilizzato quando è noto A PRIORI il numero di volte che il corpo del ciclo deve essere eseguito. Ad esempio, per stampare tutti i caratteri della tastiera, compresi quelli appartenenti alla tabella estesa di codici ASCII, assieme al loro codice ASCII, si può utilizzare un programma di questo tipo:

// inizio codice

#include <stdio.h>

int main()
{char car;
  int i;

 for(i = 32; i<=255; i++)
   {car=i;
    printf("\nIl carattere %c ha codice ASCII %4d",car,i);}

 return 0;}

// fine codice


Sfruttando l'analogia tra caratteri ed interi, possiamo rendere il codice un po' più compatto.


// inizio codice


#include <stdio.h>

int main()
{int car;      // perché ho usato un int e non un char ?

 for(car = 32; car<=255; car++)
   {printf("\nIl carattere %c ha codice ASCII %4d",car,car);}

 return 0;}

// fine codice




















Ora, sfruttando il fatto che la terza espressione viene eseguita dopo il corpo del ciclo SOLO se il ciclo viene eseguito, possiamo rendere il codice ancora più compatto


// inizio codice

#include <stdio.h>

int main()
{int car;

 for(car = 32; car<=255; printf("\nIl carattere %c ha codice ASCII %4d",car++,car))
   {;}

 return 0;}

// fine codice
















La scelta di quale sia il ciclo più adatto al problema da risolvere dipende dalle caratteristiche del problema stesso. In generale i cicli while() e for() sono completamente equivalenti; nella tabella che segue diversi cicli vengono implementati sia con il costrutto while() che con il costrutto for()

while()
for()


while(condizione)
 {istruzioni}


for( ;condizione; )
  {istruzioni}

espressione1;
while(espressione2)
  {espressione3;}
for(espressione1;espressione2;espressione3)

// ciclo infinito
while(1)
  {istruzioni;}

// ciclo infinito
for( ; 1; )
  {istruzioni;}












Usiamo ora il ciclo for per costruire una funzione che serva ad eliminare i problemi che ha getchar() nel gestire l'input di un singolo carattere. La funzione che prepareremo sarà basata su getchar(), e quindi possiederà un buffer di input, ma nel contempo ci restituirà solo un singolo carattere, ovvero il primo del buffer.

Il principio su cui si basa la funzione è il seguente:

  1. l'input viene gestito mediante getchar()
  2. una volta riempito il buffer di input la funzione deve leggere tutti i caratteri del buffer fino ad incontrare il carattere corrispondente al tasto ENTER
  3. trovato questo carattere la funzione termina l'esecuzione e restituisce il primo carattere letto











// file di intestazione myfun.h

#include <stdio.h>


char mygetchar(void);

char mygetchar()
{char leggo,temp;
 leggo =  getchar(); // prelevo dal buffer il primo carattere

// grazie al ciclo elimino i rimanenti
 for( ;(temp=getchar())!=10;)   
    {;}

 return leggo;}

// fine file di intestazione myfun.h


















Ora cerchiamo di compattare un po' il codice ....


// file di intestazione myfun.h

#include <stdio.h>


char mygetchar(void);

char mygetchar()
{char leggo;
 for(leggo =  getchar();getchar()!=10;  ) ; // il corpo del ciclo è diventato un ';'
 return leggo;}

// fine file di intestazione myfun.h














Lasciamo ora da parte i caratteri e concentriamoci su un'implementazione di una funzione effettuata in modo ricorsivo.







Problema: calcolare la potenza di 2 di ordine n


// inizio codice

#include "myfun.h"

int potenza_di_2(int);   // dichiarazione

int main()
{int numero;
 char potenza;
 
 printf("inserisci un carattere numerico, n, ti verrà restituita la potenza di due di ordine n :");
 potenza = mygetchar();

 numero=potenza_di_2(potenza-'48'); // passo da codice ASCII a valore numerico

 return 0;}

int potenza_di_2(int potenza) // implementazione, versione 1
{int numero=1,i;
 for(i = 0;i<potenza; i++)
   numero=2*numero;
 return numero;
}

// fine codice
















int potenza_di_2(int potenza) // implementazione, versione 2
{int numero=1,i;
 for(i = potenza;i > 0; i--)
   numero=2*numero;
 return numero;
}




















int potenza_di_2(int potenza) // implementazione, versione 3
{int numero=1;
 for(;potenza; potenza--) // il valore assunto dalla variabile che era stata passata come parametro
                                     // rappresenta ora la condizione di continuazione
   numero=2*numero;
 return numero;
}














int potenza_di_2(int potenza) // implementazione, versione 3
{
 if(potenza)
   return 2*potenza_di_2(potenza-1);
 else
   return 1;
}



Se potenza vale 3

int potenza_di_2(potenza = 3)
{
 if(3)
   return 2*{
                if(2)
                  return 2*{
                               if(1)
                                 return 2*{
                                              if(0)
                                                return potenza_di_2(-1);
                                              else
                                                return 1;
                                             }
                               else
                                return 1;
                             }
                    else
                 return 1;
               }
 else
   return 1;
}











Domanda: mi potevo permettere un'implementazione ricorsiva simile a quella iterativa presentata nel codice iniziale ? (ovvero con un contatore che cresce da 0 fino ad arrivare a potenza-1 ?)

CERTO !


int potenza_di_2(int potenza) // implementazione, versione 4
{int i = 0;
  if(i<potenza)
    return 2*potenza_di_2(potenza);
  else
    return 1;}


... non va bene, i viene inizializzato a 0 ad ogni chiamata e questo rischia di diventare un loop infinito ...

Farebbe comodo poter alterare la durata della variabile i, ovvero fare in modo che il valore della variabile i rimanga disponibile anche per le successive chiamate.

A questo scopo è stato introdotto lo specificatore static il quale permette di "mantenere in memoria" il valore della variabile e di non perderlo (assieme alla variabile) una volta che l'esecuzione della funzione è terminata; lo specificatore static è una delle parole chiave del linguaggio.
Il modo corretto di implementare la funzione è quindi:


int potenza_di_2(int potenza) // implementazione, versione 4
{static int i = 0;
  if(i<potenza)
    {i++;
     return 2*potenza_di_2(potenza);}
  else
     return 1;
}








ATTENZIONE: quando preferire un'implementazione ricorsiva ad una iterativa ?
In generale, per una funzione implementata in modo iterativo esiste una corrispondente funzione che permette di implementare, in modo ricorsivo, il medesimo problema.
Qual è la differenza, quindi, tra i due metodi di implementazione ?
Calcoliamo il costo in termini di memoria utilizzata.

int potenza_di_2(int potenza)
{int numero=1;
 for(;potenza; potenza--)
   numero=2*numero;
 return numero;
}
A prescindere dal valore passato come parametro, la memoria occupata è pari a 4 Byte per la variabile potenza + 4 Byte per la variabile numero
int potenza_di_2(int potenza)
{
 if(potenza)
   return 2*potenza_di_2(potenza-1);
 else
   return 1;
}
La memoria occupata dipende dal valore della variabile potenza. Fissato il valore di tale variabile, la quantità di memoria totale necessaria al funzionamento della procedura è pari 4 Byte *(valore di potenza)









All'interno della libreria standard del compilatore sono presenti anche le implementazioni di tutte le funzioni matematiche di uso comune; per utilizzare tali funzioni è necessario includere nel codice il file math.h. In particolare, per calcolare la potenza di un numero è disponibile


double pow( double,double)


il primo parametro rappresenta il numero di cui vogliamo calcolare la potenza mentre il secondo rappresenta l'esponente.


NOTA BENE: chi usa il sistema operativo LinuX deve modificare leggermente l'istruzione con cui richiama il compilatore !!!!



gcc -Wall -o nome_programma -lm programma.c