Esempio: Somma di 5 numeri interi (senza cicli)

Supponiamo di voler risolvere il seguente problema: vogliamo leggere 5 numeri interi, calcolarne la somma e stamparla.

Variante con 5 variabili
public class SommaCinqueVar {

    public static void main (String[] args) {

        System.out.print("Immetti un intero: ");
        int num1 =Input.readInt();
        System.out.print("Immetti un intero: ");
        int num2 =Input.readInt(); 
        System.out.print("Immetti un intero: ");
        int num3 =Input.readInt();
        System.out.print("Immetti un intero: ");
        int num4 =Input.readInt();
        System.out.print("Immetti un intero: ");
        int num5 =Input.readInt();

        int somma = num1 + num2 + num3 + num4 + num5;

        System.out.println("La somma e' " + somma);
    }
}

Usando cinque variabili, nello scrivere il programma non solo bisogna ripetere cinque volte le istruzioni di scrittura e lettura, ma bisogna anche usare nomi diversi per le variabili.

Variante che utilizza solo 2 variabili
public class SommaDueVar {

    public static void main (String[] args) {
        int somma = 0;        
        int num;

        System.out.print("Immetti un intero: ");
        num = Input.readInt();
        somma = somma + num;
        
        System.out.print("Immetti un intero: ");
        num = Input.readInt();
        somma = somma + num;

        System.out.print("Immetti un intero: ");
        num = Input.readInt();
        somma = somma + num;

        System.out.print("Immetti un intero: ");
        num = Input.readInt();        
        somma = somma + num;

        System.out.print("Immetti un intero: ");
        num = Input.readInt();        
        somma = somma + num;

        System.out.println ("La somma e' " + somma);
    }
}

Riutilizzando la stessa variabile ad ogni lettura perdiamo i dati letti in precedenza, ma almeno nello scrivere il programma possiamo sfruttare la tecnica del copia-e-incolla.

Si potrebbe anche usare una sola variabile (vedi SommaUnaVar), ma rimane comunque il problema di dover ripetere tante volte la stessa istruzione o lo stesso gruppo di istruzioni:

  • Notate che se volessimo sommare un numero di valori diverso da 5 dovremmo riscrivere il programma.

  • Pensate cosa dovreste scrivere se i valori da leggere fossero 100! Come controllereste che il vostro programma ne legga proprio 100 e non 99 o magari 101?

  • Infine, se non fosse possibile sapere a priori quanti valori devono essere letti come potreste scrivere il programma adatto?

Per trattare questi casi conviene usare opportunamente le istruzioni iterative messe a disposizione dai linguaggi di programmazione, Java incluso.




Istruzioni Iterative

Spesso un algoritmo per la risoluzione di un problema richiede di eseguire più volte una sequenza di passi elementari.

Le istruzioni iterative (ripetizioni) consentono di ripetere una sequenza di istruzioni in base a certe condizioni o per un numero definito di volte:

  • finché una condizione rimane vera (iterazione indeterminata).

    Per esempio: fai un giro dell'isolato di corsa finché hai fiato.

  • un numero fissato di volte (iterazione determinata).

    Per esempio: fai un giro dell'isolato di corsa per 10 volte.

Java fornisce le seguenti istruzioni iterative:

  • while

    L'istruzione while viene usata quando uno o più comandi devono essere ripetuti ciclicamente fino a quando una certa condizione diviene falsa causando la fine del ciclo.

  • for

    L'istruzione for viene usata quando uno o più comandi devono essere ripetuti un numero prefissato di volte.

  • do-while

    L'istruzione do-while viene usata al posto del while quando i comandi devono comunque essere eseguiti almeno una volta.




Esempio: Somma di 5 numeri interi (con cicli)

Riprendiamo il problema visto in precedenza sul calcolo della somma di 5 numeri interi:

Versione senza cicli
public class SommaDueVar {

