Selezione - switch
Come abbiamo detto nel paragrafo relativo all'istruzione
if, avere una ramificazione eccessiva,
ovvero troppi else + if non è cosa buona
che rende lettura e manutenzione del programma non particolarmente
agevole. Come in altri linguaggi, esiste una istruzione più chiara che
nasce apposta per casi come questi ed è introdotta dalla keyword
switch. Ricorda da vicino l'omologa
istruzione del C ma prevede anche la sicurezza dell'istruzione match usata
in Rust. Anche qui useremo la keyword else e fa la sua comparsa
l'operatore composto =>.
La struttura standard è la seguente:
switch (variabile o costante) {
valore-1 => istruzioni,
valore-2 => istruzioni,
......
valore-n => istruzioni,
},
else => istruzioni,
}
in cui le istruzioni possono essere una o più. In questo linguaggio non è possibile il fall-through ovvero la ricaduta in rami successivi dopo che se ne è utilizzato uno precedente. In molti linguaggi è necessario usare una istruzione tipo break (C, C++, C#, per dirne qualcuno). La dimenticanza di tale istruzione può creare grossi problemi. Questa situazione in Zig non si presenta e una volta che viene eseguito un ramo di codice gli altri vengono saltati automaticamente. Vediamo un esempio:
| Esempio 8.1 |
| const std = @import("std"); pub fn main() void { const x1 = 10; switch (x1) { 10 => { std.debug.print("x1 vale 10\n", .{}); std.debug.print("x1 + 5 = {}\n", .{x1 + 5}); }, 11 => std.debug.print("x1 is 11\n", .{}), else => std.debug.print("x1 is not 10\n", .{}), } } |
Come si vede, valutiamo il valore di x1 e, a seconda di esso, valorizziamo
il ramo corrispondente. Nel contempo vediamo anche il caso in cui ci siano
una o più istruzioni.
Una importante osservazione che è doveroso fare, è che
la copertura dei casi deve essere completa. Al contrario
di if che può contemplare solo alcuni casi con
switch non ci possono
essere valori non considerati. Ad esempio se togliete
else, che fa da
rastrello nell'esempio 8.1 di tutti i valori che non siano 10 o 11 ne
otterrete:
error: switch must handle all possibilities
più chiaro di così....
le domande standard a questo punto sono almeno due:
1) come si imposta
la cosa se devo gestire due o più valori nello stesso ramo?
2) come si
imposta la cosa se devo gestire un range ampio di valori sequenziali?
Vediamo le risposte:
1) modifichiamo la parte di valutazione dell'esempio 8.1
in modo che il primo branch prenda in esame 3 valori:
switch
(x1) {
10, 12, 15 => std.debug.print("x1 vale 10\n", .{}),
11 => std.debug.print("x1 is 11\n", .{}),
else =>
std.debug.print("x1 ha altri valori", .{}),
}
2) come prima stavolta nel secondo ramo inseriamo un range (dei range
riparleremo)
switch (x1) {
10, 12, 15 => std.debug.print("x1 vale 10\n",
.{}),
0...9 => std.debug.print("x1 minore di 10\n", .{}),
else => std.debug.print("x1 ha altri valori", .{}),
}
Anche switch è una espressione ed il suo valore di uscita può essere attribuito ad una variabile:
| Esempio 8.2 |
| const std = @import("std"); pub fn main() void { const x1 = 11; const x2 = switch (x1) { 10 => 100, else => 0, }; std.debug.print("x2 vale {}\n", .{x2}); } |
attribuendo in questo caso il valore 0 a x2. I tipi dei valori in uscita devono essere uguali.
La completezza richiesta nella copertura dei casi, il blocco forzato dei fall-through, la chiarezza nella gestione dei vari sono i punti di forza di switch in questo linguaggio.