Saltar al contenido

Decisiones con if

Hasta ahora, tus programas ejecutaban todas sus líneas, de arriba abajo. Al terminar esta lección vas a poder hacer que un programa decida: ejecutar unas líneas solo si se cumple una condición, o elegir entre dos caminos o entre varios. También vas a saber leer el error que aparece cuando usas una variable fuera del if donde la declaraste.

Comparar valores

La Papelería Central revisa el stock de sus cuadernos:

main.go
package main

import "fmt"

func main() {
	producto := "cuaderno"
	stock := 3
	agotado := stock == 0
	fmt.Println(stock < 5, stock >= 5)
	fmt.Println(agotado, stock != 0)
	fmt.Println(producto == "cuaderno", producto == "Cuaderno")
}

Salida:

true false
false true
true false

Cada comparación pregunta algo sobre dos valores, y su resultado es un bool: true si se cumple y false si no. stock vale 3, así que stock < 5 da true y stock >= 5 da false.

Go tiene seis operadores de comparación:

  • ==: igual a. stock == 0 da false, porque 3 no es 0.
  • !=: distinto de. stock != 0 da true.
  • < y <=: menor que, y menor o igual que.
  • > y >=: mayor que, y mayor o igual que.

== se escribe con dos signos igual. Con uno solo, =, es la asignación que ya conoces: le da un valor a una variable, no compara nada.

El resultado de una comparación es un valor como cualquier otro: agotado := stock == 0 guarda false en una variable bool. Los strings también se comparan con == y !=, letra por letra: "cuaderno" y "Cuaderno" son distintos, porque el segundo empieza con mayúscula.

Solo se comparan valores del mismo tipo. stock == "3" no compila: "3" es un texto, no un número, y Go responde con el mismatched types que viste en la lección Conversiones de tipo.

Decidir con if

Cuando quedan pocos cuadernos, la papelería quiere un aviso:

main.go
package main

import "fmt"

func main() {
	stock := 3
	if stock < 5 {
		fmt.Println("Quedan pocas unidades: pide más.")
	}
	fmt.Println("Stock:", stock)
}

Salida:

Quedan pocas unidades: pide más.
Stock: 3

if (“si”, en inglés) va seguido de una condición y de unas líneas entre llaves. Si la condición da true, Go ejecuta esas líneas; si da false, se las salta y sigue después de la }. Aquí stock < 5 es true, así que sale el aviso. Con stock := 12, el programa imprimiría solo Stock: 12.

Las llaves { y } encierran un bloque: un grupo de instrucciones que van juntas. Ya conoces uno: el cuerpo de func main. Por convención, las líneas del bloque llevan un tabulador más de sangría, como las deja gofmt: así se ve qué está dentro del if.

