Note Ingegneria Informatica · UniTN
Programmazione 1
Lezione
set, giorno?
in corso

Istruzioni condizionali

Indice 7 sezioni
  1. 1Definizioni
  2. 2Concetti
  3. 2.1Precedenze nelle condizioni
  4. 2.2Graffe e indentazione
  5. 3Metodo
  6. 4Esempi svolti a lezione
  7. 5Esercizi tipo esame
  8. 6Errori tipici
  9. 7Domande

Argomento di Programmazione 1. Fatto a lezione a settembre, prima parte del deck 3.2: slide 1-39. Prima: Algebra di Boole. Dopo: Ciclo while ed esempi.

Il filo del deck:

tre strutture          assegnazione, condizionale, iterativa
     |
if / if-else           flowchart, semantica, esempi
     |
ambiguità              dangling else: l'else va all'ultimo if
     |
?:                     l'if-else dentro un'espressione
     |
blocco { }             più istruzioni valgono come una
     |
precedenze             come si legge  a + b - 4 <= 9 && x < tot - 1
     |
trappole               graffe mancanti, esercizi 1a e 1b

1Definizioni

Le istruzioni del C (slide 4-6). Con tre strutture sintattiche si scrive qualunque programma, le stesse viste in pseudocodice in Introduzione al corso e algoritmi:

Il deck si apre con un problema, un analizzatore di testo che sostituisce una parola con un’altra (slide 3). Serve tutto questo capitolo per arrivarci, e le slide non lo risolvono ancora.

if (slide 7-9). Si usa quando un blocco di istruzioni va eseguito solo se una condizione è vera, e se è falsa non si fa niente.

if (espressione) istruzione

if-else (slide 10-12). Si usa quando c’è un blocco per il caso vero e un altro blocco, in alternativa, per il caso falso.

if (espressione) istruzione1 else istruzione2
ifif-else

Nel rombo del flowchart della slide c’è scritto “espressione ≠ 0 ?”: in C la condizione è un’espressione qualsiasi, ed è vera se il suo valore è diverso da zero. if (x) equivale a if (x != 0).

Semantica (slide 13).

  1. Si valuta la condizione.
  2. Se è VERA si esegue solo la prima istruzione (o sequenza).
  3. Se è FALSA si esegue la seconda, quella dopo else.
  4. Se manca l’else e la condizione è falsa, si prosegue con l’istruzione che segue l’if.

In ogni caso uno solo dei due rami viene eseguito, mai entrambi.

Istruzione composta, o blocco (slide 25-27). Più istruzioni in sequenza racchiuse fra { e } formano un blocco che vale come una sola istruzione. Il corpo di main è un blocco. Dopo } non serve il ;, perché la graffa delimita già. Se lo aggiungi di solito non è un errore: è un’istruzione vuota. C’è un caso in cui lo diventa, ed è fra } ed else (sotto, Errori tipici).

Serve perché la sintassi di if prevede una istruzione per ramo. Se nel ramo ne vuoi due, le metti in un blocco.

Operatore ternario ?: (slide 21-22). Forma sintetica dell’if-else, ed è l’unico operatore del C con tre operandi.

espressione1 ? espressione2 : espressione3

Si valuta espressione1: se è vera il risultato è il valore di espressione2, altrimenti quello di espressione3. Viene valutata solo una delle due.

La slide lo presenta come equivalente a if (e1) {e2;} else {e3;}. La differenza che conta: ?: è un’espressione, quindi ha un valore e si può mettere a destra di un’assegnazione o dentro un printf. L’if è un’istruzione e non ha valore. Per questo si usa per inizializzare:

int opening_time = (day == WEEKEND) ? 12 : 9;

Ambiguità del dangling else (slide 18-20). Come la frase “Carlo discute della relazione con Roberta”, che ha due letture, questa riga ne ha due:

if (C1) if (C2) S1; else S2;

a)  if (C1)                    b)  if (C1)
        if (C2) S1;                    if (C2) S1;
    else S2;                           else S2;

