- una che viene valutata SEMPRE, prima di valutare le altre due ed il corpo del for() (espr1)
- 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)
- 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:
- l'input viene gestito mediante getchar()
- 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
- 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