Tres reglas del if en Go:

  • La condición tiene que ser un bool, como una comparación.
  • Las llaves son obligatorias, aunque el bloque tenga una sola línea.
  • La { va en la misma línea que el if, como en func main() {. En la línea siguiente, no compila.

La condición va sin paréntesis: if stock < 5 {. Con paréntesis también compila, pero gofmt los quita.

Un número no sirve como condición, ni 0 ni ningún otro:

main.go
package main

import "fmt"

func main() {
	stock := 3
	if stock {
		fmt.Println("Hay stock")
	}
}

Salida:

./main.go:7:5: non-boolean condition in if statement

Lee el mensaje por partes:

  • ./main.go:7:5 es el lugar: línea 7, columna 5, donde empieza la condición.
  • non-boolean condition in if statement significa “condición no booleana en la instrucción if”. Un valor booleano es un bool: true o false.

stock es un int, y Go no decide por su cuenta si un número es verdadero o falso. Escribe la pregunta completa: if stock > 0 {.

Si vienes de JavaScript, Python o Java…

En JavaScript y en Python, if (stock) o if stock: funcionan: un 0 o un texto vacío cuentan como falso. Go no hace esa conversión: la condición es siempre un bool.

En JavaScript y en Java, la condición va entre paréntesis y las llaves son opcionales con una sola línea. En Go es al revés: los paréntesis sobran y las llaves son obligatorias. Si vienes de Python, las llaves cumplen el papel de la sangría: marcan qué líneas están dentro del if.

= no es ==

Un error típico es escribir un solo signo igual en la condición:

main.go
package main

import "fmt"

func main() {
	stock := 0
	if stock = 0 {
		fmt.Println("Agotado")
	}
}

Salida:

./main.go:7:11: syntax error: cannot use assignment stock = 0 as value

El mensaje dice “no se puede usar la asignación stock = 0 como valor”. stock = 0 le da a stock el valor 0: no pregunta nada, así que no da true ni false. Para comparar, escribe ==: if stock == 0 {.

El otro camino: else

La papelería envía gratis los pedidos de 20000 o más:

main.go
package main

import "fmt"

func main() {
	total := 15000
	if total >= 20000 {
		fmt.Println("Envío gratis")
	} else {
		fmt.Println("Envío: 3500")
	}
}

Salida:

Envío: 3500

else (“si no”) agrega un segundo bloque, que se ejecuta cuando la condición da false. De los dos bloques se ejecuta siempre uno, y solo uno. total vale 15000, y 15000 >= 20000 es false: sale el envío de 3500.

El else va en la misma línea que la } que cierra el if: } else {. En la línea siguiente, el programa no compila:

main.go
package main

import "fmt"

func main() {
	total := 15000
	if total >= 20000 {
		fmt.Println("Envío gratis")
	}
	else {
		fmt.Println("Envío: 3500")
	}
}

Salida:

./main.go:10:2: syntax error: unexpected keyword else, expected }

  • ./main.go:10:2 apunta al else, en la línea 10.
  • unexpected keyword else significa “la palabra else no se esperaba aquí”.
  • expected } significa “se esperaba }”.

Cuando una línea termina en }, Go da por terminada la instrucción. Para Go, el if terminó en la línea 9, y ninguna instrucción empieza con else: en ese lugar esperaba otra instrucción o la } que cierra main. Sube el else a la línea anterior: } else {.

Un valor por defecto, sin else

Muchas veces no hace falta else. Le das a la variable el valor más común y el if lo cambia solo cuando corresponde:

main.go
package main

import "fmt"

func main() {
	total := 25000
	envio := 3500
	if total >= 20000 {
		envio = 0
	}
	fmt.Println("Envío:", envio)
}

Salida:

Envío: 0

envio empieza en 3500. Como total es 25000, el if le asigna 0. Dentro del bloque se usa =, no :=: la variable ya existe, y el if solo cambia su valor. Al final de la lección vas a ver qué pasa con :=.

Consejo

Si un valor tiene un caso habitual y una excepción, declara la variable con el valor habitual y cambia solo la excepción con un if. Te ahorras el else, y el código se lee en el orden en que piensas el problema.

Varias opciones con else if

El stock de un producto puede estar en tres estados:

main.go
package main

import "fmt"

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

Salida:

Pocas unidades

else if agrega otra condición. Go las revisa de arriba abajo, ejecuta el bloque de la primera que da true y se salta todas las demás. Si ninguna da true, se ejecuta el else final. Puedes poner tantos else if como necesites, y el else final es opcional.

Con stock en 3, stock == 0 da false y stock < 5 da true: sale Pocas unidades, y el else ya no se revisa.

Como gana la primera condición que da true, el orden importa:

main.go
package main

import "fmt"

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

Salida:

Pocas unidades

0 también es menor que 5: la primera condición ya da true, y stock == 0 nunca se revisa. Este programa no imprime Agotado con ningún stock. Pon primero la condición más estrecha, la que cubre menos casos.

Si vienes de JavaScript, Python o Java…

Python escribe elif. Go no tiene elif ni elseif: son dos palabras, else if, en la misma línea que la } anterior.

Unir condiciones: &&, || y !

A veces una decisión depende de más de una condición:

