Como construir laços for em Go

Introdução

Na programação de computadores, uma estrutura de loop é uma estrutura de código que repete a execução de um pedaço de código, frequentemente até que alguma condição seja atingida. O uso de loops na programação de computadores permite automatizar e repetir tarefas semelhantes várias vezes. Imagine se você tivesse uma lista de arquivos que precisasse ser processada, ou se quisesse contar o número de linhas em um artigo. Você usaria um loop em seu código para resolver esses tipos de problemas.

Em Go, um loop for implementa a execução repetida de código com base em um contador de loop ou variável de loop. Ao contrário de outras linguagens de programação que têm múltiplos construtos de loop, como while, do, etc., Go apenas tem o loop for. Isto serve para tornar seu código mais claro e legível, já que você não precisa se preocupar com múltiplas estratégias para alcançar o mesmo construto de loop. Esta melhora na legibilidade e redução no carga cognitiva durante o desenvolvimento também tornará seu código menos propenso a erros do que em outras linguagens.

Neste tutorial, você vai aprender como o loop for em Go funciona, incluindo as três variantes principais de seu uso. Vamos começar mostrando como criar diferentes tipos de loops for, seguido de como percorrer tipos de dados seqüencias em Go. Concluiremos explicando como usar loops aninhados.

Declaração de Laços For e Condição

Para atender a uma variedade de casos de uso, existem três maneiras distintas de criar laços for em Go, cada uma com suas próprias capacidades. São elas: criar um laço for com uma Condição, uma ForClause ou uma RangeClause. Nesta seção, vamos explicar como declarar e usar as variantes ForClause e Condição.

Vamos primeiro olhar como podemos usar um laço for com a ForClause.

Um laço ForClause é definido como tendo uma declaração inicial, seguida por uma condição e depois por uma declaração pós-laço. Estes estão arranjados na seguinte sintaxe:

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

Para explicar o que os componentes anteriores fazem, vamos olhar para um laço for que incrementa através de um intervalo de valores especificado usando a sintaxe ForClause:

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

Vamos desconstruir este laço e identificar cada parte.

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

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

Ela afirma que estamos declarando uma variável chamada i e definindo o valor inicial como 0.

A próxima é a condição:

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

Nesta condição, declaramos que enquanto i for menor que o valor de 5, o laço deve continuar executando.

Finalmente, temos a declaração pós-laço:

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

No encadeamento de instruções, incrementamos a variável de loop i em uma unidade a cada iteração usando o operador de incremento i++.

Quando executamos este programa, a saída parece assim:

Output
0 1 2 3 4

O loop executou 5 vezes. Inicialmente, ela definiu i para 0, e então verificou se i era menor 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 fim do loop, a declaração de pós-execução de i++ foi chamada, e o valor de i foi incrementado em 1.

Nota: Lembre-se que, em programação, tendemos a começar em 0, por isso é que embora 5 números sejam impressos, eles variam de 0 a 4.

Nós não estamos limitados a começar em 0 ou a terminar em um valor específico. Podemos atribuir qualquer valor à nossa declaração inicial e também parar em qualquer valor na declaração de pós-execução. Isso permite que criemos qualquer intervalo desejado para percorrer:

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

Aqui, a iteração vai de 20 (inclusivo) até 25 (exclusivo), portanto, a saída parece assim:

Output
20 21 22 23 24

Também podemos usar nossa declaração de pós-execução para incrementar em valores diferentes. Isto é semelhante ao passo em outras linguagens:

Primeiro, vamos usar uma declaração de pós-execução com um valor positivo:

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

Neste caso, o laço for está configurado para que os números de 0 a 15 sejam impressos, mas com um incremento de 3, portanto, apenas cada terceiro número é impresso, como está aqui:

Output
0 3 6 9 12

Nós também podemos usar um valor negativo para nosso argumento de declaração posterior na estrutura de repetição, mas terão que ajustar os argumentos iniciais e condicionais adequadamente:

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

Aqui, definimos i com um valor inicial de 100, usamos a condição de i < 0 para parar em 0, e a declaração posterior decrementa o valor por 10 com o operador -=. A loop começa em 100 e termina em 0, diminuindo por 10 com cada iteração. Podemos ver isso no output:

Output
100 90 80 70 60 50 40 30 20 10

Você pode excluir a declaração inicial e a declaração posterior da sintaxe for e usar apenas a clausula condicional. Isso é conhecido como um Loop Condicional:

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

Estevemos declarando a variável i separadamente da última linha de código do loop anterior. Agora, a loop tem uma clausula condicional que checa se i é menor que 5. Se a condição avalia para true, o loop continuará a executar.

Algumas vezes você pode não saber quantas iterações você precisará para completar uma tarefa determinada. Nesse caso, você pode omitir todas as instruções e usar o operador break para sair da execução:

for {
	if someCondition {
break
}
	// fazer ação aqui
}

Um exemplo disso pode ser se você está lendo de uma estrutura indefinida como um buffer e não saber quando terminará de ler:

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

