Introducción

En el ámbito de la programación informática, un bucle es una estructura de código que forma un ciclo para ejecutar parte de un código de forma repetida, a menudo hasta que se cumple una condición. Usar bucles en el terreno de la programación informática le permite automatizar y repetir tareas similares varias veces. Imagine que tiene una lista de archivos que debe procesar, o que quiere contar el número de líneas de un artículo. Usaría un bucle en su código para resolver estos tipos de problemas.

En Go, un bucle for implementa la ejecución repetida de código en base a un contador de bucles o una variable de bucle. A diferencia de otros lenguajes de programación que tienen varias construcciones en bucle como while y do, entre otras, Go solo tiene el bucle for. Esto sirve para hacer que su código sea más claro y legible, ya que no debe preocuparse por la presencia de varias estrategias para lograr la misma construcción en bucles. Esta mejora en la legibilidad y reducción de la carga cognitiva durante el desarrollo también harán que su código esté menos expuesto a errores que en otros lenguajes.

A través de este tutorial, aprenderá sobre el funcionamiento del bucle for de Go y sobre las tres variaciones principales de su uso. Comenzaremos mostrando la forma de crear diferentes tipos de bucles for y luego veremos la forma de crear un bucle a través de tipos de datos secuenciales en Go. Para finalizar, explicaremos la forma de usar bucles anidados.

Declarar bucles ForClause y Condition

Para tener en cuenta varios casos de uso, existen tres formas distintas de crear bucles for en Go, cada una con sus propias capacidades. Implican crear un bucle for con una Condition, una ForClause o una RangeClause. En esta sección, explicaremos la forma de declarar y usar las variantes ForClause y Condition.

Veamos la manera en que podemos usar primero un bucle for con la ForClause.

Un bucle ForClause se define como un bucle que tiene una instrucción inicial seguida de una condición y luego una posinstrucción. Estas se organizan en la siguiente sintaxis:

for [ Initial Statement ] ; [ Condition ] ; [ Post Statement ] {
    [Action]
}

Para explicar la función de los componentes anteriores, veremos un bucle for que se incrementa a través de un rango de valores especificados usando la sintaxis de ForClause:

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

Desglosaremos este bucle e indentificaremos cada parte.

La primera parte del bucle es i := 0. Esta es la instrucción inicial.

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

Indica que declaramos una variable llamada i y fijamos el valor inicial en 0.

A continuación, se encuentra la condición:

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

En esta condición, indicamos que aunque i es menor que el valor 5, el bucle debería continuar ejecutándose.

Finalmente, se encuentra la posinstrucción:

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

En la posinstrucción, incrementamos la variable de bucle i en uno cada vez que se produce una iteración usando el operador de incremento i++.

Cuando ejecutamos este programa, el resultado es similar al siguiente:

Output
0 1 2 3 4

El bucle se ejecutó 5 veces. Inicialmente, se fijó i en 0 y luego se comprobó para ver si i era menor que 5. Debido a que el valor de i fue inferior a 5, se ejecutaron el bucle y la acción de fmt.PrintIn(i). Una vez finalizado el bucle, se invocó la posinstrucción i++ y el valor de i se incrementó en 1.

Nota: Tenga en cuenta que en el terreno de la programación se suele tomar el índice 0 como punto de partida. Por ello, aunque se imprimen 5 números van de 0 a 4.

No existe una limitación que impone comenzar en 0 o finalizar a un valor especificado. Podemos asignar cualquier valor a nuestra instrucción inicial y también definir el punto final en cualquier valor en nuestra posinstrucción. Esto nos permite crear cualquier intervalo deseado para el bucle:

for i := 20; i < 25; i++ {
    fmt.Println(i)
}

Aquí, la iteración va de 20 (inclusivo) a 25 (exclusivo). Por lo tanto, el resultado tendrá este aspecto:

Output
20 21 22 23 24

También podemos usar nuestra posinstrucción para el incremento en diferentes valores. Esto es similar a step en otros lenguajes:

