Golang – 04. Estruturas de Controle

Este artigo é a parte 4 de 11 na série Golang

Enquanto apresentava os tipos de variáveis, constantes, structs e funções no Go alguns elementos de estruturas de controle da linguagem foram apresentados como complemento. No entanto neste artigo estas estruturas de controle serão o foco da apresentação.

O Go prima pelo minimalismo da linguagem e por isto possui um acervo diminuto de estruturas de controle, apenas três: if, for e switch. E o for, particularmente, possui uma implementação mais poderosa que nas linguagens tradicionais, substituindo bem a necessidade do tradicional while, muito comum em outras linguagens.

O if é bem parecido com o clássico, enquanto que o switch ganhou um pouco mais de flexibilidade no Go. Antes de adentrar as discussões sobre as estruturas de controle do código vou dar uma atenção ao escopo (bloco) das declarações de variáveis e tentar trazer um pouco mais de atenção e este sutil detalhe implícito do Go.

Blocos – Mascarando Variáveis

Embora Go seja uma linguagem tipada, em diferentes escopos (blocos) é possível redeclarar o mesmo nome de variável, resultando no mascaramento da variável do escopo superior.

Normalmente, uma variável pode ser declarada apenas uma vez dentro de um escopo específico. Tentativas de redeclaração no mesmo escopo geram o erro “no new variables on left side of :=“. Entretanto, nos blocos de uma função ou em estruturas de controle, é possível redeclarar qualquer variável sem erros de compilação.

Considere o código a seguir:

package main

import "fmt"

var x int = 10

func printX() {
	fmt.Println(x)
}

func main() {
	fmt.Println(x)
	x := 20
	fmt.Println(x)
	for i := 0; i < 10; i++ {
		x := 8
		fmt.Print(x, " ")
	}
	// x := 12
	fmt.Println("\n", x)
	printX()
}

Na linha 5, a variável x é definida com o valor 10, acessível em qualquer ponto do código. Na linha 13, dentro do escopo da função main(), x é redefinida com o valor 20, mascarando a variável x do escopo global. No for loop (linha 14), x é novamente redefinida com o valor 8 (linha 16), mas somente dentro deste bloco específico.

Este código demonstra como Go lida com múltiplas declarações da variável x em escopos diferentes. O for loop redefine x dez vezes, pois cada iteração é considerada um novo escopo.

A execução do código produzirá a seguinte saída:

Bash
 
alves@arabel:shadow$ go run test.go
10
20
8 8 8 8 8 8 8 8 8 8

20
10

Embora este comportamento de shadowing possa ser intencional, geralmente não é aconselhável usar o mesmo nome de variável dessa maneira, pois pode levar a um código confuso e potencialmente a resultados inesperados. Esse processo de ocultar uma variável é conhecido como “shadowing variable” e pode ser uma fonte de erros.

Para identificar esses casos, Go oferece uma ferramenta chamada shadow. Sua instalação pode ser feita com o comando:

Bash
 
alves@arabel:shadow$ go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest

Para verificar seu código, execute o shadow como mostrado:

Bash
 
alves@arabel:shadow$ shadow test.go
.../Go/shadow/test.go:16:3: declaration of "x" shadows declaration at line 13

$

Ele apontará para a primeira ocorrência de shadowing. Após corrigir a declaração, repita o comando até que não haja mais alertas.

É uma prática recomendada executar o comando shadow em códigos complexos para evitar surpresas indesejadas.

Instrução If

A instrução if em Go é similar à encontrada em outras linguagens de programação, como C. Sua sintaxe básica é a seguinte:

if <condição 1> {
    <bloco de comandos 1>
} else if <condição 2 {
    <bloco de comandos 2>
} else {
    <bloco de comandos 3>
}

Os operadores de comparação utilizados em Go são os mesmos encontrados na maioria das linguagens de programação:

OperadorSímboloExemplo
maior>a > 2
menor<a < 2
maior ou igual>=a >= 2
menor ou igual<=a <= 2
igual==a == 2
diferente!=a != 2
e&&(a > 2) && (a < 5)
ou||(a < 2) || (a > 5)

O exemplo a seguir demonstra a aplicação da instrução if na classificação de palavras de uma frase com base no seu comprimento:

package main

