Strutture condizionali e iterative. Array e metodi

Giovedì, 5 Ottobre 2006

OBIETTIVI DELLA LEZIONE

In questa lezione:

  1. perfezioneremo la nostra conoscenza delle strutture condizionali;
  2. impareremo a eseguire più volte blocchi di codice;
  3. vedremo nuovi dettagli sugli oggetti e un nuovo oggetto: gli array.
  4. impareremo come creare ed invocare metodi.

Ripasso

In Java l'operatore logico AND si indica con &&, OR con ||, NOT con ! e XOR con ^. Le loro tabelle di verità sono come sempre:

&& false true
false false false
true false true
|| false true
false false true
true true true
!
false true
true false
^ false true
false false true
true true false

Le condizioni logiche possono essere composte per creare espressioni più complesse. Ad esempio:

        (a > 5) && (b < 10)

Quando si deve valutare una condizione logica complessa, Java interrompe (come il C) la valutazione non appena la condizione è univocamente determinata. Nell'esempio sopra se la prima condizione è falsa non c'è alcun bisogno di valutare la seconda.

Ricordiamo che valgono le leggi di De Morgan:

  • NOT(A AND B) = (NOT A) OR (NOT B)
  • NOT(A OR B) = (NOT A) AND (NOT B)

Strutture di controllo del flusso 2/4

Strutture condizionali: if

Durante la scorsa lezione, abbiamo cominciato a vedere come viene controllato il flusso di esecuzione in Java. In particolare, è stata introdotta la struttura condizionale if. E' il modo fondamentale di biforcare il flusso di esecuzione in Java. Quando il nostro programma dovrà prendere una decisione, verificherà un enunciato logico e quindi a seconda del valore di quest ultimo svolgerà un insieme di istruzioni o meno.

Vi ricordo che in Java, come in C, per raggruppare un insieme di istruzioni ed indicare che costituiscono un blocco unico, le si racchiude tra parentesi graffe:

    {
      istruzione1;
      istruzione2;
      istruzione3;
    }

Esempio: scriviamo alcune righe di codice che calcolino l'interesse annuo sul nostro conto corrente. Se il saldo è inferiore a €2000 il tasso di interesse sarà lo 1% altrimenti diventerà il 5%. Costruiamo per prima cosa il diagramma a blocchi:

Una possibile classe è la seguente:

import javax.swing.*;

class Interesse {
  public static void main(String[] args) {
    String stringaInput;
    double saldo, interesse; // scegliamo un double perché può avere decimali

    stringaInput = JOptionPane.showInputDialog("Quale è il saldo del tuo conto?");
    saldo = Double.parseDouble(stringaInput);

    // la decisione:
    if(saldo > 2000.0) { // la condizione logica va tra parentesi tonde!
      interesse = saldo * (5.0 / 100.0);
    }
    else {
      interesse = saldo * (1.0 / 100.0);
    }

    JOptionPane.showMessageDialog(null, "L'interesse annuo è " + interesse);

    System.exit(0); // essenziale quando usiamo javax.swing
  }
}

Da notare:

Strutture condizionali: l’operatore condizionale ?:

Consiferiamo il caso in cui si debba effettuare un assegnamento condizionale

if(x >= 0)
  y = 2;
else
  y = -1;

Un alternativa efficace è l'utilizzo dell'operatore condizionale ternario ?:

y = (x>=0) ? 2 : -1;

La sintassi dell'opertore è espressioneCondizionale ? valoreSeTrue : valoreSeFalse

Strutture condizionali: switch

La seconda (e ultima) struttura condizionale che vediamo è il costrutto switch - case. E' un costrutto sopravvissuto all'evoluzione della specie e deriva direttamente dalle jump-table (goto) dei linguaggi di seconda generazione. E' sopravvissuto perché in alcuni casi è molto utile! Serve quando dobbiamo confrontare una variabile di tipo intero (assolutamente non boolean, né float, né double, né long) con una serie di possibilità. Tipico esempio: scelta multipla.

import javax.swing.*;