Primero, usaremos una posinstrucción con un valor positivo:

for i := 0; i < 15; i += 3 {
    fmt.Println(i)
}

En este caso, el bucle for está configurado para que los números de 0 a 15 se impriman, aunque con un incremento de 3, con lo cual se imprime cada tercer número, de esta forma:

Output
0 3 6 9 12

También podemos aplicar un valor negativo a nuestro argumento de posinstrucción para iteraciones hacia atrás, pero tendremos que ajustar nuestra instrucción inicial y los argumentos de condición como corresponde:

for i := 100; i > 0; i -= 10 {
    fmt.Println(i)
}

Aquí, fijamos i en un valor inicial de 100, usamos la condición i < 0 para la detención en 0 y la posinstrucción aplica una reducción de 10 al valor con el operador -=. El bucle comienza en 100 y finaliza en 0; se aplica una disminución de 10 con cada iteración. Podemos ver que esto sucede en el resultado:

Output
100 90 80 70 60 50 40 30 20 10

También puede excluir la instrucción inicial y la posinstrucción de la sintaxis de for, y solo usar la condición. Esto es conoce como bucle Condition:

i := 0
for i < 5 {
    fmt.Println(i)
    i++
}

Esta vez, declaramos la variable i de forma separada del bucle for en la línea de código anterior. El bucle solo tiene una clausula de condición que comprueba para ver si i es menor que 5. Siempre que la condición se evalúe a true, el bucle seguirá repitiéndose.

A veces, es posible que no conozca el número de iteraciones que necesitará para completar una tarea concreta. En ese caso, puede omitir todas las instrucciones y usar la palabra clave break para cerrar la ejecución.

for {
    if someCondition {
        break
    }
    // do action here
}

Un ejemplo de esto podría ser un caso en el que se realicen lecturas desde una estructura con un tamaño indeterminado, como un búfer, y no se conozca el momento en que la lectura finalizará.

buffer.go
package main

import (
    "bytes"
    "fmt"
    "io"
)

func main() {
    buf := bytes.NewBufferString("one\ntwo\nthree\nfour\n")

    for {
        line, err := buf.ReadString('\n')
        if err != nil {
            if err == io.EOF {

                fmt.Print(line)
                break
            }
            fmt.Println(err)
            break
        }
        fmt.Print(line)
    }
}

En el código anterior, buf :=bytes.NewBufferString("one\ntwo\nthree\nfour\n") declara un búfer con algunos datos. Debido a que no conocemos el momento en que el búfer terminará con la lectura, creamos un bucle for sin cláusula. Dentro del bucle for, usamos line, err := buf.ReadString('\n') para leer una línea del búfer y comprobar si se produjo un error de lectura del búfer. Si se produjo un error, abordamos el error y usamos la palabra clave break para cerrar el bucle for. Con estos puntos break, no necesita incluir una condición para detener el bucle.

A lo largo de esta sección, aprendió a declarar un bucle ForClause y usarlo para la iteración a través de un intervalo de valores conocido. También aprendió a usar un bucle “Condition” para iterarlo hasta que se cumpla una condición específica. A continuación, aprenderá sobre el uso que se da a RangeClause para la iteración a través de tipos de datos secuenciales.

Aplicar bucles a través de tipos de datos secuenciales con RangeClause

Es común en Go usar bucles for para iterar los elementos de tipos de datos secuenciales o de recopilación, como segmentos, matrices y cadenas. Para facilitar esto, podemos usar un bucle for con sintaxis de RangeClause. Aunque puede repetir tipos de datos secuenciales usando la sintaxis de ForClause, la RangeClause es más limpia y fácil de leer.

Antes de que veamos la manera de usar una RangeClause, veremos la manera en que podemos repetir iterar un segmento usando la sintaxis ForClause:

main.go
package main

import "fmt"

func main() {
    sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}

    for i := 0; i < len(sharks); i++ {
        fmt.Println(sharks[i])
    }
}

Al ejecutar esto, se obtendrá el siguiente resultado y imprimirá cada elemento del segmento:

