Saltar al contenido

Bucles con for

Muchas tareas de un programa son repetir lo mismo varias veces: imprimir una etiqueta por caja, sumar las ventas de cada día, pedir cajas hasta llenar el stock. Al terminar esta lección vas a poder repetir instrucciones con for, el único bucle de Go, en todas sus formas. También vas a saber qué pasa cuando un bucle no termina nunca, y qué te muestra el sitio en ese caso.

Cambiar una variable: += y ++

Antes de repetir, una forma corta de cambiar una variable a partir de su propio valor. La Papelería Central lleva su stock de cuadernos:

main.go
package main

import "fmt"

func main() {
	stock := 20
	stock += 12
	fmt.Println("Llegó una caja:", stock)
	stock -= 5
	fmt.Println("Se vendieron 5:", stock)
	ventas := 0
	ventas++
	ventas++
	fmt.Println("Ventas del día:", ventas)
}

Salida:

Llegó una caja: 32
Se vendieron 5: 27
Ventas del día: 2

stock += 12 es una forma corta de escribir stock = stock + 12: toma el valor de stock, le suma 12 y guarda el resultado otra vez en stock. Por eso pasa de 20 a 32. stock -= 5 resta: queda en 27.

Hay una forma así para cada operador aritmético que ya conoces: +=, -=, *=, /= y %=. Por ejemplo, precio *= 2 duplica precio. Con strings, += ya lo viste en Conversiones de tipo: agrega un texto al final.

Sumar o restar 1 es tan común que tiene su propia forma: ventas++ suma 1 a ventas, y ventas-- le resta 1. ventas empieza en 0 y, después de dos ventas++, vale 2.

Los vas a usar en casi todos los bucles de esta lección: para contar, para sumar y para avanzar de un número al siguiente.

En Go, ventas++ es una instrucción completa: no da un valor. No puedes usarla a la derecha de := ni dentro de un fmt.Println:

main.go
package main

import "fmt"

func main() {
	ventas := 3
	total := ventas++
	fmt.Println("Ventas:", total)
}

Salida:

./main.go:7:17: syntax error: unexpected ++ at end of statement

  • ./main.go:7:17 apunta al ++ de la línea 7.
  • unexpected ++ at end of statement significa “++ inesperado al final de la instrucción”: Go leyó total := ventas como una instrucción completa, y el ++ le sobra.

Escribe ventas++ en su propia línea y, en la siguiente, total := ventas.

Si vienes de JavaScript, Python o Java…

En JavaScript, Java y C, ventas++ y ++ventas dan un valor, así que puedes escribir total = ventas++. En Go, ventas++ es una instrucción sola, y ++ventas, con el ++ adelante, no existe.

Python no tiene ++: escribe ventas += 1. En Go, ventas += 1 también funciona, pero lo habitual es ventas++.

Repetir con for

La papelería imprime una etiqueta para cada caja que llega:

main.go
package main

import "fmt"

func main() {
	for caja := 1; caja <= 3; caja++ {
		fmt.Println("Etiqueta para la caja", caja)
	}
	fmt.Println("Listo")
}

Salida:

Etiqueta para la caja 1
Etiqueta para la caja 2
Etiqueta para la caja 3
Listo

for (“para”, en inglés) repite el bloque que va entre sus llaves. Cada repetición se llama una vuelta (o una iteración). Su primera línea tiene tres partes, separadas por punto y coma (;):

  • El inicio, caja := 1, se ejecuta una sola vez, antes de la primera vuelta.
  • La condición, caja <= 3, se revisa antes de cada vuelta. Si da true, se ejecuta el bloque; si da false, el bucle termina.
  • El paso, caja++ (la instrucción de la sección anterior), se ejecuta al final de cada vuelta.

Sigue el programa paso a paso. caja empieza en 1: 1 <= 3 da true y se imprime la caja 1. Después, caja++ la lleva a 2, y así hasta la caja 3. Al final de esa vuelta, caja pasa a 4, y 4 <= 3 da false: el bucle termina y el programa sigue después de la }, con Listo.

El bloque del for sigue las mismas reglas que el del if: las llaves son obligatorias, la { va en la misma línea que el for, y las partes van sin paréntesis. Con paréntesis, como en otros lenguajes, el programa no compila:

main.go
package main

import "fmt"

func main() {
	for (caja := 1; caja <= 3; caja++) {
		fmt.Println("Etiqueta para la caja", caja)
	}
}

Salida:

./main.go:6:12: syntax error: unexpected :=, expected )