import (
	"fmt"
	"strings"
)

func main() {
	var contador [3]int

	// frase de https://pt.wikipedia.org/wiki/Albert_Einstein
	frase := `Nascido em uma família de judeus alemães, mudou-se 
	para a Suíça ainda jovem e iniciou seus estudos na Escola 
	Politécnica de Zurique. Após dois anos procurando emprego, 
	obteve um cargo no escritório de patentes suíço enquanto ingressava 
	no curso de doutorado da Universidade de Zurique. Em 1905, publicou 
	uma série de artigos acadêmicos revolucionários. Uma de suas obras 
	era o desenvolvimento da teoria da relatividade especial. Percebeu, 
	no entanto, que o princípio da relatividade também poderia ser 
	estendido para campos gravitacionais, e com a sua posterior teoria 
	da gravitação, de 1916, publicou um artigo sobre a teoria da 
	relatividade geral. `

	// remove \n, \t, . e ,
	frase = strings.Replace(frase, "\n", " ", -1)
	frase = strings.Replace(frase, "\t", " ", -1)
	frase = strings.Replace(frase, ",", " ", -1)
	frase = strings.Replace(frase, ".", " ", -1)
	// remove 2 espaços conssecutivos
	frase = strings.Replace(frase, "  ", " ", -1)
	frase = strings.Replace(frase, "  ", " ", -1)

	// remove espaços e divide a frase
	frase = strings.TrimSpace(frase)
	palavras := strings.Split(frase, " ")

	for _, p := range palavras {
		if len(p) <= 5 {
			contador[0]++
		} else if len(p) <= 10 {
			contador[1]++
		} else {
			contador[2]++
		}
	}

	fmt.Printf("%2d palavras com até 5 letras\n", contador[0])
	fmt.Printf("%2d palavras com 6 a 10 letras\n", contador[1])
	fmt.Printf("%2d palavras com mais de 10 letras\n", contador[2])
}

No código acima, a linha 9 declara um array contador para contar as palavras de acordo com o comprimento: até 5, de 6 a 10, e mais de 10 letras. A frase escolhida para análise é atribuída entre as linhas 12 e 22, utilizando crases para permitir a declaração em múltiplas linhas.

O código emprega o pacote strings, que oferece diversas funções úteis para manipular strings. Por exemplo, a função strings.Replace(String, oldString, newString, n) é usada para substituir ocorrências de uma substring por outra. Se n for -1, a substituição ocorrerá em todas as ocorrências da substring. Esse método é aplicado nas linhas 25 a 31 para remover caracteres especiais e espaços extras da frase.

A linha 34 usa strings.TrimSpace(String) para eliminar espaços no início e no final da string, e a linha 35 divide a frase em um slice de palavras usando o espaço como delimitador, através da função strings.Split(frase, " ").

O laço for-range, que será abordado na próxima seção, percorre o slice palavras. Dentro do laço, as condições if, else if, e else (linhas 38, 40 e 42) são usadas para contar as palavras com base em seu comprimento, classificando-as em um dos três grupos pré-definidos.

Ao executar o código, você obterá a seguinte saída, que exibe a contagem de palavras de acordo com os critérios definidos:

Bash

rudson@suzail:counter$ go run counter.go
53 palavras com até 5 letras
36 palavras com 6 a 10 letras
11 palavras com mais de 10 letras
rudson@suzail:counter$ exited with code=0 in 0.072 seconds

Neste exemplo, 53, 36 e 11 são os contadores de palavras para cada categoria de comprimento. Esta saída mostra como a instrução if em Go pode ser utilizada eficientemente para classificar e contar elementos com base em condições específicas.

Instrução For

A linguagem Go oferece uma abordagem flexível para a instrução for, que é a única estrutura de laço na linguagem. O Go adapta o for para suportar vários tipos de laços, incluindo:

  • Laço clássico ao estilo do C: Este é o formato mais tradicional, com inicialização, condição de continuação e incremento.
  • Laço condicional no início: Similar ao while em outras linguagens, onde a condição é verificada antes da execução do laço.
  • Laço infinito: Neste caso, o laço executa indefinidamente, a menos que seja interrompido por uma instrução break.
  • Laço com range: Semelhante ao for em Python, onde o laço itera sobre os elementos de uma coleção, como slices, arrays ou mapas.

