Golang: For e Condicionais (Parte 15)

·9 min de leitura

Golang: For e Condicionais (Parte 15)

Em Go, as declarações de controle de fluxo são projetadas para serem simples, claras e eficientes. Diferente de muitas outras linguagens, Go possui apenas uma construção de loop: for. As condicionais são tratadas pelas declarações if e switch.

1. O Loop for

O loop for do Go é incrivelmente versátil, servindo ao propósito de loops for, while e do-while encontrados em outras linguagens.

1.1. for Básico (como while)

A forma mais básica de for atua como um loop while. Ele continua a ser executado enquanto uma condição for verdadeira.

package main

import "fmt"

func main() {
	// Loop 'for' básico (como um loop 'while')
	i := 0
	for i < 5 {
		fmt.Printf("Loop básico: %d\n", i)
		i++
	}
}

1.2. for Tradicional (init; condition; post)

Esta é a forma mais comum, semelhante aos loops for estilo C. Possui três componentes separados por ponto e vírgula:

  • init statement: Executado antes da primeira iteração. Variáveis declaradas aqui têm escopo limitado ao loop for.
  • condition expression: Avaliada antes de cada iteração. Se true, o loop continua; caso contrário, ele termina.
  • post statement: Executado após cada iteração.
package main

import "fmt"

func main() {
	// Loop 'for' tradicional
	for j := 0; j < 5; j++ {
		fmt.Printf("Loop tradicional: %d\n", j)
	}
}

1.3. for Infinito

Se você omitir a condição, você cria um loop infinito. Isso é frequentemente usado em aplicações de servidor ou processos em segundo plano que rodam indefinidamente, tipicamente com uma declaração break explícita dentro.

package main

import "fmt"

func main() {
	// Loop 'for' infinito
	k := 0
	for {
		fmt.Printf("Loop infinito: %d\n", k)
		k++
		if k >= 3 {
			break // Sai do loop após 3 iterações
		}
	}
}

1.4. for...range Loop

A construção for...range é usada para iterar sobre elementos de várias estruturas de dados:

  • Arrays e Slices: Retorna o índice e uma cópia do elemento.
  • Strings: Retorna o índice de byte inicial do rune e o próprio rune.
  • Maps: Retorna a chave e o valor.
  • Channels: Retorna o valor recebido do channel (até que seja fechado).

Você pode omitir o índice ou o valor se não precisar deles usando o identificador em branco _.

package main

import "fmt"

func main() {
	// for...range com um slice
	numbers := []int{10, 20, 30, 40, 50}
	for index, value := range numbers {
		fmt.Printf("Elemento do slice no índice %d: %d\n", index, value)
	}

	// for...range com uma string (itera sobre runes)
	greeting := "Olá, Mundo" 
	for index, runeValue := range greeting {
		fmt.Printf("Rune da string no índice de byte %d: %c (Unicode: %U)\n", index, runeValue, runeValue)
	}

	// for...range com um map
	ages := map[string]int{"Alice": 30, "Bob": 25, "Charlie": 35}
	for name, age := range ages {
		fmt.Printf("%s tem %d anos.\n", name, age)
	}

	// Omitindo índice/chave ou valor
	for _, value := range numbers { // Apenas o valor
		fmt.Printf("Valor (sem índice): %d\n", value)
	}
	for index, _ := range numbers { // Apenas o índice
		fmt.Printf("Índice (sem valor): %d\n", index)
	}
}

1.5. break e continue

  • break: Termina o loop for mais interno imediatamente.
  • continue: Pula o restante da iteração atual e prossegue para a próxima iteração do loop.
package main

import "fmt"

func main() {
	// Usando continue
	fmt.Println("Usando continue:")
	for i := 0; i < 5; i++ {
		if i%2 == 0 { // Pula números pares
			continue
		}
		fmt.Printf("Número ímpar: %d\n", i)
	}

	// Usando break
	fmt.Println("\nUsando break:")
	for i := 0; i < 10; i++ {
		if i == 5 {
			break // Sai do loop quando i for 5
		}
		fmt.Printf("Número antes do break: %d\n", i)
	}
}

1.6. break e continue Rotulados

Go permite que você use rótulos com break e continue para controlar qual loop eles afetam, especialmente útil em loops aninhados.

package main

import "fmt"

func main() {
OuterLoop: // Rótulo para o loop externo
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			fmt.Printf("i: %d, j: %d\n", i, j)
			if i == 1 && j == 1 {
				break OuterLoop // Sai do OuterLoop
			}
		}
	}
	fmt.Println("Saiu do OuterLoop")

InnerLoop: // Rótulo para o loop interno
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			if i == 1 && j == 1 {
				continue InnerLoop // Continua o InnerLoop (próxima iteração de j)
			}
			fmt.Printf("i: %d, j: %d\n", i, j)
		}
	}
	fmt.Println("Exemplo de continue rotulado finalizado")
}

2. Declarações Condicionais

Go fornece if e switch para execução condicional.

2.1. Declaração if

A declaração if avalia uma expressão booleana e executa um bloco de código se a expressão for true. Diferente de C, C++ ou Java, os parênteses () em torno da condição não são obrigatórios, mas as chaves {} em torno do corpo são.

package main

import "fmt"

func main() {
	x := 10

	if x > 5 {
		fmt.Println("x é maior que 5")
	}
}

2.2. Declaração if...else

O bloco else é executado se a condição if for false.

package main

import "fmt"

func main() {
	x := 3

	if x > 5 {
		fmt.Println("x é maior que 5")
	} else {
		fmt.Println("x não é maior que 5")
	}
}

2.3. Cadeia if...else if...else

