Estruturas de Controle em Go
Em lições passadas, você deve ter percebido que em alguns momentos, foram citadas algumas estruturas de controle pertencentes a linguagem GoLang, onde você chegou a notar alguns exemplos.
Tais estruturas podem ser representadas por meio dos comandos: If/Else
, For
e Switch
.
O que representam recursos da linguagem que são capazes de definir o fluxo de execução do código, alterando a ordem em que as instruções serão executadas com base em condições, repetições ou desvios.
Como você já deve saber, a linguagem GoLang é imperativa e sequencial por natureza, o que significa que as instruções são executadas em uma ordem determinística e linear, seguindo um fluxo lógico estruturado.
Isso significa que para executar a terceira linha que existe dentro da função main()
:
func main(){
fmt.Println("Olá, Micilini");
x := 8 * 9
fmt.Println("Resultado é: ", x)
}
O compilador do Go, é obrigado a passar pelas linhas 1 e 2, e executar tudo o que ali existe.
No fundo no fundo, não tem como chegar na terceira linha, sem antes executar as duas primeiras, e assim sucessivamente 😉
Beleza, esse é o comportamento padrão e esperado da linguagem GoLang, mas com o advento das estruturas de controle, o padrão de execução mudou um pouco.
E é sobre este assunto que iremos explorar nesta lição!
Criando seu projeto de testes
Dentro da pasta JornadaGoLang, nós iremos criar uma nova pasta chamada de 8-estruturas-de-controle
, onde dentro dela, vamos criar o nosso arquivo main.go
:
package main
func main(){
}
Feito, isso vamos aprender a utilizar nossa primeira estrutura de controle, conhecida como estrutura condicional 😉
Estruturas Condicionais em Go
Como o próprio nome já diz, estruturas condicionais, nada mais são do que comandos que executam uma determinada lógica (A ou B) dada a uma determinada condição.
Exemplo: Se o valor de X for 10, mostra a mensagem A, caso contrário, mostra a mensagem B.
Isso faz com que, certas linhas, ou aglomerado de linhas (códigos) sejam totalmente ignorados durante a execução do seu programa.
No GoLang, nós temos as estruturas if
e else
, que são conhecidas como estruturas condicionais ou estruturas de decisão.
Elas permitem que o programa avalie uma condição booleana (verdadeira ou falsa) e, com base no resultado, execute diferentes blocos de código.
Essa estrutura é fundamental para que você possa implementar uma lógica de controle, permitindo que o fluxo do programa siga caminhos distintos de acordo com as condições estabelecidas.
E o seu uso é EXTREMAMENTE FÁCIL, observe:
if condição {
// Bloco executado se a condição for verdadeira
} else {
// Bloco executado se a condição for falsa
}
Vamos ver um exemplo simples de seu funcionamento:
package main
import "fmt"
func main() {
idade := 10
if idade = 10 {
fmt.Println("O valor é igual a 10")
} else {
fmt.Println("O valor não é igual a 10")
}
}
No código acima, nós criamos uma variável chamada idade
que armazena o valor 10
, e em seguida estamos comparando por meio de uma estrutura condicional, se a variável idade
é igual a 10
, e se for, é mostrado a mensagem "O valor é igual a 10".
Caso contrário, será mostrada a mensagem: "O valor não é igual a 10".
Entretanto, diferente de outras linguagens de programação, aqui existe algumas pequenas regrinhas de uso:
✅ Diferente de outras linguagens como C ou Java, no caso do GoLang, não é necessário (nem permitido) envolver a condição com parêntesis ()
:
if idade >= 18 {
fmt.Println("Maior de idade")
}
if (idade >= 18) { // Parênteses não são usados em Go
fmt.Println("Maior de idade")
}
✅ Mesmo que o bloco contenha apenas uma linha, o uso das chaves {}
são obrigatórias:
if nota := 8; nota >= 7 {
fmt.Println("Aprovado!")
} else {
fmt.Println("Reprovado.")
}
Além disso, caso você precise verificar múltiplas condições, você pode fazer o uso aninhado de muitos Else
e If
, observe:
package main
import "fmt"
func main() {
temperatura := 25
if temperatura > 30 {
fmt.Println("Está muito quente!")
} else if temperatura > 20 {
fmt.Println("Clima agradável.")
} else {
fmt.Println("Está frio.")
}
}
If com init
Em GoLang, o if
suporta uma forma especial chamada de "if com init". Esse recurso permite inicializar variáveis temporárias antes de avaliar a condição, tornando o código mais conciso e organizado.
Essa abordagem é útil quando você precisa calcular, ou preparar um valor apenas para a condição do if
, sem precisar declará-lo fora do escopo.
Vejamos a sua sintaxe:
if inicialização; condição {
// Bloco executado se a condição for verdadeira
}
- inicialização: Uma expressão curta (como a declaração de uma variável).
- condição: A condição booleana que será avaliada.
Vejamos isso num exemplo prático:
package main
import "fmt"
func main() {
if idade := 20; idade >= 18 {
fmt.Println("Maior de idade:", idade)
}
}
Note que dentro da condicional, nós criamos uma variável chamada idade
cujo valor inicial é 20
. Juntamente, realizamos uma análise capaz de verificar se a idade é menor ou igual a 18
, e caso for, executa a mensagem: "Maior de idade:...".
É importante ressaltar, a variável idade é inicializada apenas dentro do if
e fica restrita a esse escopo.
Isso significa dizer que, se tentarmos acessar a variável idade
fora das chaves {}
do if
, o compilador gerará um erro, exemplo:
package main
import "fmt"
func main() {
if idade := 20; idade >= 18 {
fmt.Println("Maior de idade:", idade)//Uso da variável idade é permitido, pois estamos dentro das chaves do IF
}
fmt.Println("A sua idade é: ", idade);//Aqui gera um erro no compilador...
}
Usar o if com init te ajuda com:
- A organização do seu código: Pois variáveis usadas apenas para a condição ficam localizadas no próprio
if
, evitando poluir o escopo externo. - A eficiência: Pois a lógica é executada apenas se necessário.
- A clareza: Uma vez que deixa claro que a variável pertence exclusivamente àquela verificação.
Estrutura de Repetições em Go (For)
Diferente de outras de linguagens que possuem entre duas ou três estruturas de repetição, aqui no GoLang, nós só temos uma única estrutura desse tipo, o famoso comando for
.
Neste caso, dentro do GoLang, nós somos obrigados a usar o comando for
para percorrer todos os tipos de laços.
Apesar disso, ele é extremamente versátil e pode ser usado em diferentes formas para controlar a repetição de blocos de código, seja com contadores, laços infinitos ou loops baseados em condições.
A sintaxe básica do for
é a seguinte:
for inicialização; condição; pós-execução {
// Bloco de código a ser repetido
}
A instrução For
é uma forma automatizada de fazer iterações no GoLang, pois com ela conseguimos declarar o valor de uma variável, a condição e ainda se vamos acrescentar + 1 ou diminuir -1 da variável que criamos, tudo isso dentro de uma única expressão.
Vejamos um exemplo simples da sua utilização:
package main
import "fmt"
func main() {
for i := 0; i < 9; i++ {
fmt.Println("Contador:", i)
}
//O comando acima fará a contagem de 0 até 9.
}
No comando acima, declaramos a variável chamada i
que começa com o valor de 0
, dizemos que esse loop vai continuar sendo executado até que i
seja menor que 9
, onde a cada iteração o valor de i
será acrescido de 1
.
Sendo assim, podemos dizer:
- inicialização: Executada apenas uma vez antes do loop começar. (Ex: i := 0)
- condição: Avaliada antes de cada iteração; o loop continua enquanto for
true
. (Ex: i < 5) - pós-execução: Executada após cada iteração. (Ex: i++)
Como dito anteriormente, apesar o for
funcionar de uma maneira simples, ele é totalmente versátil, e você pode usá-lo de diversas formas diferentes, vejamos alguns exemplo:
1️⃣ for com contador (Clássico)
O formato comum e mais utilizado no for
, foi feito para repetir um bloco de código em um número específico de vezes:
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
fmt.Println(i)
}
}
2️⃣ for como while (Condicional)
Caso desejar, você pode omitir a inicialização e a pós-execução, e o for
se comportará como um while
tradicional.
package main
import "fmt"
func main() {
contador := 0
for contador < 5 {
fmt.Println("Contador:", contador)
contador++
}
}
3️⃣ for infinito (Loop eterno)
Além disso, você pode criar um loop infinito, apenas omitindo todas outras partes (inicialização; condição; pós-execução):
package main
import "fmt"
func main() {
for {
fmt.Println("Executando para sempre...")
}
}
🛑 Cuidado: Este loop nunca termina, a menos que você use os comandos break
ou return
.
4️⃣ for com range (Iterando sobre coleções)
Em lições futuras, você verá que é possível criar listas de elementos em GoLang, e tais listas são conhecidas como arrays
, slices
, maps
e strings
.
Para percorrer tais listas, você pode (e deve) utilizar o for
em conjunto com range
da seguinte forma:
package main
import "fmt"
func main() {
frutas := []string{"maçã", "banana", "uva"}
for indice, fruta := range frutas {
fmt.Printf("Índice: %d, Fruta: %s\n", indice, fruta)
}
}
📌 Observações:
- O primeiro valor é o índice (ou a chave, se for um map).
- O segundo valor é o elemento.
- Se não precisar do índice ou do valor, use o underscore (_).
Vejamos um outro exemplo onde o índice é ignorado:
for _, fruta := range frutas {
fmt.Println(fruta) // Apenas imprime o valor
}
Estrutura de Repetições em Go (Switch)
Além do for
, nós temos outra estrutura de repetição bastante utilizada chamada switch
, que por sua vez, avalia uma condição, e executa o bloco de código de acordo com o seu resultado.
A sintaxe do switch
é a seguinte:
switch expressão {
case valor1:
// Código executado se expressão == valor1
case valor2:
// Código executado se expressão == valor2
default:
// Código executado se nenhum caso for atendido
}
Como você pode observar, o switch
é uma estrutura de controle que permite a execução condicional de blocos de código com base no valor de uma expressão.
Sendo considerado uma alternativa mais legível e eficiente ao uso de múltiplas instruções if/else
.
Vejamos agora um exemplo prático de sua utilização:
package main
import "fmt"
func main() {
dia := "segunda"
switch dia {
case "segunda":
fmt.Println("Início da semana!")
case "sexta":
fmt.Println("Quase final de semana!")
case "domingo":
fmt.Println("Dia de descanso!")
default:
fmt.Println("Dia comum da semana.")
}
}
Observe no comando criado acima, que dependendo do valor armazenado dentro da variável dia
, ele pode executar diferentes mensagens no terminal.
Diferente de outras linguagens de programação, o switch
do GoLang é considerado o mais poderoso pelos seguintes motivos:
- Não exige break explícito (as cláusulas não "caem" para o próximo caso, por padrão).
- Suporta múltiplos valores por caso.
- Pode trabalhar com tipos variados (inteiros, strings, booleans, etc.).
- Permite expressões condicionais.
🔎 Vejamos agora algumas características importantes do Switch:
1️⃣ Não precisa de break
Diferente de outras linguagens, aqui o switch
não precisa do comando break
para evitar que a execução continue no próximo case. Ele sai automaticamente após encontrar um caso verdadeiro.
package main
import "fmt"
func main() {
x := 2
switch x {
case 1:
fmt.Println("Um")
case 2:
fmt.Println("Dois")
case 3:
fmt.Println("Três")
}
}
2️⃣ Múltiplos valores em um único case
Você ainda pode agrupar vários valores em um único caso, desde que eles estejam separados por uma vírgula, por exemplo:
package main
import "fmt"
func main() {
dia := "sábado"
switch dia {
case "sábado", "domingo":
fmt.Println("É fim de semana!")
default:
fmt.Println("Dia útil.")
}
}
3️⃣ switch sem expressão (Switch True)
Se você omitir a expressão após o switch
, ele funciona como uma série de if/else
, avaliando cada case
como uma condição booleana:
package main
import "fmt"
func main() {
idade := 25
switch {
case idade < 18:
fmt.Println("Menor de idade.")
case idade >= 18 && idade < 60:
fmt.Println("Adulto.")
default:
fmt.Println("Idoso.")
}
}
4️⃣ fallthrough – Forçar a execução do próximo case
Por padrão, o GoLang não cai no próximo case
automaticamente. Se você quiser forçar esse comportamento, use a palavra-chave fallthrough
da seguinte forma:
package main
import "fmt"
func main() {
nota := 8
switch {
case nota >= 9:
fmt.Println("Excelente!")
fallthrough
case nota >= 7:
fmt.Println("Aprovado!")
default:
fmt.Println("Reprovado.")
}
}
⚠️ Atenção: O comando fallthrough
ignora a condição do próximo case
e sempre o executa, eivtando que você tenha que inserir diversos falltrhough
ao longo do switch
.
5️⃣ switch com tipos (Type Switch)
já o comando type switch
é usado para determinar o tipo dinâmico de uma interface:
package main
import "fmt"
func identificar(i interface{}) {
switch v := i.(type) {
case int:
fmt.Println("É um inteiro:", v)
case string:
fmt.Println("É uma string:", v)
case bool:
fmt.Println("É um booleano:", v)
default:
fmt.Println("Tipo desconhecido")
}
}
func main() {
identificar(42)
identificar("GoLang")
identificar(true)
}
Observação: Em lições futuras, nós iremos aprender o que é uma interface e como utilizá-la no seu código, portanto, não se preocupe no momento 🙃
A seguir, deixo com você algumas boas práticas que você pode seguir ao utilizar o switch
em suas aplicações feitas com Go:
- ✅ Use
switch
sem expressão para múltiplas condições. - ✅ Prefira
switch
em vez deif/else
para verificar múltiplas opções. - ✅ Use
fallthrough
com cuidado – é raro precisar dele. - ✅ Simplifique seu código com múltiplos valores em um case.
- ✅ Use
type switch
para checar tipos em interfaces.
Controlando fluxos em estruturas de repetição
Nos tópicos anteriores, você aprendeu a utilizar algumas estruturas de repetição, como é o caso do for
e do switch
.
No caso do for
, você pode fazer o uso de um comando específico chamado de break
, que é responsável por encerrar o loop atual de forma a transferir a execução do código para fora do bloco atual.
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
if i == 5 {
fmt.Println("Parando no 5")
break
}
fmt.Println(i)
}
}
Note que no comando acima, realizamos uma verificação para checar se o valor é de i
é igual a 5
, e caso for, ele para a execução do laço.
Quando utilizamos o break
em um determinado ponto do código, nós estamos dizendo ao GoLang o seguinte: "Caso ache o comando break, execute o comando, saia imediatamente do deste laço e execute tudo o que vier depois das chaves. Ou seja, tudo o que vier depois do loop".
Além dele, nós temos o comando continue
, que é responsável por pular a interação atual e ir para a próxima, vejamos um exemplo:
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
if i == 2 {
continue // Pula o número 2
}
fmt.Println(i)
}
}
No caso do comando acima, a cada laço, nós estamos mostrando no terminal o valor atual da variável i
, menos quando o valor de i
for igual a 2
. Pois nesse ponto o continue
é executado.
Como eu não gosto do número 2
, eu simplesmente pulei ele 😂
A saída será a seguinte:
0
1
3
4
É importante ressaltar que esses comandos só funcionam quando estão sendo declarados dentro de uma iteração.
Desvio incondicional de fluxo (goto)
Se você é do tempo das antigas, sabe que algumas linguagens de programação contavam com um comando bem específico conhecido como goto
.
Que nada mais é do que uma instrução de controle de fluxo, que permite mover a execução do programa diretamente para um rótulo (label) específico dentro da mesma função.
Embora seja considerada uma prática controversa durante todos esses anos, uma vez que tais práticas podem tornar o código confuso, em GoLang, o goto
tem usos legítimos para simplificar saídas aninhadas ou evitar duplicação de código.
A sintaxe da sua utilização é a seguinte:
goto label
label:
// Código que será executado
Vejamos uma utilização básica:
package main
import "fmt"
func main() {
fmt.Println("Início")
goto Fim // Salta para o rótulo "Fim"
fmt.Println("Isso não será impresso.")
Fim:
fmt.Println("Fim do programa.")
}
Notou que ao chamar o comando goto Fim
, ele pulou para o rótulo onde está escrito Fim
, e ignorou todos os outros comandos ali existentes?
Pense no goto
, como uma espécie de portal que teletransporta toda a execução do código para uma linha específica.
O uso do goto
em GoLang inclui algumas regrinhas básicas, vejamos:
- ✅ O rótulo deve estar na mesma função do comando
goto
. - ✅ O rótulo é um identificador válido seguido por
:
. - ✅ Pode ser usado para pular blocos de código, mas não pode cruzar os limites de uma função.
- ✅ O GoLang não suporta
goto
para cima em loops (goto
backward), mas permite para frente.
E já que estamos falando desse tema tão controverso, que tal analisarmos alguns prós e contras na utilização dos goto
?
Quando não usar o goto?
Embora goto
possa ser útil em alguns casos, evite usá-lo nas seguintes circunstâncias:
- Estruturas de controle convencionais (
if
,for
,switch
) são mais claras. - Pode levar a um código espaguete (código desorganizado e difícil de seguir).
- Existem outras funções bem definidas que encapsulam a lógica.
🚫 Evite abusar do goto
para garantir que seu código continue legível e fácil de manter.
Casos de uso do goto
Não é só de contras que vive a utilização do goto
, mas sim de alguns prós, não é mesmo? 😌
1️⃣ Evitar repetição de código (DRY – Don't Repeat Yourself)
O goto
pode simplificar a lógica ao lidar com múltiplas condições de erro.
package main
import "fmt"
func main() {
for i := 1; i <= 5; i++ {
if i == 3 {
goto Erro
}
fmt.Println("Processando:", i)
}
Erro:
fmt.Println("Erro detectado! Saindo do loop.")
}
2️⃣ Uma ótima alternativa para sair de loops aninhados (nested loops)
Quando você tem loops dentro de loops (famosos loops aninhados), o goto
pode ser uma forma mais eficiente de sair imediatamente de todos eles.
package main
import "fmt"
func main() {
for i := 1; i <= 3; i++ {
for j := 1; j <= 3; j++ {
fmt.Printf("i=%d, j=%d\n", i, j)
if i == 2 && j == 2 {
goto Saida
}
}
}
Saida:
fmt.Println("Loop encerrado.")
}
3️⃣ Ele pode simplificar o tratamento de erros
Ele atua de forma bem útil quando há várias verificações e você quer um ponto único de saída.
package main
import (
"errors"
"fmt"
)
func validar(valor int) error {
if valor < 0 {
return errors.New("valor negativo")
}
if valor > 100 {
return errors.New("valor muito alto")
}
return nil
}
func main() {
valor := 150
if err := validar(valor); err != nil {
fmt.Println("Erro:", err)
goto Saida
}
fmt.Println("Valor válido:", valor)
Saida:
fmt.Println("Fim do programa.")
}
Comparação entre o goto e demais estruturas de controle
Vejamos agora uma comparação direta entre goto
, break
e continue
.
Com relação ao seu uso principal:
goto
: É usado para saltar para um ponto específico.break
: É usado para sair de um loop específico.continue
: É usado para pular para a próxima iteração de um loop específico.
Com relação ao seu funcionamento em conjunto com loops:
goto
: Funciona, porém, apresenta uma menor legibilidade de código.break
: Funciona, entretanto, ele sai do loop.continue
: Funciona, e pula para a próxima iteração do loop.
Com relação ao seu funcionamento fora de um loop:
goto
: Funciona, em qualquer parte do código.break
econtinue
: Não funciona, sendo exclusivo seu funcionamento dentro de loops.
Com relação a legibilidade do código:
goto
: Este pode ser um pouco confuso se for mal utilizado.break
: Funciona de forma mais clara em loops simples e pouco aninhados.continue
: Funciona de forma clara para pular execuções.
Por fim, temos a questão da aplicabilidade ideal:
goto
: Possuí uma saída complexa de múltiplos níveis.break
: Encerra loops de forma imediata.continue
: Tem o poder de ignorar iterações específicas.
Repositório da lição
Todos os arquivos relacionados com esta lição, podem ser encontrados nos seguintes repositórios abaixo:
Conclusão
Nesta lição, você aprendeu sobre o funcionamento das estruturas de controle que nós temos no GoLang, são elas:
for
switch
break
continue
goto
Até a próxima lição 😇