For – Estilo C

A forma mais tradicional do laço for em Go é similar ao for em C. Sua estrutura é a seguinte:

for <declaração contador>; <condição de saída>; <incremento> {
    // comandos ...
}

Este formato foi utilizado em vários exemplos ao longo do texto. O código a seguir ilustra o uso de dois laços for tradicionais para imprimir um calendário de um mês fictício:

package main

import "fmt"

func main() {
	fmt.Println(" D   S   T   Q   Q   S   S")
	for i := 0; i < 31; i += 7 {
		for j := 0; j < 7; j++ {
			dia := i + j + 1
			if dia > 31 {
				break
			}
			fmt.Printf("%2d  ", dia)
		}
		fmt.Println()
	}
}

No código acima, o primeiro for (linha 7) tem um incremento de 7 (i += 7), enquanto o segundo for (linha 8) incrementa de 1 em 1. É importante destacar que a variável i só existe dentro do primeiro laço for (linhas 7 a 16), e a variável j somente dentro do segundo (linhas 8 a 12). A variável dia é redefinida a cada iteração do laço interno for j, garantindo que o dia atual seja corretamente calculado e exibido.

Ao executar este código, você verá um calendário do mês, com os dias distribuídos ao longo das semanas, ilustrando a eficácia do uso de laços aninhados em Go.

For – Condicional

O for condicional em Go é uma estrutura de laço que se executa enquanto uma condição específica for verdadeira. A sintaxe é simplificada, contendo apenas a condição de término do laço:

for <condição> {
    <comandos>
}

O exemplo a seguir demonstra o uso de dois laços for condicionais. O objetivo é verificar se um número fornecido pelo usuário é um número primo. O primeiro laço coleta um número menor que 100, e o segundo verifica se esse número é primo.

package main

import (
	"fmt"
	"strconv"
)

func main() {
	const (
		msg1 = "\nO número %d é divisível por %d e por isto não é primo\n\n"
		msg2 = "\nParabéns \"%d\" é primo.\n\n"
	)

	var (
		nstr          string = ""
		primos               = []int{2, 3, 5, 7, 11}
		numero, index int    = 0, 0
	)

	for numero == 0 {
		fmt.Print("\nEntre com um primo menor que 100: ")
		fmt.Scanln(&nstr)
		n, err := strconv.Atoi(nstr)
		if err != nil {
			fmt.Printf("\n%q não é um numero\n", nstr)
			continue
		} else if (n > 0) && (n <= 100) {
			numero = n
			continue
		}
		fmt.Printf("\n%q é maior que 100!\n", nstr)
	}

	for index < len(primos) {
		if numero%primos[index] == 0 {
			fmt.Printf(msg1, numero, primos[index])
			index = -1
			break
		}
		index++
	}

	if index >= 0 {
		fmt.Printf(msg2, numero)
	}
}

Neste código, a slice primos (linha 16) contém os primeiros números primos usados na verificação. Por limitar a fatoração aos primeiros cinco primos, o código não pode testar números maiores que 11² (121).

Existem dois laços for condicionais:

  • O primeiro laço (linhas 20 a 33) verifica se a entrada é um número e se é menor que 100.
  • O segundo laço (linhas 35 a 42) testa se o número é primo, fatorando-o pelos números na slice primos.

Vale ressaltar que este código é ilustrativo e não necessariamente o mais eficiente para determinar se um número é primo. O uso de laços condicionais aqui serve para demonstrar como essa estrutura de controle pode ser utilizada para testar condições repetidamente até que uma condição específica seja satisfeita.

Continue

O comando continue é utilizado dentro de laços for para interromper a iteração atual e prosseguir para a próxima. Quando continue é executado, o laço salta para o início, executando qualquer incremento definido e em seguida testando a condição do laço.

Por exemplo, no código mencionado anteriormente, a linha 22 espera uma entrada que deveria ser “um número menor que 100“. A conversão dessa entrada em um inteiro ocorre na linha 23 com o método strconv.Atoi(String). Se esse método retornar um erro (isto é, err não for nil), uma mensagem de erro é exibida, e o comando continue é executado, reiniciando o laço para uma nova leitura.

