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:
- assegnazione:
x = 23;,w = 'a';,r3 = (alfa*43 - x)*(delta - 32*i);; - condizionale: scegliere cosa eseguire (
if); - iterativa, o ciclo (loop): ripetere (
while, in Ciclo while ed esempi).
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
if | if-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).
- Si valuta la condizione.
- Se è VERA si esegue solo la prima istruzione (o sequenza).
- Se è FALSA si esegue la seconda, quella dopo
else. - Se manca l’
elsee 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):
- gli operatori sono divisi in gruppi con precedenze diverse, e quelli con precedenza più alta si valutano prima;
- fra operatori dello stesso gruppo decide l’associatività (da sinistra o da destra);
- le parentesi cambiano l’ordine, e si possono mettere anche solo per leggibilità.
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 , , , : vero; 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):
- Slide 35, corretta: entrambi i rami fra graffe, ognuno assegna
maxe stampa. - Slide 36: il ramo
ifsenza graffe e con due istruzioni.
L’if (a > b) max = a; printf("massimo: %d", a); else { ... }ifcontrolla solomax = a;. Ilprintfè un’istruzione normale, dopo la quale l’elsenon ha nessunifa cui attaccarsi. Non compila: gcc dàerror: 'else' without a previous 'if'. - Slide 37: il ramo
elsesenza graffe.
Compila, ma l’else max = b; printf("massimo: %d", b);elsecontrolla solomax = b;. Il secondoprintfè fuori dall’if-elsee viene eseguito sempre. Con e il programma stampamassimo: 5e poimassimo: 3. gcc con-Wallavvisa (-Wmisleading-indentation), perché l’indentazione fa credere il contrario.
Regola che evita tutti e tre i problemi: graffe sempre, anche per una sola istruzione.
3Metodo
Tracciare un if.
- Scrivi i valori correnti delle variabili.
- Metti le parentesi implicite nella condizione, poi calcolala; con
&&e||fermati appena il risultato è deciso (i++nella parte saltata non avvengono). - Individua il ramo dalle graffe, non dall’indentazione. Senza graffe il ramo è una sola istruzione, fino al primo
;. - Ogni
elseva all’ifpiù vicino che non ha ancora unelse. - 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;
- 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;. - Non canonica: compila, ma
a;eb + c;calcolano un valore e lo buttano. Non succede niente in nessuno dei due rami. gcc con-Wallavvisa: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;
}
| istruzione | x | y | z |
|---|---|---|---|
y = 1; z = 0; x = 1; | 1 | 1 | 0 |
if (x <= 1) vero, y = 0; | 1 | 0 | 0 |
z = 1; (fuori dall’if) | 1 | 0 | 1 |
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 e . Con 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
if (x = 0)invece diif (x == 0): assegna 0 axe la condizione vale sempre falso. gcc-Wallavvisa (“suggest parentheses around assignment used as truth value”).- Punto e virgola dopo la condizione:
if (x > 0);è unifcon istruzione vuota, e quello che segue viene eseguito sempre. ;fra}edelse:if (c) { ... }; else { ... }non compila, perché il;è un’istruzione vuota che chiude l’ife l’elseresta orfano.- Due istruzioni in un ramo senza graffe (slide 36 e 37).
- Credere all’indentazione per l’
else: va all’ifpiù vicino. 1 <= x <= 10al posto di1 <= x && x <= 10.- Usare
?:come istruzione con effetti (a > b ? x = 1 : y = 2;): non compila (lvalue required as left operand of assignment), perché?:lega più di = e il compilatore legge(a > b ? x = 1 : y) = 2.?:serve per scegliere un valore; per scegliere cosa fare c’è l’if. - Scrivere
main()senzaint.
7Domande
-
Qual è la sintassi di
ife diif-else? -
Descrivi la semantica di un
if-elsein quattro passi. -
Quando una condizione è considerata vera in C?
-
Cos’è un’istruzione composta e perché serve negli
if? -
A quale
ifsi attacca unelseinif (C1) if (C2) S1; else S2;? Come si ottiene l’altra lettura? -
Che differenza c’è fra l’operatore
?:e unif-else? -
Metti le parentesi implicite in
a + b - 4 <= 9 && x < tot - 1. -
Perché il programma della slide 36 non compila?
-
Cosa stampa il programma della slide 37 con
a = 5eb = 3, e perché? -
Come trovi il massimo di due numeri usando solo le due variabili
xey? -
Nell’esercizio 1a quanto valgono
x,y,zalla fine? Cambia qualcosa nell’1b? -
Con
a = 5eb = 0, quanto valebdopoif (a > 3 || b++ > 0) ...? -
Perché
if (x == 0) else y = 34;è scorretta e come la riscrivi?