ZIG - Selezione - switch
Come abbiamo detto nel paragrafo relativo all'istruzione
if, avere una ramificazione eccessiva,
ovvero troppi else + if non è cosa buona
poichè 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, nel caso in cui x1 sia
uguale a 10, vediamo anche il caso in cui ci siano una o più
istruzioni, molto semplice.
Una importante osservazione che è doveroso
fare, è che la copertura dei casi possibili per la variabile che si
esamina deve essere completa. Al contrario di if che può
contemplare solo alcuni casi possibili 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 possiamo attraverso essa attribuire un valore ad un variabile o ad una costante.
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.