Na código anterior, buf := bytes.NewBufferString("one
two
three
four
")
declara um buffer com algum dado. Porque não sabemos quando o buffer irá terminar de ler, nós criamos um laço for sem clausula. Dentro do laço for, usamos line, err = buf.ReadString
")
para ler uma linha do buffer e verificar se houve erro ao ler o buffer. Se houver um erro, tratamos o erro, e usamos o operador break para sair do laço for. Com esses pontos de break, você não precisa incluir uma condição para parar o laço. Neste trecho, aprendemos como declarar um ForClause loop e usar-lo para iterar sobre um rango conhecido de valores. Também aprendemos como usar um LoopCondition para iterar até que uma condição específica fosse satisfeita. A seguir, vamos aprender como o RangeClause é usado para iterar sobre tipos de dados seqüenciais.

Looping Through Sequential Data Types with RangeClause

Looping Through Sequential Data Types with RangeClause

É comum em Go usar laços for para iterar sobre os elementos de tipos de dados sequenciais ou de coleções, como fatias, arrays e strings. Para fazer isso mais facilmente, podemos usar um laço for com a sintaxe de RangeClause. Embora você consiga iterar por meio de tipos de dados sequenciais usando a sintaxe ForClause, a RangeClause é mais limpa e fácil de ler.

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

Executando isso dá o seguinte resultado, imprimindo cada elemento 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 na lista. Embora usamos as variáveis i e shark, poderíamos ter chamado a variável de qualquer outro nome de variável válido e obteríamos o mesmo resultado:

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

Quando usamos range the slice, ele sempre retorna dois valores. O primeiro valor será o índice que a iteração atual do loop está em, e o segundo é o valor nessa posição. Neste caso, para a primeira iteração, o índice era 0, e o valor era hammerhead.

Ocasionalmente, queremos apenas o valor dentro dos elementos de uma fatia, não o índice. Se alterarmos o código anterior para imprimir somente o valor, receberão um erro 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

Porque i é declarado no loop for, mas nunca usado, o compilador responderá com o erro de i declarado e não usado. Isso é o mesmo erro que você receberá em Go sempre que você declare uma variável e não use ela.

Por causa disso, Go tem o identificador vazio que é um sublinhamento (_). Na for loop, você pode usar o identificador vazio para ignorar qualquer valor retornado pelo operador range. Neste caso, queremos ignorar o índice, que é a primeira 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

Esta saída mostra que o loop for iterou sobre a fatia de strings e imprimiu cada item da fatia sem o índice.

Você também pode usar range para adicionar itens à 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, nós adicionamos uma string de "shark" para cada item do tamanho da fatia sharks.

Observe que não houve necessidade de usar o identificador vazio _ para ignorar qualquer valor retornado do operador range. O Go permite deixar o completo da declaração do range ausente se não precisarmos usar nenhum dos valores retornados.

Podemos também 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)
}

Na primeira linha deste exemplo, o slice integers é inicializado com dez valores vazios, mas a loop for executa para cada índice do slice e define todos os valores na lista como se fossem assim:

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 do slice integers, vemos todos os zeros. Então, nós iteramos sobre cada índice e definimos o valor para a posição atual. Quando imprimimos o valor do integers novamente, mostramos que eles agora tem um valor de 0 até 9.

Podemos também usar o operador range para iterar sobre 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

Quando iterando sobre um map, range retornará ambos o chave e o 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

Observação: É importante notar que a ordem em que um map retorna é aleatoria. Cada vez que você executar este programa, você pode obter um resultado diferente.

Agora que já conhecemos como iterar sobre dados seqüenciais com range e for loops, vamos ver como usar loops dentro de loops.

Loop Nested

As estruturas de loop podem ser aninhadas em Go, tal como podem ser em outras linguagens de programação. Aninhamento é quando temos um construto dentro de outro. Neste caso, uma estrutura de loop aninhada é um loop que ocorre dentro de outro loop. Estes podem ser úteis quando você gostaria de ter uma ação de loop realizada em cada elemento de um conjunto de dados.

As estruturas de loop aninhadas são estruturalmente semelhantes a declarações if aninhadas. Eles são construídos da seguinte forma:

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

O programa primeiro encontra o loop externo, executando sua primeira iteração. Esta primeira iteração dispara o loop interno, aninhado, que então executa até o fim. Então o programa volta para o topo do loop externo, completando a segunda iteração e novamente disparando o loop aninhado. Novamente, o loop aninhado executa até o fim, e o programa volta para o topo do loop externo até que a sequência seja concluída ou um break ou outra declaração interrompa o processo.

Vamos implementar um loop for aninhado para podermos olhar mais de perto. Neste exemplo, o loop externo vai iterar por um slice de inteiros chamado numList, e o loop interno vai iterar por um slice de strings chamado 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)
		}
	}
}

Quando executarmos este programa, receberá a seguinte saída:

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

A execução mostra que o programa completa a primeira iteração do loop externo imprimindo 1, o que então dispara a conclusão do loop interno, imprimindo a, b, c consecutivamente. Uma vez que o loop interno tenha sido completado, o programa retorna ao início do loop externo, imprime 2, e então novamente imprime todo o loop interno (a, b, c), etc.

Loopes aninhados de for podem ser úteis para iterar sobre itens dentro de fatias compostas de fatias. Na ausência de um único loop for, o programa irá exibir 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 da lista interna, vamos implementar um loop 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

Quando usamos um loop aninhado aqui, estamos capazes de iterar sobre os itens contidos nas fatias internas.

Conclusão

Nesta introdução, aprendemos como declarar e usar laços for para resolver tarefas repetidas em Go. Também lemos as três variantes diferentes de um laço for e quando usar cada uma. Para saber mais sobre como trabalhar com laços for e como controlar o fluxo deles, leia Usando Breaks e Continues Quando Trabalhando com Laços em Go.

Source:
https://www.digitalocean.com/community/tutorials/how-to-construct-for-loops-in-go