unexpected :=, expected ) significa “:= inesperado, se esperaba )”. Go tomó el ( como el inicio de un valor entre paréntesis, como en (precio + 100) * 2, y ahí no puede ir una declaración. Quita los paréntesis: for caja := 1; caja <= 3; caja++ {.

Si vienes de JavaScript, Python o Java…

En JavaScript y en Java, las tres partes van entre paréntesis: for (let i = 0; i < 3; i++) o for (int i = 0; i < 3; i++). En Go van sin paréntesis, y la variable se declara con :=.

En Python, for recorre algo, como for caja in range(1, 4):. Go no tiene for … in: para repetir una cantidad de veces, usa el for de esta sección o el range de la sección siguiente.

Sumar en un bucle

La papelería aparta dinero para una fotocopiadora: 500 la primera semana, 1000 la segunda, 1500 la tercera, y así. ¿Cuánto junta en 4 semanas?

main.go
package main

import "fmt"

func main() {
	total := 0
	for semana := 1; semana <= 4; semana++ {
		total += semana * 500
		fmt.Println("Semana", semana, "- ahorro:", total)
	}
	fmt.Println("Total:", total)
}

Salida:

Semana 1 - ahorro: 500
Semana 2 - ahorro: 1500
Semana 3 - ahorro: 3000
Semana 4 - ahorro: 5000
Total: 5000

total empieza en 0, y cada vuelta le suma lo que se aparta esa semana: semana * 500. A una variable así, que junta un resultado vuelta a vuelta, se le llama acumulador.

Fíjate dónde está declarado cada uno. total se declara antes del for, así que existe después de la } y se puede imprimir al final. semana, en cambio, se declara en la línea del for: como la variable de un if, existe solo dentro del bucle. Usarla después de la } da undefined: semana.

Consejo

Para la variable que cuenta las vueltas de un bucle corto, lo habitual en Go es un nombre de una letra, como i. Si el número significa algo (una caja, una semana, un día), un nombre como semana hace que el bloque se lea solo.

Repetir una cantidad de veces: range

Desde Go 1.22, un for puede recorrer un número entero con range:

main.go
package main

import "fmt"

func main() {
	for i := range 3 {
		fmt.Println("Vuelta", i)
	}
	for range 2 {
		fmt.Println("¡Gracias por tu compra!")
	}
}

Salida:

Vuelta 0
Vuelta 1
Vuelta 2
¡Gracias por tu compra!
¡Gracias por tu compra!

for i := range 3 repite el bloque 3 veces. En cada vuelta, i vale un número: 0, 1 y 2. Empieza en 0 y termina uno antes del número, igual que for i := 0; i < 3; i++, pero más corto. Con un 0 o un número negativo, no da ninguna vuelta.

Si no necesitas el número de la vuelta, no declares la variable: for range 2 repite el bloque 2 veces y nada más. En el bloque 3 vas a ver que range también recorre listas de valores.

Declarar i y no usarla es el error de siempre, el de toda variable que no se usa:

main.go
package main

import "fmt"

func main() {
	for i := range 2 {
		fmt.Println("¡Gracias por tu compra!")
	}
}

Salida:

./main.go:6:6: declared and not used: i

