In Swift, il costrutto switch
è stavo rivisto e potenziato rispetto alla versione originale proveniente dal C. Vediamo subito un esempio:
var operazione: Character = "x"
var a = 10, b = 5
switch operazione {
case "+":
print("\(a) + \(b) = \(a+b)")
case "-":
print("\(a) - \(b) = \(a-b)")
case "x":
print("\(a) x \(b) = \(a*b)")
case "/":
print("\(a) / \(b) = \(a/b)")
default:
print("operazione non valida")
}
La prima differenza che il lettore attento osserverà è la mancanza dell'istruzione break
per terminare ogni caso gestito dal costrutto. Questo perchè in Swift non abbiamo l'esecuzione a cascata automatica di tutti i case
dopo il primo caso corrispondente al valore esaminato.
Una seconda differenza è che ogni costrutto switch
deve essere esaustivo: o devono essere gestiti tutti i casi possibili del dominio del valore esaminato o deve essere presente il caso di default
, che viene eseguito quando nessuno dei precedenti case
corrisponde al valore esaminato.
Dato che l'esecuzione a cascata dei vari case
non è possibile, per gestire l'abbinamento con più valori possiamo utilizzare diverse opzioni. La più semplice consiste nell'elencare tutti i valori per un dato case
sulla stessa riga. Ad esempio:
var lettera: Character = "b"
switch lettera {
case "a","e","i","o","u":
print("\(lettera) è una vocale")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
print("\(lettera) è una consonante")
default:
print("\(lettera) non è ne una vocale ne una consonante")
}
La seconda possibilità è usare operatore di range, per fare abbinamento con valori multipli:
var numero: Int = 32
switch numero {
case 1...100:
print("\(numero) è compreso tra 0 e 100")
case 101...1000:
print("\(numero) è compreso tra 101 e 1000")
default:
print("\(numero) è minore di 0 o maggiore di 1000")
}
Oltre che confrontare il valore considerato con un elenco di casi o di range, il costrutto switch
di Swift può lavorare anche su valori di tipo tupla.
Facciamo un esempio supponendo che una tupla rappresenti un punto su un piano, considerando un quadrato 4x4 il cui angolo inferiore sinistro corrisponda all'origine degli assi cartesiani (0,0):
Quadrato 4x4 su piano cartesiano
Con lo switch
possiamo confrontare un punto arbitrario, identificato da una tupla, con i punti che compongono il nostro quadrato:
var punto = (4, 2)
switch punto {
case (0, 0):
print("angolo in basso a sx")
case (4, 4):
print("angolo in alto a dx")
case (4, 0):
print("angolo in basso a dx")
case (0, 4):
print("angolo in alto a sx")
case (_, 4):
print("lato superiore")
case (4, _):
print("lato sinistro")
case (0, _):
print("lato destro")
case (_, 0):
print("lato inferiore")
default:
print("dentro o fuori dai bordi")
}
Il precedente blocco di codice stamperà sulla console la posizione del punto rispetto al nostro quadrato (su quale bordo, se in un angolo o dentro o fuori dai bordi). Notiamo che è possibile utilizzare il carattere speciale _
(il trattino basso, o underscore) per abbinare (o ignorare) uno dei componenti della tupla.
Questo esempio ci permette di illustrare un'altra delle caratteristiche del costrutto Swift che lo rende diverso da quello originale del C: oltre a non eseguire i confronti a cascata (senza utilizzare l'istruzione break
), lo switch
eseguirà solo il primo caso che si accoppia con il valore considerato, anche se ne sono possibili più di uno. Se infatti consideriamo uno degli angoli, anche i casi relativi ai lati che si incontrano sull'angolo in questione sarebbero accoppiati, ma lo switch
si fermerà al primo caso che incontra nell'elenco.
Un lettore attento però si sarà accorto che nel caso dei lati siamo stati poco precisi: se il punto da confrontare ha una delle coordinate negative, otterremo in output uno dei lati, cosa che in realtà non è veritiera, dato che il nostro quadrato si trova sul primo quadrante di un ipotetico piano cartesiano. Il costrutto switch
è così flessibile che è in grado di gestire anche queste eventualità. Per far ciò dobbiamo introdurre altre due caratteristiche di questo potente costrutto: il value binding e la parola riservata where
.
Il meccanismo di value binding ci permette di definire delle variabili o costanti temporanee da accoppiare al valore confrontato dallo switch
nel corpo di ogni case
. Vediamo subito un esempio:
switch punto {
case (let x, 4):
print("sul lato superiore, con x = \(x)")
case (4, let y):
print("sul lato sinistro, con y = \(y)")
case (0, let y):
print("sul lato destro, con y = \(y)")
case (let x, 0):
print("sul lato inferiore, con x = \(x)")
default:
print("dentro o fuori dai bordi")
}
Anziché quindi ignorare uno dei valori delle coordinate del punto considerato con _
, questo viene "catturato" ed associato alla costante x
o y
a seconda dei casi. Possiamo tuttavia aggiungere delle condizioni per affinare i nostri criteri di accoppiamento, tramite la parola riservata where
, in modo da essere sicuri che il nostro punto abbia ascissa e ordinata positive (sia, cioè, nel primo quadrante):
punto = (-4, 2)
switch punto {
case (let x, 4) where x >= 0:
print("sul lato superiore, con x = \(x)")
case (4, let y) where y >= 0:
print("sul lato sinistro, con y = \(y)")
case (0, let y) where y >= 0:
print("sul lato destro, con y = \(y)")
case (let x, 0) where x >= 0:
print("sul lato inferiore, con x = \(x)")
default:
print("dentro o fuori dai bordi")
}
Possiamo quindi completare il nostro esercizio aggiungendo un'ulteriore condizione per assicurarci che, oltre ad essere sul primo quadrante, il nostro punto non sia al di fuori del quadrato (x
o y
siano minori o uguali a 4):
punto = (0, 5)
switch punto {
case (0,0):
print("angolo in basso a sx")
case (4,4):
print("angolo in alto a dx")
case (4,0):
print("angolo in basso a dx")
case (0,4):
print("angolo in alto a sx")
case (let x, 4) where x >= 0 && x <= 4:
print("sul lato superiore, con x = \(x)")
case (4, let y) where y >= 0 && y <= 4:
print("sul lato destro, con y = \(y)")
case (0, let y) where y >= 0 && y <= 4:
print("sul lato sinistro, con y = \(y)")
case (let x, 0) where x >= 0 && x <= 4:
print("sul lato inferiore, con x = \(x)")
default:
print("dentro o fuori dai bordi")
}
Come abbiamo già ripetuto, il costrutto switch
, a differenza del C, termina l'esecuzione dopo aver eseguito il codice corrispondente ad un certo case
. Se volessimo, invece, avere un comportamento simile a quello del C, possiamo utilizzare la parola riservata fallthrough
per far sì che venga eseguito a cascata soltanto il corpo del successivo caso.
numero = 33
switch numero {
case let x where x % 2 == 0:
print("\(x) è divisibile per 2")
case let x where x % 2 != 0:
print("\(x) non è divisibile per 2")
fallthrough
default:
print("ma è un numero intero")
}
Nel precedente esempio vediamo come nel secondo caso viene eseguito anche il corpo del case
successivo (in questo esempio il caso di default, ma poteva essere un qualsiasi altro caso "regolare").
Infine, è possibile usare anche la parola riservata break
per interrompere l'esecuzione immediata dello switch. Ma perchè mai dovremmo farne uso se questo è il comportamento di default? Potrebbe tornare utile nel caso in cui vogliamo ignorare esplicitamente dei case
, oppure quando non vogliamo gestire esplicitamente il caso di default
(ricordiamo, infatti, che ogni switch
deve essere esaustivo). Vediamo un esempio:
numero = 33
switch numero {
case let x where x % 3 == 0:
break
case let x where x % 2 == 0:
print("\(x) è divisibile per 2")
case let x where x % 2 != 0:
print("\(x) non è divisibile per 2")
default:
break
}
Nell'esempio precedente, non gestiamo il caso in cui il numero sia divisibile per 3, nè il caso di default
.
Il Playground completo, con tutti gli esempi illustrati in questa lezione, è dispobile a questo link.