Ciclo For in Linguaggio C

L'istruzione for è una delle principali istruzioni di controllo del flusso in C. Permette di realizzare cicli iterativi di istruzioni.

La sintassi dell'istruzione for include tre parti: l'espressione inizializzazione, l'espressione di controllo che determina la continuazione del ciclo e l'operazione di fine iterazione.

L'istruzione for è particolarmente utile quando si vuole eseguire un blocco di codice per un numero noto di volte. Tuttavia, l'istruzione for è talmente flessibile da poter realizzare qualsiasi tipo di ciclo attraverso di essa.

In questa lezione vedremo nel dettaglio la sintassi e l'impiego delle istruzioni for per la realizzazione di cicli.

Concetti Chiave
  1. L'istruzione for permette di realizzare cicli specificando tre espressioni: l'espressione di inizializzazione, l'espressione di controllo e l'operazione di fine iterazione;
  2. Sebbene l'istruzione for venga usata per eseguire delle istruzioni un numero noto di volte, la sua flessibilità permette di realizzare qualunque tipo di ciclo;
  3. Le tre espressioni non sono obbligatorie;
  4. Usando l'operatore virgola è possibile combinare più espressioni.

Istruzione for

L'istruzione for è l'istruzione più versatile ma anche più complessa per realizzare cicli in linguaggio C. Infatti, il suo utilizzo ideale è per i casi in cui vi è una variabile di conteggio ma può essere anche applicata anche per altri tipi di cicli.

L'istruzione for ha la forma:

for ( espressione_1 ; espressione_2 ; espressione_3 ) istruzione;

Per comprendere lo scopo delle tre espressioni contenute nell'istruzione for riprendiamo l'esempio del conto alla rovescia che già abbiamo analizzato nelle lezioni sui cicli while e sui cicli do while.

Vogliamo stampare a schermo un conto alla rovescia partendo da 10. Possiamo implementare questa funzionalità utilizzando un ciclo for in questo modo:

int n;
for (n = 10; n > 0; --n) {
    printf("%d\n", n);
}
printf("Partenza!\n");

Questo ciclo for nella pratica svolge i seguenti passaggi:

  • Inizializza la variabile n a zero utilizzando la prima espressione n = 0;
  • Verifica se la variabile n è maggiore di zero utilizzando la seconda espressione n > 0;
  • Dato che l'espressione precedente risulta vera, esegue il corpo del ciclo;
  • Finito il corpo del ciclo, modifica la variabile n utilizzando la terza espressione --n;
  • Testa nuovamente la condizione espressa dalla seconda espressione n > 0;
  • Se quest'ultima risulta vera riesegue il corpo del ciclo, altrimenti esce dal ciclo.

Quindi, il ciclo for è strettamente correlato ad un ciclo while tant'è vero che è sempre possibile riscrivere un ciclo for in un ciclo while.

In pratica, un'istruzione for di questo tipo:

for ( espressione_1 ; espressione_2 ; espressione_3 ) istruzione;

è equivalente ad un ciclo while scritto in questo modo:

espressione_1;
while (espressione_2) {
    istruzione;
    espressione_3;
}

Per cui possiamo ricavare lo scopo delle tre espressioni:

  1. espressione_1 rappresenta l'espressione di inizializzazione: si tratta di un'espressione che viene eseguita sempre una singola volta all'inizio prima che il ciclo abbia inizio.
  2. espressione_2 rappresenta l'espressione di controllo: questa espressione controlla la terminazione del ciclo. Se questa espressione risulta vera il ciclo continua altrimenti termina.
  3. espressione_3 rappresenta l'opearazione di fine iterazione: questa espressione viene eseguita ad ogni iterazione dopo il corpo del ciclo.

Utilizzando questo schema possiamo, infatti, riscrivere il codice dell'esempio in questo modo:

/* Formato ciclo for */
int n;
for (n = 10; n > 0; --n) {
    printf("%d\n", n);
}
printf("Partenza!\n");

/* Formato ciclo while */
int n;
n = 10;
while (n > 0) {
    printf("%d\n", n);
    --n;
}
printf("Partenza!\n");

