Saltar al contenido

Decisiones con switch

Cuando una decisión tiene muchos caminos, una cadena de else if se vuelve larga y repite lo mismo en cada línea. Al terminar esta lección vas a poder elegir entre varios caminos con switch: comparar un valor con una lista de opciones, agrupar varios valores en una opción y escribir una condición en cada una. También vas a saber leer el error que aparece cuando repites un valor, por qué un break dentro de un switch no termina el for y cómo salir del for desde un switch.

Elegir según un valor: switch

La caja de la Papelería Central muestra un mensaje distinto según cómo paga cada cliente:

main.go
package main

import "fmt"

func main() {
	metodo := "tarjeta"
	switch metodo {
	case "efectivo":
		fmt.Println("Paga en la caja")
	case "tarjeta":
		fmt.Println("Pasa la tarjeta por el lector")
	case "transferencia":
		fmt.Println("Envía el comprobante")
	default:
		fmt.Println("Método no aceptado")
	}
	fmt.Println("¡Gracias por tu compra!")
}

Salida:

Pasa la tarjeta por el lector
¡Gracias por tu compra!

switch metodo { toma el valor de metodo y lo compara con el de cada case (“caso”), de arriba abajo. "efectivo" no coincide; "tarjeta" sí. Go ejecuta las líneas de ese case, y ahí termina el switch: el programa sigue después de su }, con ¡Gracias por tu compra!.

Cada case lleva un valor, dos puntos (:) y, debajo, las líneas que se ejecutan si coincide. Esas líneas no van entre llaves: el bloque de un case termina donde empieza el siguiente.

default (“por defecto”) es el bloque que se ejecuta cuando ningún case coincide: con metodo := "cheque", el programa imprimiría Método no aceptado. Es opcional. Sin default, si nada coincide, el switch no hace nada. Puede ir en cualquier lugar, pero lo habitual es ponerlo al final.

De todo el switch se ejecuta un solo bloque, o ninguno (salvo con fallthrough, que verás más adelante en esta lección). Un case también puede quedar sin líneas: si coincide, no hace nada, y el switch termina igual.

Este switch hace lo mismo que una cadena de if y else if que compara metodo con cada texto, pero metodo se escribe una sola vez. Como en toda comparación, los valores de los case tienen que ser del mismo tipo que el del switch: aquí, strings. Con case 1:, el programa no compila.

Si vienes de JavaScript, Python o Java…

En JavaScript, C y el switch clásico de Java (con :), cuando un case coincide, el programa sigue de largo por los case de abajo hasta encontrar un break. Por eso allá cada case termina con break;. En Go es al revés: cada case termina solo, y no hace falta break.

Python no tiene switch: lo habitual es if y elif. Desde Python 3.10 existe match, con case y case _: en el papel de default.

Declarar una variable en el switch

La papelería guarda los cuadernos en cajas de 12. Si el stock no llena la última caja, quiere saber cuántos sobran:

main.go
package main

import "fmt"

func main() {
	stock := 30
	switch resto := stock % 12; resto {
	case 0:
		fmt.Println("Solo cajas completas")
	default:
		fmt.Println("Sobran", resto, "cuadernos")
	}
}

Salida:

Sobran 6 cuadernos

Como en el if, puedes declarar una variable antes del valor, separada con un punto y coma: switch resto := stock % 12; resto {. Go calcula primero resto, que vale 6, y después lo compara con cada case. resto existe solo dentro del switch: después de su }, ya no existe.

Varios valores en un case

La papelería abre con horarios distintos según el día. Los días van del 1 (lunes) al 7 (domingo):

main.go
package main

import "fmt"

func main() {
	dia := 6
	switch dia {
	case 1, 2, 3, 4, 5:
		fmt.Println("Abierto de 8 a 18")
	case 6:
		fmt.Println("Abierto de 9 a 13")
	case 7:
		fmt.Println("Cerrado")
	default:
		fmt.Println("Ese día no existe")
	}
}

Salida:

Abierto de 9 a 13

Un case puede tener varios valores, separados por comas. Coincide si el valor del switch es igual a cualquiera de ellos: case 1, 2, 3, 4, 5: es lo mismo que dia == 1 || dia == 2 || …, pero más corto. dia vale 6, que no está en el primer case y sí en el segundo.

Un valor escrito tal cual puede estar en un solo case. Si el sábado se cuela en la lista de días hábiles, el programa no compila:

main.go
package main

import "fmt"

func main() {
	dia := 6
	switch dia {
	case 1, 2, 3, 4, 5, 6:
		fmt.Println("Abierto de 8 a 18")
	case 6:
		fmt.Println("Abierto de 9 a 13")
	}
}

Salida:

./main.go:10:7: duplicate case 6 (constant of type int) in expression switch
	./main.go:8:22: previous case

