Introdução

Em programação de computadores, um loop é uma estrutura de código que faz um loop para executar repetidamente uma parte de um código, frequentemente até que alguma condição seja alcançada. O uso de loops em programação de computadores permite que você automatize e repita tarefas semelhantes várias vezes. Vamos supor que você tivesse uma lista de arquivos que precisasse processar, ou se quisesse contar o número de linhas em um artigo. Você poderia usar um loop em seu código para resolver esses tipos de problemas.

Na linguagem Go, um loop for implementa a execução repetida de um código baseado em um contador ou uma variável de loop. Ao contrário do que ocorre com outras linguagens de programação que têm vários constructos de looping como o while, do etc., o go tem somente o loop for. Isso serve para tornar seu código mais claro e mais legível, considerando que você não precisa se preocupar com várias estratégias para chegar no mesmo constructo de looping. Essa legibilidade aprimorada e a redução da carga cognitiva durante o desenvolvimento também tornarão o seu código menos propenso a erros do que com outras linguagens.

Neste tutorial, você aprenderá como o loop for do Go funciona, incluindo as três grandes variações de sua utilização. Vamos começar mostrando como criar diferentes tipos de loops for, seguido de como fazer o loop através de tipos de dados sequenciais em Go. Vamos terminar explicando como usar os loops aninhados

Declarando o ForClause e os loops de condição

Para atender a uma variedade de casos de uso, existem três maneiras diferentes de criar loops for em Go, cada qual com seus próprios recursos. Essas maneiras são para criar um loop for com uma Condition, uma ForClause, ou uma RangeClause. Nesta seção, explicaremos como declarar e usar as variantes ForClause e Condição.

Vamos ver como podemos usar um loop for com o ForClause primeiro.

Um ForClause loop é definido como tendo uma instrução inicial, seguida de uma condição e, depois, uma instrução de post. Eles são organizados com a seguinte sintaxe:

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

Para explicar o que os componentes anteriores fazem, vejamos um loop for que incrementa por meio de uma gama especificada de valores usando a sintaxe do ForClause:

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

Vamos desmembrar esse loop e identificar cada parte.

A primeira parte do loop é i := 0. Esta é a instrução inicial:

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

Essa instrução afirma que estamos declarando uma variável chamada i e definindo o valor inicial para 0.

Em seguida, temos a condição:

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

Nessa condição, afirmamos que enquanto o i for menor do que o valor 5, o loop deve continuar em looping.

Por fim, temos a instrução post:

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

Na instrução post, incrementamos a variável do loop i em um toda vez que uma iteração ocorrer, usando o operador de incremento i++.

Quando executamos esse programa, o resultado fica parecido com este:

Output
0 1 2 3 4

O loop executou 5 vezes. Inicialmente, ele definiu i para 0 e, depois, verificou se o i era menor do que 5. Como o valor de i era menor que 5, o loop executou e a ação de fmt.Println(i) foi executada. Após o loop terminar, o instrução post i++ foi chamada e o valor i foi incrementado em 1.

Nota: tenha em mente que, em programação, tendemos a começar no índice 0, o que explica por que, a despeito da impressão de 5 números, eles variam de 0 a 4.

Não estamos limitados a iniciar no 0 ou concluir em um valor especificado. Podemos atribuir qualquer valor para nossa instrução inicial e parar em qualquer valor em nossa instrução post. Isso nos permite criar qualquer intervalo desejado para fazer o loop:

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

Aqui, a iteração vai de 20 (inclusive) até 25 (exclusive), de modo que o resultado fica parecido com este:

Output
20 21 22 23 24

Também podemos usar nossa instrução post para incrementar em diferentes valores. Esse procedimento é parecido com o step de outras linguagens:

Primeiro, vamos usar uma instrução post com um valor positivo:

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

Neste caso, o loop for foi configurado para que os números de 0 a 15 sejam impressos, mas a incrementos de 3, de modo a imprimir apenas um número a cada três, desta forma:

Output
0 3 6 9 12