main.go
package main

import "fmt"

func main() {
	stock := 8
	pedido := 3
	total := 15000
	socio := true
	pausado := false
	if pedido > 0 && pedido <= stock {
		fmt.Println("Se puede vender")
	}
	if total >= 20000 || socio {
		fmt.Println("Envío gratis")
	}
	if !pausado {
		fmt.Println("Producto a la venta")
	}
}

Salida:

Se puede vender
Envío gratis
Producto a la venta

Son los operadores lógicos, y trabajan con valores bool:

  • && (“y”) da true solo si las dos condiciones dan true. pedido > 0 y pedido <= stock se cumplen las dos: se puede vender.
  • || (“o”) da true si al menos una da true. total >= 20000 da false, pero socio es true: el envío es gratis.
  • ! (“no”) da lo contrario de lo que tiene a su derecha. pausado es false, así que !pausado es true.

socio y pausado ya son bool, así que van solos en la condición. socio == true también funciona, pero no agrega nada.

En una misma condición, Go calcula primero las comparaciones, después && y al final ||, igual que * va antes que +. Por eso pedido > 0 && pedido <= stock no necesita paréntesis. Y por eso estas dos condiciones son la misma: envío gratis desde 20000, o desde 10000 para los socios.

total >= 20000 || socio && total >= 10000
total >= 20000 || (socio && total >= 10000)

Consejo

Cuando mezclas && y || en una condición, escribe los paréntesis aunque no cambien el resultado, como en la segunda forma. Quien lee no tiene que recordar qué va primero, y gofmt los respeta.

Si vienes de JavaScript, Python o Java…

Python escribe and, or y not. En Go son &&, || y !: con and, el programa no compila.

Python también acepta 1 <= pedido <= stock. En Go, esa línea no compila: Go calcula 1 <= pedido, que da un bool, y no puede comparar ese bool con stock. Escribe las dos comparaciones unidas con &&: 1 <= pedido && pedido <= stock.

Como en JavaScript, Python y Java, Go deja de revisar en cuanto sabe el resultado: si lo de la izquierda de && da false, lo de la derecha no se revisa.

Declarar una variable en el if

La papelería quiere tener al menos 10 cuadernos. Si hay menos, calcula cuántos pedir:

main.go
package main

import "fmt"

func main() {
	stock := 4
	minimo := 10
	if faltan := minimo - stock; faltan > 0 {
		fmt.Println("Pide", faltan, "unidades")
	} else {
		fmt.Println("Stock suficiente")
	}
}

Salida:

Pide 6 unidades

Antes de la condición, un if puede llevar una declaración corta, separada de la condición con un punto y coma (;). Go ejecuta primero la declaración, faltan := minimo - stock, que guarda 6, y después revisa la condición, faltan > 0.

La parte del programa donde existe una variable es su alcance (en inglés, scope). Una variable declarada en la línea del if existe en su condición y en todos sus bloques, también en los de else if y else. Después de la } final, ya no existe:

main.go
package main

import "fmt"

func main() {
	stock := 4
	minimo := 10
	if faltan := minimo - stock; faltan > 0 {
		fmt.Println("Pide", faltan, "unidades")
	}
	fmt.Println("Faltaban", faltan)
}

Salida:

./main.go:11:26: undefined: faltan

Lee el mensaje por partes:

  • ./main.go:11:26 es el lugar: la línea 11, después del if, donde se usa faltan.
  • undefined: faltan significa “faltan no está definida”.

faltan sí está declarada, pero en el if: en la línea 11, fuera de su alcance, Go no la conoce. Si necesitas la variable después del if, declárala antes, en su propia línea: faltan := minimo - stock, y debajo, if faltan > 0 {.

Consejo

Usa la declaración en el if cuando la variable solo sirve para esa decisión. Así su alcance es el más corto posible: el resto de la función no la ve y no puede usarla por error.

Variables dentro de un bloque

La regla del alcance vale para todo bloque: una variable declarada entre las llaves de un if existe solo hasta su }. Usarla después da el mismo undefined.