    public static void main (String[] args) {
        int somma = 0;        
        int num;

        System.out.print("Immetti un intero: ");
        num = Input.readInt();
        somma = somma + num;
        
        System.out.print("Immetti un intero: ");
        num = Input.readInt();
        somma = somma + num;

        System.out.print("Immetti un intero: ");
        num = Input.readInt();
        somma = somma + num;

        System.out.print("Immetti un intero: ");
        num = Input.readInt();        
        somma = somma + num;

        System.out.print("Immetti un intero: ");
        num = Input.readInt();        
        somma = somma + num;

        System.out.println ("La somma e' " + somma);
    }
}

Utilizzando le istruzioni iterative possiamo scrivere un codice più compatto e più facile da modificare.

Versione con ciclo while
public class SommaWhile {

    public static void main (String[] args) {
        final int N = 5; // valori da sommare
        int count = 1;   // conta i valori letti
        int somma = 0;   // somma dei valori letti
        int num;         // ultimo valore letto

        while (count <= N) {
            System.out.print("Immetti un intero: ");
            num = Input.readInt();
            somma = somma + num;
            count++;
        }        

        System.out.println ("La somma e' " + somma);
    }
}

Versione con ciclo for
public class SommaFor {

    public static void main (String[] args) {
        final int N = 5; // valori da sommare
        int count = 1;   // conta i valori letti
        int somma = 0;   // somma dei valori letti
        int num;         // ultimo valore letto

        for (count=1; count<=N; count++) {
            System.out.print("Immetti un intero: ");
            num = Input.readInt();
            somma = somma + num;
        }
        
        System.out.println ("La somma e' " + somma);
    }
}

NOTA: Questo è un esempio di iterazione determinata: l'esecuzione deve avvenire un numero prefissato di volte (5) e si consiglia quindi l'uso del for.

Notate che questa volta per modificare il numero di valori da leggere basta modificare il valore della costante N. Inoltre il valore di N potrebbe essere letto in input, rendendo il programma in grado di calcolare la somma di un qualsiasi numero di valori.




Il comando while: Sintassi

while ( <condizione> )
    <corpo>

dove:

  • <condizione> è un'espressione di tipo boolean, chiamata anche la guardia del ciclo.
  • <corpo>  può essere:
  • un'istruzione semplice,
  • un'istruzione composta,
  • un blocco di istruzioni.



Il comando while: Significato

while ( <condizione> )
    <corpo>

L'istruzione while valuta inizialmente l'espressione booleana <condizione>.

  • Se la valutazione ritorna il valore true viene eseguito il <corpo>, al termine del quale si ri-esegue tutto il comando while.
  • Se la valutazione ritorna il valore false l'esecuzione passa all'istruzione immediatamente successiva al corpo del while (senza eseguire il <corpo>).



Comando while: Esempi (1)

Supponiamo di voler scrivere un programma Java per stampare una scacchiera 4x4 come quella sotto:
            +--+--+--+--+
            |  |  |  |  |
            +--+--+--+--+
            |  |  |  |  |
            +--+--+--+--+
            |  |  |  |  |
            +--+--+--+--+
            |  |  |  |  |
            +--+--+--+--+

Di norma nell'iterazione determinata si utilizza un contatore, in questo caso il contatore conta il numero di righe da stampare.

Versione con ciclo while
public class ScacchieraWhile {

    public static void main(String[] args) {

        int i = 1; // variabile di conrollo

        System.out.println("+--+--+--+--+");

        while (i<=4) {
            System.out.println("|  |  |  |  |");
            System.out.println("+--+--+--+--+");
            i++; // IMPORTANTE!
        }
    }
}

Spesso, come in questo caso, la guardia del while controlla il valore di una o più variabili, tali variabili si dicono variabili di controllo del ciclo.

Le variabili di controllo vanno inizializzate opportunamente prima del while, e devono essere aggiornate nel corpo per garantire la terminazione del ciclo: in questo caso il contatore i è la variabile di controllo.