In generale, quindi, lo schema di un ciclo for con le sue tre espressioni segue il seguente diagramma di flusso:

Diagramma di flusso dell'istruzione for
Figura 1: Diagramma di flusso dell'istruzione for

Dal momento che l'espressione di inizializzazione e l'espressione di fine iterazione sono eseguite come istruzioni, il loro valore non conta. Importa l'effetto che tali espressioni hanno. Per cui, tipicamente, tali espressioni sono espressioni di assegnamento e decremento o incremento.

Definizione

Istruzione for

L'istruzione for in linguaggio C permette di realizzare cicli iterativi.

La sintassi generale è la seguente:

for ( espr_inizializzazione; espr_controllo; espr_fine_iterazione ) istruzione;

Le tre espressioni che compaiono all'interno dell'istruzione for sono:

  1. espr_inizializzazione: è l'espressione di inizializzazione e viene eseguita prima dell'inizio del ciclo;
  2. espr_controllo: è l'espressione di controllo e viene verificata all'inizio di ogni iterazione. Se risulta vera viene eseguito il corpo del ciclo altrimenti il ciclo termina;
  3. espr_fine_iterazione: è l'espressione di fine iterazione e viene eseguita al termine di ogni iterazione dopo il corpo del ciclo ma prima di verificare l'espressione di controllo alla prossima iterazione.

Cicli for tipici

Spesso il ciclo for è la scelta ideale per cicli che contano, ossia incrementano o decrementano una variabile. Un'istruzione for che conta per un numero di volte pari ad n ha una delle seguenti forme tipiche:

  • Contare da 0 ad n - 1:

    for (i = 0; i < n; ++i)
    
  • Contare da 1 ad n:

    for (i = 1; i <= n; ++i)
    
  • Contare all'indietro da n - 1 a 0:

    for (i = n - 1; i >= 0; --i)
    
  • Contare all'indietro da n a 1:

    for (i = n; i > 0; --i)
    