Lee el mensaje por partes:

  • ./main.go:10:7 apunta al 6 del segundo case, en la línea 10.
  • duplicate case 6 significa “case repetido: 6”.
  • (constant of type int) dice que 6 es una constante de tipo int: como se compara con dia, Go le da el tipo de dia.
  • in expression switch significa “en un switch de expresión”, el nombre que Go le da al switch con un valor.
  • La segunda línea, con sangría, es parte del mismo error: previous case (“case anterior”) apunta al primer 6, al final de la línea 8.

Go ejecuta solo el primer case que coincide, así que el segundo 6 no se usaría nunca. El compilador no lo deja pasar, porque casi siempre es un valor puesto en el case equivocado. Quita el 6 del case que no le corresponde: aquí, del primero.

Si vienes de JavaScript, Python o Java…

En JavaScript, Java y C, dos case seguidos, como case 6: case 7:, comparten el bloque de abajo, porque el primero sigue de largo hasta el segundo. En Go no: un case 6: sin líneas no hace nada, y el sábado no mostraría ningún horario. Para que dos valores compartan un bloque, ponlos en el mismo case: case 6, 7:.

Un switch sin valor

A veces, cada opción depende de una condición, no de un valor exacto. Es el estado del stock de la lección Decisiones con if, escrito con switch:

main.go
package main

import "fmt"

func main() {
	stock := 3
	switch {
	case stock == 0:
		fmt.Println("Agotado")
	case stock < 5:
		fmt.Println("Pocas unidades")
	default:
		fmt.Println("Disponible")
	}
}

Salida:

Pocas unidades