Vediamo adesso un esempio di iterazione indeterminata.

Supponiamo adesso di voler calcolare il massimo di una sequenza di valori interi, letti dall'input, e terminata dal valore 0 che fa parte della sequenza. Il programma MaxSequenza offre una delle possibili soluzioni.

// Stampa il massimo di una sequenza di interi
// fornita dall'utente, che termina con 0

public class MaxSequenza { 

    public static void main (String[] args) {
        int max = 0;   
        System.out.print("Dammi un intero (0 per terminare): ");
        int n = Input.readInt(); 
        while (n != 0){
            if (n > max) max = n;
            System.out.print("Dammi un intero (0 per terminare): ");
            n = Input.readInt();
        }
        System.out.println("Il massimo e' " + max);
    }
}

Nota: Perché possiamo inizializzare max a 0?
Come potremmo evitare di ripetere il codice?
(Vedremo che in casi come questo l'uso del do-while è più naturale)




Comando while: Esempi (2)

Le istruzioni while possono essere annidate.
Versione con cicli while annidati
public class ScacchieraWhileAnnidata {

    public static void main(String[] args) {

        final int N = 4; // dimensione scacchiera

        int i = 1; // contatore righe
        int j = 1; // contatore colonne

        // stampa il bordo superiore
        while (j<=N) {
            System.out.print("+--");
            j++;
        }
        System.out.println("+");

        while (i<=N) {

            // stampa le celle della riga i
            j = 1;
            while (j<=N) {
                System.out.print("|  ");
                j++;
            }
            System.out.println("|");

            // stampa il bordo inferiore della riga i
            j = 1;
            while (j<=N) {
                System.out.print("+--");
                j++;
            }
            System.out.println("+");

            i++; // IMPORTANTE!
        }
    }
}


Il seguente programma calcola quanti anni ci vogliono per raddoppiare un certo capitale applicando gli interessi composti ad un tasso di interesse fisso. Sia il saldoIniziale che il tasso di interesse sono forniti dall'utente.

public class RaddoppioInv {  
    public static void main(String[] args) {

        System.out.println("Tasso di interesse:");
        // leggo un numero in virgola mobile
        double tasso = Input.readDouble();

        System.out.println("Saldo iniziale:");
        // leggo un numero in virgola mobile
        double saldoIniziale = Input.readDouble();

        int anno = 0;
        double saldo = saldoIniziale;
        double interesse = 0;

        // accumula progressivamente gli interessi
        // fino a quando il saldo si raddoppia 

        while (saldo < 2 * saldoIniziale) {
            anno++;
            interesse = saldo * tasso/100;
            saldo = saldo + interesse;
        }

        System.out.println("L'investimento si raddoppia dopo " 
                           + anno + " anni.");
    }
}




Istruzione for

Alcuni dei cicli che abbiamo visto fino ad ora hanno alcune caratteristiche comuni:
  • utilizzano una variabile di controllo (contatore)
  • la guardia verifica se la variabile di controllo ha raggiunto un limite prefissato
  • ad ogni iterazione si esegue un'azione
  • al termine di ogni iterazione viene incrementato (decrementato) il valore della variabile di controllo

Versione con ciclo while
public class ScacchieraWhile {

    public static void main(String[] args) {

        int i = 1; // contatore

        System.out.println("+--+--+--+--+");

        while (i<=4) {
            System.out.println("|  |  |  |  |");
            System.out.println("+--+--+--+--+");
            i++; // IMPORTANTE!
        }
    }
}

L'istruzione for permette di gestire direttamente questi aspetti.

Versione con ciclo for
public class ScacchieraFor {

    public static void main(String[] args) {

        System.out.println("+--+--+--+--+");

        for (int i=1; i<=4; i++) {
            System.out.println("|  |  |  |  |");
            System.out.println("+--+--+--+--+");
        }
    }
}



Il comando for: Sintassi

for ( <inizializzazione> ; <condizione> ; <aggiornamento> )
    <corpo>

dove:

  • <inizializzazione> è di solito un comando di assegnamento, che assegna un valore iniziale alla variabile di controllo. La variabile può essere dichiarata prima del for, o in <inizializzazione>.
  • <condizione> è un'espressione di tipo boolean.
  • <aggiornamento> è di solito un comando che aggiorna la variabile di controllo (ad esempio, la incrementa di 1).
  • <corpo>   può essere:
  • un'istruzione semplice
  • un'istruzione composta
  • un blocco di istruzioni
NOTA: in generale, sia <inizializzazione> che <aggiornamento> possono essere liste di istruzioni separate da virgole

 



Il comando for: Significato

for ( <inizializzazione> ; <condizione> ; <aggiornamento> )
    <corpo>
  1. L'istruzione for esegue inizialmente i comandi <inizializzazione>, dove solitamente sono inizializzate le variabili di controllo.
  2. Successivamente valuta l'espressione booleana <condizione> che di solito contiene condizioni sulle variabili di ciclo.

    • Se  <condizione> ha valore true  viene eseguito il comando <corpo>.  Quindi si eseguono i comandi <aggiornamento> che di solito modificano le variabili di controllo; si torna poi al passo 2.
    • Se <condizione> ritorna il valore false, l'esecuzione passa invece all'istruzione immediatamente successiva al corpo del for.



Comando for : Esempi (1)

  • Calcolo di interessi maturati in 20 anni:
        ...
        for (int i=1; i<=20; i++) {
            double interesse = saldo * tasso / 100;
            saldo = saldo + interesse;
        }
        ...
  • Il programma NumeriF.java visualizza i numeri da 1 a 20 in colonna, mediante un ciclo for
        ...
        for (int i=1; i<=20; i++) 
            System.out.println (i);
        ...
  • Attenzione! Le variabili di controllo dichiarate in <inizializzazione> non sono visibili al di fuori del ciclo
        ...
        for (int x=1; x<=10; x = x + 1) 
            System.out.println (x); 
        System.out.println (x); // Errore: x non visibile!
        ...



Comando for : Esempi (2)

  • for annidati: il seguente ciclo visualizza un quadrato di 12 righe per 40 colonne di caratteri "*" (vedere RettangoloStar.java)
        ...
        int altezza = 12, larghezza = 40; 
        for (int i = 0; i < altezza; i++) {
            for (int j = 0; j < larghezza; j++)
                System.out.print("*"); 
            System.out.println();
        }
        ...




Relazione tra comandi for e while (1)

I comandi for e while sono equivalenti dal punto di vista computazionale.

In particolare, un comando for del tipo:

for ( <inizializzazione> ; <condizione> ; <aggiornamento> )
    <corpo>

può sempre essere sostituito con il comando while equivalente:

{  <inizializzazione>;
   while ( <condizione> ) {
       <corpo>;
       <aggiornamento>;
   }
}

Ad esempio, il codice
 

        ...
        for (int i=1 ; i<=20 ; i++) {
            double interesse = saldo * tasso / 100; 
            saldo = saldo + interesse;
        }
        ...

è equivalente a
 

        ...
        {  int i=1; // i visibile anche fuori del ciclo
           while (i <= 20) { 
               double interesse = saldo * tasso / 100; 
               saldo = saldo + interesse;
               i++;
           }    
        }
        ...

 



Relazione tra comandi for e while (2)

La scelta tra i comandi for e while è soprattutto una questione di buono stile di programmazione.

È buona prassi scegliere secondo i seguenti criteri:
 
 

for
Se il numero di volte che bisogna ripetere il <corpo> è conosciuto nel momento in cui inizia l'esecuzione del comando 
while
In tutti gli altri casi, ad esempio quando il numero di volte che bisogna ripetere il <corpo> dipende da un valore letto all'interno del <corpo>

Esempio: RicercaLineareIncerta. È migliore la soluzione con il for oppure quella con il while?

public class RicercaLineareIncerta {

  /* Restituisce la posizione della prima occorrenza della
     lettera 'a' in una stringa fornita dall'utente, se
     esiste. E' un esempio di Ricerca Lineare Incerta.
  */

    public static void main(String[] args) {
        System.out.print("Dammi una stringa: ");
        String str = Input.readLine();

        // Soluzione con 'for'.
        
        int foundIndex = -1;
        for (int i = 0; i < str.length(); i++)
            if ((foundIndex == -1) && (str.charAt(i) == 'a'))
                foundIndex = i;
        if (foundIndex == -1) System.out.println("[FOR] 'a' non trovata");
        else
            System.out.println("[FOR] Prima posizione di 'a' nella stringa: " 
                           + foundIndex);

        // Soluzione con 'while'.
        int i = 0;
        boolean trovata = false;
        while (!trovata && i < str.length()){ 
            if (str.charAt(i) == 'a') trovata = true;
            i++;
        }
        if (!trovata) System.out.println("[WHILE] 'a' non trovata");
        else
            System.out.println("[WHILE] Prima posizione di 'a' nella stringa: " 
                           + (i-1));

    }
}

Per fare un programma che legga da tastiera una sequenza di interi non nulli, terminata dal valore 0 (ma di lunghezza imprecisata), quale costrutto usereste?

Esempio: SommaSequenza.java
 
 




Errori frequenti

Sapreste individuare gli errori nei seguenti spezzoni di programma
    ...
        for (int i=1; i<=10; i++);
            System.out.println(i);
    ...


    ...
        int j=1;
        while (j<=10) 
            System.out.println(j);
            j++;
    ...


    ...
        for (int i=1; i<=10; i=i++)
            System.out.println(i);
    ...



Il comando do-while: Sintassi

do 
   <corpo>
while ( <condizione> );

dove:

  • <corpo>   può essere:
  • un'istruzione semplice
  • un'istruzione composta
  • un blocco di istruzioni
  • <condizione> è un'espressione di tipo boolean.



Comando do-while : Significato

do 
   <corpo>
while ( <condizione> );
  1.   L'istruzione do-while esegue inizialmente le istruzioni del <corpo>.

  2.   Successivamente valuta la <condizione>.

    • Se la valutazione ritorna il valore true si torna al passo 1.

    • Se la valutazione ritorna il valore false l'esecuzione passa all'istruzione immediatamente successiva al comando do-while.
Le variabili di controllo vanno inizializzate prima del do-while, e aggiornate nelle istruzioni per permettere la terminazione del ciclo.

La differenza sostanziale tra i comandi do-while e while è che usando do-while il <corpo> viene eseguito almeno una volta (esempi: MaxSequenzaDW.java e SommaSequenzaDW.java).





Comando do-while : Esempi

Stampa di un intero invertendo le cifre: stampa 654321
    ...
    int n = 123456;
    do { 
        System.out.print( n%10 ); 
        n /= 10; // equivale a:   n = n/10;
    } while ( n > 0 ); 
    System.out.println();
    ...


Ripetizione di input finché il valore non è ammissibile
    ...
    int valore;
    do {
        System.out.println("Scegli un numero tra 1 e 10:"); 
        valore = Input.readInt(); 
    } while ( valore<1 || valore>10 );
    ...


Analogamente ai cicli for, un ciclo do-while si può sempre sostituire con un ciclo while. Il codice seguente è equivalente a quello dell'ultimo esempio.
 

    ...
    int valore=0; // dobbiamo inizializzare valore!
    while ( valore<1 || valore>10 ) { 
        System.out.println("Scegli un numero tra 1 e 10:"); 
        valore = Input.readInt(); 
    }
    ...