class Multipla {
  public static void main(String[] args) {
    String stringaInput;
    int numero;
    char risposta;

    stringaInput = JOptionPane.showInputDialog("Scegli un numero");
    numero = Integer.parseInt(stringaInput);

    stringaInput = JOptionPane.showInputDialog("Cosa vuoi farne?\n" +
      "Premi R per radice quadrata,\n" +
      "      E per il quadrato,\n" +
      "      C per il cubo e\n" +
      "      n o N per uscire.");
    risposta = stringaInput.charAt(0);

    switch(risposta) { // risposta è un char ma viene implicitamente trasformato in un int
      case 'R':        // inizia qui se risposta == 'R'
        JOptionPane.showMessageDialog(null, "La radice quadrata è " + Math.sqrt(numero));
        break;         // finisce qui
      case 'E':        // inizia qui se risposta == 'E'
        JOptionPane.showMessageDialog(null, "Il quadrato è " + Math.pow(numero,2.0));
        break;         // finisce qui
      case 'C':        // inizia qui se risposta == 'E'
        JOptionPane.showMessageDialog(null, "Il cubo è " + Math.pow(numero,3.0));
        break;         // finisce qui
      case 'n':        // inizia qui se risposta == 'n'
      case 'N':        // inizia qui se risposta == 'N'
        JOptionPane.showMessageDialog(null, "Faccio come vuoi tu.");
        break;         // finisce qui
      default:         // in ogni altro caso inizia qui
        JOptionPane.showMessageDialog(null, "Non sai stare al gioco.");
        break;         // finisce qui, ma il break non sarebbe stato necessario visto 
                       // che siamo alla fine del blocco e saremmo usciti comunque.
    }

    System.exit(0); // da non dimenticare!
  }
}

Ripasso: nomi identificatori

Java è un linguaggio "case-sensitive", cioè differenzia tra lettere minuscole e maiuscole (come il C ma diversamente dal Fortran, ad es.).

Attenzione: è un errore molto comune chiamare una variabile con un nome tutto minuscolo e referenziarla poi con una iniziale maiuscola!

        int saldo;

        Saldo = ... // SBAGLIATO! saldo è tutto minuscolo

Un altro errore comune è quello di indicare il nome di un tipo primitico con la maiuscola. Attenzione: i tipi primitivi non hanno iniziale maiuscola! Ma hanno iniziale maiuscola le pseudoclassi che contengono i metodi che agiscono sui tipi primitivi (sono pur sempre classi). Esempio chiarificatore:

        Int numero; // SBAGLIATO, i tipi primitivi hanno nomi minuscoli
        Stringa input; // Giusto, le stringhe sono oggetti!

        numero = int.ParseInt(input); // Sbagliato, int è un tipo primitivo e non ha metodi!
        numero = Integer.ParseInt(input); // Giusto, Integer è una classe

Confronto tra stinghe e altre funzioni utili

Una variabile di tipo char può solo contenere una lettera. Per rappresentare una successione di lettere dobbiamo utilizzare una stringa. Una stringa NON è un tipo di dato primitivo e per contenerla serve un oggetto.

L'implementazione di un oggetto è normalmente tenuta nascosta (encapsulation). Tutti gli oggetti saranno contenuti nello heap("mucchio"), lo spazio tipicamente riservato in altri linguaggi per l'allocazione di memoria dinamica.



String non è un tipo di dato! E’ una classe. Classe = "progetto per costruire un dato oggetto"

Indicando una classe come tipo di dato indichiamo di voler creare una variabile riferimento a quel tipo di oggetto.

Ripasso: concatenamento stringhe

Per concatenare due stringhe dobbiamo utilizzare l'operatore +. Rivediamo alcuni esempi.

Esempio: abbiamo le tre variabili altezza, lunghezza e larghezza e vogliamo una produrre una stringa del tipo:

        "Le dimensioni della scatola sono: 13 x 10 x 2 cm (L x W x H)"

Per ottenerla:

        "Le dimensioni della scatola sono: " + lunghezza +
        " x " + larghezza + " x " + altezza + " cm (L x W x H)";

