Índice do curso
Lição 4 — Coleções: slices e maps
Por Dorian Chávez · fundador da Hábil e arquiteto de integração ·
Duração: 90 minutos (ou 2 sessões de 45).
Ao terminar, você vai conseguir:
- Distinguir um array de um slice e dizer por que você sempre vai usar o segundo.
- Explicar por que copiar um slice não copia os dados, e quando isso vai te morder.
- Usar um map e saber por que a sua iteração sai desordenada de propósito.
- Percorrer coleções com
for rangee saber o que significa o_. - Ordenar resultados para que o seu programa produza sempre a mesma saída.
- Ler um arquivo e convertê-lo em uma lista de dados.
Por que isso importa
Esta é a lição em que quase todo mundo tropeça. E não por ser difícil, mas porque há uma armadilha que nenhum curso explica até que ela já tenha te mordido: em Go, copiar um slice não copia os seus dados, e esse comportamento não produz nenhum erro — corrompe dados em silêncio.
O revisor precisa de coleções para duas coisas muito concretas: uma lista de serviços que você vai
consultar (isso são slices) e um relatório que associa cada serviço ao seu estado (isso são maps). Sem
slices nem maps não há programa: você não consegue ter "vários serviços" nem "o estado de cada um" com o que
viu até a Lição 3.
Este é o mapa da lição:
| 4.1 | Arrays: os que você quase não vai usar |
| 4.2 | Slices: os que você vai usar sempre |
| 4.3 | A armadilha da memória compartilhada |
| 4.4 | Maps |
| 4.5 | A ordem aleatória, e por que ela é deliberada |
| 4.6 | Ler um arquivo de verdade |
Os conceitos
4.1 Arrays: os que você quase não vai usar
Um array é uma lista de tamanho fixo. O tamanho faz parte do tipo.
Fig. 4.1 | Arrays, para que você os reconheça.
1 // fig04_01.go
2 // Muestra arreglos, que en Go casi no se usan directamente.
3 package main
4
5 import "fmt"
6
7 func main() {
8 var puertos [3]int // tres enteros, todos en 0
9 fmt.Println("recien creado:", puertos)
10
11 puertos[0] = 443
12 puertos[1] = 8080
13 fmt.Println("con valores:", puertos)
14 fmt.Println("cuantos caben:", len(puertos))
15
16 otros := [3]int{80, 443, 8443}
17 fmt.Println("otro arreglo:", otros)
18
19 copia := otros // los arreglos SÍ se copian completos
20 copia[0] = 999
21 fmt.Println("original:", otros)
22 fmt.Println("copia: ", copia)
23 }
$ go run fig04_01.go
recien creado: [0 0 0]
con valores: [443 8080 0]
cuantos caben: 3
otro arreglo: [80 443 8443]
original: [80 443 8443]
copia: [999 443 8443]
Repare em duas coisas:
Linha 8. O array nasce com os valores zero, como tudo em Go. Não há lixo.
Linhas 19-22. Ao atribuir um array a outra variável, ele é copiado por completo: modificar a cópia não toca no original. Lembre-se disso, porque com os slices não acontece o mesmo, e é aí que está a armadilha desta lição.
4.2 Slices: os que você vai usar sempre
Um slice é uma lista de tamanho variável. É o que em outras linguagens você chamaria de lista ou array dinâmico.
Fig. 4.2 | Criar e fazer crescer um slice.
1 // fig04_02.go
2 // Crea un slice y lo hace crecer con append.
3 package main
4
5 import "fmt"
6
7 type Servicio struct {
8 Nombre string
9 Puerto int
10 }
11
12 func main() {
13 // forma 1: vacio, y crece
14 var nombres []string
15 fmt.Printf("vacio: %v · largo %d · es nil: %t\n", nombres, len(nombres), nombres == nil)
16
17 nombres = append(nombres, "catalogo")
18 nombres = append(nombres, "pagos", "inventario")
19 fmt.Printf("con datos: %v · largo %d\n", nombres, len(nombres))
20
21 // forma 2: con valores desde el inicio
22 servicios := []Servicio{
23 {Nombre: "catalogo", Puerto: 443},
24 {Nombre: "pagos", Puerto: 443},
25 {Nombre: "local", Puerto: 8080},
26 }
27 fmt.Println("cuantos servicios:", len(servicios))
28
29 // acceder por posicion, empezando en 0
30 fmt.Println("el primero:", servicios[0].Nombre)
31 fmt.Println("el ultimo: ", servicios[len(servicios)-1].Nombre)
32 }
$ go run fig04_02.go
vacio: [] · largo 0 · es nil: true
con datos: [catalogo pagos inventario] · largo 3
cuantos servicios: 3
el primero: catalogo
el ultimo: local
Linha 15. Um slice sem inicializar vale nil e o seu tamanho é 0. Mas repare em algo importante:
Linha 17: nombres = append(nombres, "catalogo"). Repare que é preciso reatribuir. append não
modifica o slice: retorna um novo, e se você não guardar o resultado, ele se perde.
Linha 31: servicios[len(servicios)-1]. Em Go não há índice negativo como em Python: para o último
elemento, ele é calculado. E se você passar do final, o programa quebra:
Fig. 4.3 | Sair do intervalo.
1 // fig04_03.go — este programa COMPILA pero truena
2 package main
3
4 import "fmt"
5
6 func main() {
7 puertos := []int{443, 8080}
8 fmt.Println(puertos[0])
9 fmt.Println(puertos[5]) // solo hay 2 elementos
10 }
$ go run fig04_03.go
443
panic: runtime error: index out of range [5] with length 2
goroutine 1 [running]:
main.main()
/tmp/fig04_03.go:9 +0x1c
exit status 2
4.3 A armadilha da memória compartilhada
Esta é a seção mais importante da lição. É um comportamento que surpreende todo mundo, que não produz nenhum erro, e que pode corromper dados em silêncio.
4.3.1 O problema
Fig. 4.4 | Copiar um slice não copia os dados.
1 // fig04_04.go
2 // Demuestra que asignar un slice NO copia sus datos.
3 package main
4
5 import "fmt"
6
7 func main() {
8 original := []int{443, 8080, 3000}
9 copia := original // parece una copia...
10
11 copia[0] = 999 // modifico la "copia"
12
13 fmt.Println("original:", original)
14 fmt.Println("copia: ", copia)
15 }
$ go run fig04_04.go
original: [999 8080 3000]
copia: [999 8080 3000]
Você modificou copia e original mudou. Não houve erro, não houve aviso, e os dois slices mostram o
mesmo.
Compare com a Fig. 4.1, onde o mesmo código com um array copiava de fato. A diferença é de fundo.
4.3.2 Por que acontece
Um slice não contém os dados: é uma janela que aponta para eles. Por dentro, ele guarda três coisas:
| um ponteiro | para onde estão os dados de verdade |
o tamanho (len) |
quantos elementos ele tem agora |
a capacidade (cap) |
quantos cabem antes de ter de se mudar |
Quando você escreve copia := original, Go copia essas três coisas — não os dados. As duas variáveis
ficam apontando para o mesmo lugar.
É como te dar o endereço de uma casa em vez de construir uma igual para você: se você pinta a «sua» casa, a minha muda, porque é a mesma.
4.3.3 A solução
Fig. 4.5 | Copiar de verdade.
1 // fig04_05.go
2 // Las dos formas de copiar un slice de verdad.
3 package main
4
5 import "fmt"
6
7 func main() {
8 original := []int{443, 8080, 3000}
9
10 // forma 1: make + copy
11 copia1 := make([]int, len(original))
12 copy(copia1, original)
13 copia1[0] = 111
14
15 // forma 2: append sobre un slice nil (mas corta)
16 copia2 := append([]int(nil), original...)
17 copia2[1] = 222
18
19 fmt.Println("original:", original)
20 fmt.Println("copia1: ", copia1)
21 fmt.Println("copia2: ", copia2)
22 }
$ go run fig04_05.go
original: [443 8080 3000]
copia1: [111 8080 3000]
copia2: [443 222 3000]
Agora sim eles são independentes.
Linha 11: make([]int, len(original)). make cria um slice com espaço reservado. É a forma de
dizer «quero um slice deste tamanho, com os seus valores zero».
Linha 16: original.... Esses três pontos significam «espalhe os elementos um por um». Sem eles
você estaria tentando adicionar o slice completo como um único elemento, e não compila.
4.3.4 E a parte realmente traiçoeira: append
Fig. 4.6 | O mesmo código se comporta de forma diferente conforme a capacidade.
1 // fig04_06.go
2 // append comparte o no memoria segun la capacidad disponible.
3 package main
4
5 import "fmt"
6
7 func main() {
8 // CASO A: hay capacidad de sobra (cap 5, largo 3)
9 a := make([]int, 3, 5)
10 a[0], a[1], a[2] = 1, 2, 3
11 fmt.Printf("A: len=%d cap=%d %v\n", len(a), cap(a), a)
12
13 b := append(a, 99) // cabe: NO se muda, comparte memoria
14 b[0] = 777
15 fmt.Println(" tras modificar b, a vale:", a, "← cambió")
16
17 // CASO B: no hay capacidad (cap 3, largo 3)
18 c := make([]int, 3, 3)
19 c[0], c[1], c[2] = 1, 2, 3
20 fmt.Printf("B: len=%d cap=%d %v\n", len(c), cap(c), c)
21
22 d := append(c, 99) // NO cabe: se muda a otra memoria
23 d[0] = 777
24 fmt.Println(" tras modificar d, c vale:", c, "← NO cambió")
25 }
$ go run fig04_06.go
A: len=3 cap=5 [1 2 3]
tras modificar b, a vale: [777 2 3] ← cambió
B: len=3 cap=3 [1 2 3]
tras modificar d, c vale: [1 2 3] ← NO cambió
Leia essa saída duas vezes. É o mesmo código —append e depois modificar— e o resultado é
diferente conforme a capacidade que havia de sobra.
4.4 Maps
Um map guarda pares de chave e valor. É o que em outras linguagens se chama dicionário ou tabela associativa.
Fig. 4.7 | Criar, escrever e ler um map.
1 // fig04_07.go
2 // Operaciones basicas con un map.
3 package main
4
5 import "fmt"
6
7 type Estado struct {
8 Codigo int
9 Ms int
10 }
11
12 func main() {
13 // se crea con make, o vacio con {}
14 estados := make(map[string]Estado)
15
16 estados["catalogo"] = Estado{Codigo: 200, Ms: 142}
17 estados["pagos"] = Estado{Codigo: 200, Ms: 87}
18 estados["inventario"] = Estado{Codigo: 503, Ms: 2310}
19
20 fmt.Println("cuantos:", len(estados))
21
22 // leer una llave que SÍ existe
23 e := estados["catalogo"]
24 fmt.Println("catalogo:", e.Codigo, e.Ms)
25
26 // 🔑 leer una que NO existe: devuelve el valor cero, SIN error
27 fantasma := estados["no-existe"]
28 fmt.Printf("no-existe: %+v ← el valor cero\n", fantasma)
29
30 // la forma correcta de preguntar: el segundo valor
31 if e, hay := estados["pagos"]; hay {
32 fmt.Println("pagos si esta:", e.Codigo)
33 }
34 if _, hay := estados["no-existe"]; !hay {
35 fmt.Println("no-existe NO esta")
36 }
37
38 // borrar
39 delete(estados, "inventario")
40 fmt.Println("tras borrar:", len(estados))
41 }
$ go run fig04_07.go
cuantos: 3
catalogo: 200 142
no-existe: {Codigo:0 Ms:0} ← el valor cero
pagos si esta: 200
no-existe NO esta
tras borrar: 2
Linhas 27-28: aqui está o que é preciso entender. Ler uma chave que não existe não dá erro:
retorna o valor zero do tipo. Para um int isso é 0, que poderia ser um valor legítimo.
4.4.1 O map nil: o único que morde
Fig. 4.8 | Um map nil pode ser lido mas não escrito.
1 // fig04_08.go — este programa truena al escribir
2 package main
3
4 import "fmt"
5
6 func main() {
7 var m map[string]int // nil: NO inicializado
8
9 fmt.Println("leer de un map nil:", m["x"]) // esto funciona
10 fmt.Println("su largo:", len(m)) // esto tambien
11
12 m["x"] = 1 // esto truena
13 }
$ go run fig04_08.go
leer de un map nil: 0
su largo: 0
panic: assignment to entry in nil map
goroutine 1 [running]:
main.main()
/tmp/fig04_08.go:12 +0x3c
exit status 2
4.5 A ordem aleatória, e por que ela é deliberada
Fig. 4.9 | O mesmo programa, duas execuções, duas ordens.
1 // fig04_09.go
2 // El recorrido de un map sale en orden distinto cada vez.
3 package main
4
5 import "fmt"
6
7 func main() {
8 puertos := map[string]int{
9 "catalogo": 443,
10 "pagos": 443,
11 "inventario": 8080,
12 "reportes": 3001,
13 }
14
15 for nombre, puerto := range puertos {
16 fmt.Printf("%s=%d ", nombre, puerto)
17 }
18 fmt.Println()
19 }
$ go run fig04_09.go
reportes=3001 catalogo=443 pagos=443 inventario=8080
$ go run fig04_09.go
pagos=443 inventario=8080 reportes=3001 catalogo=443
$ go run fig04_09.go
inventario=8080 reportes=3001 catalogo=443 pagos=443
Três execuções, três ordens diferentes. E não é um defeito: Go faz isso de propósito, tornando aleatório o ponto de partida em cada iteração.
4.5.1 Ordenar para que a saída seja estável
Se você precisa de ordem, ela é pedida explicitamente.
Fig. 4.10 | Percorrer um map em ordem alfabética.
1 // fig04_10.go
2 // Ordena las llaves para producir una salida estable.
3 package main
4
5 import (
6 "fmt"
7 "sort"
8 )
9
10 type Estado struct {
11 Codigo int
12 Ms int
13 }
14
15 func main() {
16 estados := map[string]Estado{
17 "reportes": {Codigo: 0, Ms: 0},
18 "catalogo": {Codigo: 200, Ms: 142},
19 "inventario": {Codigo: 503, Ms: 2310},
20 "pagos": {Codigo: 200, Ms: 87},
21 }
22
23 // 1. saca las llaves a un slice
24 nombres := make([]string, 0, len(estados))
25 for nombre := range estados {
26 nombres = append(nombres, nombre)
27 }
28
29 // 2. ordenalas
30 sort.Strings(nombres)
31
32 // 3. recorre el slice ordenado, no el map
33 fmt.Println("SERVICIO CODIGO TIEMPO")
34 fmt.Println("-----------------------------")
35 for _, nombre := range nombres {
36 e := estados[nombre]
37 fmt.Printf("%-12s %6d %5dms\n", nombre, e.Codigo, e.Ms)
38 }
39 }
$ go run fig04_10.go
SERVICIO CODIGO TIEMPO
-----------------------------
catalogo 200 142ms
inventario 503 2310ms
pagos 200 87ms
reportes 0 0ms
E agora sim: a mesma saída, sempre. Rode o programa dez vezes e ela não muda.
Linha 24: make([]string, 0, len(estados)). Tamanho 0, capacidade len(estados). Você sabe quantas
chaves vai adicionar, então o espaço é reservado de uma vez.
Linha 25: for nombre := range estados. Sobre um map, range dá a chave na primeira variável.
Sobre um slice dá o índice. É uma diferença que convém ter presente:
| Coleção | primeira variável | segunda variável |
|---|---|---|
| slice | o índice (0, 1, 2…) | o elemento |
| map | a chave | o valor |
Linha 35: for _, nombre := range nombres. Aqui nombres é um slice, então a primeira variável é
o índice, e eu não preciso dele: por isso _.
4.6 Ler um arquivo de verdade
Até agora os dados estiveram escritos no programa. Isso não serve: cada mudança exige recompilar.
Fig. 4.11 | Ler serviços a partir de um arquivo de texto.
Primeiro o arquivo de dados, servicios.txt:
catalogo https://catalogo.example.com 443
pagos https://pagos.example.com 443
inventario https://inventario.example.com 8080
E o programa:
1 // fig04_11.go
2 // Lee los servicios desde un archivo de texto.
3 package main
4
5 import (
6 "fmt"
7 "os"
8 "strconv"
9 "strings"
10 )
11
12 type Servicio struct {
13 Nombre string
14 URL string
15 Puerto int
16 }
17
18 // Cargar lee el archivo y devuelve los servicios, o un error.
19 func Cargar(ruta string) ([]Servicio, error) {
20 datos, err := os.ReadFile(ruta)
21 if err != nil {
22 return nil, fmt.Errorf("leyendo %s: %w", ruta, err)
23 }
24
25 var servicios []Servicio
26 lineas := strings.Split(strings.TrimSpace(string(datos)), "\n")
27
28 for i, linea := range lineas {
29 linea = strings.TrimSpace(linea)
30 if linea == "" || strings.HasPrefix(linea, "#") {
31 continue // salta vacias y comentarios
32 }
33
34 campos := strings.Fields(linea) // separa por espacios
35 if len(campos) != 3 {
36 return nil, fmt.Errorf("%s linea %d: esperaba 3 campos, hay %d",
37 ruta, i+1, len(campos))
38 }
39
40 puerto, err := strconv.Atoi(campos[2])
41 if err != nil {
42 return nil, fmt.Errorf("%s linea %d: puerto invalido %q: %w",
43 ruta, i+1, campos[2], err)
44 }
45
46 servicios = append(servicios, Servicio{
47 Nombre: campos[0],
48 URL: campos[1],
49 Puerto: puerto,
50 })
51 }
52 return servicios, nil
53 }
54
55 func main() {
56 servicios, err := Cargar("servicios.txt")
57 if err != nil {
58 fmt.Fprintln(os.Stderr, "error:", err)
59 os.Exit(1)
60 }
61
62 fmt.Printf("cargados %d servicios:\n", len(servicios))
63 for _, s := range servicios {
64 fmt.Printf(" %-12s %-38s :%d\n", s.Nombre, s.URL, s.Puerto)
65 }
66 }
$ go run fig04_11.go
cargados 3 servicios:
catalogo https://catalogo.example.com :443
pagos https://pagos.example.com :443
inventario https://inventario.example.com :8080
E vamos testar o que acontece quando algo dá errado:
$ go run fig04_11.go # con el archivo renombrado
error: leyendo servicios.txt: open servicios.txt: no such file or directory
$ echo $?
1
Linha 20: os.ReadFile. Lê o arquivo completo e retorna os seus bytes. Para arquivos de configuração
é perfeito; para um arquivo de vários gigabytes existem outras formas, porque isto carrega tudo na
memória.
Linha 26: string(datos). Converte os bytes em texto. strings.TrimSpace remove espaços e quebras de
linha do início e do final — sem isso, a última linha vazia produziria um serviço fantasma.
Linha 34: strings.Fields. Separa por espaços, colapsando os repetidos. É mais robusto que
strings.Split(linea, " "), que com dois espaços seguidos te daria um campo vazio.
Linha 40: strconv.Atoi. Converte texto em inteiro (ASCII to integer). Retorna erro se não for um
número, e estamos verificando isso.
Linhas 58-59: a diferença entre um script e um programa.
O erro que você vai ver
Esta lição deixa duas mensagens de panic gravadas na memória, porque são as duas mais frequentes de
todo o curso depois do ponteiro nulo:
panic: runtime error: index out of range [N] with length M (Fig. 4.3). Você pediu uma posição N que
não existe em uma coleção de tamanho M. O que significa: o índice válido mais alto é M-1, não M.
Como se corrige: verifique len(coleccion) antes de indexar, sobretudo se o índice vem de fora
(um arquivo, um argumento, uma requisição) e não foi você mesmo que o escreveu no código.
panic: assignment to entry in nil map (Fig. 4.8). Você tentou escrever em um map que nunca foi criado —
var m map[string]Estado deixa m em nil, e um map nil pode ser lido mas não escrito. O que
significa: falta o make(map[K]V) ou o map[K]V{} que reserva a tabela por dentro. Como se
corrige: crie o map antes de usá-lo como destino de uma atribuição; se você só vai lê-lo, nil não dá
nenhum problema.
E há um terceiro caso que não quebra, e por isso é o mais perigoso dos três: modificar um slice compartilhado (Fig. 4.4 e Fig. 4.6) não produz nenhuma mensagem de erro. O programa continua rodando com dados incorretos. Esse é exatamente o motivo de a seção 4.3 existir.
O que se faz errado
- Supor que
copia := originalcopia os dados de um slice. Copia o ponteiro, o tamanho e a capacidade — não os dados. As duas variáveis acabam compartilhando memória (Fig. 4.4). - Guardar um slice que você recebeu como parâmetro sem copiá-lo antes. O comportamento de
appenddepende da capacidade que o slice traz, que quase nunca é controlada por quem o recebe (Fig. 4.6). Se você vai guardar um slice alheio, copie-o; se só vai lê-lo, não é preciso. - Confiar que um
for rangesobre um map vai sair sempre na mesma ordem. Nunca foi garantido, e Go o torna aleatório de propósito (Fig. 4.9) para que o erro apareça hoje e não no dia em que crescer o banco de dados em produção. - Ler
m[llave]sem verificarokquando o valor zero é ambíguo. Ummap[string]intque guarda tempos de resposta não consegue distinguir «respondeu em 0 ms» de «nunca foi consultado» se você não usa a forma de dois valores. - Indexar algo que vem de fora —um arquivo,
os.Args, uma resposta de rede— sem verificarlenprimeiro. É a causa mais comum doindex out of rangedesta lição, e se evita com uma linha. - Escrever em um map antes de criá-lo com
makeou com{}. O compilador não detecta isso porquevar m map[K]Vé código válido; opanicsó aparece quando o programa roda e tenta escrever.
Exercícios
Perguntas
4.1 Qual é a diferença entre [3]int e []int?
4.2 Por que [3]int e [4]int são tipos diferentes, e que consequência prática isso tem?
4.3 O que isto imprime e por quê?
a := []int{1, 2, 3}
b := a
b[0] = 99
fmt.Println(a[0])
4.4 Cite as duas formas de copiar um slice de verdade.
4.5 Por que o mesmo append às vezes compartilha memória com o original e às vezes não?
4.6 O que m["no-existe"] retorna em um map[string]int, e por que isso é perigoso?
4.7 Qual é a assimetria entre um slice nil e um map nil?
4.8 Por que a iteração de um map sai desordenada, e como você obtém uma ordem fixa?
4.9 Em for a, b := range x, o que é a se x é um slice? E se é um map?
4.10 O que strings.Fields faz que strings.Split(s, " ") não faz?
4.11 Por que os erros vão para os.Stderr e não para a saída normal?
4.12 Este programa tem dois problemas. Encontre-os.
func main() {
var m map[string]int
m["catalogo"] = 443
lista := []int{1, 2, 3}
fmt.Println(lista[3])
}
Exercícios de código
4.13 Escreva func Contar(estados map[string]Estado) (ok, fallas int) que conte quantos estados
têm código 200-299 e quantos não. Go retorna vários valores: use isso.
4.14 Escreva func Nombres(servicios []Servicio) []string que retorne só os nomes, ordenados.
4.15 (A armadilha) Escreva uma função func Guardar(s []int) que guarde o slice em uma variável global
e depois o imprima. Chame-a assim:
datos := make([]int, 3, 10)
datos[0], datos[1], datos[2] = 1, 2, 3
Guardar(datos)
datos = append(datos, 4)
datos[0] = 999
// ahora imprime lo que guardó Guardar
Preveja o que vai imprimir antes de rodar. Depois rode. Se acertou, você entendeu a seção 4.3; se não, leia-a de novo — é a que sai mais cara.
4.16 Estenda Cargar da Fig. 4.11 para que aceite um quarto campo opcional com o timeout em
milissegundos. Se não vier, use 5000. Cuide para que um arquivo com três campos continue funcionando.
4.17 Escreva func Agrupar(servicios []Servicio) map[int][]Servicio que agrupe por porta. Teste que
uma porta com três serviços tenha todos eles.
4.18 (Encontre o erro) Diga o que está errado em cada um:
// (a)
var m map[string]Estado
m["catalogo"] = Estado{}
// (b)
append(servicios, nuevo)
// (c)
for i := range servicios {
fmt.Println(servicios[i+1].Nombre)
}
// (d)
ms := estados["catalogo"].Ms
if ms == 0 {
fmt.Println("respondio instantaneo")
}
4.19 (Projeto do curso) Leve o seu programa ao próximo passo:
Cargar(ruta string) ([]Servicio, error)que leia o arquivo, como a Fig. 4.11.RevisarTodosque retornemap[string]Estado.Reporte(estados map[string]Estado) stringque produza a tabela ordenada alfabeticamente.- Uma contagem final: «3 de 5 responderam».
- Que o programa aceite o caminho do arquivo como argumento:
os.Args[1]. 🔴 Verifiquelen(os.Args)antes de lê-lo, ou você vai ter umindex out of rangequando alguém o rodar sem argumentos. - Que saia com código 0 se tudo respondeu e 1 se algo falhou.
Soluções
4.1 [3]int é um array: tamanho fixo, parte do tipo, e é copiado ao ser atribuído. []int é um
slice: tamanho variável, e ao ser atribuído compartilha os dados.
4.2 Porque o tamanho faz parte do tipo. A consequência: uma função que recebe [3]int não aceita um
[4]int, o que torna os arrays pouco práticos. Por isso se usam slices.
4.3 Imprime 99. b := a não copia os dados: as duas variáveis apontam para a mesma memória.
4.4 make + copy, ou append([]T(nil), original...).
4.5 Porque depende da capacidade. Se o slice tem espaço de sobra, append escreve ali mesmo e
continua compartilhando memória; se não cabe, ele se muda para memória nova e deixa de compartilhar. Como você quase nunca
controla a capacidade, o mesmo código pode se comportar de forma diferente.
4.6 Retorna 0, o valor zero, sem erro. É perigoso porque 0 pode ser um dado legítimo, então
você não consegue distinguir «vale zero» de «não está». Resolve-se com v, ok := m[k].
4.7 Um slice nil aceita append sem problema. Um map nil quebra ao ser escrito
(assignment to entry in nil map), embora possa ser lido. Os maps precisam ser criados com make.
4.8 Porque a sua ordem nunca foi garantida, e Go a torna aleatória de propósito para que você não escreva
programas que dependam de uma ordem acidental. Para ordem fixa: tirar as chaves para um slice, ordená-las com
sort.Strings, e percorrer o slice.
4.9 Se x é um slice, a é o índice. Se é um map, a é a chave.
4.10 Fields separa por espaços colapsando os repetidos, então dois espaços seguidos não
produzem um campo vazio. Também trata as tabulações como separador.
4.11 Para que a saída normal fique limpa e possa ser usada em um pipe, e porque quem chama o programa espera encontrar os erros ali. Junto com o código de saída diferente de zero, é o que faz com que um programa possa ser automatizado.
4.12 Os dois: (1) m é um map nil e escrever nele provoca panic: assignment to entry in nil map —
falta m := make(map[string]int); (2) lista[3] sai do intervalo, porque os índices válidos são 0, 1 e
2 — panic: index out of range [3] with length 3.
Exercícios de código (4.13 a 4.19): suas soluções verificadas —compiladas e executadas— serão adicionadas
na entrega em que for fechado o capítulo de exercícios com solução do curso completo; não são publicadas
sem ter rodado go build sobre cada uma.
Como sei que consegui
- Você consegue explicar, sem ver o texto, por que
copia := originalnão copia os dados de um slice — e desenhá-lo (ponteiro, tamanho, capacidade). - Você previu corretamente o que o exercício 4.15 imprime antes de rodá-lo. Se não acertou, repetiu a seção 4.3 até acertar.
- O seu
Cargar(exercício 4.16) continua funcionando com arquivos de três campos e aceita o quarto opcional. - O seu
Reporteproduz a mesma saída, na mesma ordem, em dez execuções seguidas. - O seu programa não quebra com
index out of rangese você o roda sem argumentos: você verificoulen(os.Args)antes de leros.Args[1]. - Você anotou no
bitacora.mdo resultado do exercício 4.15: o que previu e o que aconteceu de verdade. É o que separa entender os slices de achar que os entende.
Resumo
- Um array (
[3]int) tem tamanho fixo, o tamanho faz parte do tipo, e é copiado ao ser atribuído. Quase não se usa diretamente. - Um slice (
[]int) tem tamanho variável e é o que se usa sempre. - Um slice guarda ponteiro, tamanho e capacidade: não contém os dados, aponta para eles.
- 🔴 Atribuir um slice NÃO copia os dados: as duas variáveis compartilham memória. Para copiar de verdade,
make+copyouappend([]T(nil), s...). - 🔴
appendcompartilha ou não memória conforme a capacidade disponível, então o mesmo código pode se comportar de forma diferente. Se você vai guardar um slice alheio, copie-o. - Um slice
nilfunciona comappend,lenerange. Um mapnilquebra ao ser escrito. - Ler uma chave inexistente de um map retorna o valor zero sem erro. Use
v, ok := m[k]quando a ausência importar. - A iteração de um map é aleatória de propósito. Para ordem fixa: chaves para um slice,
sort, e percorrer o slice. - Em
range, a primeira variável é o índice em slices e a chave em maps._descarta. os.ReadFilelê um arquivo completo;strings.Fieldssepara por espaços colapsando repetidos;strconv.Atoiconverte texto em inteiro e retorna erro.- Os erros vão para
os.Stderre o programa sai com código diferente de zero.
Para ler mais
- Go Slices: usage and internals — o blog oficial da equipe de Go, explica o ponteiro/tamanho/capacidade com desenhos. Comece por aqui.
- Go maps in action — blog oficial, cobre a ordem aleatória e o map
nilcom mais detalhe do que cabe nesta lição. - Go by Example: Slices e Maps — código mínimo, bom como referência rápida.
- Effective Go — Slices — a seção específica sobre o
padrão de dois passos (
make+copy) e por queappendse comporta como se comporta.
Termos desta lição
append |
adiciona elementos a um slice e retorna o resultado |
| array | lista de tamanho fixo; o tamanho faz parte do tipo |
capacidade (cap) |
quantos elementos cabem em um slice antes de ele se mudar |
copy |
copia elementos entre slices |
delete |
remove uma chave de um map |
index out of range |
panic por pedir uma posição que não existe |
tamanho (len) |
quantos elementos ele tem agora |
| map | coleção de pares chave→valor |
make |
cria slices, maps e canais com espaço reservado |
nil map |
map não criado; pode ser lido mas não escrito |
os.Stderr |
saída de erro, separada da normal |
| slice | lista de tamanho variável; uma janela sobre os dados |
sort.Strings |
ordena um slice de texto |
strings.Fields |
separa um texto por espaços, colapsando repetidos |
strconv.Atoi |
converte texto em inteiro |
valor de dois resultados (v, ok) |
forma de ler um map distinguindo a ausência |
Prefere e-mail? Escreva para hola@habil.mx