Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

1Arreglos

En Go, los arreglos o arrays son estructuras de datos que almacenan una cantidad arbitraria de valores del mismo tipo. A nivel de memoria, todos sus elementos se encuentran en posiciones contiguas.

El tamaño de un array es definido al momento de su creación y determina su “tipo”. Es decir, un array de enteros de 7 elementos tiene un tipo diferente a un array de enteros de 3 elementos.

Podemos declarar un array de la siguiente forma:

1
var numeros [7]int

Aquí creamos una variable de tipo [7]int a la que referenciaremos con el nombre numeros.

Para acceder a los elementos de un arreglo o modificarlos, utilizamos su índice. Tal como sucede en la mayoría de los lenguajes de programación, los índices en Go comienzan en 0 y terminan en largo - 1.

1
2
3
4
numeros[0] = 42
numeros[3] = 1337

fmt.Println(numeros[0] + numeros[3])
1379

En el caso de un array de 7 elementos, podremos acceder a los elementos en los índices desde el 0 hasta el 6 inclusive. Intentar acceder a un índice fuera de este rango va a causar un error.

1
numeros[7]
panic: runtime error: index out of range [7] with length 7

A diferencia de los slices que veremos más adelante, cuando un array es pasado como argumento en una función o método, este se pasa por valor. Es decir, se crea una copia completa del arreglo, por lo que las modificaciones dentro de la función no afectan al arreglo original (a menos que usemos punteros).

En Go para conocer el largo de un array existe la función len.

1
len(numeros)
7

Para recorrer un array en Go existe la instrucción range, que genera un iterador sobre el array devolviendo el índice (i) y el valor (v), en cada iteración del for. Veamos un ejemplo:

1
2
3
4
5
nombres := [4]string{"Fabián", "Martín", "Valeria", "Santiago"}

for i, v := range nombres {
    fmt.Println(i, "|", v)
}
0 | Fabián
1 | Martín
2 | Valeria
3 | Santiago

2Slices

Los slices (o tajadas) en castellano representan secuencias de longitud variable cuyos elementos son del mismo tipo. Un tipo de slice se escribe como []T, donde los elementos son de tipo T; se asemeja a un tipo de array sin tamaño.

Los arreglos y los slices están estrechamente relacionados. Un slice es una estructura de datos ligera que da acceso a una subsecuencia (o a todos) los elementos de un arreglo. Ese arreglo se conoce como el arreglo subyacente del slice.

Un slice tiene tres componentes internos:

La longitud no puede superar la capacidad. Las funciones len y cap devuelven estos valores para cualquier slice.

Podemos imaginar que un slice es como una ventana que podemos deslizar sobre un arreglo y nos permite acceder a una parte del mismo.

1
var s []byte
Estructura interna de un slice.

Estructura interna de un slice.

Estructura interna de un slice.

Estructura interna de un slice.

1
s = make([]byte, 5, 5)
Slice de longitud 5 y capacidad 5: El arreglo subyacente tiene tamaño 5 y la ventana del slice “ve” todo el arreglo.

Slice de longitud 5 y capacidad 5: El arreglo subyacente tiene tamaño 5 y la ventana del slice “ve” todo el arreglo.

Slice de longitud 5 y capacidad 5: El arreglo subyacente tiene tamaño 5 y la ventana del slice “ve” todo el arreglo.

Slice de longitud 5 y capacidad 5: El arreglo subyacente tiene tamaño 5 y la ventana del slice “ve” todo el arreglo.

A medida que hacemos slicing de s, observamos los cambios en la estructura de datos del slice y su relación con el arreglo subyacente:

1
s = s[2:4]
Slice de longitud 2 y capacidad 3: La ventana del slice ahora ve desde la posición 2 del arreglo subyacente hasta la posición 3 (longitud 2, el último elemento no se incluye). Sin embargo, la capacidad es 3, lo que indica que el slice todavía puede crecer una posición más sobre el mismo arreglo.

Slice de longitud 2 y capacidad 3: La ventana del slice ahora ve desde la posición 2 del arreglo subyacente hasta la posición 3 (longitud 2, el último elemento no se incluye). Sin embargo, la capacidad es 3, lo que indica que el slice todavía puede crecer una posición más sobre el mismo arreglo.

Slice de longitud 2 y capacidad 3: La ventana del slice ahora ve desde la posición 2 del arreglo subyacente hasta la posición 3 (longitud 2, el último elemento no se incluye). Sin embargo, la capacidad es 3, lo que indica que el slice todavía puede crecer una posición más sobre el mismo arreglo.

Slice de longitud 2 y capacidad 3: La ventana del slice ahora ve desde la posición 2 del arreglo subyacente hasta la posición 3 (longitud 2, el último elemento no se incluye). Sin embargo, la capacidad es 3, lo que indica que el slice todavía puede crecer una posición más sobre el mismo arreglo.

El slicing no copia los datos del slice. En su lugar, crea un nuevo valor de slice que apunta a otra porción del arreglo original. Esto hace que las operaciones con slices sean tan eficientes como manipular índices de arreglos. Modificar los elementos de un slice modifica los elementos del arreglo subyacente:

1
s = s[:cap(s)]
Slice de longitud 3 y capacidad 3: Ahora la ventana del slice se agrandó y ve desde la posición 2 hasta el final del arreglo subyacente.