Também podemos usar um valor negativo para que o nosso argumento de instrução post faça a iteração retroativamente. Teremos, porém, que ajustar devidamente nossa instrução inicial e os argumentos de condição:

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

Aqui, definimos i para um valor inicial de 100, usamos a condição i < 0 para parar em 0 e a instrução post para diminuir o valor em 10 com o operador -=. O loop começa em 100 e termina em 0, diminuindo o valor em 10 a cada iteração. Podemos ver isso ocorrer no resultado:

Output
100 90 80 70 60 50 40 30 20 10

Também é possível excluir a instrução inicial e a instrução post da sintaxe for e usar apenas a condição. Trata-se de um loop de Condição:

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

Desta vez, declaramos a variável i separadamente do loop for na linha anterior do código. O loop tem apenas uma cláusula de condição que verifica se o i é menor do que 5. Desde que a condição avalie como true, o loop continuará a iterar.

Às vezes, você pode não saber o número de iterações que serão necessárias para concluir uma determinada tarefa. Neste caso, é possível omitir todas as instruções e usar a palavra-chave break para sair da execução:

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

Como exemplo disso, vamos supor que estivéssemos lendo uma estrutura de tamanho indeterminado como a de um buffer e não soubéssemos quando terminaríamos a leitura:

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)
    }
}

No código anterior, o buf :=bytes.NewBufferString("one\ntwo\nthree\nfour\n") declara um buffer com alguns dados. Como não sabemos quando o buffer irá terminar a leitura, criamos um loop for sem cláusula. Dentro do loop for, usamos line, err := buf.ReadString('\n') para ler uma linha do buffer e verificamos se existe um erro ao ler do buffer. Se houver, lidamos com o erro e usamos a palavra-chave break para sair do loop for. Com esses pontos de break, não é necessário incluir uma condição para interromper o loop.

Nesta seção, aprendemos como declarar um loop do ForClause e a usá-lo para iterar por meio de uma gama conhecida de valores. Também aprendemos a usar um loop de condição para iterar até que uma condição específica seja cumprida. Em seguida, vamos aprender como o RangeClause é usado para iterar através de tipos de dados sequenciais.

Fazendo looping através de tipos de dados sequenciais com o RangeClause

Na linguagem Go, é comum o uso de loops for para iterar sobre os elementos de tipos de dados sequenciais ou dados de coleta sequencial ou tipos de dados de coleta, como fatias, matrizes e strings. Para facilitar esse processo, podemos utilizar um loop for com a sintaxe do RangeClause. Embora você possa fazer loops em tipos de dados sequenciais usando a sintaxe do ForClause, a RangeClause é mais clara e fácil de ler.

Antes de examinarmos o uso da RangeClause, vejamos como podemos iterar por meio de uma fatia, usando a sintaxe do 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])
    }
}

Executar isso dará o seguinte resultado, imprimindo cada um dos elementos da fatia:

Output
hammerhead great white dogfish frilled bullhead requiem

Agora, vamos usar a RangeClause para executar o mesmo conjunto de ações:

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)
    }
}

Neste caso, estamos imprimindo cada item da lista. Embora tenhamos usado as variáveis i e shark, poderíamos ter chamado as variáveis por qualquer outro nome de variável válido e, ainda assim, obteríamos o mesmo resultado:

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

Ao usar range em uma fatia, ele irá sempre retornar dois valores. O primeiro valor será o índice em que a iteração atual do loop está e o segundo será o valor naquele índice. Neste caso, para a primeira iteração, o índice era 0, e o valor era hammerhead.

Às vezes, queremos apenas o valor dentro dos elementos da fatia, não do índice. Se alterarmos o código anterior para imprimir apenas o valor, no entanto, vamos receber um erro de tempo de compilação:

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

Como o i foi declarado no loop for, mas nunca foi usado, o compilador responderá com o erro i declared and not used. Este é o mesmo erro que você receberá no Go sempre que for declarar uma variável e não a utilizar.