Para múltiplas condições, você pode encadear cláusulas else if.

package main

import "fmt"

func main() {
	score := 85

	if score >= 90 {
		fmt.Println("Nota: A")
	} else if score >= 80 {
		fmt.Println("Nota: B")
	} else if score >= 70 {
		fmt.Println("Nota: C")
	} else {
		fmt.Println("Nota: F")
	}
}

2.4. if com Declaração Curta

Um idioma comum em Go é incluir uma declaração curta antes da condição. Esta declaração é tipicamente usada para inicializar uma variável que é então usada na condição. O escopo da variável é limitado aos blocos if e else.

package main

import (
	"fmt"
	"strconv" // Pacote para conversão de string
)

func main() {
	// Exemplo 1: Verificação de erro
	if num, err := strconv.Atoi("123"); err == nil {
		fmt.Printf("Número convertido: %d\n", num)
	} else {
		fmt.Printf("Erro ao converter string: %v\n", err)
	}

	// Exemplo 2: Inicialização de variável
	if length := len("Programação Go"); length > 10 {
		fmt.Printf("String é longa, comprimento: %d\n", length)
	} else {
		fmt.Printf("String é curta, comprimento: %d\n", length)
	}

	// 'num' e 'length' não são acessíveis aqui
	// fmt.Println(num) // Isso causaria um erro de compilação
}

Este padrão é particularmente útil para tratamento de erros, pois permite que você verifique um erro imediatamente após uma chamada de função que pode retornar um.

2.5. Declaração switch

A declaração switch fornece uma maneira mais limpa de escrever longas cadeias if-else if.

2.5.1. switch Básico

O switch do Go é mais flexível do que em muitas outras linguagens. Ele automaticamente fornece um break após cada case, o que significa que a execução não "cai" para o próximo case por padrão.

package main

import "fmt"

func main() {
	day := "Wednesday"

	switch day {
	case "Monday":
		fmt.Println("Início da semana.")
	case "Tuesday", "Wednesday", "Thursday": // Múltiplas expressões em um case
		fmt.Println("Meio da semana.")
	case "Friday":
		fmt.Println("Quase fim de semana!")
	default: // Caso default opcional se nenhum outro case corresponder
		fmt.Println("Fim de semana!")
	}
}
2.5.2. switch sem uma Expressão (Tagless Switch)

Uma declaração switch pode ser usada sem uma expressão, efetivamente tornando-se uma maneira mais limpa de escrever a lógica if-else if-else. Cada case então contém uma expressão booleana.

package main

import "fmt"

func main() {
	age := 25

	switch { // Nenhuma expressão aqui
	case age < 18:
		fmt.Println("Menor")
	case age >= 18 && age < 65:
		fmt.Println("Adulto")
	default:
		fmt.Println("Idoso")
	}
}
2.5.3. Palavra-chave fallthrough

Se você deseja explicitamente que a execução "caia" para o próximo case (como em C/C++), você pode usar a palavra-chave fallthrough. Isso raramente é necessário e deve ser usado com cautela, pois pode tornar o código mais difícil de ler.

package main

import "fmt"

func main() {
	num := 2

	switch num {
	case 1:
		fmt.Println("Case 1")
		fallthrough // A execução continua para o case 2
	case 2:
		fmt.Println("Case 2")
		fallthrough // A execução continua para o case 3
	case 3:
		fmt.Println("Case 3")
	default:
		fmt.Println("Case default")
	}
	// Saída:
	// Case 2
	// Case 3
}
2.5.4. type switch

Um type switch é usado para determinar o tipo dinâmico de um valor de interface.

package main

import "fmt"

func printType(i interface{}) {
	switch v := i.(type) { // 'v' é o valor com seu tipo concreto
	case int:
		fmt.Printf("Tipo: int, Valor: %d\n", v)
	case string:
		fmt.Printf("Tipo: string, Valor: %s\n", v)
	case bool:
		fmt.Printf("Tipo: bool, Valor: %t\n", v)
	default:
		fmt.Printf("Tipo desconhecido: %T, Valor: %v\n", v, v)
	}
}

func main() {
	printType(10)
	printType("olá")
	printType(true)
	printType(3.14)
}

Melhores Práticas e Armadilhas Comuns

  • Legibilidade: Prefira for...range para iterar sobre coleções sempre que possível, pois é frequentemente mais conciso e menos propenso a erros do que o gerenciamento manual de índices.
  • Declarações Curtas: Aproveite a sintaxe de declaração curta de if e switch para declarar variáveis cujo escopo deve ser limitado ao bloco condicional, especialmente para tratamento de erros.
  • switch vs. if-else if: Para mais de duas ou três condições distintas em uma única variável, switch geralmente leva a um código mais limpo e legível do que uma longa cadeia if-else if. Para lógica booleana complexa envolvendo múltiplas variáveis, if-else if (ou um switch sem expressão) pode ser mais apropriado.
  • fallthrough: Use fallthrough com moderação. Ele pode tornar o código mais difícil de entender. A maioria dos programas Go não o utiliza.
  • Loops Infinitos: Ao criar loops infinitos (for {}), sempre garanta que haja uma condição break clara ou que o loop seja gerenciado por sinais externos (por exemplo, fechamento de canais, cancelamento de contexto) em programas concorrentes.
  • Escopo de Variáveis: Esteja atento ao escopo das variáveis, especialmente com variáveis declaradas em declarações init de loop for ou declarações curtas de if/switch. Elas são acessíveis apenas dentro de seus respectivos blocos.

Este mergulho profundo cobre os aspectos essenciais dos loops for e das declarações condicionais em Go, fornecendo uma base sólida para escrever lógica de controle de fluxo em suas aplicações.