Slice de longitud 3 y capacidad 3: Ahora la ventana del slice se agrandó y ve desde la posición 2 hasta el final del arreglo subyacente.

Slice de longitud 3 y capacidad 3: Ahora la ventana del slice se agrandó y ve desde la posición 2 hasta el final del arreglo subyacente.

Slice de longitud 3 y capacidad 3: Ahora la ventana del slice se agrandó y ve desde la posición 2 hasta el final del arreglo subyacente.

Múltiples slices pueden compartir el mismo array subyacente y pueden referirse a partes superpuestas de ese array. La siguiente figura muestra un array de cadenas para los meses del año y dos slices superpuestos de este. El array se declara como:

1
2
meses := [12]string{"Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio",
    "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"}

El operador slice s[i:j] crea un nuevo slice con los elementos desde i hasta j-1 del array o slice s, donde 0ijcap(s)0 \leq i \leq j \leq \texttt{cap(s)} y el resultado tiene j-i elementos. Si se omite i, se toma 0; si se omite j, se toma len(s).

Sobre meses, podemos crear slices que referencien subconjuntos. Por ejemplo:

Definamos slices superpuestos para el segundo trimestre y el invierno:

1
2
t2 := meses[3:6]
invierno := meses[5:8]
1
fmt.Println("t2 =", t2, "\ninvierno =", invierno)
t2 = [Abril Mayo Junio]
invierno = [Junio Julio Agosto]
Dos slices sobre el mismo array de meses.

Dos slices sobre el mismo array de meses.

Dos slices sobre el mismo array de meses.

Dos slices sobre el mismo array de meses.

Hacer slicing más allá de cap(s) causa un pánico, pero hacer slicing más allá de len(s) extiende el slice, por lo que el resultado puede ser más largo que el original:

1
fmt.Println(invierno[:20])
panic: runtime error: slice bounds out of range [:20] with capacity 7
1
2
inviernoSinFin := invierno[:5]
fmt.Println(inviernoSinFin)
[Junio Julio Agosto Septiembre Octubre]

2.1Agregando elementos a un slice

Para agregar elementos a un slice se usa la función append. Esta recibe un slice y uno o más elementos del mismo tipo, y devuelve un nuevo slice con todos los elementos originales más los nuevos.

Si el slice resultante entra en la capacidad actual, append reutiliza el mismo arreglo subyacente. Si no entra, append crea un nuevo arreglo subyacente con capacidad aproximadamente el doble de la original, copia todos los elementos y luego agrega los nuevos.

1
2
s := []int{1, 2, 3}
fmt.Println(s, "\nlen =", len(s), "\ncap =", cap(s))
[1 2 3]
len = 3
cap = 3
1
2
s = append(s, 4, 5)
fmt.Println(s, "\nlen =", len(s), "\ncap =", cap(s))
[1 2 3 4 5]
len = 5
cap = 6

Puede darse el caso en el que, si tenemos dos slices sobre un mismo array subyacente, al agregar un elemento a uno de los slices, el otro también se vea modificado:

1
2
3
4
5
6
7
x := make([]int, 0, 4)
x = append(x, 0, 1, 2)

y := x
y = append(y, 3)

fmt.Println("x =", x, "\ny =", y)
x = [0 1 2]
y = [0 1 2 3]
Slices x e y comparten el mismo arreglo subyacente.

Slices x e y comparten el mismo arreglo subyacente.

Slices x e y comparten el mismo arreglo subyacente.

Slices x e y comparten el mismo arreglo subyacente.

1
2
3
x = append(x, 4)

fmt.Println("x =", x, "\ny =", y)
x = [0 1 2 4]
y = [0 1 2 4]
Modificar x también afecta a y al compartir el arreglo subyacente.

Modificar x también afecta a y al compartir el arreglo subyacente.

Modificar x también afecta a y al compartir el arreglo subyacente.

Modificar x también afecta a y al compartir el arreglo subyacente.

También si el slice sobre el que agregamos el nuevo elemento no tiene más capacidad para agregar elementos, se crea un nuevo slice con aproximadamente el doble de capacidad y se copian los elementos del slice original:

1
2
3
y = append(y, 4)

fmt.Println("x =", x, "\ny =", y)
x = [0 1 2 4]
y = [0 1 2 4 4]
Al superar la capacidad, y apunta a un nuevo arreglo subyacente.

Al superar la capacidad, y apunta a un nuevo arreglo subyacente.

Al superar la capacidad, y apunta a un nuevo arreglo subyacente.

Al superar la capacidad, y apunta a un nuevo arreglo subyacente.

Cuando y = append(y, 4) superó la capacidad, append creó un nuevo arreglo subyacente para y y copió allí los elementos. El slice x, en cambio, sigue apuntando al arreglo subyacente original.

Por eso, modificar los valores de y ya no afecta a x:

1
2
3
y[3] = 3

fmt.Println("x =", x, "\ny =", y)
x = [0 1 2 4]
y = [0 1 2 3 4]

3Ejercicios

Los ejercicios de este capítulo están en 05-arreglos-slices/ejercicios/ del repositorio taller-go. Cada directorio contiene un README.md con el enunciado y los esqueletos para resolverlo.

4Enlaces recomendados