declared and not used: i dice que i está declarada y no se usa. Quítala: for range 2 {.

Consejo

Para repetir algo una cantidad fija de veces, for range n (o for i := range n, si usas el número) es la forma más clara: no hay inicio, condición ni paso que escribir mal.

Repetir mientras se cumpla: for como “while”

La papelería le debe 10000 a un proveedor, y le paga 3000 cada mes. ¿En cuántos meses termina?

main.go
package main

import "fmt"

func main() {
	deuda := 10000
	meses := 0
	for deuda > 0 {
		deuda -= 3000
		meses++
	}
	fmt.Println("Pagada en", meses, "meses")
}

Salida:

Pagada en 4 meses

Este for tiene solo la condición: repite el bloque mientras deuda > 0 dé true. La deuda pasa por 7000, 4000, 1000 y -2000. En ese momento, deuda > 0 da false y el bucle termina, con meses en 4. Al cuarto mes le basta con pagar 1000, pero el programa solo cuenta los meses.

Como en el for de tres partes, la condición se revisa antes de cada vuelta. Si da false desde el principio, el bloque no se ejecuta ni una vez: con deuda := 0, el programa imprime Pagada en 0 meses.

Úsalo cuando no sabes de antemano cuántas vueltas hacen falta: repite hasta que algo cambie.

Si vienes de JavaScript, Python o Java…

Go no tiene while ni do … while: el for con solo la condición es el while de otros lenguajes. Con while (deuda > 0) {, el programa no compila.

Un bucle que no termina

Algo dentro del bloque tiene que acercar la condición a false. Si te olvidas de deuda -= 3000, la deuda sigue en 10000, deuda > 0 da true en cada vuelta y el bucle no termina nunca:

deuda := 10000
meses := 0
for deuda > 0 {
	meses++
}

Un programa así no tiene error de compilación: compila, se ejecuta y se queda dando vueltas. En tu computadora, seguiría hasta que lo detuvieras a mano. En los ejercicios de este sitio, se detiene después de unos segundos, y la franja de resultado muestra:

  • el título “Tu programa no terminó”;
  • la explicación “¿Un bucle infinito o un deadlock?”, con el texto “Tu programa tardó más de lo permitido y lo detuvimos. Lo más común es un bucle que nunca termina o goroutines que se esperan entre sí.”;
  • la pista “Revisa la condición de salida de tus bucles for y que cada espera en un channel tenga quién la atienda.”

Una goroutine es una tarea que se ejecuta al mismo tiempo que otras; un channel, una forma de pasar datos entre goroutines; y un deadlock, un bloqueo en el que varias se esperan entre sí. Los verás en el bloque 7, de concurrencia. Por ahora, si ves ese mensaje, busca un bucle cuya condición nunca llega a false.

Si el bucle imprime en cada vuelta, lo más probable es que el sitio lo detenga por imprimir demasiado: la franja muestra el título “Demasiada salida” y la explicación “Tu programa imprimió demasiado”.

Un bucle sin condición: for y break

La caja de la papelería suma precios hasta que la persona que cobra escribe un 0:

main.go
package main

import "fmt"

func main() {
	total := 0
	for {
		var precio int
		fmt.Scan(&precio)
		if precio == 0 {
			break
		}
		total += precio
	}
	fmt.Println("Total:", total)
}

Entrada:

1200 850 3000 0

Salida:

Total: 5050

Un for sin nada antes de la { no tiene condición: repite el bloque para siempre. Para salir está break (“romper”): termina el bucle en ese momento, y el programa sigue después de la } del for.

En cada vuelta, el programa lee un precio. Si es 0, break termina el bucle antes de sumar. Si no, lo suma a total y pasa a la vuelta siguiente. Lee 1200, 850 y 3000, y con el 0 sale: el total es 5050.

Aquí la decisión de salir va en el medio de la vuelta: primero hay que leer el precio, y solo entonces se sabe si hay que seguir. Un for con condición la revisa al principio de la vuelta, cuando todavía no se leyó nada: habría que leer un precio antes del bucle y otro al final de cada vuelta.

break funciona en todas las formas de for, no solo en esta. Siempre termina el for que lo encierra, aunque esté dentro de un if.

Consejo

Si la condición para seguir se puede revisar al principio de cada vuelta, escríbela en el for: for deuda > 0 {. Deja for sin condición y break para cuando la decisión de salir va en el medio o al final de la vuelta.

Saltar una vuelta: continue

La papelería reparte pedidos todos los días, salvo los domingos. En sus primeros 10 días, el único domingo es el día 7:

main.go
package main

import "fmt"

func main() {
	for dia := 1; dia <= 10; dia++ {
		if dia%7 == 0 {
			continue
		}
		fmt.Print(dia, " ")
	}
	fmt.Println()
}

Salida:

1 2 3 4 5 6 8 9 10

continue (“continuar”) termina la vuelta actual y salta a la siguiente: el resto del bloque no se ejecuta en esa vuelta. Con dia en 7, dia%7 == 0 da true (el resto de dividir 7 entre 7 es 0), y continue salta el fmt.Print: el 7 no aparece.

fmt.Print(dia, " ") imprime el día y un espacio, sin terminar la línea; el fmt.Println() del final la termina.

A diferencia de break, el bucle no termina. En un for de tres partes, continue también ejecuta el paso, dia++, antes de revisar la condición otra vez: por eso sigue con el día 8.

En un for con solo la condición no hay paso, y ahí continue tiene una trampa. Si saltas la línea que cambia la variable de la condición, la vuelta siguiente es igual a la anterior:

dia := 1
for dia <= 10 {
	if dia%7 == 0 {
		continue
	}
	dia++
}

Al llegar a 7, continue salta el dia++: dia se queda en 7 para siempre, y es otro bucle que no termina.

Consejo

Un continue al principio del bloque sirve para descartar los casos que no interesan, como el domingo. Así, el resto del bloque trabaja solo con los que sí, sin otro nivel de sangría dentro de un if.

En resumen

  • stock += 12 es stock = stock + 12, y existe para cada operador aritmético que ya conoces. ventas++ suma 1 y ventas-- resta 1: son instrucciones solas, no valores.
  • for inicio; condición; paso { … } es el bucle de tres partes. La variable del inicio existe solo dentro del for.
  • for i := range n repite el bloque n veces, con i de 0 a n - 1 (desde Go 1.22). Sin la variable, for range n.
  • for condición { … } es el “while” de Go. for { … } repite para siempre, hasta un break. Si la condición nunca llega a false, el bucle no termina, y el sitio lo detiene a los pocos segundos.
  • break termina el bucle; continue salta a la vuelta siguiente.

Ejercicios

Completa la función cajasEnPila, que devuelve cuántas cajas tiene una pila de filas filas: 1 caja en la fila 1, 2 en la fila 2, y así.

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"

// cajasEnPila devuelve cuántas cajas hay en una pila de filas filas:
// la fila 1 tiene 1 caja, la fila 2 tiene 2, y así hasta abajo.
func cajasEnPila(filas int) int {
	total := 0
	for fila := 1; fila <= filas; fila++ {
		// Escribe aquí: suma a total las cajas de esta fila.
	}
	return total
}

func main() {
	fmt.Println("Pila de 3 filas:", cajasEnPila(3))
	fmt.Println("Pila de 4 filas:", cajasEnPila(4))
}
 

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