Um segundo continue é usado quando o número está entre 1 e 100 (linhas 27 a 30). Aqui, o continue reinicia o laço, mas um break teria sido mais adequado para encerrar o laço, já que a condição desejada foi satisfeita.

Break

O comando break finaliza a execução do laço for em que está inserido, transferindo o controle do programa para a instrução imediatamente após o laço.

Retomando a explicação do código, o laço for (linhas 34 a 41) realiza a divisão do número fornecido pelos primeiros números primos contidos no slice primos. Se, em algum momento, o número for divisível por um desses primos (resto da divisão igual a zero na linha 35), o número não é primo. Uma mensagem é exibida (linha 36), index é definido como negativo, e o laço é interrompido por um break (linha 38).

Se o index chegar ao fim do slice primos sem encontrar um divisor, conclui-se que o número é primo. Nesse caso, o index mantém um valor positivo ao sair do laço, e uma mensagem confirmando que o número é primo é exibida (linha 44), finalizando o código.

for – Infinito

Em Go, um laço infinito é criado utilizando a instrução for sem argumentos. A sintaxe para tal é simplesmente:

for {
    // comandos
}

Neste formato, o laço for executará indefinidamente, a menos que seja interrompido por um comando de controle de fluxo, como break. Embora Go também suporte o comando goto, que permite um desvio incondicional de fluxo para um ponto marcado no código, o uso de goto é geralmente desencorajado em favor de construções mais claras, como break ou continue. O goto requer um rótulo (label) para indicar para onde o fluxo deve ser desviado, mas seu uso pode tornar o código mais difícil de seguir e manter.

Com uma modificação simples no código anterior, podemos ilustrar o uso de um laço for infinito. Na linha 20, o laço infinito é utilizado para aguardar a entrada de um número pelo usuário. Quando um número válido (dentro do intervalo de 1 a 100) é inserido, um break na linha 29 interrompe o laço, permitindo que o programa prossiga para a verificação se o número é primo.

package main

import (
	"fmt"
	"strconv"
)

func main() {
	const (
		msg1 = "\nO número %d é divisível por %d e por isto não é primo\n\n"
		msg2 = "\nParabéns \"%d\" é primo.\n\n"
	)

	var (
		nstr          string = ""
		primos               = []int{2, 3, 5, 7, 11}
		numero, index int    = 0, 0
	)

	for {
		fmt.Print("\nEntre com um primo menor que 100: ")
		fmt.Scanln(&nstr)
		n, err := strconv.Atoi(nstr)
		if err != nil {
			fmt.Printf("\n%q não é um numero\n", nstr)
			continue
		} else if (n > 0) && (n <= 100) {
			numero = n
			break
		}
		fmt.Printf("\n%q é maior que 100!\n", nstr)
	}

	for index < len(primos) {
		if numero%primos[index] == 0 {
			fmt.Printf(msg1, numero, primos[index])
			index = -1
			break
		}
		index++
	}

	if index >= 0 {
		fmt.Printf(msg2, numero)
	}
}

Neste exemplo revisado, o laço infinito for é uma maneira eficiente de esperar por uma condição desejada (neste caso, a entrada de um número válido) antes de prosseguir com o restante do programa. Isso demonstra a flexibilidade e a utilidade de um laço for infinito em situações onde o número de iterações não é conhecido de antemão ou depende de condições externas.

for – Range

O for-range em Go é uma estrutura de laço que percorre os elementos de coleções como arrays, slices ou maps, semelhante ao for em Python. A diferença principal é que o Go retorna tanto o índice quanto o valor de cada elemento durante a iteração (enquanto que em Python, por padrão, apenas os valores são retornados).

package main

import "fmt"

func main() {
	primos := []int{2, 3, 5, 7}

	for i, v := range primos {
		fmt.Printf("Índice: %d  Valor: %d\n", i, v)
	}

	for _, v := range primos {
		fmt.Printf("Valor: %d\n", v)
	}

	for i, _ := range primos {
		fmt.Printf("Índice: %d\n", i)
	}
}

No exemplo acima, o for-range é aplicado à slice primos, que contém os primeiros quatro números primos. No primeiro laço for-range (linhas 8 a 10), tanto o índice quanto o valor de cada elemento na slice primos são capturados pelas variáveis i e v.

