Vamos do mais simples ao mais sofisticado, sempre com explicação linha por linha.
Calculadora simples (nível: iniciante)
go
package main
import (
"bufio" // leitura eficiente do terminal
"fmt"
"os"
"strconv" // conversão de string para número
"strings" // manipulação de strings
)
func main() {
// bufio.NewReader cria um leitor com buffer (mais eficiente)
leitor := bufio.NewReader(os.Stdin)
fmt.Print("Digite a primeira operação (ex: 10 + 5): ")
// Lê até encontrar quebra de linha
linha, _ := leitor.ReadString('\n')
// TrimSpace remove espaços e \n das pontas
linha = strings.TrimSpace(linha)
// Split divide em pedaços usando " " como separador
partes := strings.Split(linha, " ")
if len(partes) != 3 {
fmt.Println("Formato inválido. Use: número operador número")
return
}
// Atoi = ASCII to Integer
a, err := strconv.Atoi(partes[0])
if err != nil {
fmt.Println("Primeiro número inválido")
return
}
b, err := strconv.Atoi(partes[2])
if err != nil {
fmt.Println("Segundo número inválido")
return
}
// O switch decide qual operação aplicar
switch partes[1] {
case "+":
fmt.Println("Resultado:", a+b)
case "-":
fmt.Println("Resultado:", a-b)
case "*":
fmt.Println("Resultado:", a*b)
case "/":
if b == 0 {
fmt.Println("Não dá pra dividir por zero!")
return
}
fmt.Println("Resultado:", a/b)
default:
fmt.Println("Operador desconhecido")
}
}Buscador de palavras em arquivo (nível: intermediário)
go
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
// countOccurrences percorre o arquivo linha por linha
// e conta quantas vezes a palavra aparece.
func countOccurrences(caminho, palavra string) (int, error) {
arquivo, err := os.Open(caminho)
if err != nil {
// %w "embrulha" o erro original, preservando a causa
return 0, fmt.Errorf("abrir arquivo: %w", err)
}
defer arquivo.Close() // garante fechamento ao sair da função
// Scanner lê o arquivo sem carregar tudo na memória.
// Crucial para arquivos grandes (logs de GB).
scanner := bufio.NewScanner(arquivo)
count := 0
palavraLower := strings.ToLower(palavra)
for scanner.Scan() {
linha := strings.ToLower(scanner.Text())
// strings.Count conta ocorrências não sobrepostas
count += strings.Count(linha, palavraLower)
}
// Verifica se houve erro durante a leitura
if err := scanner.Err(); err != nil {
return 0, fmt.Errorf("scanner: %w", err)
}
return count, nil
}
func main() {
if len(os.Args) != 3 {
fmt.Println("Uso: programa <arquivo> <palavra>")
os.Exit(1)
}
caminho := os.Args[1]
palavra := os.Args[2]
n, err := countOccurrences(caminho, palavra)
if err != nil {
fmt.Println("Erro:", err)
os.Exit(1)
}
fmt.Printf("A palavra %q aparece %d vez(es) em %s\n", palavra, n, caminho)
}Worker pool concorrente (nível: avançado)
Um cenário real: você tem 1000 URLs para baixar e quer fazer isso em paralelo, mas limitando a 10 conexões simultâneas para não derrubar a rede.
go
package main
import (
"fmt"
"net/http"
"sync"
"time"
)
// Result é o que cada worker produz
type Result struct {
URL string
Status int
Erro error
}
// worker pega URLs do channel "trabalhos" e devolve em "resultados"
func worker(id int, trabalhos <-chan string, resultados chan<- Result, wg *sync.WaitGroup) {
// Avisa que esse worker terminou quando a função retornar
defer wg.Done()
client := &http.Client{Timeout: 5 * time.Second}
for url := range trabalhos {
fmt.Printf("[Worker %d] Buscando %s\n", id, url)
resp, err := client.Get(url)
if err != nil {
resultados <- Result{URL: url, Erro: err}
continue
}
resp.Body.Close()
resultados <- Result{URL: url, Status: resp.StatusCode}
}
}
func main() {
urls := []string{
"https://go.dev",
"https://google.com",
"https://github.com",
"https://wikipedia.org",
"https://stackoverflow.com",
}
trabalhos := make(chan string, len(urls))
resultados := make(chan Result, len(urls))
// WaitGroup permite esperar várias goroutines terminarem
var wg sync.WaitGroup
// Cria 3 workers
numWorkers := 3
for w := 1; w <= numWorkers; w++ {
wg.Add(1) // incrementa contador antes de iniciar
go worker(w, trabalhos, resultados, &wg)
}
// Envia trabalhos
for _, url := range urls {
trabalhos <- url
}
close(trabalhos) // sinaliza fim para os workers
// Goroutine que fecha resultados quando todos os workers terminarem
go func() {
wg.Wait()
close(resultados)
}()
// Lê todos os resultados (bloqueia até resultados ser fechado)
for res := range resultados {
if res.Erro != nil {
fmt.Printf("ERRO %s -> erro: %v\n", res.URL, res.Erro)
} else {
fmt.Printf("OK %s -> status %d\n", res.URL, res.Status)
}
}
}O que aprendemos aqui:
- Worker pool: padrão clássico para limitar paralelismo.
- WaitGroup: sincroniza fim de várias goroutines.
- Channels direcionais:
<-chan(só recebe) echan<-(só envia) deixam o código mais seguro. defer wg.Done(): garante que o contador é decrementado mesmo se houver panic.