На этом шаге рассмотрим массивы в Go.
Массив представляет собой последовательность фиксированной длины из нуля или более элементов определенного типа. Из-за фиксированной длины массивы редко используются в Go непосредственно.
Доступ к отдельным элементам массива осуществляется с помощью обычных обозначений индексирования, значения индексов в которых имеют значения от нуля до значения, на единицу меньшего длины массива. Встроенная функция len возращает количество элементов в массиве.
var а [3]int // Массив ив трех целых чисел fmt.Println(a[0]) // Вывод первого элемента fmt.Println(a[len(a)-1]) // Вывод последнего элемента, а[2] // Вывод значений индексов и элементов for i, v := range а { fmt.Printf("%d %d\n", i, v) } //Вывод только элементов for v := range a { fmt.Printf("%d\n", v) }
По умолчанию элементам новой переменной массива изначально присваиваются нулевые значения типа элемента (для чисел это значение 0). Для инициализации массива списком значений можно использовать литерал массива:
var q [3]int = [3]int{l, 2, 3} var r [3]int = [3]int{l, 2} fmt.Println(r[2]) // 0
Если в литерале массива на месте длины находится троеточие "...", то длина массива определяется количеством инициализаторов. Определение q можно упростить до
q := [...]int{l, 2, 3} fmt.Printf("%T\n", q) // [3]int
Размер массива является частью его типа, так что типы [3]int и [4]int различны. Размер должен быть константным выражением, т.е. выражением, значение которого может быть вычислено во время компиляции программы.
q := [3]int{l, 2, 3} q = [4]int{l, 2, 3, 4} //Ошибка компиляции: нельзя присвоить // [4]int переменной типа [3]int
Выше используется упорядоченный список значений, но можно также указать список пар "индекс-значение":
type Currency int const ( USD Currency = iota EUR GBP RUR ) symbol := [...]string{USD: "$", EUR: "€", FRA: "₣", RUR: "₽"} fmt.Println(RUR, symbol[RUR]) // 3 "₽"
В этом случае индексы могут появляться в любом порядке, а некоторые из них могут быть опущены; как и прежде, неуказанные значения получают нулевое значение типа элемента. Например,
r := [...]int{99:-l}
определяет массив r со ста элементами, среди которых ненулевым является только последний элемент, значение которого равно -1.
Если тип элемента массива является сравниваемым, то таким же является и тип массива, так что мы можем сравнить два массива такого типа непосредственно, с помощью оператора ==, который сообщает, все ли соответствующие элементы массивов равны. Оператор ! = является отрицанием оператора ==.
а := [2]int{l, 2} b := [...]int{l, 2} с := [2]int{l, 3} fmt.Println(a == b, a == с, b == с) // true false false d := [3]int{l, 2} fmt.Println(a == d) // Ошибка компиляции: разные типы [2]int и [3]int
Задание 1. Приведем пример функции Sum256 из пакета crypto/sha256, которая генерирует криптографический хеш, или дайджест, SHA256 сообщения, хранящегося в произвольном байтовом срезе. Дайджест состоит из 256 битов, поэтому его типом является [32] byte. Если два дайджеста совпадают, то вероятно, что соответствующие сообщения одинаковы; если же дайджесты различаются, то различаются и сообщения. Приведем программу выводящую и сравнивающую дайджесты SHA256 для "a" и "A".
Раскрыть/скрыть решение и комментарии.
При вызове функции копия каждого значения аргумента присваивается переменной соответствующего параметра, так что функция получает копию, а не оригинал. Передача таким образом больших массивов может быть неэффективной, а любые изменения, вносимые функцией в элементы массива, будут влиять только на копию, но не на оригинал. Поэтому Go работает с массивами как с любым другим типом, но это поведение отличается от поведения языков, которые неявно передают массивы по ссылке.
Конечно, можно явно передать указатель на массив, так что любые изменения, которые функция будет делать в элементах массива, будут видны вызывающей функции. Приведенная ниже функция заполняет нулями содержимое массива [32]byte:
func zero(ptr *[32]byte) { for i := range ptr { ptr[i] = 0 } }
Литерал массива [32]byte{} дает нам массив из 32 байтов. Каждый элемент массива имеет нулевое значение для типа byte, которое просто равно нулю. Мы можем использовать этот факт для написания другой версии функции zero:
func zero(ptr *[32]byte) { *ptr = [32]byte){} }
Применение указателя на массив оказывается эффективным и позволяет вызываемой функции изменять переменные вызывающей функции, но массивы остаются негибким решением из-за присущего им фиксированного размера. Например, функция zero не примет указатель на переменную [16]byte; нет также никакого способа добавления или удаления элементов массива. По этим причинам, за исключением особых случаев, таких как хеш SHA256 фиксированного размера, массивы в качестве параметров функции используются редко; вместо этого обычно используются срезы.
На следующем шаге рассмотрим срезы в Go.