Массив — это последовательность фиксированной длины, состоящая из элементов одного типа. Массивы используются для работы с набором элементов, количество которых не изменяется, и там, где нужно иметь набор однотипных элементов известной длины.

Представим, что у нас есть задача хранить набор значений: например, среднесуточную температуру за каждый день недели (всегда 7 дней).

Тогда здесь можно объявить массив:

var lastWeekTemp [7]int 

Число в скобках определяет длину массива, а литерал после скобок — тип элементов.

В Go количество элементов в массиве — это часть типа, то есть массивы [3]int и [5]int относятся к разным типам.

Чтобы определить в коде такую переменную, компилятор выделит в памяти область, размер которой будет определяться количеством элементов, умноженным на размер элементов. Каждый элемент будет проинициализирован значением по умолчанию.

Все элементы в памяти расположатся последовательно, поэтому доступ к ним будет иметь константную сложность O(1).

Для обращения к элементам массива используются квадратные скобки []:

tempOnWednesday := lastWeekTemp[2] 

В Go индексация массивов начинается с нуля, как и в большинстве других языков программирования.

Довольно часто встречается ситуация, когда при объявлении массива требуется задать значения массива сразу. В Go можно совместить объявление и инициализацию в одной конструкции.

Инициализация производится с помощью литерала массива:

thisWeekTemp := [7]int {-3,5,7} // [-3 5 7 0 0 0 0]
rgbColor := [3]uint8 {255, 255, 128} // [255 255 128] 

Здесь указывается сначала тип массива [7]int, а затем в фигурных скобках через запятую — элементы массива, то есть список инициализации.

Для компилятора тип массива в приоритете: если вы указали массив из семи элементов, но в списке инициализации всего три, то оставшиеся элементы будут проинициализированы значениями по умолчанию.

Компилятор следит за размером списка инициализации, и уже такую конструкцию скомпилировать не получится:

rgbColor := [3]uint8{1, 2, 3, 4} // array index 3 out of bounds [0:3] 

Количество элементов в массиве может быть выведено автоматически по длине списка инициализации. Для этого используется следующая конструкция:

rgbColor := [...]uint8{255, 255, 128} // [255 255 128] len = 3
rgbaColor := [...]uint8{255, 255, 128, 1} // [255 255 128 1] len = 4 

Три точки указывают компилятору, что размер массива должен быть выведен исходя из размера списка инициализации.

Иногда возникает необходимость указать в списке инициализации только один или несколько элементов массива, а другие не трогать.

Если бы вам понадобилось указать значение среднесуточной температуры в воскресенье, код вряд ли порадовал бы своей утончённостью:

thisWeekTemp := [7]int {0,0,0,0,0,0,11} // [0 0 0 0 0 0 11] 

Для больших массивов это превратилось бы в нечитаемую конструкцию:

var thisWeekTemp [7]int // [0 0 0 0 0 0 0]
thisWeekTemp[6] = 11 // [0 0 0 0 0 0 11] 

Однако в списке инициализации можно указать только нужные элементы и их индексы. Индекс и значение указываются через двоеточие.

thisWeekTemp := [7]int {6:11, 2:3} // [0 0 3 0 0 0 11] 

Размер массива может быть получен встроенной функцией len. Так как размер массива известен на этапе компиляции, то вычисление этой функции при компиляции подменяется конкретным значением.

Если попытаться обратиться к элементу массива за пределами его размера, то сработает механизм защиты памяти в Go и начнётся паника. В отличие от языка C, компилятор и рантайм Go контролируют выход за пределы массива, не позволяя обратиться к недопустимым областям памяти.

Многомерные массивы

Многомерные массивы создаются так же, как и одномерные. Каждая размерность массива указывается в отдельных квадратных скобках.

var thisMonthTemp [4][7]int // массив из четырёх недель, каждая из которых — массив из семи дней  

Многомерные массивы удобно представлять себе как массив массивов:

var rgbImage [1080][1920][3]uint8 // изображение — это массив из 1080 строк длиной в 1920 пикселей. Каждый пиксель — массив из трёх байт
            // 1080 — размер массива
            // [1920][3]uint8 — тип элемента 