Esa regla tiene una trampa. Este programa quiere dejar el envío en 0 para un pedido de 25000, pero dentro del if usa := en lugar de =:

main.go
package main

import "fmt"

func main() {
	total := 25000
	envio := 3500
	if total >= 20000 {
		envio := 0
	}
	fmt.Println("Envío:", envio)
}

Salida:

./main.go:9:3: declared and not used: envio

El error está en la línea 9, dentro del if: declared and not used: envio significa “envio está declarada y no se usa”. Pero envio se usa en la última línea… ¿Qué pasó?

envio := 0 declara una variable nueva: crea otra envio, distinta de la de afuera, que solo existe hasta la } del if. Esa variable nueva no se usa en ningún lado, y la de afuera sigue en 3500.

En la lección Variables y valores cero viste que := necesita al menos una variable nueva: no te deja declarar otra vez una que ya existe. Eso vale dentro de un mismo bloque. Un bloque nuevo, como el de un if, sí puede declarar una variable con el mismo nombre que una de afuera, y desde ahí hasta su }, ese nombre es el de la nueva.

La corrección es envio = 0, con =: así el if cambia la variable de afuera. Aquí te avisó el compilador, pero no siempre pasa: si el bloque usara la envio nueva, por ejemplo para imprimirla, el programa compilaría y la de afuera seguiría valiendo 3500. Para cambiar una variable desde un if, usa =.

En los ejercicios: completar una función

En los ejercicios de esta lección vas a completar una función: un bloque de código con nombre que recibe valores y devuelve un resultado. Se ve así:

// etiqueta devuelve "Oferta" si el precio es menor que 1000.
func etiqueta(precio int) string {
	texto := "Normal"
	// Escribe aquí tu código.
	return texto
}

La primera línea dice cómo se llama la función (etiqueta), qué recibe entre paréntesis (precio, un int) y de qué tipo es lo que devuelve (string). Dentro, precio es una variable que ya tiene un valor: el que se pasa al llamar a la función, como en etiqueta(800). return texto termina la función y devuelve el valor de texto.

Escribe tu código antes del return, sin cambiar la primera línea. Al pulsar Ejecutar, el sitio no ejecuta main: corre una prueba oculta que llama a tu función con varios valores y revisa lo que devuelve.

Si alguno no es el esperado, verás una línea como etiqueta(800) = "Normal"; se esperaba "Oferta": con 800, tu función devolvió "Normal", y tenía que devolver "Oferta". El check_test.go:12 del principio de la línea es el lugar de la prueba oculta, no de tu código. Las funciones las veremos en la lección Funciones.

En resumen

  • Las comparaciones ==, !=, <, <=, > y >= dan un bool. && (“y”), || (“o”) y ! (“no”) las combinan.
  • if condición { … } ejecuta su bloque solo si la condición da true. La condición es siempre un bool, las llaves son obligatorias y la { va en la misma línea que el if.
  • } else { da el otro camino. else if agrega condiciones, que Go revisa de arriba abajo: se ejecuta solo el bloque de la primera que da true.
  • Una declaración corta en el if, como if faltan := …; faltan > 0 {, crea una variable que solo existe en ese if y en sus else. Usarla después da undefined.
  • Dentro de un bloque, = cambia una variable de afuera y := declara otra nueva, aunque tenga el mismo nombre.

Ejercicios

Completa la función envio, que devuelve el costo de envío de un pedido: 3500, o 0 si el total es 20000 o más.

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"

// envio devuelve el costo de envío de un pedido: 3500, o 0 si el
// total es 20000 o más (envío gratis).
func envio(total int) int {
	costo := 3500
	// Escribe aquí: si el total es 20000 o más, el envío es gratis.
	return costo
}

func main() {
	fmt.Println("Envío de un pedido de 8000:", envio(8000))
	fmt.Println("Envío de un pedido de 25000:", envio(25000))
}
 

¿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ú.