En Go tenemos dos tipos de arrays o arreglos:
-
Los que llaman arrays, son arreglos con una cantidad definida de elementos,es decir, tienen un tamaño fijo y no pueden crecer.
-
Los llamados slices, que son los arreglos o arrays más comunes, en los cuales podemos añadir los elementos que queramos.
Y porque tenemos dos tipos? Pues básicamente es por el consumo de recursos, los arrays definidos, solo se reservan los recursos que necesitan, los slices o llamemoslos arrays dinámicos tienen siempre reservado una cantidad mayor de recursos para poder crecer. Más abajo veremos como saber esta cantidad.
Arrays definidos
La forma de declararlos es bastante sencilla:
//supongamos que son 3
var rangers [3]string
rangers[0] = "Ranger Rojo"
rangers[1] = "Ranger Negro"
rangers[2] = "Ranger Amarillo"
//El uso es como todos los arrays
fmt.Println(rangers)
fmt.Println(rangers[0])
fmt.Println(rangers[1])
fmt.Println(rangers[2])
Si ejecutamos esto:
Hasta aqui todo parece normal, vamos a intentar añadir alguno mas:
rangers[3] = "Ranger Verde"
Vemos como el propio visual studio nos avisa:
La única forma que tendriamos de ampliar ese array, o más bien de trabajar con los datos que contiene, es hacer una copia del array en otro con más cantidad de elementos posibles(o que fuera un slice).
Más abajo veremos como hacer eso, ya que es común para ambos tipos de array.
SLICES
Continuemos con los slices, que serán posiblemente el tipo más usado, sobretodo al principio.
Tenemos varias formas de crear slices, cada una tiene sus peculiaridades.
La primera:
var rangers []string
rangers[0] = "Ranger Rosa"
fmt.Println(rangers)
En un principio todo parece normal, el compilador no se queja tampoco, cool, pero vamos a ejecutarlo:
Como veis tenemos un bonito Panic (para el que no se lo imagine es como muestra los errores) pero ¿porque?
Go al inicializar los slices si lo hacemos de esa forma los inicializa por defecto "vacios" pero no entendamos vacios como cualquier otro elemento que podemos asignarle un valor directamente, si no lo inicializa con "ausencia de todo" incluso de tamaño.
Pero esto no significa que no podamos usarlo, Go tiene un método append que nos permite añadir los elementos que necesitemos:
rangers = append(rangers, "Ranger Rosa", "Ranger Rojo")
fmt.Println(rangers)
Podriamos añadir todos los que queramos, y como vemos funciona perfectamente:
Antes de continuar con la siguiente forma de inicializar slices vamos a hablar de "Tamaño" y "Capacidad"
¿¿??
Si jejejeje, con los slices en aplicaciones complejas tendremos que tener en cuenta estas dos cosas, pensando en la optimización por supuesto.
-
Tamaño: Básicamente es el tamaño que tiene ocupado el slice, lo que en otros lenguajes consultaríamos con un array.length
-
Capacidad: Es la cantidad de recursos reservada para ese slice.
Es decir si pensamos en los slices como cajas, tenemos que un slice con una capacidad de 5 son 5 cajas, de las cuales 2 estan cerradas con algo dentro(tamaño 2) y otras 3 estan abiertas a la espera de tener algo dentro, pero estan ocupando su espacio en el suelo ya.
Ahora veamos la segunda forma de crear un slice:
rangers := make([]string,10)
//Aqui tendriamos un slice con 10 de tamaño y 10 de capacidad
rangers := make([]string,5,10)
//Y aqui tendriamos un slice con 5 de tamaño y 10 de capacidad
Veamos un ejemplo de código para ilustrar un poco mejor esto:
var rangers []string
fmt.Println("Sin inicializar, es decir tamaño 0")
fmt.Println(rangers)
rangers = append(rangers, "Ranger Rosa", "Ranger Rojo")
fmt.Println("Append al no inicializado")
fmt.Println(rangers)
rangers2 := make([]string, 10)
fmt.Println("Usando make para asignar tamaño y capacidad iguales")
fmt.Println(rangers2)
rangers3 := make([]string, 5, 10)
fmt.Println("Usando make para asignar tamaño y capacidad distintos")
fmt.Println(rangers3)
Si ejecutamos este código tendriamos un resultado como este:
Si os fijais tenemos bastantes diferencias en cuanto a la inicialización por defecto de cada uno de los slices, el primero realmente tendria un valor similar a null, en el segundo hemos inicializado ese null con dos elementos, en el tercero se ha aplicado la inicialización por defecto de go de strings(es decir "") sobre 10 elementos, en el ultimo esa misma inicialización sobre 5.
Ahora ya podemos usar lo que llamariamos asignación directa:
rangers2[2] = "Ranger Verde"
rangers3[3] = "Ranger Negro"
fmt.Println(rangers2)
fmt.Println(rangers3)
Bien hemos comprobado que podemos añadir ahora por lo menos dentro del "tamaño inicializado", si nos fijamos cada elemento tiene una posición definida dentro del slice. Veamos ahora lo que pasa si intentamos añadir en una ubicación por encima del tamaño(ojo no de la capacidad). Lo vamos a intentar on ranger3 que tiene una capacidad de 10 y un tamaño de 5:
Vemos como nos vuelve a dar el error de que nos hemos salido del rango permitido. Para añadir por encima de esto tenemos que usar el mismo método que antes con append
rangers3 = append(rangers3, "Ranger Rosa", "Ranger Rojo")
fmt.Println(rangers3)
Si ahora ejecutamos veremos como despues del espacio que teniamos antes ha añadido los dos rangers nuevos.
OJO a lo de los espacios vamos a ver una ultima forma de inicializar Arrays y Slices y volveremos a este ejemplo para ver cosas sobre la capacidad y el tamaño.
Como última forma vamos a ver la inicialización directa de ambos casos:
//Array definido
rangers2 := [2]string{"Ranger Verde", "Ranger Blanco"}
//Slice
rangers3 := []string{"Ranger Rosa", "Ranger Amarillo"}
fmt.Println(rangers2)
fmt.Println(rangers3)
Como tal no notamos ninguna diferencia a la hora de mostrarlos:
Atención si hacemos esto:
rangers2 := [3]string{"Ranger Verde", "Ranger Blanco"}
Nos añade,en este caso un string(podria ser cualquier tipo que hubieramos indicado) vacio en el elemento faltante.
Bien ahora vamos a probar a añadir:
rangers2 := [2]string{"Ranger Verde", "Ranger Blanco"}
rangers3 := []string{"Ranger Rosa", "Ranger Amarillo"}
rangers2 = append(rangers2, "Ranger Negro")
rangers3 = append(rangers3, "Ranger Blanco")
Si vemos el array definido se queja:
Como tal sigue restringiendo su ampliación, lo contrario que el slice.
Ahora vamos a ver la diferencia real entre uno y otro.
Len y Cap
Go tiene como casi todos los lenguajes una forma de comprobar la cantidad de elementos que tiene un array,normalmente suele tener que ver con length, pues Go no iba a ser menos tenemos nuestro len:
rangers3 := []string{"Ranger Rosa", "Ranger Amarillo"}
fmt.Println(len(rangers3))
Esto nos devuelve un 2, cosa normal no?? Pero como tal antes hemos hablado de la capacidad también, en este caso Go tiene también una forma de ver la capacidad que tenemos reservada(básicamente es reserva en memoria)
rangers3 := []string{"Ranger Rosa", "Ranger Amarillo"}
fmt.Println("Tamaño",len(rangers3))
fmt.Println("Capacidad",cap(rangers3))
Esto nos devolveria:
Hasta aqui todo normal no?? Todo tiene sentido verdad?? Vale ahora vamos a añadirle algo para aumentarlo, recordemos que con capacidad 2 no podemos usar asignación directa para los siguientes elementos como tal tenemos que usar append, vamos a usar append y veamos que nos devuelve ahora:
rangers3 := []string{"Ranger Rosa", "Ranger Amarillo"}
rangers3 = append(rangers3, "Ranger Blanco")
fmt.Println("Tamaño", len(rangers3))
fmt.Println("Capacidad", cap(rangers3))
Para nuestra sorpresa al ejecutar vemos esto:
UPS!!! y que ha pasaaaaaooooo!!!????, es simple Go lo que hace es duplicar la capacidad anterior, por lo que si no tenemos en cuenta y tratamos con slices muy grandes, por ejemplo imaginemos un array de 100 elementos, si añadimos 1 mas para hacer 101 tendriamos una capacidad de 200 siendo realmente inecesario.
Veamoslo con un ejemplo añadiendo al código anterior los 2 rangers restantes:
rangers3 := []string{"Ranger Rosa", "Ranger Amarillo"}
rangers3 = append(rangers3, "Ranger Blanco")
fmt.Println("Tamaño", len(rangers3))
fmt.Println("Capacidad", cap(rangers3))
rangers3 = append(rangers3, "Ranger Rojo", "Ranger Negro")
fmt.Println("Tamaño", len(rangers3))
fmt.Println("Capacidad", cap(rangers3))
Vemos que tenemos de repente una capacidad de 8:
Creo que con esto mas o menos queda clara la diferencia entre los arrays definidos y los slices, hablando de consumo de memoria y del propio uso de uno u otro, siempre sera recomendable tener arrays definidos, veremos más adelante alguna forma de pasar de uno a otro.
En el próximo post veremos tambien como trabajar con ambos, para recorrerlos, por ejemplo, y varias cosas mas.