I quattro pattern riportati sopra sono tra quelli più comuni che si possono incontrare quando si sviluppa in linguaggio C. Utilizzandoli è possibile evitare di commettere gli errori più comuni:

  • Utilizzare < invece di > e viceversa nell'espressione di controllo. Da notare che nel contare all'insù nel ciclo for abbiamo usato gli operatori < o <=. Viceversa, nel contare all'indietro abbiamo usato gli operatori > o >=.
  • Utilizzare nell'espressione di controllo l'operatore di uguaglianza ==. Spesso questo è un errore in quanto all'inizio di un ciclo for, così come per un ciclo while, l'espressione di controllo deve essere vera. Può diventare falsa al termine del ciclo. Per cui un'espressione di controllo del tipo i == n non ha molto senso perché non sarebbe vera inizialmente.
  • Errori di tipo scarto di uno (dall'inglese off by one). Questi errori nascono da situazioni in cui si conta un elemento in più e possono nascere in un ciclo for quando si sbaglia l'espressione di controllo. Ad esempio succede quando si utilizza l'espressione i <= n anziché i < n.

Omettere le espressioni in un ciclo for

Il ciclo for è molto flessibile. Infatti, le tre espressioni dell'istruzione for non sono obbligatorie.

Possono esistere casi in cui non tutte le espressioni sono necessarie per cui, in linguaggio C, è possibile ometterne qualcuna se non addirittura tutte e tre.

Se omettiamo la prima espressione il ciclo for non effettua nessuno step di inizializzazione all'avvio del ciclo. Ad esempio, tornando al conto alla rovescia, possiamo riscrivere il codice evitando la prima espressione in questo modo:

int n;
n = 10;
for (; n > 0; --n) {
    printf("%d\n", n);
}
printf("Partenza!\n");

In questo caso abbiamo effettuato l'inizializzazione della variabile n a parte per cui la prima espressione non è necessaria. Da notare che però il punto è virgola deve rimanere anche se l'espressione è assente.

Omettendo la terza espressione in un'istruzione for allora il ciclo for non effettua alcuna operazione al termine di un'iterazione. A questo punto è responsabilità del corpo del ciclo di assicurarsi che l'espressione di controllo diventi falsa.

Ritornando sempre all'esempio del conto alla rovescia, possiamo omettere la terza espressione in questo modo:

int n;
for (n = 10; n > 0;) {
    printf("%d\n", n);
    --n;
}
printf("Partenza!\n");

Abbiamo omesso la terza espressione ma abbiamo dovuto aggiungere la riga --n. In assenza di questa riga, la variabile n non verrebbe mai decrementata e l'espressione di controllo non diventerebbe mai falsa.

Mettendo insieme i due pezzi precedenti, possiamo notare che se omettiamo sia la prima espressione che la terza il ciclo for non diventa altro che un ciclo while sotto altra forma.

Tornando all'esempio del conto alla rovescia, possiamo riscrivere il ciclo for omettendo prima e terza espressione in questo modo:

int n;
n = 10;
for (; n > 0;) {
    printf("%d\n", n);
    --n;
}
printf("Partenza!\n");

Ma il codice di sopra è del tutto identico al ciclo while che segue:

int n = 0;
n = 10;
while (n > 0) {
    printf("%d\n", n);
    --n;
}
printf("Partenza!\n");

In questi casi, tuttavia, il ciclo while è più leggibile e chiaro, per cui è preferibile usare while in questi casi.

Infine, se omettiamo la seconda espressione essa viene considerata vera di default. In questo caso il ciclo for non termina mai a meno che non sia terminato in altro modo, come vedremo nelle prossime lezioni. Ad esempio è possibile implementare un ciclo infinito in questo modo:

for (;;) {
    /* Ciclo infinito */
    /* ... */
}

Istruzione for e C99

Nel linguaggio C, per lo standard C89, le variabili vanno dichiarate all'inizio. Strettamente parlando, per lo standard C89 le variabili vanno dichiarate all'inizio dello scope. Nelle prossime lezioni vedremo che cos'è lo scope delle variabili. Per il momento basta sapere che in C89 una variabile va dichiarata prima di qualunque istruzione.

In base a ciò, se prendiamo l'esempio del conto alla rovescia dobbiamo scrivere il programma in questo modo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <stdio.h>

int main() {
    int n;
    for (n = 10; n > 0; --n) {
        printf("%d\n", n);
    }
    printf("Partenza!\n");
    return 0;
}

In altre parole, abbiamo dovuto dichiarare dapprima la variabile n alla riga 4.

A partire dallo standard C99, invece, le variabili possono essere dichiarate ovunque. Per cui, se stiamo usando un compilatore C99 (e quasi tutti i moderni compilatori supportano lo standard C99) possiamo sostituire alla prima espressione di un ciclo for una dichiarazione.

Per cui possiamo riscrivere il codice di sopra in questo modo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/* Versione C99 */
#include <stdio.h>

int main() {
    for (int n = 10; n > 0; --n) {
        printf("%d\n", n);
    }
    printf("Partenza!\n");
    return 0;
}

In questo modo la variabile n viene dichiarata direttamente nel ciclo for e non è necessario dichiararla prima. Tuttavia, in questo modo la variabile n non è accessibile dall'esterno del ciclo for. Questo è un accenno alle regole di visibiltà che studieremo più avanti.

Realizzare cicli for che dichiarano le proprie variabili di controllo è sempre una buona idea. Come sempre, rende più leggibili i programmi. Tuttavia, se bisogna accedere alla variabile di controllo dall'esterno del ciclo, specialmente quando questo termina, è necessario usare la vecchia forma di ciclo for.

Operatore virgola

La flessibilità del ciclo for in linguaggio C sta anche nella possibilità che è possibile utilizzare espressioni multiple adoperando l'operatore virgola.

Ad esempio, è possibile scrivere un ciclo for che inizializzi più di una variabile o che modifichi più variabili ad ogni iterazione.

Per far questo si usa l'operatore virgola che ha la seguente forma:

espressione_1 , espressione_2

L'utilizzo dell'operatore virgola dà origine ad una nuova espressione con un suo risultato. L'operatore virgola viene valutato in due passi:

  1. Viene dapprima valutata la prima espressione, espressione_1, e il suo risultato viene scartato;
  2. Viene successivamente valutata la seconda espressione, espressione_2 e il suo risultato diventa il risultato di tutta l'espressione.

Ad esempio, possiamo scrivere le seguenti righe di codice:

int x;
x = (5 * 6, 7 + 8);

A questo punto il programma valuta prima l'espressione 5 * 6 e il suo risultato, 30, viene scartato. Successivamente valuta l'espressione 7 + 8 e il suo risultato, 15, diventa il risultato di tutta l'espressione e, di conseguenza, diventa il nuovo valore della variabile x.

L'operatore virgola ha una precedenza inferiore a tutti gli altri operatori, motivo per cui, nell'esempio di prima, abbiamo dovuto racchiudere l'espressione tra parentesi. Infatti, se avessimo scritto il codice seguente:

x = 5 * 6, 7 + 8;

La variabile x avrebbe come valore finale il valore 30 dato che l'assegnamento ha la precedenza sull'operatore virgola.

L'operatore virgola, inoltre, è associativo a sinistra. Motivo per cui, possiamo realizzare assegnamenti multipli in questo modo:

x = 1, y = 2, z = x + y;

Facendo così, il compilatore interpreta l'istruzione in questo modo:

((x = 1), (y = 2), (z = (x + y)));

Quindi opererà in questo modo:

  1. Assegna il valore 1 alla variabile x;
  2. Assegna il valore 2 alla variabile y;
  3. Valuta il risultato di x+y, ossia 3, e lo assegna a z.

L'operatore virgola può essere usato quando ci sono situazioni in cui possiamo usare una sola espressione ma si desidera inserire più di un'espressione. L'operatore ci permette, quindi, di incollare due o più espressioni insieme a formare una singola espressione. In maniera simile, le parentesi graffe ci permettono di aggregare insieme più istruzioni a formare un'istruzione composta.

L'esigenza di aggregare più espressioni tra di loro non emerge spesso. Tuttavia, i cicli for sono il caso più comune in cui ciò accade.

Per esempio, supponiamo di voler scrivere un programma che calcoli la somma dei primi 10 numeri naturali. Possiamo scrivere il nostro programma in questo modo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <stdio.h>

int main() {
    int s;
    int i;

    s = 0;
    for (i = 1; i < 10; ++i) {
        s += i;
    }

    printf("%d\n", s);
    return 0;
}

Nel programma di sopra, abbiamo inizializzato la variabile s alla riga 7 in maniera separata. Possiamo usare l'operatore virgola e scrivere:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <stdio.h>

int main() {
    int s;
    int i;

    for (s = 0, i = 1; i < 10; ++i) {
        s += i;
    }

    printf("%d\n", s);
    return 0;
}

Adesso, nella riga 7, usando l'operatore virgola abbiamo inizializzato entrambe le variabili. In questo modo l'istruzione for diventa più flessibile.

Ricapitolando:

Definizione

Operatore virgola

L'operatore virgola , è un operatore che permette di combinare più espressioni in una singola espressione. L'operatore virgola ha la seguente forma:

espressione_1, espressione_2

L'operatore virgola valuta prima l'espressione espressione_1 e scarta il suo risultato. Successivamente valuta l'espressione espressione_2 e il suo risultato diventa il risultato dell'intera espressione.

L'operatore virgola ha una precedenza inferiore a tutti gli altri operatori e ha associatività a sinistra.

In Sintesi

In questa lezione abbiamo studiato l'istruzione for che consente la realizzazione di cicli in maniera flessibile.

Abbiamo visto che la sintassi di un'istruzione for prevede tre espressioni:

  1. L'espressione di inizializzazione;
  2. L'espressione di controllo;
  3. L'operazione di fine iterazione.

Abbiamo visto che non sono obbligatorie e vi possono essere casi in cui possono essere omesse. La flessibilità di un'istruzione for sta proprio in questo, in quanto giocando con le tre espressioni è possibile realizzare cicli del tutto equivalenti a cicli while.

Inoltre, usando l'operatore virgola è possibile creare espressioni più complesse per la realizzazione di cicli for.

Nella prossima lezione vedremo come è possibile uscire da un ciclo.