No segundo laço (linhas 12 a 14), o índice é ignorado (usando “_”) e apenas o valor de cada elemento é capturado pela variável v. O terceiro laço (linhas 16 a 18) inverte isso, retornando apenas o índice de cada elemento.

O caractere underscore (“_”) é usado para descartar componentes do par (índice, valor) que não são necessários. Isso já foi utilizado em exemplos anteriores para ignorar valores não desejados.

A execução deste código produzirá a seguinte saída:

Bash

alves@arabel:for-range$ go run range.go

Índice: 0 Valor: 2
Índice: 1 Valor: 3
Índice: 2 Valor: 5
Índice: 3 Valor: 7
Valor: 2
Valor: 3
Valor: 5
Valor: 7
Índice: 0
Índice: 1
Índice: 2
Índice: 3

O for-range também pode ser útil no código de verificação de números primos. Para isso, remova a variável index na linha 17 e substitua o último for condicional por um for-range, com algumas alterações nas linhas 35, 36, 37 e 40, conforme o código abaixo:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	const (
		msg1 = "\nO número %d é divisível por %d e por isto não é primo\n\n"
		msg2 = "\nParabéns \"%d\" é primo.\n\n"
	)

	var (
		nstr            string = ""
		primos                 = []int{2, 3, 5, 7, 11}
		numero, e_primo        = 0, true
	)

	for {
		fmt.Print("\nEntre com um primo menor que 100: ")
		fmt.Scanln(&nstr)
		n, err := strconv.Atoi(nstr)
		if err != nil {
			fmt.Printf("\n%q não é um numero\n", nstr)
			continue
		} else if (n > 0) && (n <= 100) {
			numero = n
			break
		}
		fmt.Printf("\n%q é maior que 100!\n", nstr)
	}

	for _, p := range primos {
		if numero%p == 0 {
			fmt.Printf(msg1, numero, p)
			e_primo = false
			break
		}
	}

	if e_primo {
		fmt.Printf(msg2, numero)
	}
}

A variável index foi substituída por e_primo (é primo), uma booliana que é inicializada como verdadeira e se torna falsa se o número testado não for primo.

O for-range entra na linha 34 para percorrer os valores da slice primos na variável p, descartando o índice. Se a divisão do número pelo primo em p resultar em resto zero (linha 35), o número não é primo, e e_primo se torna falso.

A última mudança é na linha 42, para verificar se e_primo ainda é verdadeiro após o laço.

Valor Passado como Cópia

Um aspecto importante do for-range em Go é que os valores iterados são cópias dos elementos originais da coleção (array, slice ou map) e não referências diretas a esses elementos. Isso significa que qualquer alteração feita aos valores dentro do laço for-range não afetará os elementos da coleção original.

package main

import "fmt"

func main() {
	primos := []int{2, 3, 5, 7}

	for _, v := range primos {
		v += 10
		fmt.Println(v)
	}

	fmt.Println(primos)
}

Neste exemplo, modificamos os valores capturados pelo for-range dentro do laço. Apesar dessas alterações, a coleção original (primos, neste caso) permanece inalterada. A execução deste código resultará na seguinte saída:

Bash

alves@arabel:for-range2$ go run range.go

12
13
15
17
[2 3 5 7]

Isso ilustra claramente que, no for-range de Go, a variável v recebe uma cópia do valor de cada elemento da slice primos. Assim, mesmo que v seja alterada dentro do laço, a slice original primos não é afetada.

Esse comportamento é particularmente importante para evitar efeitos colaterais indesejados em situações onde a intenção é manipular apenas os valores temporários e não a coleção original. Se for necessário modificar a coleção original, deve-se usar o índice para acessar e alterar cada elemento diretamente.

Padrões Avançados com For

Além dos usos tradicionais do laço for, Go oferece padrões mais avançados que podem ser úteis em situações específicas. Estes incluem o uso de labels e a aplicação de estratégias menos convencionais.

Uso de Labels em Laços

Em Go, um label pode ser usado para identificar um laço for. Isso é especialmente útil em laços aninhados, onde você pode querer sair ou continuar em um laço externo a partir de um laço interno. O uso de labels torna o controle do fluxo mais claro e direto.

package main

import "fmt"

