Lezione 1 di 7 · 18 min di lettura
Prendere decisioni
if, else ed else if. Le graffe che C3 pretende, le variabili che vivono in un blocco e l'operatore ternario.
Da una lista a un bivio
Finora i nostri programmi erano una lista: il computer eseguiva le istruzioni dall’alto in basso, tutte, sempre. Utile, ma rigido. Un programma vero deve poter dire “se succede questo, fai quello”. È il primo passo per farlo pensare.
Lo strumento è l’istruzione if (“se”), e ricordi il tipo bool del modulo 1? Eccolo al lavoro:
import std::io;
fn void main()
{
int temperature = 17;
if (temperature < 19)
{
io::printn("Turning the heating on.");
}
io::printn("Done.");
}Turning the heating on.
Done.Leggila così: ”se la temperatura è minore di 19, esegui quello che c’è tra le graffe”. La condizione tra parentesi, temperature < 19, è un confronto: produce un bool, cioè true o false. Se è true il blocco viene eseguito; se è false viene saltato e il programma riprende subito dopo la graffa chiusa.
Prova a cambiare 17 in 22: il programma stampa solo Done..
Dettagli nerd Come fa il processore a «scegliere»?
Il processore non sa cosa sia un if. Sa fare due cose molto più semplici: confrontare due numeri, e saltare a un’altra istruzione del programma, invece di passare alla successiva. Un if diventa proprio questo: “confronta temperature con 19; se non è minore, salta oltre il blocco”. Si chiama salto condizionato.
I processori moderni sono così veloci che non aspettano di sapere l’esito del confronto: scommettono su quale strada verrà presa e intanto cominciano a eseguirla. È la branch prediction (previsione dei salti). Se indovinano, tempo risparmiato; se sbagliano, buttano via il lavoro fatto e ripartono, perdendo qualche decina di cicli di clock. Per questo un if che va quasi sempre dalla stessa parte costa pochissimo.
Altrimenti: else
Spesso vuoi fare una cosa oppure un’altra. Per il ramo “altrimenti” c’è else:
int age = 15;
if (age >= 18)
{
io::printn("You can vote.");
}
else
{
io::printn("Not yet: wait a few years.");
}Not yet: wait a few years.Esattamente uno dei due blocchi viene eseguito, mai entrambi, mai nessuno.
Più strade: else if
E quando le strade sono più di due? Si mettono in fila con else if:
import std::io;
fn void main()
{
int score = 72;
if (score >= 90)
{
io::printn("Excellent");
}
else if (score >= 70)
{
io::printn("Good");
}
else if (score >= 50)
{
io::printn("Sufficient");
}
else
{
io::printn("Insufficient");
}
}GoodLe condizioni vengono controllate dall’alto in basso, e vince la prima vera: le altre non vengono nemmeno guardate. Con 72, score >= 90 è falsa, score >= 70 è vera: stampa Good e ha finito. Anche se score >= 50 sarebbe vera pure lei, non ci arriva mai. L’else finale raccoglie tutto quello che non è stato preso prima.
Quiz
In questa versione le condizioni sono in un altro ordine. Cosa stampa con int score = 95;? if (score >= 50) { io::printn("Sufficient"); } else if (score >= 90) { io::printn("Excellent"); }
Le graffe: quando C3 le pretende
Se il blocco contiene una sola istruzione, puoi scrivere l’if tutto su una riga, senza graffe:
if (n < 0) n = -n; // if n is negative, make it positiveMa C3 mette due paletti, e il compilatore li fa rispettare:
- L’istruzione deve stare sulla stessa riga dell’
if. Se vai a capo senza graffe, l’errore è:The 'then' part of a single line if-statement must start on the same line as the 'if' or use '{ }'. - Se c’è un
else, le graffe sono obbligatorie, anche per una sola istruzione:if-statements with an 'else' must use '{ }' even around a single statement.
Condizioni composte
Nel modulo 1 abbiamo visto &&, || e !. Dentro un if danno il meglio:
int hour = 14;
bool is_weekend = false;
if (hour >= 9 && hour < 18 && !is_weekend)
{
io::printn("The shop is open.");
}The shop is open.“Se sono le 9 o più, e prima delle 18, e non è weekend.” Ricorda il cortocircuito: appena una condizione di un && è falsa, le successive non vengono valutate.
Le variabili vivono nel loro blocco
Una variabile dichiarata dentro le graffe esiste solo lì dentro. Quando il blocco finisce, sparisce:
int price = 120;
if (price > 100)
{
int discount = 20;
price -= discount;
}
io::printn(price);
io::printn(discount); // error!La prima stampa va benissimo (price è dichiarata fuori), la seconda no: fuori dal blocco discount non esiste più, e il compilatore risponde “‘discount’ could not be found, did you spell it right?”. Questa “zona di vita” di una variabile si chiama scope (visibilità).
E c’è una regola in più, tutta di C3: dentro un blocco non puoi dichiarare una variabile con lo stesso nome di una già visibile fuori.
int x = 1;
{
int x = 2; // error: 'x' would shadow a previous declaration.
}In molti linguaggi è permesso (si chiama shadowing, “mettere in ombra”) ed è una fonte classica di confusione: due x diverse a poche righe di distanza. C3 lo vieta, così x è sempre e solo una.
Dettagli nerd Dove finisce una variabile quando il blocco si chiude?
Le variabili locali vivono in una zona di memoria chiamata stack (pila). Immagina una pila di piatti: quando entri in un blocco il programma “appoggia” sopra la pila lo spazio per le sue variabili, quando esci lo toglie. Non viene cancellato niente: semplicemente quello spazio torna libero, pronto per il prossimo blocco. È velocissimo, perché basta spostare un numero (il puntatore dello stack) su o giù.
Ecco perché discount non esiste più fuori dalle graffe: il suo posto nella pila è già stato restituito. Rivedremo lo stack quando parleremo di funzioni.
L’operatore ternario
Capita spesso di voler scegliere tra due valori, non tra due blocchi di codice. Per questo esiste l’operatore ternario condizione ? se_vero : se_falso:
int n = 7;
String parity = n % 2 == 0 ? "even" : "odd";
io::printn(parity);
int count = 1;
io::printfn("%d %s", count, count == 1 ? "item" : "items");odd
1 itemSi chiama “ternario” perché ha tre pezzi. È un’espressione: produce un valore, quindi puoi metterlo dove metteresti un valore, come dentro una printfn. Il secondo esempio risolve un problema eterno: “1 items” è brutto, e così il plurale si sistema da solo.
Quiz
Quale di queste righe non compila in C3?
Esercizio · sul tuo computer
Il meteo in tre righe
Scrivi weather.c3 con due variabili: int temperature = 23; e int humidity = 70;. Il programma deve stampare tre righe:
Coldse la temperatura è sotto 18,Comfortablese è fino a 24 compreso, altrimentiHot(una catenaif/else if/else);Humidse l’umidità supera 60, altrimentiDry(prova a usare il ternario dentroio::printn);Muggy daysolo se la temperatura supera 20 e l’umidità supera 60; altrimenti niente.
Riporta l’output. Poi, per esercizio, cambia i valori e prevedi cosa stamperà prima di lanciarlo.
Mostra una soluzione (prima prova da solo!)
import std::io;
fn void main()
{
int temperature = 23;
int humidity = 70;
if (temperature < 18)
{
io::printn("Cold");
}
else if (temperature <= 24)
{
io::printn("Comfortable");
}
else
{
io::printn("Hot");
}
io::printn(humidity > 60 ? "Humid" : "Dry");
if (temperature > 20 && humidity > 60) io::printn("Muggy day");
}Ricapitolando
if (condizione) { ... }esegue il blocco solo se la condizione è vera.elseè il ramo “altrimenti”;else ifmette in fila più alternative, e vince la prima vera.- Senza graffe, l’istruzione deve stare sulla stessa riga dell’
if; con unelsele graffe sono obbligatorie. - Una variabile dichiarata in un blocco vive solo lì (scope), e non può avere lo stesso nome di una già visibile.
c ? a : bsceglie tra due valori;a ?: bdà un valore di riserva.
Una catena di else if che confronta la stessa variabile con tanti valori diventa presto lunga. Per quel caso C3 ha uno strumento su misura: switch.