Sintaxe essencial de Go: variáveis, constantes, controle de fluxo e arrays/slices

Capítulo 4

Tempo estimado de leitura: 12 minutos

+ Exercício

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 if com 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.

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

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 len para o que já existe; use cap para 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 make usando len do 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)
}

Agora responda o exercício sobre o conteúdo:

Ao fazer a = append(a, 99) em um subslice criado a partir de s, qual prática ajuda a evitar que o append sobrescreva elementos “depois” no backing array original?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Subslice normalmente herda capacidade e compartilha o mesmo backing array, então um append pode sobrescrever dados além do fim lógico. Usar s[a:b:c] limita a capacidade do subslice, fazendo o append realocar e evitando a sobrescrita.

Próximo capitúlo

Mapas, strings e manipulação de texto em Go

Arrow Right Icon
Capa do Ebook gratuito Go (Golang) para Iniciantes: Fundamentos, Concorrência e Estrutura de Projetos
22%

Go (Golang) para Iniciantes: Fundamentos, Concorrência e Estrutura de Projetos

Novo curso

18 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.