func main() {
OuterLoop:
	for i := 0; i < 5; i++ {
	InterLoop:
		for j := 0; j < 5; j++ {
			for k := 0; k < 3; k++ {
				if i+j == 3 {
					fmt.Println("\nSaindo do loop com i+j == 3: i =", i, "e j =", j)
					continue InterLoop
				} else if i*j > 6 {
					fmt.Println("\nSaindo do loop com i*j == 6: i =", i, "e j =", j)
					break OuterLoop
				}
				fmt.Printf("(%d+%d+%d = %d); ", i, j, k, i+j+k)
			}
		}
	}
}

Neste código:

  • O continue InterLoop é usado para interromper a execução atual do laço mais interno (k) e continuar a execução no início do laço intermediário (j), rotulado como InterLoop.
  • O break OuterLoop é usado para sair completamente dos laços aninhados e retomar a execução após o laço rotulado como OuterLoop.
  • A adição do terceiro laço aninhado (k) e a verificação condicional com i+j e i*j fornecem um cenário mais complexo e realista para demonstrar o controle de fluxo com continue e break usando labels.

Este padrão é útil quando você precisa que um laço continue executando enquanto múltiplas condições são verdadeiras (ou falsas).

Laços For com Condições Múltiplas

O for em Go pode ser configurado para trabalhar com múltiplas condições, permitindo um controle mais refinado do fluxo de execução.

package main

import "fmt"

func main() {
    for i := 0; i < 10 && i != 5; i++ {
        fmt.Println("Valor de i:", i)
    }
}

Aqui, o laço for é configurado para continuar executando enquanto i for menor que 10 e diferente de 5. Assim que i atinge 5, o loop é interrompido.

Este padrão é útil quando você precisa que um laço continue executando enquanto múltiplas condições são verdadeiras (ou falsas).

Estruturas de Controle e Performance

O desempenho de um programa em Go pode ser significativamente influenciado pela maneira como as estruturas de controle são utilizadas. Vamos considerar algumas situações onde a escolha e a implementação do laço podem afetar a eficiência.

Uso Eficiente de Laços

Laços podem ser otimizados de várias maneiras para melhorar a performance. Por exemplo, evitar cálculos desnecessários dentro do laço, minimizar o acesso a variáveis fora do escopo do laço, e utilizar estruturas de dados apropriadas podem fazer uma diferença significativa. Veja o exemplo a seguir:

package main

import "fmt"

func main() {
    const n = 10000
    var sum int

    for i := 0; i < n; i++ {
        sum += i
    }

    fmt.Println("Soma:", sum)
}

Neste exemplo, mantemos o laço simples e evitamos operações desnecessárias dentro dele.

Escolha da Estrutura de Laço

A escolha entre for, for-range, e outras estruturas de controle deve ser feita com base na necessidade específica. Por exemplo, for-range é mais adequado para iterar sobre slices ou arrays, mas se você precisa de controle mais fino sobre o índice, um for tradicional pode ser mais eficiente.

package main

import "fmt"

func main() {
    slice := []int{1, 2, 3, 4, 5}

    // Usando for tradicional
    for i := 0; i < len(slice); i++ {
        fmt.Printf("%d ", slice[i])
    }
    fmt.Println()

    // Usando for-range
    for _, v := range slice {
        fmt.Printf("%d ", v)
    }
    fmt.Println()
}

Neste exemplo, ambos os loops têm propósitos similares, mas o for-range oferece uma sintaxe mais limpa quando o índice não é necessário.

Impacto da Condição de Laço

A condição de parada de um laço também afeta o desempenho. Condições mais complexas podem aumentar o custo de cada iteração.

package main

import "fmt"

func main() {
    const n = 10000

    // Loop com condição simples
    for i := 0; i < n; i++ {
        // Lógica do loop
    }

    // Loop com condição mais complexa
    for i := 0; i < n && someOtherCondition(i); i++ {
        // Lógica do loop
    }
}

func someOtherCondition(i int) bool {
    // Alguma lógica complexa
    return true
}

Aqui, a primeira iteração do laço tem uma condição de parada simples, enquanto a segunda inclui uma chamada de função, aumentando potencialmente o custo de cada iteração.

Conclusão