Aquí no hay nada entre switch y la {. En un switch sin valor, cada case es una condición, como la de un if: tiene que dar un bool. Go ejecuta el bloque del primer case que da true. Con stock en 3, stock == 0 da false y stock < 5 da true: sale Pocas unidades.

Es la misma cadena de if, else if y else, sin repetir else if en cada línea. Para Go, un switch sin valor es un switch true {: busca el primer case igual a true.

Un error típico es dejar el valor después de switch y escribir condiciones en los case:

main.go
package main

import "fmt"

func main() {
	stock := 3
	switch stock {
	case stock == 0:
		fmt.Println("Agotado")
	case stock < 5:
		fmt.Println("Pocas unidades")
	}
}

Salida:

./main.go:8:7: cannot convert stock == 0 (untyped bool value) to type int
./main.go:10:7: cannot convert stock < 5 (untyped bool value) to type int

switch stock { compara stock, un int, con el valor de cada case. Pero stock == 0 es una condición: da un bool. Para compararlo con stock, Go tendría que convertirlo a int, y no puede. Lee la primera línea por partes:

  • ./main.go:8:7 apunta al primer case, en la línea 8.
  • cannot convert stock == 0 significa “no se puede convertir stock == 0”.
  • (untyped bool value) dice qué es: un valor bool sin tipo, el resultado de una comparación.
  • to type int significa “al tipo int”, el de stock.

La segunda línea es el mismo error en el otro case. Un case 1: en el switch metodo { de la primera sección da un error parecido: Go tampoco puede convertir el número 1 a string para compararlo.

Si los case son condiciones, borra el valor: switch {. Si son valores, como case 0:, deja switch stock {.

El orden de los case

Go revisa los case de arriba abajo y se detiene en el primero que coincide: los de abajo ya no se revisan. En un switch sin valor, varias condiciones pueden dar true a la vez, y gana la primera:

main.go
package main

import "fmt"

func main() {
	stock := 0
	switch {
	case stock < 5:
		fmt.Println("Pocas unidades")
	case stock == 0:
		fmt.Println("Agotado")
	}
}

Salida:

Pocas unidades

0 también es menor que 5: el primer case ya da true, y stock == 0 nunca se revisa. Este programa no imprime Agotado con ningún stock. Como en la cadena de else if, pon primero la condición más estrecha, la que cubre menos casos.

En un switch con valor, esto no pasa con valores escritos tal cual, como 6 o "tarjeta": Go no te deja repetirlos, así que dos case no pueden coincidir a la vez.

Consejo

Cuando una decisión tiene tres caminos o más, un switch suele leerse mejor que una cadena de else if: cada opción queda en su línea, alineada con las demás. Para dos caminos, lo habitual es if y else.

fallthrough: pasar al case siguiente

Los socios de la papelería tienen niveles, y cada nivel suma los beneficios de los de abajo:

main.go
package main

import "fmt"

func main() {
	nivel := 2
	switch nivel {
	case 3:
		fmt.Println("Envío gratis")
		fallthrough
	case 2:
		fmt.Println("10% de descuento")
		fallthrough
	case 1:
		fmt.Println("Acumula puntos")
	}
}

Salida:

10% de descuento
Acumula puntos

fallthrough (“seguir de largo”) va al final de un case y hace que Go ejecute también las líneas del case siguiente, sin revisar su valor. nivel vale 2: Go ejecuta ese case, y el fallthrough lo lleva al case 1.

Tiene que ser la última línea de su case, y no puede ir en el último case del switch, porque no hay uno siguiente: Go responde cannot fallthrough final case in switch. Casi nunca hace falta: es bueno reconocerlo si lo ves, pero la mayoría de los switch no lo usan.

break dentro de un switch

La papelería atiende de 12 a 16. A las 14 se corta la luz y tiene que cerrar. Este programa intenta terminar el for con un break:

main.go
package main

import "fmt"

func main() {
	for hora := 12; hora <= 16; hora++ {
		switch hora {
		case 13:
			fmt.Println(hora, "- almuerzo")
		case 14:
			fmt.Println(hora, "- se cortó la luz: cerramos")
			break
		default:
			fmt.Println(hora, "- atendiendo")
		}
	}
}

Salida:

12 - atendiendo
13 - almuerzo
14 - se cortó la luz: cerramos
15 - atendiendo
16 - atendiendo

Después de cerrar, la papelería sigue atendiendo a las 15 y a las 16. ¿Por qué no terminó el for?

En la lección Bucles con for viste que break termina el for que lo encierra. En realidad termina el for o el switch más cercano que lo encierra. Aquí, el más cercano es el switch: el break termina el switch, que iba a terminar igual, y el for sigue con la hora siguiente.

Para terminar el for desde un case, ponle un nombre al for y úsalo en el break:

main.go
package main

import "fmt"

func main() {
horario:
	for hora := 12; hora <= 16; hora++ {
		switch hora {
		case 13:
			fmt.Println(hora, "- almuerzo")
		case 14:
			fmt.Println(hora, "- se cortó la luz: cerramos")
			break horario
		default:
			fmt.Println(hora, "- atendiendo")
		}
	}
	fmt.Println("Papelería cerrada")
}

Salida:

12 - atendiendo
13 - almuerzo
14 - se cortó la luz: cerramos
Papelería cerrada

horario:, en la línea de arriba del for, es una etiqueta: un nombre para el for, seguido de dos puntos. break horario termina el for que lleva ese nombre, aunque esté dentro de un switch. El programa sigue después de la } del for, con Papelería cerrada.

La etiqueta se escribe como un nombre de variable, y gofmt la deja un tabulador más a la izquierda que el for. Como una variable, tiene que usarse: una etiqueta sin ningún break que la nombre da label horario defined and not used.

Hay otras dos formas de salir:

  • Una variable de control: un bool, como abierto := true, que el case cambia a false y que el for revisa en su condición: hora <= 16 && abierto. El for termina al revisar la condición, al principio de la vuelta siguiente. Si después del switch hubiera más líneas, se ejecutarían igual en esa vuelta, la de las 14.
  • Dentro de una función, como en los ejercicios, return termina la función entera, con el for y el switch incluidos. Lo verás en la lección Funciones.

continue no tiene este problema: un switch no es un bucle, así que un continue dentro de un switch salta a la vuelta siguiente del for, como siempre.

En resumen

  • switch valor { compara el valor con cada case, de arriba abajo, y ejecuta solo el primero que coincide. No hace falta break. default se ejecuta si ninguno coincide.
  • Un case puede tener varios valores separados por comas, pero un valor escrito tal cual puede estar en un solo case (duplicate case).
  • switch {, sin valor, lleva una condición en cada case y ejecuta el primero que da true: el orden importa. fallthrough pasa al case siguiente sin revisarlo.
  • Dentro de un switch, break termina el switch, no el for que lo encierra. Para salir del for desde un case, ponle una etiqueta (horario:) y escribe break horario.

Ejercicios

Completa la función precioCopia, que devuelve el precio de una fotocopia según su tipo: 100 si es "negro", 500 si es "color" y 0 si es cualquier otro.

Una prueba oculta llama a tu función; main no se ejecuta.

  main.go

Para salir del editor con el teclado, pulsa Escape y luego Tab.

package main

import "fmt"

// precioCopia devuelve el precio de una fotocopia: 100 en blanco y
// negro, 500 en color y 0 si el tipo no existe.
func precioCopia(tipo string) int {
	precio := 0
	switch tipo {
	case "negro":
		precio = 100
		// Escribe aquí el case de las copias en color.
	}
	return precio
}

func main() {
	fmt.Println("Copia en negro:", precioCopia("negro"))
	fmt.Println("Copia en color:", precioCopia("color"))
}
 

¿Ver la solución?

La solución reemplazará tu código en el editor. Antes de verla, prueba con una pista: se aprende más cuando lo resuelves tú.