Per costruire una stringa che, stampata, stia su più righe, ad esempio come:

Questa è una stringa
su due righe!

basta inserire la sequenza di escape '\n' nel punto opportuno, cioè:

        "Questa è una stringa\nsu due righe!"

Operare sulle stringhe?

In un linguaggio di programmazione strutturato a questo punto avremmo studiato le funzioni che "permettono di operare sulle stringhe". Ma Java è un linguaggio orientato agli oggetti (OO) e in un linguaggio OO il centro del programma sono i dati stessi. Quindi non manipoleremo gli oggetti, ma più gentilemente, invieremo agli oggetti dei messaggi con delle richieste specifiche. L'oggetto che riceve il messaggio invocherà il metodo corrispondente. La classe specifica quali metodi l’oggetto dispone.

Vediamo un esempio. Nel codice sopra per avere il primo carattere della stringa abbiamo scritto la seguente istruzione:

    risposta = stringaInput.charAt(0);     

Scomponiamola:

Altri metodi utili della classe String

NOTA BENE: se vogliamo confrontare due stringhe in una espressione logica NON dobbiamo usare l'operatore == ma il metodo equals(). Questo è valido in genere per qualunque oggetto! L'operatore == confronta il riferimento all'oggetto.

Strutture di controllo del flusso 3/4

Spesso è necessario ripetere numerose volte lo stesso blocco di istruzioni e tutti i linguaggi di programmazione propongono strutture di controllo per i cicli. In questo il Java assomiglia quasi completamente al C (ad onor del vero la versione 1.5 di Java introduce un nuovo tipo di ciclo, somigliante al foreach del PHP ma non presente in C).

Fondamentalmente, un ciclo è una struttura di controllo che esegue un blocco di istruzioni fino a che una condizione logica rimane vera.

Cicli: while e do-while