Доступ к элементам многомерного массива осуществляется через квадратные скобки:

 line := rgbImage [2] // 3-я строка в изображении
 pixel := rgbImage[2][3] // 4-й пиксель в третьей строке изображения
 red :=  rgbImage[2][3][1] // значение синей компоненты (второй байт) 4-го пикселя в третьей строке изображения 

Обход значений массива

Для работы с массивами часто используются циклы. Например, когда нужно посчитать среднюю температуру (округлённо) в течение недели:

var weekTemp = [7]int{5, 4, 6, 8, 11, 9, 5} 
 
sumTemp := 0
 
for i:= 0; i < len(weekTemp); i++ {
    sumTemp += weekTemp[i]
}
 
average := sumTemp / len(weekTemp) 

Здесь вводится дополнительная переменная i, которая увеличивается на каждом шаге. В Go есть более удобная конструкция for range, которая позволяет обойти элементы массива последовательно, не используя дополнительные переменные:

var weekTemp = [7]int{5, 4, 6, 8, 11, 9, 5} 
 
sumTemp := 0
 
for _, temp := range weekTemp {
    sumTemp += temp
}
 
average := sumTemp / len(weekTemp) 

Оператор range на каждой итерации возвращает индекс и значение следующего элемента в массиве.

Note

Обратите внимание на следующий важный момент: конструкция _, temp := range weekTemp создаёт новую переменную temp, тип которой будет определяться типом элемента массива.

Этой переменной на каждой итерации цикла будет присваиваться следующее значение из массива. Если изменить значение переменной temp, то это не повлияет на значения в массиве.

Чтобы получить доступ к элементу массива, понадобится индекс:

var weekTemp = [7]int{5, 4, 6, 8, 11, 9, 5} 
// weekTemp [5 4 6 8 11 9 5]
for _, temp := range weekTemp {
   temp = 0 
}
// weekTemp [5 4 6 8 11 9 5] ! — значения не изменились
// если значение элемента не используется, можно опустить вторую переменную
for i := range weekTemp {
   weekTemp[i] = 0 
}
// weekTemp [0 0 0 0 0 0 0] ! — значения изменились 

Переменные массивов можно присваивать друг другу, однако у них должен быть одинаковый тип, причём количество и тип элементов должны совпадать.

В процессе присваивания выполняется полное копирование массива, и если программа обрабатывает достаточно большие массивы данных, то эти копирования могут существенно замедлить работу программы и увеличить потребление памяти.

Передача массива (как и переменной любого другого типа) в функцию — это копирование его значения в переменную аргумента функции. Здесь тоже будет полное копирование.

Note

С циклом for — range связан ещё один важный момент — операнд range копируется во временную переменную, которая уже используется для обхода.

Это также может замедлить выполнение программы для массивов, поэтому следует использовать взятие указателя, чтобы не простаивать:

for i, temp := range &weekTemp {
    fmt.Println(i, temp)
} 

Преимущества применения массивов

  1. Элементы массива всегда располагаются в памяти последовательно, этому радуется процессор и ускоряет выполнение программы.
  2. Массивы имеют фиксированную длину, поэтому выделение памяти под массив происходит ровно один раз в момент его объявления.
  3. Время доступа к элементам массива минимальное.
  4. Go проверяет выход за пределы массива на этапе компиляции, если может вычислить значение индекса элемента на этапе компиляции, и во время исполнения программы. В первом случае будет ошибка компиляции, а во втором — паника. Панику лучше не допускать.

Недостатки применения массивов

  1. Массивы могут быть только фиксированной длины: если количество элементов нам заранее неизвестно, память придётся выделять с запасом.
  2. Массивы передаются и присваиваются с полным копированием элементов, что грозит внезапным ухудшением производительности и увеличенным расходом памяти.
  3. Для обработки массивов разных габаритов придётся писать разные функции (если не используются дженерики).

Массивы следует применять крайне обдуманно, когда размеры вашего массива точно известны на этапе компиляции. Это позволяет ускорить работу программы.

Чтобы исправить недостатки массивов, в Go введены слайсы, которые будут рассмотрены ниже.


📂 Go | Последнее изменение: 17.08.2024 22:10