Convenzione del C: l’else si attacca all’ultimo if rimasto senza else, cioè il più vicino. Vale la lettura b, qualunque sia l’indentazione. Per avere la a servono le graffe:

if (C1) {if (C2) S1;} else S2;

La differenza si vede quando C1 è falsa: con la lettura b non si esegue niente, con la a si esegue S2. gcc con -Wall avvisa (“suggest explicit braces to avoid ambiguous ‘else’”).

2Concetti

2.1Precedenze nelle condizioni

(slide 28-34) Una condizione mescola tre tipi di operatori: logici (A && B), aritmetici (a * 2), relazionali (a <= b). Le regole (slide 29):

La tabella completa è in Espressioni, operatori e costanti. L’ordine che serve per le condizioni, dall’alto:

!  ++  --  -(unario)      unari
*  /  %                   moltiplicativi
+  -                      additivi
<  <=  >  >=              relazionali
==  !=                    uguaglianza
&&                        AND logico
||                        OR logico
?:                        condizionale
=  +=  -= ...             assegnazione

Quindi l’aritmetica si fa prima dei confronti, e i confronti prima di && e ||. Per questo x > 0 && x < 10 funziona senza parentesi.

Esempio della slide 34: a + b - 4 <= 9 && x < tot - 1

a + b - 4 <= 9 && x < tot - 1
((a + b) - 4) <= 9 && x < (tot - 1)          additivi, associativi da sinistra
(((a + b) - 4) <= 9) && (x < (tot - 1))      relazionali
((((a + b) - 4) <= 9) && (x < (tot - 1)))    AND logico, per ultimo

Con a=5a = 5, b=6b = 6, x=3x = 3, tot=10tot = 10: 5+6−4=7≤95 + 6 - 4 = 7 \leq 9 vero; 3<93 < 9 vero; il risultato è 1.

2.2Graffe e indentazione

L’indentazione serve a chi legge (slide 16), il compilatore la ignora. Quello che conta sono le graffe. Tre versioni del massimo di due numeri (slide 35-37):

Regola che evita tutti e tre i problemi: graffe sempre, anche per una sola istruzione.

3Metodo

Tracciare un if.

  1. Scrivi i valori correnti delle variabili.
  2. Metti le parentesi implicite nella condizione, poi calcolala; con && e || fermati appena il risultato è deciso (i ++ nella parte saltata non avvengono).
  3. Individua il ramo dalle graffe, non dall’indentazione. Senza graffe il ramo è una sola istruzione, fino al primo ;.
  4. Ogni else va all’if più vicino che non ha ancora un else.
  5. Aggiorna la tabella solo per le istruzioni del ramo eseguito.

Scrivere una condizione. Traduci la frase in italiano pezzo per pezzo con && (e), || (o), ! (non). “x fra 1 e 10” è x >= 1 && x <= 10, mai 1 <= x <= 10.

4Esempi svolti a lezione

Esempi di sintassi (slide 14-15).

if (x == 0) z = 5; else y = z + w*y;
if (x == 0) {z = 5;} else {y = z + w*y;}
if ((x+y)*(z-2) > (23+v)) {z = x + 1; y = 13 + x;}
if ((x == y && z > 3) || w != y)
    z = 5;
else {
    y = z + w*y;
    x = z;
}

La prima e la seconda sono identiche: le graffe attorno a una sola istruzione non cambiano niente. Nella terza il blocco contiene due assegnazioni, eseguite entrambe o nessuna. Nella quarta la condizione è un’espressione logica: && per AND e || per OR, come in Algebra di Boole.

La slide 16 scrive if (x>0) printf(x); else printf(-x); per stampare il valore assoluto. È lo pseudo-C del corso: in C vero è printf("%d", x).

Istruzioni scorrette e non canoniche (slide 17).

1)  if (x == 0)  else y = 34;
2)  if (x == 0)  a; else b + c;
  1. Scorretta: manca l’istruzione del ramo vero. gcc: expected expression before 'else'. Se nel caso vero non c’è niente da fare si nega la condizione: if (x != 0) y = 34;.
  2. Non canonica: compila, ma a; e b + c; calcolano un valore e lo buttano. Non succede niente in nessuno dei due rami. gcc con -Wall avvisa: statement with no effect.

Massimo di due numeri (slide 23). Il programma della slide, con il controllo di scanf e una dichiarazione per riga:

#include <stdio.h>

int main(void)
{
    int m;
    int n;
    int max;

    if (scanf("%d", &m) != 1) {
        return 1;
    }
    printf("m=%d\n", m);
    if (scanf("%d", &n) != 1) {
        return 1;
    }
    printf("n=%d\n", n);

    if (m > n) max = m; else max = n;

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

Con input 7 e 3 stampa m=7, n=3, max=7. Se i due numeri sono uguali m > n è falso e max prende n, che è lo stesso valore: il caso va bene senza trattarlo a parte.

Sfida: solo due variabili (slide 24). Si può fare senza max? Sì: se y è più grande la si copia in x, e alla fine x è il massimo. Si perde il valore iniziale di x, che non serve più.

#include <stdio.h>

int main(void)
{
    int x;
    int y;

    if (scanf("%d %d", &x, &y) != 2) {
        return 1;
    }
    if (y > x) {
        x = y;
    }
    printf("max=%d\n", x);
    return 0;
}

Con input 4 9 stampa max=9. Un’altra strada è non salvare niente: printf("max=%d\n", x > y ? x : y);.

Dangling else (slide 20). Le due letture scritte con le graffe, con C1 falsa:

#include <stdio.h>

int main(void)
{
    int c1 = 0;
    int c2 = 1;

    if (c1) {
        if (c2) printf("S1\n");
        else printf("S2 (else del secondo if)\n");
    }

    if (c1) {
        if (c2) printf("S1\n");
    } else printf("S2 (else del primo if)\n");

    return 0;
}

Output: una sola riga, S2 (else del primo if). Il primo if è la lettura b, quella che il C sceglie quando le graffe mancano: con C1 falsa non esegue niente.

Operatore ternario (slide 22). WEEKEND nella slide non è definito; qui è una costante con #define:

#include <stdio.h>

#define FERIALE 0
#define WEEKEND 1

int main(void)
{
    int day = WEEKEND;
    int opening_time = (day == WEEKEND) ? 12 : 9;

    printf("apre alle %d\n", opening_time);
    day = FERIALE;
    printf("apre alle %d\n", day == WEEKEND ? 12 : 9);
    return 0;
}

Output: apre alle 12, poi apre alle 9.

Esercizi 1a e 1b (slide 38-39). Quanto valgono x, y e z alla fine?

#include <stdio.h>

int main(void)
{
    int x;
    int y;
    int z;

    y = 1;
    z = 0;
    x = 1;
    if (x <= 1)
        y = 0;
    z = 1;
    printf("x=%d y=%d z=%d\n", x, y, z);
    return 0;
}
istruzionexyz
y = 1; z = 0; x = 1;110
if (x <= 1) vero, y = 0;100
z = 1; (fuori dall’if)101

Output: x=1 y=0 z=1. L’1b mette y = 0; fra graffe e il risultato è identico: con una sola istruzione nel ramo le graffe non cambiano niente. Il tranello è l’indentazione dell’1a, che non c’è: z = 1; è eseguita comunque, anche se la condizione fosse falsa.

La slide scrive main() senza tipo. Era il C degli anni ‘80 (“int implicito”); dal C99 non è più valido, e gcc 14 e successivi lo rifiutano con error: return type defaults to 'int'. Si scrive int main(void).

5Esercizi tipo esame

Esercizio 1. Scrivi l’output esatto.

#include <stdio.h>

int main(void)
{
    int x = 4;
    int y = 7;
    int z = 0;

    if (x > 2 && y < 5)
        z = 1;
    else if (x % 2 == 0)
        z = 2;
    else
        z = 3;

    if (z == 2) {
        if (y > x)
            y = y - x;
        else
            x = x - y;
    }
    z += x > y ? x : y;
    printf("%d %d %d\n", x, y, z);
    return 0;
}
Soluzione

x > 2 && y < 5: 1 && 0, falso. Si passa all’else, che contiene un altro if: 4 % 2 è 0, vero, quindi z = 2. z vale 2, si entra: y > x (7 > 4) vero, y = 3. x > y ? x : y: 4 > 3, vale 4. z = 2 + 4 = 6. Output: 4 3 6 (verificato).

Esercizio 2. Scrivi l’output esatto.

#include <stdio.h>

int main(void)
{
    int a = 5;
    int b = 0;
    int c = 1;

    if (a > 3 || b++ > 0)
        c = c + 10;
    if (b == 0 && a-- > 4)
        c = c * 2;
    printf("%d %d %d\n", a, b, c);
    return 0;
}
Soluzione

Primo if: a > 3 è vero, l’OR è già deciso e b++ non si esegue. c = 11, b resta 0. Secondo: b == 0 vero, si valuta a-- > 4: confronta 5 > 4 (vero), poi a diventa 4. c = 22. Output: 4 0 22 (verificato).

Esercizio 3 (stile matricola). Scrivi l’output con matricola = 238517.

#include <stdio.h>

int main(void)
{
    int matricola = 238517;
    int u = matricola % 10;
    int d = matricola / 10 % 10;
    int r;

    r = u % 2 ? u * 2 : u / 2;
    if (d > u)
        r = r + d;
    else
        r = r - d;
    printf("u=%d d=%d r=%d\n", u, d, r);
    return 0;
}
Soluzione

u = 7 (unità), d = 1 (decine). u % 2 vale 1, vero: r = 14. d > u falso: r = 14 - 1 = 13. Output: u=7 d=1 r=13 (verificato). All’esame rifallo con la tua matricola.

Esercizio 4. Con quali valori di a e b il frammento stampa Z? Metti le graffe che rendono esplicito ciò che fa il compilatore.

if (a > 0)
    if (b > 0)
        printf("X");
else
    printf("Z");
Soluzione

L’indentazione inganna: l’else va al secondo if, il più vicino. Con le graffe:

if (a > 0) {
    if (b > 0)
        printf("X");
    else
        printf("Z");
}

Stampa Z quando a>0a > 0 e b≤0b \leq 0. Con a≤0a \leq 0 non stampa niente.

Esercizio 5. Scrivi un programma che legge tre interi e stampa il massimo, in una sola espressione con ?:.

Soluzione
#include <stdio.h>

int main(void)
{
    int a;
    int b;
    int c;

    if (scanf("%d %d %d", &a, &b, &c) != 3) {
        return 1;
    }
    int max = a > b ? (a > c ? a : c) : (b > c ? b : c);
    printf("max=%d\n", max);
    return 0;
}

Con 3 9 5 stampa max=9, con 7 -2 1 stampa max=7 (verificato). Se a > b il massimo è fra a e c, altrimenti fra b e c.

Esercizio 6. Scrivi un programma che legge un anno e stampa se è bisestile. Un anno è bisestile se è divisibile per 4 ma non per 100, oppure se è divisibile per 400.

Soluzione
#include <stdio.h>

int main(void)
{
    int anno;

    if (scanf("%d", &anno) != 1) {
        printf("input non valido\n");
        return 1;
    }
    if ((anno % 4 == 0 && anno % 100 != 0) || anno % 400 == 0) {
        printf("%d bisestile\n", anno);
    } else {
        printf("%d non bisestile\n", anno);
    }
    return 0;
}

Provato: 2024 bisestile, 1900 non bisestile, 2000 bisestile, 2026 non bisestile. Le parentesi attorno al primo && non sono obbligatorie (&& lega più di ||) ma rendono chiaro il ragionamento.

Esercizio 7. Metti tutte le parentesi implicite in x + 1 > y * 2 || !z && y != 0.

Soluzione
((x + 1) > (y * 2)) || ((!z) && (y != 0))

! prima di tutto, poi *, poi +, poi i confronti, poi &&, per ultimo ||.

6Errori tipici

7Domande