Um guia completo para matrizes e fatias de Golang

A tradução do artigo foi preparada especificamente para os alunos do curso Golang Developer , cujas aulas começam hoje!




No início, é fácil perceber matrizes e fatias como uma e a mesma coisa, mas com nomes diferentes: ambas são uma estrutura de dados para representar coleções. No entanto, na realidade, eles são muito diferentes um do outro.

Neste artigo, veremos suas diferenças e implementações no Go.

Passaremos a exemplos para que você possa tomar uma decisão mais informada sobre onde aplicá-los.

Matrizes


Uma matriz é uma coleção de tamanho fixo. A ênfase aqui é colocada em um tamanho fixo, porque assim que você definir o comprimento da matriz, mais tarde não poderá alterá-lo.

Vejamos um exemplo. Vamos criar uma matriz de quatro valores inteiros:

arr := [4]int{3, 2, 5, 4} 


Comprimento e Tipo


No exemplo acima, a variável arr definida como uma matriz do tipo [4]int , o que significa que a matriz consiste em quatro elementos. É importante observar que o tamanho 4 incluído na definição de tipo.

Daqui resulta que, de fato, matrizes de comprimentos diferentes são matrizes de tipos diferentes. Você não pode igualar matrizes de comprimentos diferentes entre si e não pode atribuir o valor de uma matriz a outra neste caso:

 longerArr := [5]int{5, 7, 1, 2, 0} longerArr = arr // This gives a compilation error longerArr == arr // This gives a compilation error 


Descobri que é fácil falar sobre matrizes em termos de estruturas. Se você tentasse criar uma estrutura semelhante a uma matriz, provavelmente obteria o seguinte:

 // Struct equivalent for an array of length 4 type int4 struct { e0 int e1 int e2 int e3 int } // Struct equivalent for an array of length 5 type int5 struct { e0 int e1 int e2 int e3 int e5 int } arr := int4{3, 2, 5, 4} longerArr := int5{5, 7, 1, 2, 0} 

Na verdade, isso não é recomendado, mas é uma boa maneira de ter uma idéia de por que matrizes de comprimentos diferentes são matrizes de tipos diferentes.


Representação de memória


A matriz é armazenada como uma sequência de n blocos de um determinado tipo:



Essa memória é alocada quando você inicializa uma variável de matriz.

Passar por link


Go não passa por referência, mas tudo passa por valor. Se você atribuir o valor da matriz a outra variável, o valor atribuído será simplesmente copiado.



Se você deseja passar apenas uma "referência" para a matriz, use ponteiros:



Ao alocar memória e em uma função, uma matriz é na verdade um tipo de dados simples e funciona da mesma maneira que as estruturas.

Fatias


Fatias podem ser consideradas como uma implementação estendida de matrizes.
Fatias foram implementadas no Go para cobrir alguns dos casos de uso extremamente comuns que os desenvolvedores encontram ao trabalhar com coleções, como redimensionar coleções dinamicamente.

Uma declaração de fatia é muito semelhante a uma declaração de matriz, exceto que o especificador de comprimento é omitido:

 slice := []int{4, 5, 3} 


Se você apenas olhar o código, parece que as fatias e matrizes são bastante semelhantes, mas a principal diferença está na implementação e nas condições de uso.

Representação de memória


Uma fatia é alocada de maneira diferente de uma matriz e é essencialmente um ponteiro modificado. Cada fatia contém três blocos de informações:

  1. Ponteiro para uma sequência de dados.
  2. O comprimento, que determina o número de elementos que estão atualmente contidos na fatia.
  3. Capacidade (capacidade), que determina o número total de células de memória fornecidas.




Segue-se que fatias de diferentes comprimentos podem ser atribuídas umas às outras. Eles são do mesmo tipo e o ponteiro, comprimento e volume podem variar:

 slice1 := []int{6, 1, 2} slice2 := []int{9, 3} // slices of any length can be assigned to other slice types slice1 = slice2 


Uma fatia, diferentemente de uma matriz, não aloca memória durante a inicialização. De fato, as fatias são inicializadas com um valor nil .

Passar por link


Quando você atribui uma fatia a outra variável, ainda está passando o valor. Aqui, o valor refere-se apenas ao ponteiro, comprimento e volume, e não à memória ocupada pelos próprios elementos.



Adicionando novos itens


Para adicionar novos elementos à fatia, você deve usar a função de append .

 nums := []int{8, 0} nums = append(nums, 8) 


Sob o capô, será como atribuir um valor especificado para um novo elemento e, depois disso - retornar uma nova fatia. O comprimento da nova fatia será mais um.



Se, ao adicionar um elemento, o comprimento aumenta em um e, assim, excede o volume declarado, é necessário fornecer um novo volume (nesse caso, o volume atual geralmente dobra).

É por isso que geralmente é recomendado criar uma fatia com o comprimento e o volume especificados previamente (especialmente se você tiver uma idéia clara do tamanho da fatia que precisa):

 arr := make([]int, 0, 5) // This creates a slice with length 0 and capacity 5 


O que usar: matrizes ou fatias?


Matrizes e fatias são coisas completamente diferentes e, portanto, seus casos de uso também variam.

Vejamos alguns exemplos de código aberto e a biblioteca padrão Go para entender o que usar e quando.

Caso 1: UUID


UUIDs são dados de 128 bits que são frequentemente usados ​​para marcar um objeto ou entidade. Geralmente eles são representados como valores hexadecimais, separados por traços:

 e39bdaf4-710d-42ea-a29b-58c368b0c53c 


Na biblioteca do Google UUID , o UUID é representado como uma matriz de 16 bytes:

 type UUID [16]byte 

Isso faz sentido, pois sabemos que o UUID consiste em 128 bits (16 bytes). Não vamos adicionar ou remover nenhum bytes do UUID e, portanto, o uso da matriz para representá-lo será.

Caso 2: classificando valores inteiros


Neste exemplo, usaremos a função sort.Ints da biblioteca padrão de classificação :

 s := []int{5, 2, 6, 3, 1, 4} // unsorted sort.Ints(s) fmt.Println(s) // [1 2 3 4 5 6] 


A função sort.Ints pega uma fatia de números inteiros e os classifica em ordem crescente de valores. É preferível usar fatias aqui por dois motivos:

  1. O número de números inteiros não é especificado (o número de números inteiros para classificação pode ser qualquer um);
  2. Os números precisam ser classificados em ordem crescente. O uso de uma matriz garantirá que toda a coleção de números inteiros seja passada como um valor; portanto, a função classificará sua própria cópia, não a coleção passada para ela.


Conclusão


Agora que examinamos as principais diferenças entre matrizes e fatias, bem como seus casos de uso, desejo dar algumas dicas para facilitar a decisão de qual design usar:

  1. Se uma entidade for descrita por um conjunto de elementos não vazios de comprimento fixo, use matrizes.
  2. Ao descrever uma coleção à qual você deseja adicionar ou da qual excluir itens, use fatias.
  3. Se a coleção puder conter qualquer número de elementos, use fatias.
  4. Você mudará a coleção de alguma maneira? Nesse caso, as fatias devem ser usadas.


Como você pode ver, as fatias cobrem a maioria dos cenários para a criação de aplicativos Go. No entanto, as matrizes têm o direito de existir e, além disso, são incrivelmente úteis, especialmente quando um caso de uso adequado aparece.

Source: https://habr.com/ru/post/pt465613/


All Articles