Esses exemplos demonstram como diferentes abordagens na estruturação de laços podem afetar o desempenho de um programa em Go. A escolha apropriada e a implementação eficiente de estruturas de controle são essenciais para garantir que o programa seja tão eficiente quanto possível. Entender o custo associado a cada operação dentro de um laço, escolher a estrutura de laço correta para a tarefa e simplificar as condições de parada do laço são aspectos cruciais para otimizar o desempenho.

Em resumo, ao escrever laços em Go, sempre considere:

  • Minimizar as operações dentro do laço: Evite cálculos ou chamadas de função desnecessárias dentro do laço.
  • Escolher a estrutura de laço correta: for, for-range, e outras variações devem ser usadas de acordo com a necessidade específica.
  • Simplificar a condição de laço: Condições de parada complexas podem aumentar o custo de cada iteração.

Estas práticas são importantes para manter o seu código Go limpo, legível e também eficiente em termos de desempenho.

Instrução Switch

A instrução switch em Go é uma ferramenta flexível e poderosa que permite uma variedade de usos além das comparações diretas de igualdade. Esta seção explora três maneiras distintas de utilizar o switch em Go, cada uma adequada a diferentes cenários.

Switch Sem Variável de Caso

Este estilo de switch é útil para avaliações mais complexas que não se baseiam em simples comparações de igualdade.

package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"strings"
)

func lerArquivo(nomeArq string) (texto string) {
	file, err := os.Open(nomeArq)
	if err != nil {
		log.Fatalf("falha na abertura do arquivo %s", nomeArq)
	}
	defer file.Close()

	reader := bufio.NewReader(file)

	for {
		line_bytes, _, err := reader.ReadLine()
		if err != nil {
			return
		}
		texto += string(line_bytes) + "\n"
	}
}

func main() {
	var (
		contador = [3]int{}
		remover  = []string{"\n", "\t", ",", ".", "—"}
	)

	const arq = "albert.txt"

	// ler o texto e remove trima seu conteúdo
	texto := strings.TrimSpace(lerArquivo(arq))

	fmt.Println(texto)

	for _, c := range remover {
		texto = strings.Replace(texto, c, " ", -1)
	}

	// remove 2 ou mais espaços consecutivos
	lenText := 0
	for lenText != len(texto) {
		lenText = len(texto)
		texto = strings.Replace(texto, "  ", " ", -1)
	}

	// divide em uma slice por palavras
	palavras := strings.Split(texto, " ")

	for _, palavra := range palavras {
		comprimento := len(palavra)
		switch {
		case comprimento == 0:
			continue
		case comprimento <= 5:
			contador[0]++
		case comprimento <= 10:
			contador[1]++
		default:
			contador[2]++
		}
	}

	fmt.Printf("\n%2d palavras com até 5 letras\n", contador[0])
	fmt.Printf("%2d palavras com 6 a 10 letras\n", contador[1])
	fmt.Printf("%2d palavras com mais de 10 letras\n", contador[2])
}

O switch aparece nas linhas 58 à 67, dentro do laço for. Neste exemplo, o switch avalia o comprimento de cada palavra e incrementa o contador apropriado. A utilização de case sem uma variável específica torna o código mais claro, especialmente quando lidamos com condições que não são simples comparações diretas

Switch com Variável de Escopo Local

Esta abordagem define uma variável dentro do próprio switch, limitando seu escopo apenas a este bloco.

	...
	// Alteração no código para Switch com Variável de Escopo Local
	for _, palavra := range palavras {
    	switch comprimento := len(palavra); {
		case comprimento == 0:
    		continue
        case comprimento <= 5:
            contador[0]++
        case comprimento <= 10:
            contador[1]++
        default:
            contador[2]++
        }
    }
	...

Aqui, comprimento é definida dentro do switch, tornando o código mais conciso e evitando conflitos de nomes em blocos maiores. Essa variável fica restrita ao escopo do switch e não do for, como no código anterior.

Switch ao Estilo C/C++ ou Java

Este estilo se assemelha ao uso do switch em linguagens como C/C++ ou Java, onde a variável de caso é comparada diretamente com valores nos case.

	...
	// Alteração no código para Switch ao Estilo C/C++ ou Java
    for _, palavra := range palavras {
        switch comprimento := len(palavra); comprimento {
        case 0:
            continue
        case 1, 2, 3, 4, 5:
            contador[0]++
        case comprimento <= 10:
            contador[1]++
        default:
            contador[2]++
        }
    }
	...

