Lezione 5 di 7 · 18 min di lettura
foreach, un elemento alla volta
Un primo assaggio di array, e il ciclo pensato per scorrerli. Indici, stringhe lettera per lettera, cicli al contrario e gli errori di "fuori dai limiti".
Tante variabili in una: l’array
Supponi di dover gestire i voti di quattro esami. Con quello che sai finora scriveresti int score1, int score2, int score3, int score4. E con cento esami? Serve un modo per tenere tanti valori dello stesso tipo sotto un solo nome. È l’array.
int[*] scores = { 18, 25, 30, 27 };Leggila così: ”scores è un array di int, e il compilatore conta da solo quanti sono ([*])”. I valori stanno tra graffe, separati da virgole. Potevi anche scrivere il numero esplicitamente: int[4] scores = { 18, 25, 30, 27 };.
Per usare un singolo elemento si scrive il suo indice tra quadre, e si parte a contare da zero:
io::printn(scores.len); // how many elements
io::printn(scores[0]); // the first one
io::printn(scores[3]); // the fourth (and last)
scores[1] = 28; // change the second one
io::printn(scores); // the whole array4
18
27
[18, 28, 30, 27].len è la lunghezza, come per le stringhe. E sì, printn sa stampare un array intero.
Agli array dedicheremo un modulo intero (con le slice, le liste che crescono e molto altro). Per ora ci basta questo: una fila di valori, numerati da 0.
Dettagli nerd Perché si comincia a contare da zero?
Un array è una fila di caselle una attaccata all’altra in memoria. Un int occupa 4 byte, quindi scores sono 16 byte consecutivi. Per trovare un elemento, il computer fa un solo calcolo: indirizzo dell’inizio + indice × 4. Il primo elemento sta esattamente all’inizio, cioè a distanza zero: da qui scores[0]. L’indice, in realtà, è uno spostamento (offset) rispetto all’inizio.
Questo calcolo è anche il motivo per cui leggere scores[500] è istantaneo come leggere scores[0]: nessuna ricerca, solo una moltiplicazione e una somma.
Fuori dai limiti
Cosa succede se chiedi un elemento che non esiste? Se l’indice è un numero scritto nel codice, il compilatore se ne accorge subito:
io::printn(scores[4]);
// error: An index of '4' is out of range, a value between 0 and 3 was expected.Ma se l’indice viene calcolato mentre il programma gira, lo si scopre solo allora. Questo ciclo ha il classico errore del <= al posto del <:
for (int i = 0; i <= 4; i++)
{
io::printn(scores[i]);
}18
25
30
27
ERROR: 'Array index out of bounds (array had size 4, index was 4)'
in bounds.main (bounds.c3:7) [bounds] [inline]Il programma si ferma con un messaggio chiarissimo: l’array aveva 4 elementi e tu hai chiesto quello di indice 4, cioè il quinto. Ti dice anche il file e la riga.
foreach: per ogni elemento…
Scorrere un array con un for si può fare, ma bisogna gestire l’indice, il limite, il < giusto. Scorrere una collezione dall’inizio alla fine è così comune che C3 ha un ciclo apposta, foreach (“per ogni”):
int[*] scores = { 18, 25, 30, 27 };
foreach (score : scores)
{
io::printf("%d ", score);
}
io::printn();18 25 30 27Leggilo così: ”per ogni score in scores”. A ogni giro, score contiene il prossimo elemento. Niente indici, niente limiti da calcolare: impossibile sbagliare il <. Il tipo di score lo deduce il compilatore; se vuoi, puoi scriverlo: foreach (int score : scores).
Anche con l’indice
Se oltre al valore ti serve la sua posizione, metti due nomi: il primo è l’indice, il secondo il valore.
foreach (i, score : scores)
{
io::printfn("Exam %d: %d", i + 1, score);
}Exam 1: 18
Exam 2: 25
Exam 3: 30
Exam 4: 27L’indice parte da 0, per questo stampiamo i + 1: gli umani contano da 1.
Al contrario: foreach_r
Per scorrere dall’ultimo al primo c’è foreach_r (r sta per reverse):
foreach_r (score : scores) io::printf("%d ", score);
io::printn();27 30 25 18Una stringa, lettera per lettera
Ricordi che una String è una fila di char? Allora si può scorrere con foreach:
String word = "hello";
foreach (c : word) io::printf("%c-", c);
io::printn();h-e-l-l-o-(Attenzione con le lettere accentate: come abbiamo visto, in UTF-8 una è occupa due byte, quindi foreach la vedrebbe come due char separati. Per il testo semplice, nessun problema.)
Quiz
Cosa stampa foreach (score : scores) score = 0; seguito da io::printn(scores[0]);, se scores vale { 18, 25, 30, 27 }?
Lo schema del “cerca e accumula”
Con foreach e un paio di variabili prima del ciclo puoi rispondere a quasi tutte le domande su una collezione: il massimo, il minimo, la somma, quanti elementi rispettano una condizione. Lo schema è sempre lo stesso:
- prima del ciclo, prepara le variabili (una somma a 0, un massimo uguale al primo elemento…);
- dentro il ciclo, aggiornale guardando un elemento alla volta;
- dopo il ciclo, usa il risultato.
Lo metti in pratica nell’esercizio.
Esercizio · sul tuo computer
La settimana del meteo
Scrivi temps.c3 con le temperature massime di una settimana:
int[*] temps = { 18, 21, 19, 24, 22, 17, 20 };Con un solo foreach, calcola la temperatura massima, la minima, la somma e quanti giorni hanno superato i 20 gradi (20 escluso). Poi stampa:
Max:e il massimo;Min:e il minimo;Average:e la media con una cifra decimale (attenzione alla divisione intera!);Days above 20:e il conteggio.
Suggerimento: inizializza massimo e minimo con temps[0], non con 0. Riporta l’output.
Mostra una soluzione (prima prova da solo!)
import std::io;
fn void main()
{
int[*] temps = { 18, 21, 19, 24, 22, 17, 20 };
int max = temps[0];
int min = temps[0];
int sum = 0;
int warm_days = 0;
foreach (t : temps)
{
if (t > max) max = t;
if (t < min) min = t;
sum += t;
if (t > 20) warm_days++;
}
io::printfn("Max: %d", max);
io::printfn("Min: %d", min);
io::printfn("Average: %.1f", (double)sum / temps.len);
io::printfn("Days above 20: %d", warm_days);
}Perché temps[0] e non 0? Se tutte le temperature fossero negative (un inverno siberiano), un massimo partito da 0 resterebbe 0, un valore che nell’array non c’è nemmeno. Partire dal primo elemento funziona sempre.
Ricapitolando
- Un array tiene tanti valori dello stesso tipo:
int[*] scores = { 18, 25, 30 };. - Gli elementi si leggono con
scores[i], partendo da 0;.lendà la lunghezza. - Uscire dai limiti è un errore: al momento della compilazione se possibile, altrimenti mentre il programma gira.
foreach (x : array)scorre gli elementi;foreach (i, x : array)dà anche l’indice (di tiposz).foreach_rscorre al contrario;foreachfunziona anche sulle stringhe.- La variabile del
foreachè una copia: modificarla non cambia l’array.
Sai scegliere, ripetere e scorrere. Manca l’ultimo grande strumento del modulo: dare un nome a un pezzo di codice, per usarlo quando vuoi. Le funzioni.