Esempio: calcoliamo la somma dei numeri interi minori ed uguali del numero N (dall'algebra elementare sappiamo che la somma sarà pari a N*(N+1)/2). Per prima cosa costruiamoci un diagramma a blocchi:

Una possibile soluzione è la seguente
import javax.swing.*;

class SommaInteri {
  public static void main(String[] args) {
    String stringaInput;
    int numero, N, somma;

    stringaInput = JOptionPane.showInputDialog("Scegli il numero N");
    N = Integer.parseInt(stringaInput);

    somma = 0;
    numero = 1;
    while(numero <= N) {
      somma += numero;
      numero++;
    }

    JOptionPane.showMessageDialog(null, "La somma è " + somma);
  }
}

Se si vuole che la condizione logica sia valutata alla fine del ciclo, si può utilizzare il costrutto do-while

do {
  istruzioniDelCiclo
} while(condizioneLogica);

Cicli: for

Il costrutto for è, per quanto ci riguarda, assolutamente equivalente al while. Le parti fondamentali del ciclo sono raggruppate in un costrutto più sintetico: for(condizioneIniziale; condizioneLogica; incremento). La parte centrale dell'esempio precedente andrebbe riscritta come:

    somma = 0;
    for(numero = 1; numero <= N; numero++) {
      somma += numero;
    }

In definitiva, i due costrutti seguenti sono equivalenti:

istrInizializzazione;
while(condizioneLogica) {
  istruzione1;
  istruzione2;
  ...
  istrIncremento;
  }
for(istrInizializzazione; condizioneLogica; istrIncremento) {
  istruzione1;
  istruzione2;
  ...
  }

Prediligeremo il for quando il numero di iterazioni è prefissato, il while altrimenti. Ma si potrebbe anche usare sempre uno dei due costrutti...

Attenzione alla condizione di permanenza nel ciclo! Evitare se possibile test di uguaglianza su valori in virgola mobile!

Cicli: operatori break e continue

break: permette di interrompere l’esecuzione del ciclo

continue: permettere di interrompere l’iterazione corrente

Entrambi gli operatori sono tipicamente utilizzate con una struttura condizionale. Le istruzioni non sono mai necessarie in un programma ed è meglio evitarle per migliorare la leggibilità.

Array

Un "array" è una collezione di elementi dello stesso tipo. A ciascun elemento è associata una posizione nell'array: in Java come in C le posizioni all'interno dell'array (gli "indici") partono da 0.

Gli "array" in Java sono degli oggetti. Un esempio, un array di 10 double:

Dichiarazione di un array

Per dichiarare una variabile (riferimento a) array si utilizza la sintassi

double[] movimenti;

Notate la posizione delle parentesi quadre. Per motivi storici (venire incontro alle abitudini dei programmatori C) è possibile anche la sintassi alternativa:

double movimenti[];

E' sconsigliata!

Costruzione dell’array

Per costruire l'array si utilizza la sintassi:

movimenti = new double[10]; 

Che cosa è "new"? È un operatore unario prefisso, con il quale richiediamo alla JVM di creare un oggetto (nello heap):

new costruttoreOggetto()

In generale, per creare un array utilizzaremo la sintassi:

new OggettoOTipoPrimitivo[numeroDiElementi]

Le variabili di tipo primitivo contenute nell'array vengono inizializzate ai valori di default indicati nella tabella della lezione scorsa. Vi anticipo che le variabili di tipo riferimento ad oggetto vengono inizializzate a null. Vediamo quale è il processo di dichiarazione e creazione di un array da parte della JVM in questa slide.

Infine ecco un possibile codice che produce l'array della figura vista in precedenza:

class ArrayDiDouble {
  public static void main(String[] args) {
    double[] movimenti = {12.32, 0.3, 28.78, 12.45, 9.01, 1.16, 26.9, 18.04, 11.08, 5.39};

    for(int i=0; i<movimenti.length; i++)
      System.out.println(movimenti[i]);
  }
}

Osservazione: nell'esempio è presentato un metodo di creazione e inizializzazione alternativo che utilizza una sorta di costante letterale per l'array:

double[] movimenti = {12.32, 0.3, 28.78, 12.45, 9.01, 1.16, 26.9, 18.04, 11.08, 5.39};

Il costrutto è equivalente a:

double[] movimenti = new double[10];
movimenti[0] = 12.32;
movimenti[1] = 0.3;
movimenti[2] = 28.78;
movimenti[3] = 12.45;
...

Come in C, gli elementi si manipolano utilizzando l’operatore [ ].

Lunghezza di un array

L’oggetto array contiene una variabile con la lunghezza (cioè il numero di elementi contenuti nell'array: movimenti.length

L'esistenza di proprietà di un oggetto è un concetto comune a tutti gli oggetti. Vengono chiamate in modi diversi: campi o variabili di istanza. Conterranno informazioni che caratterizzeranno gli oggetti stessi. Es: auto: numero di Km percorsi; vestito: taglia, etc.

Vediamo come utilizzare il campo length in questo esempio, che prevede che l'utente inserisca 10 dati:

import javax.swing.*;

class ArrayDiDouble {
  public static void main(String[] args) {
    String stringaInput;
    double[] movimenti = new double[10];

    for(int i=0; i<movimenti.length; i++) {
      stringaInput = JOptionPane.showInputDialog("Inserisci l'elemento numero " + i);
      movimenti[i] = Double.parseDouble(stringaInput);
    }

    for(int i=0; i<movimenti.length; i++)
      System.out.println(movimenti[i]);

    System.exit(0);
  }
}

Metodi

Abbiamo già utilizzato dei metodi. Due tipologie:

Metodo = insieme di istruzioni raggruppate insieme per svolgere una determinata operazione. Perché utilizzare dei metodi: 1) Riutilizzare il codice; 2) Nascondere l’implementazione (encapsulation). System.out.printl() non sappiamo come funziona ma lo abbiamo utilizzato senza problemi!
Sintassi:

modificatori tipoValoreRestituito nomeMetodo(tipo1 par1, tipo2 par2, ...) {
  istruzione1;
  istruzione2;
  ...
  return valore;
}

Un po' di terminologia:

Per invocare un metodo:

Passaggio parametri

Supponiamo di invocare il metodo:

public static double Math.sqrt(double a);