Neste formato, o switch é utilizado para comparações diretas entre a variável comprimento e os valores especificados nos case. Esta forma é útil para testar a igualdade com múltiplos valores específicos.

Conclusão

O switch em Go oferece uma grande flexibilidade na escrita de código, permitindo escolher a abordagem que melhor se adapta às necessidades do seu programa. Seja para condições complexas, escopo local de variáveis ou comparações diretas, o switch provê uma sintaxe clara e eficiente, facilitando a legibilidade e manutenção do código. Embora a escolha entre switch e if-else dependa do contexto específico e da preferência pessoal, o switch geralmente oferece uma maneira mais limpa e organizada de lidar com múltiplas condições.

Defer – Adiando uma Ação

O comando defer é uma construção essencial para o controle de fluxo e o gerenciamento de recursos. Ele é usado para adiar a execução de uma função até que a função que o envolve seja concluída, seja normalmente ou por meio de um retorno de erro. O defer é frequentemente usado para garantir que recursos, como arquivos, sejam fechados corretamente, mesmo em casos de erro.

Aqui está um exemplo simples de como o defer funciona em Go:

package main

import (
    "fmt"
)

func message() {
	defer fmt.Println("Isso será exibido por último")
    fmt.Println("Isso será exibido primeiro")
}

func main() {
  	message()
}

Neste exemplo, a função fmt.Println("Isso será exibido por último") é adiada até que a função message() seja concluída. Portanto, o resultado será:

Bash

Isso será exibido primeiro
Isso será exibido por último

O defer é útil em situações em que você deseja garantir que determinadas ações sejam realizadas antes que uma função seja concluída, independentemente de ocorrerem erros ou não. Isso é particularmente útil para liberar recursos, como fechar arquivos ou conexões de rede, de forma segura.

Considerações Finais

Ao concluir este artigo sobre as estruturas de controle em Go, é importante refletir sobre a versatilidade e eficiência que estas estruturas oferecem. Go foi projetada para ser uma linguagem concisa, poderosa e, ao mesmo tempo, simples de entender. As estruturas de controle em Go, incluindo if, for, e switch, são testemunhos dessa filosofia.

  1. Simplicidade e Poder do if e for: Vimos como a instrução if mantém sua simplicidade clássica, mas pode ser poderosamente empregada em conjunto com a gestão de erros e condições complexas. O for, sendo a única estrutura de laço em Go, demonstra uma flexibilidade incrível, abrangendo desde laços simples até iterações complexas com for-range e condições múltiplas.
  2. Flexibilidade do switch: O switch em Go não é apenas uma ferramenta para evitar longas cadeias de if-else, mas também uma estrutura que oferece maior clareza e eficiência na avaliação de condições múltiplas. Com sua capacidade de lidar com variáveis de escopo local e estilos de comparação variados, o switch se destaca como um elemento crucial para a escrita de um código Go limpo e eficiente.
  3. Impacto no Desempenho: Abordamos como diferentes estruturas de controle podem influenciar o desempenho do seu programa em Go. A escolha entre for, for-range e switch deve ser feita com um entendimento de suas implicações no desempenho, especialmente em contextos de aplicações de alta performance.
  4. Estilo e Legibilidade do Código: Em Go, a legibilidade e a clareza do código são tão importantes quanto a eficiência. As estruturas de controle da linguagem são projetadas para encorajar um estilo de codificação que seja não apenas eficiente, mas também limpo e fácil de entender.
  5. Adaptação às Necessidades Específicas: Cada projeto tem suas peculiaridades e desafios. Go oferece a flexibilidade para que programadores escolham a estrutura de controle mais adequada para cada situação, equilibrando entre simplicidade, clareza e eficiência.

Este artigo, explorando as estruturas de controle em Go, fornece as bases para que você possa aproveitar ao máximo as capacidades da linguagem em seus projetos. Com essas ferramentas, você está bem equipado para escrever programas Go que não são apenas funcionais, mas também bem estruturados e eficientes.

Deixe um comentário

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.