- Golang – 01. Introdução
- Golang – 02. Tipos Básicos
- Golang – 03. Structs e Funções e Métodos
- Golang – 04. Estruturas de Controle
- Golang – 05. Gerenciando Pacotes
- Golang – 06. Biblioteca Padrão I – fmt e strings
- Golang – 07. Biblioteca Padrão II – os, os/exec e os/user
- Golang – 08. Interface
- Golang – 09. Goroutines – Concorrência e Paralelismo
- Golang – 10. Goroutines – Canais
- Golang – 11. Pacotes e Documentação no Go
Índice
- 1. Blocos – Mascarando Variáveis
- 2. Instrução If
- 3. Instrução For
- 3.1. For – Estilo C
- 3.2. For – Condicional
- 3.2.1. Continue
- 3.2.2. Break
- 3.3. for – Infinito
- 3.4. for – Range
- 3.4.1. Valor Passado como Cópia
- 3.5. Padrões Avançados com For
- 3.5.1. Uso de Labels em Laços
- 3.5.2. Laços For com Condições Múltiplas
- 3.5.3. Estruturas de Controle e Performance
- 3.5.3.1. Uso Eficiente de Laços
- 3.5.3.2. Escolha da Estrutura de Laço
- 3.5.3.3. Impacto da Condição de Laço
- 3.5.3.4. Conclusão
- 4. Instrução Switch
- 4.1. Switch Sem Variável de Caso
- 4.2. Switch com Variável de Escopo Local
- 4.2.1. Switch ao Estilo C/C++ ou Java
- 4.3. Conclusão
- 5. Defer – Adiando uma Ação
- 6. Considerações Finais
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:
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:
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:
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:
Operador | Símbolo | Exemplo |
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:
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:
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:
alves@arabel:for-range2$ go run range.go12
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 comoInterLoop
. - O
break OuterLoop
é usado para sair completamente dos laços aninhados e retomar a execução após o laço rotulado comoOuterLoop
. - A adição do terceiro laço aninhado (
k
) e a verificação condicional comi+j
ei*j
fornecem um cenário mais complexo e realista para demonstrar o controle de fluxo comcontinue
ebreak
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á:
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.
- Simplicidade e Poder do
if
efor
: Vimos como a instruçãoif
mantém sua simplicidade clássica, mas pode ser poderosamente empregada em conjunto com a gestão de erros e condições complexas. Ofor
, sendo a única estrutura de laço em Go, demonstra uma flexibilidade incrível, abrangendo desde laços simples até iterações complexas comfor-range
e condições múltiplas. - Flexibilidade do
switch
: Oswitch
em Go não é apenas uma ferramenta para evitar longas cadeias deif-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, oswitch
se destaca como um elemento crucial para a escrita de um código Go limpo e eficiente. - Impacto no Desempenho: Abordamos como diferentes estruturas de controle podem influenciar o desempenho do seu programa em Go. A escolha entre
for
,for-range
eswitch
deve ser feita com um entendimento de suas implicações no desempenho, especialmente em contextos de aplicações de alta performance. - 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.
- 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.