En esta página
Slices y maps
Arrays, slices y maps: las colecciones de Go
Go tiene tres tipos de colecciones fundamentales: arrays (raramente usados directamente), slices (la colección más usada) y maps (tablas hash con tipos estáticos). Entender cómo funcionan internamente es clave para escribir código Go eficiente y libre de bugs.
Arrays: colecciones de tamaño fijo
Un array en Go tiene un tamaño fijo que es parte de su tipo. [3]int y [5]int son tipos completamente distintos:
// Declaración
var a [5]int // [0 0 0 0 0]
b := [3]string{"a", "b", "c"} // [a b c]
c := [...]int{1, 2, 3, 4, 5} // tamaño inferido: [5]int
// Acceso
fmt.Println(b[0]) // "a"
b[1] = "z"
// Iterar
for i, v := range c {
fmt.Printf("c[%d] = %d\n", i, v)
}Los arrays son tipos por valor: asignarlos o pasarlos como parámetros hace una copia completa. Por esta razón, en la práctica casi siempre usas slices.
Slices: la colección dinámica de Go
Un slice es una vista sobre un array subyacente. Tiene tres propiedades:
- Puntero: al primer elemento del array subyacente
- Longitud (
len): número de elementos accesibles - Capacidad (
cap): número de elementos desde el puntero hasta el final del array subyacente
// Varias formas de crear un slice
s1 := []int{1, 2, 3, 4, 5} // literal
s2 := make([]int, 5) // [0 0 0 0 0], len=5, cap=5
s3 := make([]int, 3, 10) // [0 0 0], len=3, cap=10
var s4 []int // nil slice
fmt.Println(len(s1), cap(s1)) // 5 5
fmt.Println(len(s2), cap(s2)) // 5 5
fmt.Println(len(s3), cap(s3)) // 3 10
fmt.Println(s4 == nil) // true`append`: agregar elementos
append es la función fundamental para slices. Si la capacidad es suficiente, agrega al mismo array. Si no, asigna un nuevo array más grande (generalmente duplicando la capacidad) y copia los elementos:
s := []int{1, 2, 3}
s = append(s, 4) // [1 2 3 4]
s = append(s, 5, 6, 7) // [1 2 3 4 5 6 7]
// Agregar otro slice
otra := []int{8, 9, 10}
s = append(s, otra...) // [1 2 3 4 5 6 7 8 9 10]
// Siempre asigna el resultado de append
s = append(s, 11) // NO: append(s, 11) sin asignarSlicing: crear sub-slices
s := []int{0, 10, 20, 30, 40, 50}
// s[inicio:fin] — fin es exclusivo
s1 := s[1:4] // [10 20 30], len=3, cap=5
s2 := s[:3] // [0 10 20], desde el inicio
s3 := s[3:] // [30 40 50], hasta el final
s4 := s[:] // copia de la referencia al mismo array
// Three-index slicing: controlar la capacidad del sub-slice
s5 := s[1:3:4] // [10 20], len=2, cap=3Cuidado: los sub-slices comparten memoria
original := []int{1, 2, 3, 4, 5}
sub := original[1:3] // [2 3], comparte array con original
sub[0] = 99
fmt.Println(original) // [1 99 3 4 5] — ¡original fue modificado!
// Para evitar esto, usa copy
copia := make([]int, 2)
copy(copia, original[1:3])
copia[0] = 99
fmt.Println(original) // [1 2 3 4 5] — sin cambio`copy`: copiar elementos entre slices
src := []int{1, 2, 3, 4, 5}
dst := make([]int, 3)
n := copy(dst, src) // copia min(len(dst), len(src)) elementos
fmt.Println(n, dst) // 3 [1 2 3]
// copy también funciona con strings a []byte
bs := make([]byte, 5)
n = copy(bs, "hola")
fmt.Println(n, bs) // 4 [104 111 108 97 0]Eliminar elementos de un slice
Go no tiene una función delete para slices. El patrón estándar usa append:
s := []int{1, 2, 3, 4, 5}
i := 2 // índice a eliminar
// Eliminar elemento en índice i (sin mantener orden)
s[i] = s[len(s)-1]
s = s[:len(s)-1]
// [1 2 5 4]
// Eliminar elemento en índice i (manteniendo orden)
s = append(s[:i], s[i+1:]...)
// Cuidado: esto comparte memoria con el original si hay otros sub-slicesPatrones comunes con slices
// Stack (pila) con un slice
stack := []int{}
stack = append(stack, 1, 2, 3) // push
top := stack[len(stack)-1] // peek
stack = stack[:len(stack)-1] // pop
// Filtrar elementos (filter idiomático)
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
pares := nums[:0] // reutilizar el backing array
for _, n := range nums {
if n%2 == 0 {
pares = append(pares, n)
}
}
// o con make para copia independiente
var pares2 []int
for _, n := range nums {
if n%2 == 0 {
pares2 = append(pares2, n)
}
}Maps: tablas hash tipadas
Un map es una colección no ordenada de pares clave-valor con tipos estáticos para ambos:
// Crear un map
m1 := map[string]int{
"uno": 1,
"dos": 2,
"tres": 3,
}
m2 := make(map[string]int) // map vacío listo para usar
var m3 map[string]int // nil map — NO se puede escribir en él
// Operaciones básicas
m2["clave"] = 42 // escribir
valor := m2["clave"] // leer (zero value si no existe)
delete(m2, "clave") // eliminar
fmt.Println(len(m2)) // número de paresEl idiom "comma ok"
Cuando lees de un map, siempre deberías usar el idiom "comma ok" para distinguir entre "clave no existe" y "clave existe con zero value":
scores := map[string]int{"Ana": 95, "Luis": 0}
// Sin comma ok — no puedes distinguir
a := scores["Ana"] // 95
p := scores["Pedro"] // 0 — ¿zero value o realmente 0?
// Con comma ok — forma correcta
if val, ok := scores["Luis"]; ok {
fmt.Printf("Luis existe con valor %d\n", val) // Luis existe con valor 0
}
if _, ok := scores["Pedro"]; !ok {
fmt.Println("Pedro no está en el map")
}Iterar sobre un map
m := map[string]int{"a": 1, "b": 2, "c": 3}
// El orden de iteración NO está garantizado
for clave, valor := range m {
fmt.Printf("%s: %d\n", clave, valor)
}
// Solo claves
for clave := range m {
fmt.Println(clave)
}
// Iteración ordenada: extraer claves, ordenar, iterar
import "sort"
claves := make([]string, 0, len(m))
for k := range m {
claves = append(claves, k)
}
sort.Strings(claves)
for _, k := range claves {
fmt.Printf("%s: %d\n", k, m[k])
}Maps con valores de tipo struct
type Estudiante struct {
Nombre string
Nota float64
}
alumnos := map[string]Estudiante{
"A001": {Nombre: "Ana", Nota: 9.5},
"A002": {Nombre: "Luis", Nota: 8.7},
}
// Para modificar un campo, debes reemplazar todo el struct
estudiante := alumnos["A001"]
estudiante.Nota = 10.0
alumnos["A001"] = estudiante
// Alternativa: usar *Estudiante como valor
alumnosPuntero := map[string]*Estudiante{
"A001": {Nombre: "Ana", Nota: 9.5},
}
alumnosPuntero["A001"].Nota = 10.0 // modificación directaNil vs. vacío: la distinción importante
// Slice nil
var sNil []int
fmt.Println(sNil == nil) // true
fmt.Println(len(sNil)) // 0 — seguro
sNil = append(sNil, 1) // seguro — append maneja nil
// Slice vacío
sVacío := []int{}
fmt.Println(sVacío == nil) // false
fmt.Println(len(sVacío)) // 0
// JSON: nil → null, vacío → []
// Map nil
var mNil map[string]int
fmt.Println(mNil == nil) // true
fmt.Println(len(mNil)) // 0 — seguro
val := mNil["key"] // 0 — seguro
// mNil["key"] = 1 // ¡PANIC! no se puede escribir en map nil
// Map vacío
mVacío := make(map[string]int) // o map[string]int{}
mVacío["key"] = 1 // seguroCon el dominio de slices y maps, en la siguiente lección exploraremos los punteros — cómo Go gestiona la memoria sin la aritmética peligrosa de C.
Inicia sesión para guardar tu progreso