arrays - form - ¿Por qué tener matrices en Go?
javascript get form values (4)
Cada conjunto podría ser un segmento, pero no todos los segmentos podrían ser un conjunto. Si tiene un tamaño de colección fijo, puede obtener una mejora de rendimiento menor al usar una matriz. Como mínimo, ahorrará el espacio ocupado por el encabezado del sector.
Entiendo la diferencia entre arrays y slices en Go. Pero lo que no entiendo es por qué es útil tener matrices en absoluto. ¿Por qué es útil que una definición de tipo de matriz especifique una longitud y un tipo de elemento? ¿Por qué cada "matriz" que usamos no puede ser una porción?
Hay más en las arrays que la longitud fija: son comparable y son valores (no tipos de referencia o puntero).
Existen innumerables ventajas de los arreglos sobre los segmentos en ciertas situaciones, todos juntos más que justificar la existencia de conjuntos (junto con los segmentos). A verlos. (Ni siquiera estoy contando que las matrices sean los bloques de construcción de las rebanadas).
1. Ser comparable significa que puedes usar matrices como claves en los mapas , pero no cortes. Sí, podría decir ahora que por qué no hacer comparables rebanadas entonces, para que esto solo no justifique la existencia de ambas. La igualdad no está bien definida en los sectores. Preguntas frecuentes: ¿Por qué los mapas no permiten cortes como claves?
No implementan la igualdad porque la igualdad no está bien definida en tales tipos; Existen múltiples consideraciones que involucran la comparación superficial vs. profunda, la comparación de puntero versus valor, cómo tratar con los tipos recursivos, etc.
2. Las
matrices también pueden brindarle una mayor seguridad en tiempo de compilación
, ya que los límites del índice se pueden verificar en tiempo de compilación (la longitud de la matriz debe evaluarse a una
constant
no negativa representable por un valor de tipo
int
):
s := make([]int, 3)
s[3] = 3 // "Only" a runtime panic: runtime error: index out of range
a := [3]int{}
a[3] = 3 // Compile-time error: invalid array index 3 (out of bounds for 3-element array)
3.
También
pasar o asignar valores de matriz implícitamente hará una copia
de toda la matriz, por lo que se "separará" del valor original.
Si pasa un segmento, seguirá haciendo una copia pero solo del
encabezado
del segmento, pero el valor del segmento (el encabezado) apuntará a la misma matriz de respaldo.
Esto puede o no ser lo que quieres.
Si desea "separar" un segmento del "original", debe copiar explícitamente el contenido, por ejemplo, con la función incorporada
copy()
en un nuevo segmento.
a := [2]int{1, 2}
b := a
b[0] = 10 // This only affects b, a will remain {1, 2}
sa := []int{1, 2}
sb := sa
sb[0] = 10 // Affects both sb and sa
4.
Además, dado que la longitud de la matriz es parte del tipo de matriz, las
matrices con diferente longitud son tipos distintos
.
Por un lado, esto puede ser un "dolor en el culo" (por ejemplo, si escribe una función que toma un parámetro de tipo
[4]int
, no puede usar esa función para tomar y procesar una matriz de tipo
[5]int
) , pero esto también puede ser una ventaja: puede usarse para
especificar explícitamente la longitud
de la matriz que se espera.
Por ejemplo, si desea escribir una función que tome una dirección IPv4, se puede modelar con el
[4]byte
tipo
[4]byte
.
Ahora tiene una garantía en tiempo de compilación de que el valor pasado a su función tendrá exactamente 4 bytes, ni más ni menos (que de todos modos sería una dirección IPv4 no válida).
5. En
relación con lo anterior,
la longitud de la matriz también puede servir para fines de documentación
.
Un
[4]byte
tipo
[4]byte
documenta correctamente que IPv4 tiene 4 bytes.
Una variable
rgb
de tipo
[3]byte
indica que hay 1 byte para cada componente de color.
En algunos casos, incluso se saca y está disponible, documentado por separado;
por ejemplo en el paquete
crypto/md5
:
md5.Sum()
devuelve un valor de tipo
[Size]byte
donde
md5.Size
es una constante que es
16
: la longitud de una suma de comprobación MD5.
6. También son muy útiles cuando se planifica el diseño de memoria de los tipos de estructura , vea la respuesta de JimB aquí, y esta respuesta con mayor detalle y un ejemplo de la vida real .
7. Además, dado que los sectores son encabezados y (casi) siempre se pasan tal cual (sin punteros), la especificación del lenguaje es más restrictiva con respecto a los punteros a sectores que a los matrices . Por ejemplo, la especificación proporciona múltiples shorthands para operar con punteros a matrices, mientras que el mismo da un error en tiempo de compilación en caso de cortes (porque es raro usar punteros a cortes, si todavía quiere / tiene que hacerlo, debe ser explícito sobre cómo manejarlo; lea más en esta respuesta ).
Tales ejemplos son:
-
Cortar un puntero
p
en la matriz:p[low:high]
es una abreviatura de(*p)[low:high]
. Sip
es un puntero para cortar, este es un error en tiempo de compilación ( especificación: Expresiones de corte). -
Indexación de un puntero
p
a la matriz:p[i]
es una abreviatura de(*p)[i]
. Sip
es puntero a un segmento, este es un error de tiempo de compilación ( especificación: expresiones de índice ).
Ejemplo:
pa := &[2]int{1, 2}
fmt.Println(pa[1:1]) // OK
fmt.Println(pa[1]) // OK
ps := &[]int{3, 4}
println(ps[1:1]) // Error: cannot slice ps (type *[]int)
println(ps[1]) // Error: invalid operation: ps[1] (type *[]int does not support indexing)
8.
Acceder a elementos de matriz (individuales) es más eficiente
que acceder a elementos de segmento;
Como en el caso de los segmentos, el tiempo de ejecución debe pasar por una desreferencia de puntero implícita.
Además,
"las expresiones
len(s)
y
cap(s)
son constantes si el tipo de
s
es una matriz o un puntero a una matriz"
.
Puede ser sorprendente, pero incluso puedes escribir:
type IP [4]byte
const x = len(IP{}) // x will be 4
Es válido y se evalúa en tiempo de compilación aunque
IP{}
no sea una
expresión constante,
por lo
que,
por ejemplo,
const i = IP{}
sería un error en tiempo de compilación.
Después de esto, ni siquiera es sorprendente que lo siguiente también funcione:
const x2 = len((*IP)(nil)) // x2 will also be 4
Nota: cuando se extiende sobre una matriz completa frente a un segmento completo, puede que no haya ninguna diferencia de rendimiento, ya que obviamente se puede optimizar de modo que el puntero en el encabezado del segmento solo se desreferencia una vez. Para detalles / ejemplo, vea Array vs Slice: velocidad de acceso .
Consulte las preguntas relacionadas en las que se puede usar una matriz / tiene más sentido que una porción:
¿Por qué usar matrices en lugar de rebanadas?
Hash con clave como tipo de matriz
Golang: ¿Cómo verifico la igualdad de tres valores con elegancia?
Cortar un puntero de corte pasado como argumento
Y esto es solo por curiosidad: una porción puede contenerse a sí misma mientras que una matriz no puede . (En realidad, esta propiedad facilita la comparación ya que no tiene que lidiar con estructuras de datos recursivas).
Blogs de lectura obligatoria:
Las matrices son más eficientes para ahorrar espacio. Si nunca actualiza el tamaño del segmento (es decir, comienza con un tamaño predefinido y nunca lo supera), realmente no hay mucha diferencia de rendimiento. Pero hay una sobrecarga adicional en el espacio, ya que un segmento es simplemente un contenedor que contiene la matriz en su núcleo. Contextualmente, también mejora la claridad, ya que hace que el uso previsto de la variable sea más evidente.
Las matrices son valores, y a menudo es útil tener un valor en lugar de un puntero.
Los valores se pueden comparar, por lo tanto, puede usar matrices como claves de mapa.
Los valores siempre se inicializan, por lo que no es necesario inicializarlos o
make
como lo hace con un segmento.
Las matrices le dan un mejor control del diseño de la memoria, donde como no puede asignar espacio directamente en una estructura con un segmento, puede hacerlo con una matriz:
type Foo struct {
buf [64]byte
}
Aquí, un valor de
Foo
contendrá un valor de 64 bytes, en lugar de un encabezado de segmento que debe inicializarse por separado.
Las matrices también se utilizan para rellenar estructuras para que coincidan con la alineación cuando interoperan con el código C y para evitar el intercambio falso para un mejor rendimiento de la caché.
Otro aspecto para mejorar el rendimiento es que puede definir mejor el diseño de la memoria que con los segmentos, porque la localidad de los datos puede tener un gran impacto en los cálculos intensivos de memoria. Anular la referencia de un puntero puede llevar un tiempo considerable en comparación con las operaciones que se realizan en los datos, y copiar valores más pequeños que una línea de caché conlleva un costo muy bajo, por lo que el código crítico de rendimiento a menudo usa matrices solo por esa razón.