Output
hammerhead great white dogfish frilled bullhead requiem

Ahora, usaremos RangeClause para realizar el mismo conjunto de acciones:

main.go
package main

import "fmt"

func main() {
    sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}

    for i, shark := range sharks {
        fmt.Println(i, shark)
    }
}

En este caso, imprimiremos cada elemento de la lista. Aunque usamos las variables i y shark, podríamos haber invocado la variable con cualquier otro nombre de variable válido y obtendríamos el mismo resultado:

Output
0 hammerhead 1 great white 2 dogfish 3 frilled 4 bullhead 5 requiem

Cuando use range en un segmento, siempre mostrará dos valores. El primer valor será el índice en el que se encuentre la iteración actual del bucle, y el segundo será el valor de ese índice. En este caso, para la primera iteración, el índice fue 0 y el valor hammerhead.

A veces, solo deseamos los valores dentro de los elementos de segmentos, no el índice. Si cambiamos el código anterior para que solo imprima el valor, veremos un error de tiempo de compilación:

main.go
package main

import "fmt"

func main() {
    sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}

    for i, shark := range sharks {
        fmt.Println(shark)
    }
}
Output
src/range-error.go:8:6: i declared and not used

Debido a que i se declara en el bucle for, pero nunca se utiliza, el compilador responderá con el error i declared and not used. Éste es el mismo error que verá en Go cuando declare una variable y no la utilice.

Debido a esto, Go cuenta con el identificador de espacios en blanco, que es un guión bajo (_). En un bucle for, puede usar el identificador de espacios en blanco para ignorar cualquier valor mostrado a partir de la palabra clave range. En este caso, nos conviene ignorar el índice, que es el primer argumento mostrado.

main.go
package main

import "fmt"

func main() {
    sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}

    for _, shark := range sharks {
        fmt.Println(shark)
    }
}
Output
hammerhead great white dogfish frilled bullhead requiem

Este resultado muestra que el bucle for se iteró a través del segmento de cadenas e imprimió cada elemento del segmento sin el índice.

También puede usar range para añadir elementos a una lista:

main.go
package main

import "fmt"

func main() {
    sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}

    for range sharks {
        sharks = append(sharks, "shark")
    }

    fmt.Printf("%q\n", sharks)
}
Output
['hammerhead', 'great white', 'dogfish', 'frilled', 'bullhead', 'requiem', 'shark', 'shark', 'shark', 'shark', 'shark', 'shark']

Aquí, añadimos una cadena de marcador de "shark" para cada elemento de la extensión del segmento sharks.

Observe que no tuvimos que usar el identificador de espacios en blanco _ para ignorar valores de retorno del operador range. Go nos permite dejar afuera toda la parte de la declaración de la instrucción range si no necesitamos usar ninguno de los valores de retorno.

También podemos usar el operador range para completar los valores de un segmento:

main.go
package main

import "fmt"

func main() {
    integers := make([]int, 10)
    fmt.Println(integers)

    for i := range integers {
        integers[i] = i
    }

    fmt.Println(integers)
}

En este ejemplo, el segmento integers se inicia con diez valores vacíos, pero el bucle for establece todos los valores de la lista de esta forma:

Output
[0 0 0 0 0 0 0 0 0 0] [0 1 2 3 4 5 6 7 8 9]

La primera vez que imprimimos el valor del segmento integers, lo único que vemos son ceros. A continuación, realizamos una iteración a través de cada índice y fijamos el valor en el índice actual. Luego, cuando imprimimos el valor de integers por segunda vez, se muestra que ahora todos tienen un valor de 0 a 9.

También podemos usar el operador range para la iteración a través de cada carácter en una cadena:

main.go
package main

import "fmt"

func main() {
    sammy := "Sammy"

    for _, letter := range sammy {
        fmt.Printf("%c\n", letter)
    }
}
Output
S a m m y

Cuando realicemos la iteración a través de un mapa, range mostrará la clave y el valor:

main.go
package main

import "fmt"