Al momento dell’invocazione:

  1. vengono creati sullo stack delle variabili che corrispondono ai parametri formali
  2. i parametri formali vengono inizializzati con il valore dei parametri attuali (o “argomenti”): a <- 3.0
  3. il controllo del flusso di esecuzione è trasferito al metodo
  4. al termine del metodo le variabili vengono cancellate dallo stack

Il passaggio parametri in Java è sempre per valore! Non esiste il passaggio parametro per indirizzo. Attenzione però che quando passate ad un metodo una variabile di tipo riferimento ad oggetto, quello che passate è il riferimento!

Un errore tipico

Il passaggio parametri per valore con tipi di dato primitivi è immediato da capire ma porta ad eventuali errori. Vediamone uno tipico:

class Scambia {
  public static void main(String[] args) {
    int primoNumero = 10, secondoNumero = 20;

    scambiaNumeriInteri(primoNumero, secondoNumero);
    System.out.println("primoNumero = " + primoNumero + " e secondoNumero = " + secondoNumero);
  }

  static void scambiaNumeriInteri(int a, int b) {
    int temp;

    temp = a;
    a = b;
    b = temp;

    // a e b sono variabili temporanee che qui smettono di esistere.
  }
}

Quando il metodo di classe scambiaNumeriInteri è stato invocato, sono state create sullo stack due nuove variabili intere, a e b, in cui sono stati copiati primoNumero e secondoNumero. Al termine dell'esecuzione del metodo, le variabili vengono rimosse dallo stack e nessuna modifica si propaga nel metodo main.

main()

main() è un metodo static che Viene invocato direttamenete dalla JVM. DEVE essere specificato come public static void.

Lab

Algoritmi da sapere: Massimo di un array

Questo non è un corso di algoritmi, ma di sicuro è necessario imparare bene alcuni algoritmi basilari. Tra questi la ricerca del massimo (o del minimo) di un array. L'idea è semplice:

  1. Il primo elemento dell'array (o l'ultimo se volete "sfogliare" l'array al contrario) è sicuramente maggiore di tutti quelli che abbiamo esaminato in precedenza (quanti ne abbiamo già esaminati? Nessuno, dunque un qualunque numero è sicuramente maggiore di nessun numero). Dunque,
    massimo=elenco[0].
  2. A partire dal secondo elemento dell'array, confrontiamo il nuovo valore esaminato con il massimo trovato fino a quel punto. Se il nuovo elemento è maggiore, questi diventa il nuovo massimo. Dunque, se elenco[i=1]>massimo, allora massimo=elenco[i=1].
  3. Ripetiamo il passo 2, per ogni elemento dell'array
  4. .

Esercizio 1 [Soluzione]

Scrivere un programma che chieda all'utente di inserire tre numeri double. Il programma deve scegliere il maggiore e stamparlo a video.

Esercizio 2 [Soluzione]

Un anno bisestile ha 366 giorni. Un anno è bisestile se è divisibile per 4 (es: 1980), se non è divisibile per 100 (il 1900 non era bisestile), se è divisibile per 400 (il 2000 era bisestile). Il calendario gregoriano è stato introdotto nel 1582 e prima di esso non esistevano gli anni bisestili. Scrivere un programma che chieda all'utente un anno e che risponda indicando se quell'anno sia (stato) bisestile o meno.

Esercizio 3 [Soluzione]

Riscrivere il seguente codice, sostituendo il ciclo for con uno while

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

Esercizio 4 [Soluzione]

Scrivere un programma che stampi a video il seguente pattern:

        1
        22
        333
        4444
        55555
        666666
        7777777
        88888888
        999999999
        

Esercizio 5 [Soluzione]

Scrivere un programma che stampi a video il seguente pattern:

        ########              #
        #######              ##
        ######              ###
        #####              ####
        ####              #####
        ###              ######
        ##              #######
        #              ########
        

Esercizio 6 [Soluzione]

Scrivere un programma che chieda all'utente di inserire 7 numeri double e li metta in un array. Quindi il programma deve indicare all'utente quale era il numero maggiore.

©2006 Roberto Sassi