Note Ingegneria Informatica · UniTN
Programmazione 1
in corso

Switch

Indice 7 sezioni
  1. 1Definizioni
  2. 2Concetti
  3. 2.1Flowchart con e senza break
  4. 2.2Più valori sullo stesso blocco
  5. 2.3switch dentro un ciclo
  6. 3Metodo
  7. 4Esempi svolti a lezione
  8. 4.1Lo switch su numero
  9. 4.2Lettura e categorizzazione di un numero
  10. 4.3Conteggio di cifre, spazi e altri caratteri
  11. 5Esercizi tipo esame
  12. 6Errori tipici
  13. 7Domande

Argomento di Programmazione 1. Fatto a lezione fra settembre e ottobre, deck 3.3: slide 16-31. Prima: Cicli for e do-while, che copre anche il resto del deck (break e continue nei cicli, Böhm-Jacopini). Base: Istruzioni condizionali.

switch (espressione intera)
   |   si valuta una volta
   v
confronto con case v1, v2, ...   il primo uguale è il punto di ingresso
   |   nessuno uguale: default, se c'è; altrimenti niente
   v
esegue da lì in giù               anche i blocchi dei case dopo (fall-through)
   |
break                             esce dallo switch

1Definizioni

switch (slide 16). Struttura di selezione multipla: sceglie fra più strade in base al valore di un’espressione. Può sostituire una catena di if-else. Il prof dice che non è necessaria (tutto si fa con if-else, vedi Böhm-Jacopini in Cicli for e do-while) ma comoda in certe situazioni.

Sintassi (slide 17).

switch (<espressione intera>) {
    case v1: { blocco istruzioni }
    case v2: { blocco istruzioni }
    ...
    default: { blocco istruzioni }
}

Esecuzione (slide 18-20). Si valuta l’espressione, poi la si confronta con v1, poi con v2, e così via. Al primo case uguale si entra, e da lì si eseguono il blocco di v1, poi quello di v2, e tutti i successivi fino alla fine dello switch. I case sono solo etichette di ingresso, non confini. Questo comportamento si chiama fall-through (caduta).

break nello switch (slide 21). Per eseguire solo il blocco del proprio case, lo si chiude con break, che restituisce il controllo all’istruzione dopo lo switch. Il break è opzionale: toglierlo è il modo di far proseguire l’esecuzione nel blocco dopo.

switch (<espressione intera>) {
    case v1: { blocco istruzioni } break;
    case v2: { blocco istruzioni } break;
    ...
    default: { blocco istruzioni } break;
}

Il break dopo default, se default è l’ultimo, non cambia niente; si mette per abitudine, così aggiungere un case dopo non introduce una caduta.

Differenze con l’if-else (slide 31). Lo switch si usa solo quando:

Condizioni come x>10x > 10, a<ba < b o “fra 18 e 30” non si scrivono con un case: servono gli if.

2Concetti

2.1Flowchart con e senza break

(slide 22-23) Con il break ogni blocco, finito, va diretto all’uscita: è una catena di if-else if. Senza il break dopo il blocco 1 (la freccia rossa “tolto break” della slide 23), chi entra da val1 esegue il blocco 1 e poi anche il blocco 2, senza rifare il confronto con val2.

con break                         senza break dopo il blocco 1
exp==val1 ? -> blocco 1 -> fine   exp==val1 ? -> blocco 1 -> blocco 2 -> fine
exp==val2 ? -> blocco 2 -> fine   exp==val2 ? -------------> blocco 2 -> fine
...                               ...
default     -> blocco d -> fine   default     -> blocco d -> fine

2.2Più valori sullo stesso blocco

Il fall-through serve: case vuoti scritti uno sotto l’altro portano tutti allo stesso blocco.

case 3:
case 4:
case 5:
    printf("Valore positivo piccolo");
    break;

Si entra da 3, 4 o 5 e si arriva comunque al printf. È il modo di esprimere “3 o 4 o 5”, visto che un case non accetta intervalli. Un case vuoto non dà warning; un case che fa qualcosa e poi cade nel successivo sì (-Wimplicit-fallthrough, attivo con -Wextra). Quando la caduta è voluta si scrive il commento /* fall through */ prima del case successivo: gcc lo riconosce e chi legge capisce che non è una dimenticanza.

2.3switch dentro un ciclo

break dentro uno switch esce dallo switch, non dal ciclo che lo contiene: il ciclo va avanti con il giro successivo. Per uscire dal ciclo da dentro uno switch serve una variabile di controllo nella condizione del ciclo. continue invece, anche se scritto dentro lo switch, agisce sul ciclo.

3Metodo

Tracciare uno switch.

  1. Calcola il valore dell’espressione.
  2. Scorri i case dall’alto e trova quello uguale. Se non c’è, vai a default; se non c’è nemmeno quello, lo switch non fa niente.
  3. Da lì esegui tutto verso il basso, ignorando le etichette case che incontri.
  4. Fermati al primo break o alla } di chiusura.