func main() {
    sammyShark := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}

    for key, value := range sammyShark {
        fmt.Println(key + ": " + value)
    }
}
Output
color: blue location: ocean name: Sammy animal: shark

Nota: Es importante observar que el orden en el cual un mapa se muestra es aleatorio. Cada vez que ejecute este programa, es posible que obtenga un resultado diferente.

Ahora que aprendió iterar datos secuenciales con bucles for range, veremos la forma de usar bucles dentro de bucles.

Bucles for anidados

Como en el caso de otros lenguajes de programación, es posible anidar bucles en Go. La anidación tiene lugar cuando hay una construcción dentro de otra. En este caso, un bucle anidado es un bucle que se produce dentro de otro. Estos pueden ser útiles cuando desea que una acción continua se realice sobre cada elemento de un conjunto de datos.

Los bucles anidados tienen una estructura similar a la de las instrucciones if anidadas. Se construyen de la siguiente forma:

for {
    [Action]
    for {
        [Action]  
    }
}

El programa primero encuentra el bucle externo y ejecuta su primera iteración. Esta primera iteración activa el bucle anidado interno, que luego se ejecuta hasta finalizar. Luego, el programa vuelve a la parte superior del bucle externo, completa la segunda iteración y de nuevo activa el bucle anidado. De nuevo, el bucle anidado se ejecuta hasta finalizar y el programa vuelve a la parte superior del bucle externo hasta que la secuencia se completa o un salto u otra instrucción interrumpa el proceso.

Implementaremos un bucle for anidado para poder ver más de cerca. En este ejemplo, el bucle externo realizará la iteración en un segmento de enteros llamado numList y el bucle interno realizará iteraciones a través de un segmento de cadenas llamado alphaList.

main.go
package main

import "fmt"

func main() {
    numList := []int{1, 2, 3}
    alphaList := []string{"a", "b", "c"}

    for _, i := range numList {
        fmt.Println(i)
        for _, letter := range alphaList {
            fmt.Println(letter)
        }
    }
}

Cuando ejecute este programa, verá el siguiente resultado:

Output
1 a b c 2 a b c 3 a b c

El resultado muestra que el programa completa la primera iteración del bucle externo imprimiendo 1, lo que activa la finalización del bucle interno e imprime a, b y c de forma consecutiva. Una vez completado el bucle interno, el programa vuelve a la parte superior del bucle externo, imprime 2 y luego imprime de nuevo el bucle interno en su totalidad (a, b, y c), etc.

Los bucles for anidados pueden ser útiles para iteraciones a través de elementos en segmentos compuestos de segmentos. En un segmento compuesto de segmentos, si usamos únicamente un bucle for, el programa producirá cada lista interna como un elemento:

main.go
package main

import "fmt"

func main() {
    ints := [][]int{
        []int{0, 1, 2},
        []int{-1, -2, -3},
        []int{9, 8, 7},
    }

    for _, i := range ints {
        fmt.Println(i)
    }
}
Output
[0 1 2] [-1 -2 -3] [9 8 7]

Para acceder a cada elemento individual de los segmentos internos, implementaremos un bucle for anidado:

main.go
package main

import "fmt"

func main() {
    ints := [][]int{
        []int{0, 1, 2},
        []int{-1, -2, -3},
        []int{9, 8, 7},
    }

    for _, i := range ints {
        for _, j := range i {
            fmt.Println(j)
        }
    }
}
Output
0 1 2 -1 -2 -3 9 8 7

Cuando usamos un bucle for anidado aquí, podemos realizar iteraciones en los elementos individuales contenidos en los segmentos.

Conclusión

En este tutorial, aprendió a declarar y usar bucles for para resolver tareas repetitivas en Go. También aprendió las tres variaciones diferentes de un bucle for y la manera de usarlas. Para obtener más información sobre los bucles for y la forma de controlar el flujo de ellos, lea Cómo usar instrucciones Break y Continue cuando se trabaja con bucles en Go.

0 Comments

Creative Commons License