Por isso, o Go tem o identificador em branco, que é um sublinhado (_). Em um loop for, é possível utilizar o identificador em branco para ignorar qualquer valor retornado da palavra-chave range. Neste caso, queremos ignorar o índice, que é o primeiro argumento retornado.

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

Esse resultado mostra que o loop for iterou por toda a fatia de strings e imprimiu cada item da fatia sem o índice.

Também é possível usar o range para adicionar itens a uma 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']

Aqui, adicionamos uma string com espaço reservado de "shark"para cada item do comprimento da fatia sharks.

Note que não precisamos usar o identificador em branco _ para ignorar nenhum dos valores retornados do operador range. O Go nos permite omitir toda a parte da instrução range se não precisarmos usar qualquer um do valores retornados.

Também podemos usar o operador range para preencher valores de uma fatia:

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)
}

Neste exemplo, a fatia integers é inicializada com dez valores em branco, mas o loop for define todos os valores desta forma:

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

A primeira vez que imprimimos o valor da fatia integers, vemos todos os zeros. Então, iteramos pro meio de cada índice e definimos o valor para o índice atual. Em seguida, imprimimos o valor de integers uma segunda vez, mostrando que todos eles agora têm um valor de 0 a 9.

Também podemos usar o operador range para iterar por meio de cada caractere em uma string:

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

Ao iterar por um mapa, o range retornará a key (chave) e o** value** (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: é importante notar que a ordem na qual um mapa retorna é aleatória. Cada vez que você executa esse programa, você pode obter um resultado diferente.

Agora que aprendemos como iterar em dados sequenciais com loops for do tipo range, vamos ver como usar loops dentro de loops.

Loops for aninhados

Assim como acontece em outras linguagens de programação, os loops também podem ser aninhados em Go. Nesting (aninhamento) é quando temos um constructo dentro de outro. Neste caso, um loop aninhado é um loop que ocorre dentro de outro loop. Eles podem ser úteis quando o que se quer é uma ação em loop sendo realizada em cada elemento de um conjunto de dados.

Os loops aninhados são estruturalmente semelhantes às instruções aninhadas if. Eles são construídos desta forma:

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

Primeiro, o programa encontra o loop externo, executando sua primeira iteração. Essa primeira iteração aciona o loop interno, o loop aninhado, o qual é executado até sua finalização. Em seguida, o programa retorna para o topo do loop externo, finalizando a segunda iteração e acionando novamente o loop aninhado. Novamente, o loop aninhado executa até sua finalização e o programa retorna para o topo do loop externo até a sequência estar completa ou que uma instrução break ou outra interrompa o processo.

Vamos implementar um loop for aninhado para analisarmos melhor. Neste exemplo, o loop externo irá iterar por uma fatia de inteiros chamada de numList e o loop interno irá iterar por uma fatia de strings chamada 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)
        }
    }
}

Ao executarmos esse programa, vamos receber o seguinte resultado:

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

O resultado ilustra que o programa termina a primeira iteração do loop externo imprimindo 1, que, por sua vez, aciona a conclusão do loop interno, imprimindo a, b e c, consecutivamente. Assim que o loop interno for finalizado, o programa retorna para o topo do loop externo, imprime o número 2, e imprime novamente o loop interno em sua totalidade (a, b, c) etc.

Os loops for aninhados podem ser úteis para iterar através de itens dentro de fatias compostas por fatias. Em uma fatia composta por fatias, se usarmos apenas um loop for, o programa dará como resultado cada lista interna como um item:

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 acessar cada item individual das fatias internas, implementaremos um loop for aninhado:

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

Ao usarmos um loop for aninhado aqui, podemos iterar com os itens individuais contidos nas fatias.

Conclusão

Neste tutorial, aprendemos a declarar e usar loops for para resolver tarefas repetitivas no Go. Também aprendemos as três diferentes variações de um loop for e quando usá-las. Para aprender mais sobre os loops for e como controlar o fluxo deles, leia o artigo Usando as instruções break e continue ao trabalhar com loops em Go.

0 Comments

Creative Commons License