Scrivere uno switch.

  1. Controlla che la scelta dipenda da un solo valore intero o carattere. Se ci sono intervalli larghi o confronti, usa gli if.
  2. Un case per valore, con i valori dello stesso gruppo uno sotto l’altro.
  3. break alla fine di ogni gruppo, default per tutto il resto, anche solo per segnalare un input non valido.

Da switch a if-else. Ogni gruppo di case diventa una condizione con ||: case 3: case 4: case 5: diventa if (x <mark> 3 || x </mark> 4 || x == 5), default diventa l’else finale. Con un fall-through fra due gruppi il blocco del primo va ripetuto in testa al secondo.

4Esempi svolti a lezione

4.1Lo switch su numero

(slide 24-26) La slide mostra lo switch su una variabile intera numero (l’espressione è il solo identificatore) e fa due domande. Sulla slide printf(”Invalido") apre la stringa con una virgoletta tipografica: non compila. Versione completa e corretta, con \n in ogni stampa:

#include <stdio.h>

int main(void)
{
    int numero;

    if (scanf("%d", &numero) != 1) {
        return 1;
    }
    switch (numero) {
    case 0:
        printf("Nessuno\n");
        break;
    case 1:
        printf("Uno\n");
        break;
    case 2:
        printf("Due\n");
        break;
    case 3:
    case 4:
    case 5:
        printf("Valore positivo piccolo\n");
        break;
    default:
        printf("Invalido\n");
        break;
    }
    return 0;
}
numerooutput
0Nessuno
1Uno
3, 4, 5Valore positivo piccolo
6Invalido
-1Invalido

(verificati)

Q1: cosa succede se numero vale 3? Si entra da case 3:, che è vuoto, si cade in case 4: e case 5:, anch’essi vuoti, e si arriva al printf: stampa Valore positivo piccolo, poi il break esce.

Q2: e se vale -1? Nessun case corrisponde: si esegue default e stampa Invalido. Lo stesso per 6 o per 1000.

4.2Lettura e categorizzazione di un numero

(slide 27) Il programma della slide legge un “numero piccolo intero positivo” e lo classifica. I commenti della slide dicono: 0 dà “Zero”, 1 dà “Uno”, da 1 a 3 “piccolo”, 4, 5 e oltre “grande”. Il codice non fa questo, ed è un ottimo esercizio di tracing. Codice della slide, con il controllo di scanf e il commento /* fall through */ che rende esplicita la caduta (senza, gcc con -Wextra avvisa this statement may fall through):

#include <stdio.h>

int main(void)
{
    int Valore;

    printf("Inserisci numero piccolo intero positivo\n");
    if (scanf("%d", &Valore) != 1) {
        return 1;
    }
    switch (Valore) {
    case 0: printf("Zero\n"); break;
    case 1: printf("Uno\n");
        /* fall through */
    case 2:
    case 3: printf("Piccolo\n"); break;
    case 4:
    case 5:
    default: printf("Invalido\n");
    }
    return 0;
}

Cosa stampa davvero, dopo la richiesta (verificato):

Valoreoutputperché
0Zerobreak subito
1Uno poi Piccolocase 1 non ha break: cade nel 2, nel 3, e si ferma al break del 3
2, 3Piccolo
4, 5Invalidocase 4 e case 5 sono vuoti e cadono nel default
9, -1Invalidonessun case: default

Le incongruenze della slide, da segnalare all’esame se chiedono “trova l’errore”:

4.3Conteggio di cifre, spazi e altri caratteri

(slide 28-30) Il testo: dalla sequenza di caratteri in standard input, terminata da EOF, contare

La slide 29 suggerisce di partire dal ciclo di copia con getchar di Input e output di caratteri e aggiungere i contatori e lo switch. Soluzione della slide 30, con una dichiarazione per riga (nella soluzione il contatore degli spazi si chiama nwhite e quello degli altri nother, e c’è una int i mai usata):

#include <stdio.h>

int main(void)
{
    int c;
    int nwhite = 0;
    int nother = 0;
    int n02 = 0;
    int n35 = 0;
    int n69 = 0;

    while ((c = getchar()) != EOF) {
        switch (c) {
        case '0': case '1': case '2':
            n02++;
            break;
        case '3': case '4': case '5':
            n35++;
            break;
        case '6': case '7': case '8': case '9':
            n69++;
            break;
        case ' ': case '\t': case '\n':
            nwhite++;
            break;
        default:
            nother++;
            break;
        }
    }
    printf("\n---------------\n");
    printf("Gravi Insufficienze=%d\n", n02);
    printf("Insufficienze=%d\n", n35);
    printf("Sufficienze=%d\n", n69);
    printf("white space=%d\n", nwhite);
    printf("other=%d\n", nother);
    return 0;
}

Con l’input della slide, 02a 3A seguito subito da EOF (nessun invio dopo la A):

caratterecasecontatore
0'0'n02 = 1
2'2'n02 = 2
adefaultnother = 1
spazio' 'nwhite = 1
3'3'n35 = 1
Adefaultnother = 2

Output (verificato):


---------------
Gravi Insufficienze=2
Insufficienze=1
Sufficienze=0
white space=1
other=2

Corrisponde ai valori attesi della slide 28 (n02=2, n35=1, n69=0, ns=1, no=2). Se l’input si scrive da tastiera e si preme invio prima di EOF, l’a capo è un carattere in più e white space diventa 2.

Perché funziona: c è un int ma contiene il codice ASCII del carattere, e '0', '\t' sono costanti intere (48, 9). Lo switch confronta numeri. I case sullo stesso rigo sono la stessa cosa dei case uno sotto l’altro. Il break qui esce solo dallo switch: il while prosegue con il carattere successivo.

5Esercizi tipo esame

Esercizio 1. Scrivi l’output esatto.

#include <stdio.h>

int main(void)
{
    int i;

    for (i = 0; i < 4; i++) {
        switch (i) {
        case 0:
            printf("a");
            /* fall through */
        case 1:
            printf("b");
            break;
        case 2:
            printf("c");
            /* fall through */
        default:
            printf("d");
        }
    }
    printf("\n");
    return 0;
}
Soluzione
iingressostampa
0case 0a, cade in case 1: b, break
1case 1b, break
2case 2c, cade in default: d
3defaultd

Output: abbcdd (verificato). All’esame i commenti /* fall through */ non ci saranno: il break mancante va visto da sé.

Esercizio 2. Scrivi un programma che legge un’espressione nella forma a op b, con a e b interi e op uno fra +, -, * (o x), /, e stampa il risultato. Divisione per zero e operatore sconosciuto vanno segnalati.

Soluzione
#include <stdio.h>

int main(void)
{
    int a;
    int b;
    char op;

    if (scanf("%d %c %d", &a, &op, &b) != 3) {
        printf("input non valido\n");
        return 1;
    }
    switch (op) {
    case '+':
        printf("%d\n", a + b);
        break;
    case '-':
        printf("%d\n", a - b);
        break;
    case '*':
    case 'x':
        printf("%d\n", a * b);
        break;
    case '/':
        if (b == 0) {
            printf("divisione per zero\n");
        }
        else {
            printf("%d\n", a / b);
        }
        break;
    default:
        printf("operatore sconosciuto\n");
        break;
    }
    return 0;
}

7 + 5 dà 12, 7 / 2 dà 3 (divisione intera), 7 / 0 dà divisione per zero, 6 x 3 dà 18, 7 % 2 dà operatore sconosciuto (verificati). Lo spazio prima di %c nel formato salta gli spazi: senza, op leggerebbe lo spazio dopo il 7. Il controllo b == 0 è un if dentro un case: lo switch sceglie l’operatore, non sa confrontare b.

Esercizio 3. Riscrivi con una catena di if-else lo switch della slide 27 (quello di Valore), in modo che stampi esattamente le stesse cose per ogni input.

Soluzione

Il caso 1 stampa due righe, quindi va trattato a parte:

if (Valore == 0) {
    printf("Zero\n");
}
else if (Valore == 1) {
    printf("Uno\n");
    printf("Piccolo\n");
}
else if (Valore == 2 || Valore == 3) {
    printf("Piccolo\n");
}
else {
    printf("Invalido\n");
}

4 e 5 finiscono nell’else come tutti gli altri: è quello che fa lo switch originale.

Esercizio 4. Quali di questi frammenti sono errori di compilazione?

  1. switch (x) { case 1.5: ... } con x di tipo float
  2. switch (c) { case 'a': case 'A': n++; break; }
  3. switch (x) { case y: ... } con y variabile int
  4. switch (x) { case 1: ... case 1: ... }
  5. switch (x) { default: n++; }
Soluzione
  1. Errore: l’espressione dello switch e i case devono essere interi.
  2. Corretto: due etichette sullo stesso blocco, conta la a minuscola e maiuscola.
  3. Errore: un case vuole una costante, non una variabile.
  4. Errore: valore duplicato in due case.
  5. Corretto, anche se inutile: default da solo si esegue sempre.

Esercizio 5. Scrivi l’output esatto con input ab1 x.

#include <stdio.h>

int main(void)
{
    int c;
    int n = 0;

    while ((c = getchar()) != EOF) {
        switch (c) {
        case 'a':
            n += 10;
            break;
        case 'b':
            n += 20;
            /* fall through */
        case '1':
            n += 1;
            break;
        case ' ':
            continue;
        default:
            n = n * 2;
        }
        printf("%d ", n);
    }
    printf("\n");
    return 0;
}
Soluzione
caratterecosa succedenstampa
a+10+101010
b+20+20, cade in '1': +1+13131
1+1+13232
spaziocontinue: salta il printf32niente
xdefault: ⋅2\cdot 26464

Output: 10 31 32 64 seguito da a capo, con input senza invio finale (verificato). Il continue dentro lo switch agisce sul while. Con l’invio finale ci sarebbe un giro in più: '\n' va al default e stampa 128.

6Errori tipici

7Domande