Variáveis: var vs declaração curta (:=)
Em Go, você declara variáveis principalmente de duas formas: com var (mais explícito) e com a declaração curta := (mais comum dentro de funções). Ambas criam variáveis, mas têm regras diferentes de escopo, tipagem e inicialização.
var: explícito, útil quando você precisa do tipo ou do valor zero
- Declaração com tipo: você define o tipo e recebe o valor zero (ex.:
0,"",false,nil). - Declaração com inicialização: o tipo pode ser inferido a partir do valor.
- Pode ser usado em nível de pacote (fora de funções), ao contrário de
:=.
package main
import "fmt"
var global int = 10 // permitido no nível de pacote
func main() {
var a int // valor zero: 0
var b = "Go" // tipo inferido: string
var c, d = 1, 2 // múltiplas variáveis
fmt.Println(a, b, c, d, global)
}:=: declaração curta com inferência (somente dentro de funções)
A declaração curta cria e inicializa variáveis de uma vez. Ela é muito usada para reduzir verbosidade, mas exige que pelo menos uma variável do lado esquerdo seja nova no escopo atual.
package main
import "fmt"
func main() {
x := 42 // int
y := "texto" // string
z := 3.14 // float64
fmt.Printf("x=%v (%T), y=%v (%T), z=%v (%T)\n", x, x, y, y, z, z)
}Armadilha comum: := pode “redeclaração parcial”
Se você usar := com variáveis já existentes, Go permite desde que pelo menos uma seja nova. Isso pode esconder bugs quando você acha que está atribuindo, mas na verdade está criando uma variável nova em um escopo interno.
package main
import "fmt"
func main() {
x := 1
{
x := 2 // NOVA variável no escopo interno (shadowing)
fmt.Println("interno:", x)
}
fmt.Println("externo:", x)
}Passo a passo para evitar shadowing acidental:
- Quando a variável já existe e você só quer mudar o valor, use
=. - Evite reutilizar nomes em blocos internos (especialmente em
ifcom inicialização).
Constantes: tipadas e não tipadas
Constantes em Go são declaradas com const e devem ser definidas com valores que o compilador conhece em tempo de compilação. O ponto mais importante: constantes podem ser não tipadas (untyped) e “se adaptam” ao contexto, ou tipadas (typed) e seguem regras rígidas de tipo.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
Constantes não tipadas (flexíveis)
package main
import "fmt"
func main() {
const n = 10 // não tipada
var i int = n
var f float64 = n
fmt.Println(i, f)
}A constante n pode ser usada como int ou float64 porque ainda não “fixou” um tipo.
Constantes tipadas (restritivas)
package main
import "fmt"
func main() {
const n int = 10 // tipada
// var f float64 = n // erro: não converte automaticamente
var f float64 = float64(n)
fmt.Println(f)
}Regra prática: use constantes não tipadas quando quiser flexibilidade (ex.: tamanhos, fatores, limites), e tipadas quando quiser garantir o tipo (ex.: APIs, interoperabilidade, evitar conversões implícitas no raciocínio).
iota para sequências de constantes
package main
import "fmt"
const (
StatusNovo = iota
StatusProcessando
StatusFinalizado
)
func main() {
fmt.Println(StatusNovo, StatusProcessando, StatusFinalizado)
}Controle de fluxo: if, switch e for
if com inicialização
Go permite declarar uma variável no próprio if. Essa variável existe apenas dentro do if e do else.
package main
import "fmt"
func main() {
if n := 7; n%2 == 0 {
fmt.Println("par", n)
} else {
fmt.Println("ímpar", n)
}
// fmt.Println(n) // erro: n não existe aqui
}Passo a passo:
- Coloque a inicialização antes do
;. - A condição vem depois do
;. - Use quando a variável só faz sentido naquele bloco (ex.: resultado de função, parse, validação).
switch expressivo (sem precisar de break)
O switch em Go é mais expressivo: por padrão, ele não faz fallthrough (não “cai” no próximo caso). Você pode usar switch com expressão, sem expressão, com múltiplos valores por caso e com inicialização.
package main
import "fmt"
func main() {
dia := 3
switch dia {
case 1, 7:
fmt.Println("fim de semana")
case 2, 3, 4, 5, 6:
fmt.Println("dia útil")
default:
fmt.Println("inválido")
}
}Switch sem expressão (útil como “if encadeado” mais legível):
package main
import "fmt"
func main() {
temp := 28
switch {
case temp < 0:
fmt.Println("congelando")
case temp < 15:
fmt.Println("frio")
case temp < 30:
fmt.Println("agradável")
default:
fmt.Println("quente")
}
}Switch com inicialização:
package main
import "fmt"
func main() {
switch n := 10; {
case n%2 == 0:
fmt.Println("par")
default:
fmt.Println("ímpar")
}
}Fallthrough existe, mas use com cuidado: ele ignora a condição do próximo caso e apenas executa o bloco seguinte.
for em suas variações
Go tem apenas um comando de repetição: for. Ele cobre o papel de while, do-while (com padrão) e loops tradicionais.
1) for clássico (inicialização; condição; pós)
for i := 0; i < 3; i++ {
fmt.Println(i)
}2) for como “while”
i := 0
for i < 3 {
fmt.Println(i)
i++
}3) Loop infinito com break e continue
i := 0
for {
i++
if i%2 == 0 {
continue
}
fmt.Println("ímpar:", i)
if i >= 5 {
break
}
}4) for range em slices/arrays
range retorna índice e valor. Se você não precisa de um deles, use _.
nums := []int{10, 20, 30}
for i, v := range nums {
fmt.Println(i, v)
}
for _, v := range nums {
fmt.Println("valor:", v)
}Arrays e Slices: modelo mental e operações essenciais
Arrays e slices parecem similares, mas têm diferenças fundamentais:
- Array: tamanho fixo faz parte do tipo (ex.:
[3]int). É um valor; ao atribuir, ele copia todos os elementos. - Slice: visão dinâmica sobre um array subjacente (backing array). Tem comprimento (
len) e capacidade (cap).
Arrays: tamanho fixo e cópia por valor
package main
import "fmt"
func main() {
a := [3]int{1, 2, 3}
b := a // copia
b[0] = 99
fmt.Println("a:", a)
fmt.Println("b:", b)
}Quando usar array? Quando o tamanho é realmente fixo e faz sentido no tipo (ex.: 16 bytes, 32 bytes, matriz pequena com tamanho conhecido). No dia a dia, slices são mais comuns.
Slices: len, cap e o backing array
Um slice contém: ponteiro para o backing array, comprimento e capacidade. A capacidade é quantos elementos você pode ter a partir do início do slice (até o fim do backing array) antes de precisar realocar.
package main
import "fmt"
func main() {
s := []int{1, 2, 3}
fmt.Printf("s=%v len=%d cap=%d\n", s, len(s), cap(s))
}Criando slices com make
make([]T, len, cap) cria um slice com backing array alocado. Se você omitir cap, ele será igual ao len.
package main
import "fmt"
func main() {
a := make([]int, 3) // len=3 cap=3, valores 0
b := make([]int, 3, 10) // len=3 cap=10
fmt.Printf("a=%v len=%d cap=%d\n", a, len(a), cap(a))
fmt.Printf("b=%v len=%d cap=%d\n", b, len(b), cap(b))
}Passo a passo (boa prática):
- Se você sabe aproximadamente quantos elementos vai adicionar, crie com capacidade maior para reduzir realocações.
- Use
lenpara o que já existe; usecappara o que pode crescer sem realocar.
append: crescimento e realocação
append adiciona elementos ao slice. Se a capacidade for suficiente, ele reutiliza o backing array; caso contrário, aloca um novo backing array e copia os elementos. Por isso, o slice retornado por append deve ser capturado.
package main
import "fmt"
func main() {
s := make([]int, 0, 2)
fmt.Printf("start s=%v len=%d cap=%d\n", s, len(s), cap(s))
for i := 1; i <= 5; i++ {
s = append(s, i)
fmt.Printf("after append %d: s=%v len=%d cap=%d\n", i, s, len(s), cap(s))
}
}Armadilha comum: ignorar o retorno de append.
// errado (pode não alterar o slice original)
append(s, 10)
// certo
s = append(s, 10)Fatiamento (slicing): s[a:b] e a capacidade “herdada”
Ao fatiar, você cria um novo slice que aponta para o mesmo backing array. O comprimento vira b-a. A capacidade, por padrão, vai de a até o fim da capacidade original.
package main
import "fmt"
func main() {
s := []int{10, 20, 30, 40, 50}
a := s[1:3] // {20,30}
fmt.Printf("s=%v len=%d cap=%d\n", s, len(s), cap(s))
fmt.Printf("a=%v len=%d cap=%d\n", a, len(a), cap(a))
}Armadilha crítica: slices compartilham backing array
Se dois slices compartilham o mesmo backing array, modificar um pode afetar o outro. Isso acontece tanto por atribuição quanto por fatiamento.
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4}
a := s[0:2] // {1,2}
b := s[1:3] // {2,3}
fmt.Println("antes")
fmt.Println("s:", s)
fmt.Println("a:", a)
fmt.Println("b:", b)
a[1] = 99 // altera s[1] e também b[0]
fmt.Println("depois")
fmt.Println("s:", s)
fmt.Println("a:", a)
fmt.Println("b:", b)
}Como tornar o comportamento observável: imprima len e cap e observe quando append realoca (capacidade muda) e quando não realoca (capacidade permanece e o compartilhamento continua).
Controlando capacidade ao fatiar: expressão completa s[a:b:c]
Go permite limitar a capacidade do slice resultante usando a forma completa: s[a:b:c], onde c define o limite de capacidade (até c-a). Isso é útil para evitar que um append em um subslice sobrescreva elementos “depois” no backing array.
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
a := s[0:2:2] // len=2 cap=2 (cap limitada)
fmt.Printf("s=%v len=%d cap=%d\n", s, len(s), cap(s))
fmt.Printf("a=%v len=%d cap=%d\n", a, len(a), cap(a))
a = append(a, 99) // força realocação (cap era 2)
fmt.Println("após append")
fmt.Println("s:", s)
fmt.Println("a:", a)
}copy: cópia explícita para evitar compartilhamento
Se você precisa de um slice independente, copie os dados para um novo backing array.
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4}
a := s[0:2]
clone := make([]int, len(a))
copy(clone, a)
a[0] = 99
fmt.Println("s:", s)
fmt.Println("a:", a)
fmt.Println("clone:", clone)
}Passo a passo para clonar um slice:
- Crie um novo slice com
makeusandolendo original. - Use
copy(dest, src). - Modifique o clone e observe que o original não muda.
Removendo elementos de um slice (padrão comum)
Para remover o elemento no índice i, você pode “colar” as partes antes e depois com append. Isso reutiliza o backing array e pode manter referências a elementos removidos (importante quando o slice contém ponteiros/structs grandes).
package main
import "fmt"
func main() {
s := []int{10, 20, 30, 40, 50}
i := 2 // remover 30
s = append(s[:i], s[i+1:]...)
fmt.Println(s)
}Exercícios graduais (com observação de len/cap)
Exercício 1: var vs := e valores zero
Tarefa: declare três variáveis usando var sem inicialização (tipos diferentes) e imprima seus valores. Depois, declare três variáveis com := e imprima tipo e valor com %T.
package main
import "fmt"
func main() {
var a int
var b string
var c bool
fmt.Println("zeros:", a, b, c)
x := 10
y := "go"
z := true
fmt.Printf("x=%v (%T) y=%v (%T) z=%v (%T)\n", x, x, y, y, z, z)
}Exercício 2: constantes tipadas vs não tipadas
Tarefa: crie uma constante não tipada k e atribua a variáveis int e float64. Depois, crie uma constante tipada kt int e faça a conversão explícita para float64.
package main
import "fmt"
func main() {
const k = 5
var i int = k
var f float64 = k
fmt.Println(i, f)
const kt int = 5
var f2 float64 = float64(kt)
fmt.Println(f2)
}Exercício 3: if com inicialização
Tarefa: dado um número, use if n := ... para decidir se é múltiplo de 3 e imprimir a mensagem. Em seguida, tente acessar n fora do if e observe o erro (não compile; apenas entenda o escopo).
package main
import "fmt"
func main() {
if n := 9; n%3 == 0 {
fmt.Println("múltiplo de 3:", n)
} else {
fmt.Println("não é múltiplo de 3:", n)
}
}Exercício 4: switch sem expressão
Tarefa: classifique uma nota (0 a 100) em faixas usando switch sem expressão: A (>=90), B (>=80), C (>=70), D (>=60), F (<60).
package main
import "fmt"
func main() {
nota := 83
switch {
case nota >= 90:
fmt.Println("A")
case nota >= 80:
fmt.Println("B")
case nota >= 70:
fmt.Println("C")
case nota >= 60:
fmt.Println("D")
default:
fmt.Println("F")
}
}Exercício 5: for e soma acumulada
Tarefa: some os números de 1 a 10 usando for clássico e imprima o acumulador a cada iteração.
package main
import "fmt"
func main() {
soma := 0
for i := 1; i <= 10; i++ {
soma += i
fmt.Println("i=", i, "soma=", soma)
}
}Exercício 6: observando crescimento de cap com append
Tarefa: crie um slice vazio com make([]int, 0, 1). Faça append de 1 a 8 e imprima len e cap após cada inserção. Anote em quais pontos a capacidade muda.
package main
import "fmt"
func main() {
s := make([]int, 0, 1)
for i := 1; i <= 8; i++ {
s = append(s, i)
fmt.Printf("i=%d s=%v len=%d cap=%d\n", i, s, len(s), cap(s))
}
}Exercício 7: armadilha do backing array (compartilhamento)
Tarefa: crie s := []int{1,2,3,4,5}, faça a := s[:2] e b := s[2:4]. Em seguida, faça a = append(a, 99) e imprima s, a, b com len/cap. Observe se b ou s mudaram.
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
a := s[:2]
b := s[2:4]
fmt.Printf("antes: s=%v len=%d cap=%d\n", s, len(s), cap(s))
fmt.Printf("antes: a=%v len=%d cap=%d\n", a, len(a), cap(a))
fmt.Printf("antes: b=%v len=%d cap=%d\n", b, len(b), cap(b))
a = append(a, 99)
fmt.Printf("depois: s=%v len=%d cap=%d\n", s, len(s), cap(s))
fmt.Printf("depois: a=%v len=%d cap=%d\n", a, len(a), cap(a))
fmt.Printf("depois: b=%v len=%d cap=%d\n", b, len(b), cap(b))
}Exercício 8: evitando sobrescrita com s[a:b:c] e clonagem
Tarefa: repita o exercício anterior, mas crie a := s[:2:2] (cap limitada) e compare o resultado após append. Depois, clone b com copy e modifique o clone para confirmar que não afeta s.
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
a := s[:2:2]
b := s[2:4]
a = append(a, 99)
fmt.Println("s:", s)
fmt.Println("a:", a)
fmt.Println("b:", b)
cloneB := make([]int, len(b))
copy(cloneB, b)
cloneB[0] = 777
fmt.Println("b:", b)
fmt.Println("cloneB:", cloneB)
}