Ответ 1
Для массивов есть нечто большее, чем просто фиксированная длина: они сопоставимы и являются значениями (не ссылочными или указательными типами).
В определенных ситуациях существует множество преимуществ массивов перед срезами, которые вместе более чем оправдывают существование массивов (вместе со срезами). Пусть увидят их. (Я даже не считаю массивы строительными блоками срезов.)
1. Сравнение означает, что вы можете использовать массивы в качестве ключей на картах, но не срезы. Да, вы могли бы сейчас сказать, что почему бы не сделать срезы сопоставимыми, чтобы одно это не оправдывало существование обоих. Равенство не очень хорошо определено на срезах. Часто задаваемые вопросы: почему карты не допускают использование срезов в качестве ключей?
Они не реализуют равенство, потому что равенство плохо определено для таких типов; Есть несколько соображений, касающихся поверхностного и глубокого сравнения, сравнения указателя и значения, как обращаться с рекурсивными типами и т.д.
2. Массивытакже могут обеспечить более высокую безопасность во время компиляции, поскольку границы индекса можно проверять во время компиляции (длина массива должна быть равна неотрицательной константе, представляемой значением типа 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. Также передача или присвоение значений массива неявно создаст копию всего массива, поэтому он будет "отделен" от исходного значения. Если вы передадите срез, он все равно сделает копию, но только из заголовка среза, но значение среза (заголовок) будет указывать на тот же массив резервных копий. Это может или не может быть то, что вы хотите. Если вы хотите "отделить" срез от "оригинального", вам необходимо явно скопировать содержимое, например, с помощью встроенной функции copy()
для нового среза.
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. Кроме того, поскольку длина массива является частью типа массива, массивы разной длины являются разными типами. С одной стороны, это может быть "боль в заднице" (например, вы пишете функцию, которая принимает параметр типа [4]int
, вы не можете использовать эту функцию, чтобы взять и обработать массив типа [5]int
), но это также может быть преимуществом: это может быть использовано для явного указания длины массива, который ожидается. Например. Вы хотите написать функцию, которая принимает адрес IPv4, ее можно смоделировать с помощью типа [4]byte
. Теперь у вас есть гарантия времени компиляции, что значение, передаваемое вашей функции, будет иметь ровно 4 байта, не больше и не меньше (что в любом случае будет неверным IPv4-адресом).
5. По сравнению с предыдущим, длина массива также может служить целям документации. Тип [4]byte
правильно документирует, что IPv4 имеет 4 байта. Переменная rgb
типа [3]byte
сообщает, что для каждого компонента цвета есть 1 байт. В некоторых случаях он даже вынимается и доступен, документируется отдельно; например, в пакете crypto/md5
: md5.Sum()
возвращает значение типа [Size]byte
, где md5.Size
является константой 16
: длина контрольной суммы MD5.
6. Они также очень полезны при планировании структуры памяти структурных типов, см. ответ JimB здесь и этот ответ более подробно и в реальном примере.
7. Кроме того, поскольку срезы являются заголовками и они (почти) всегда передаются как есть (без указателей), языковая спецификация более ограничительна в отношении указателей на срезы, чем указателей на массивы. Например, спецификация предоставляет несколько сокращений для работы с указателями на массивы, в то время как то же самое дает ошибку времени компиляции в случае срезов (поскольку редко использовать указатели на срезы, если вы все еще хотите/должны это делать, вы должны быть о том, как с этим обращаться, читайте в этом ответе).
Вот такие примеры:
Нарезка указателя
p
на массив:p[low:high]
является сокращением для(*p)[low:high]
. Еслиp
является указателем на слайс, это ошибка времени компиляции (spec: выражения слайса).Индексирование указателя
p
на массив:p[i]
является сокращением для(*p)[i]
. Еслиp
является указателем на фрагмент, это ошибка времени компиляции (spec: выражения индекса).
Пример:
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. Доступк (одиночным) элементам массива более эффективен, чем к элементам слайса; как и в случае срезов, среда выполнения должна проходить через неявную разыменование указателя. Также "выражения len(s)
и cap(s)
являются константами, если тип s
является массивом или указателем на массив".
Может быть, это удивительно, но вы даже можете написать:
type IP [4]byte
const x = len(IP{}) // x will be 4
Он действителен и оценивается во время компиляции, даже если IP{}
не является константным выражением, например, const i = IP{}
будет ошибкой во время компиляции! После этого даже не удивительно, что работает следующее:
const x2 = len((*IP)(nil)) // x2 will also be 4
Note: When ranging over a complete array vs a complete slice, there may be no performance difference at all as obviously it may be optimized so that the pointer in the slice header is only dereferenced once. For details / example, see Array vs Slice: accessing speed.
См. связанные вопросы, где можно использовать массив/имеет больше смысла, чем фрагмент:
Зачем использовать массивы вместо слайсов?
Хеш с ключом в качестве типа массива
Как элегантно проверить равенство трех значений?
Нарезка указателя фрагмента, переданного в качестве аргумента
И это просто для любопытства: фрагментможет содержать сам себя, а массив -. (На самом деле это свойство облегчает сравнение, поскольку вам не нужно иметь дело с рекурсивными структурами данных).
Обязательно прочитайте блоги: