---
title: "Массивы"
description: "Массивы go: тип [3]int с фиксированной длиной, литералы и [...]int, копирование при присваивании, сравнение == и сетки [2][3]int. Примеры в песочнице."
course: "Go с нуля: базовый синтаксис и конструкции"
url: "https://golangmaster.ru/courses/go-basics/lessons/16-massivy/"
published: "2026-09-19"
updated: "2026-09-28"
minutes: 15
---

# Массивы

*Курс «Go с нуля: базовый синтаксис и конструкции», урок 16 из 25. ~15 минут. Обновлено 28.09.2026. Источник: https://golangmaster.ru/courses/go-basics/lessons/16-massivy/*

Массивы go — это коллекции с фиксированной длиной, зашитой прямо в тип: [3]int и [4]int — два разных типа, и присвоить один другому не выйдет. В этом уроке разберём литералы (полный, короткий [...]int и индексированный), нулевые значения, перебор, копирование при присваивании, сравнение через == и сетки [2][3]int — а в конце честно ответим, почему в прикладном коде массивы встречаются редко и что занимает их место.

### Массивы go: фиксированная длина как часть типа

Шахматная доска — 64 клетки и ни одной больше. Пиксель RGB — три канала. Хеш SHA-256 — ровно 32 байта. Когда размер коллекции известен заранее и меняться не будет, для неё в Go есть специальный тип — массив. Записывается он как [n]T, где n — количество элементов, а T — тип каждого: [3]int — три числа, [4]string — четыре строки, [32]byte — тридцать два байта.

Главное, что нужно запомнить про массивы go: фиксированная длина — часть типа. [3]int и [4]int — два разных, никак не связанных типа, как int и string; присвоить значение одного переменной другого не получится, компилятор остановит программу раньше, чем она запустится. Массив нельзя растянуть, обрезать или приписать к нему ещё один элемент — размер выбирается один раз при создании и живёт с массивом до конца.

Длину возвращает функция len(): для массива это константа, известная уже из типа. Начинка по умолчанию — нулевая: свежий [3]int — это три нуля, свежий [2]string — две пустые строки. Память под массив выделяется вся сразу, одним куском, без перестановок и перераспределений — этим массивы и берут, когда размер действительно не меняется. Посмотрим на обе формы объявления.

### Две формы объявления

```go
package main

import "fmt"

func main() {
        // Нулевое значение: под все элементы уже выделены нули
        var zeros [3]int
        var names = [3]string{"анна", "борис", "вера"}

        fmt.Println(zeros, len(zeros))
        fmt.Println(names, len(names))
}
```

На настоящем Go программа напечатает «[0 0 0] 3» и «[анна борис вера] 3»: форма var zeros [3]int без литерала создаёт массив из нулей — это его нулевое значение. Блок помечен как незапускаемый из-за честной оговорки: запасной TS-интерпретатор песочницы (он же проверочный в probe-скриптах) форму var без литерала понимает неправильно и делает массив нулевой длины. Основной движок песочницы — настоящий Go — покажет три нуля. Все ранимые примеры урока поэтому объявляют массивы литералами: для них оба движка совпадают.

### Песочница: первый массив

Запустите: неделя напечатана одной строкой, длина семь, а «четверг» — это индекс 3, потому что счёт идёт с нуля. Поменяйте temps[6] = 25 на temps[0] = 25 и сравните вывод: нулевой индекс и есть понедельник. А теперь попробуйте прочитать temps[7] — индексы кончаются на шести, и песочница ответит ошибкой «индекс за границами» с указанием длины: та же ошибка, что у срезов из прошлых уроков. Заметьте и литерал: семь элементов для [7]int — ровно в полного размера; формы с пропусками разберём ниже.

```go
package main

import "fmt"

func main() {
        temps := [7]int{18, 21, 19, 24, 26, 22, 20}
        fmt.Println(temps)
        fmt.Println("дней:", len(temps))

        fmt.Println("четверг:", temps[3])
        temps[6] = 25
        fmt.Println(temps)
}
```

### Проверьте себя: разные типы

**Вопрос:** В программе написано: a := [3]int{1, 2, 3}, а следом var b [4]int = a. Что скажет настоящий Go?

- Присвоит с потерей: b получит [1 2 3 0]
- Скомпилирует: длины выровняются автоматически
- ✅ Не скомпилирует: [3]int и [4]int — разные типы
- Скомпилирует только с явным приведением [4]int(a)

Длина — часть типа, поэтому [3]int и [4]int несовместимы: настоящий Go ответит ошибкой компиляции вида cannot use a (variable of type [3]int) as [4]int value. Приведения [4]int(a) в языке не существует — длину массива менять нельзя вообще. Честная оговорка: запасной TS-интерпретатор песочницы такую проверку пропускает и молча присваивает, а основной движок (настоящий Go) — отклоняет; проверяйте на настоящем компиляторе.

### Литералы массива: полный, короткий и [...]int

Массив из одних нулей нужен редко, поэтому массив почти всегда создают с литералом — перечислением значений в фигурных скобках. Полная форма повторяет тип: [3]int{5, 0, 9} — три элемента, все на местах. Короткая форма [...]int доверяет подсчёт компилятору: [...]int{40, 25, 17} — это тоже [3]int, только тройку выписывать не пришлось; допишете четвёртое значение — длина пересчитается сама. Для таблиц-констант, состав которых ещё дрожит, это удобно: тип в итоге всё равно конкретный, [4]int, а не «что-то с тремя точками».

Есть и две формы с нулями. Индексированный литерал назначает значения по адресам: [4]int{0: 100, 2: 200} кладёт 100 в ячейку 0, 200 — в ячейку 2, а ячейки 1 и 3 остаются нулями. Частичный литерал [4]int{1, 2} заполняет первые ячейки подряд, а хвост добивает нулями. Обе формы — редкие гости в прикладном коде, но для таблиц с пропусками (коды ошибок, номера клавиш) пригождаются. И важная договорённость на будущее: нули на пропусках — не ошибка, а правило языка; недостающие значения считаются заданными нулевым значением своего типа.

### Четыре формы литерала

```go
package main

import "fmt"

func main() {
        full := [3]int{5, 0, 9}
        short := [...]string{"go", "rust", "python"}
        sparse := [4]int{0: 100, 2: 200} // индексы 0 и 2, пропуски — нули
        partial := [4]int{1, 2}          // начало подряд, хвост — нули

        fmt.Println(full, len(full))
        fmt.Println(short, len(short))
        fmt.Println(sparse, len(sparse))
        fmt.Println(partial, len(partial))
}
```

Настоящий Go напечатает «[5 0 9] 3», «[go rust python] 3», «[100 0 200 0] 4» и «[1 2 0 0] 4». Запускать здесь нечего: индексированная и частичная формы в запасном TS-интерпретаторе разбираются без учёта объявленной длины — получаются «[100 200]» и «[1 2]», то есть вывод разъезжается с настоящим Go. Первые две формы — полная и с [...] — в обоих движках одинаковы, и только они используются во всех ранимых примерах этого урока. На локальном компиляторе все четыре строки дадут то, что написано здесь.

### Что выведет код?

```go
package main

import "fmt"

func main() {
        a := [4]int{3, 1, 4, 1}
        b := a
        b[0] = 100
        fmt.Println(a[0] + b[0])
}
```

**Что выведет программа?**

- ✅ 103: a хранит 3, а b — свою копию со 100
- 200: b и a — один массив, в обеих ячейках 100
- 6: присваивание копирует только заголовок, данные общие
- Ошибку: массив нельзя изменить после создания

Массив — значение: b := a скопировала все четыре элемента, и запись b[0] = 100 осталась внутри копии. В a[0] по-прежнему 3, в b[0] теперь 100, сумма 103. Такой вывод дают и песочница, и настоящий Go — присваивание массивов у них совпадает.

### Массив — значение: копирование при присваивании

Вот привычка, от которой придётся отучиться, если вы пришли из Python или JavaScript: в Go массив — это значение, а не ссылка. Присваивание b := a копирует все элементы в новый массив — не заголовок, не указатель, а всю коробку целиком. Поменяли что-то в b — a осталось прежним. То же при передаче в функцию: параметр-массив получает собственную копию, и сколько бы функция ни старалась, оригинал она не тронет. Это родственно факту из урока про range, где переменная v тоже была копией элемента — тут копируется уже весь массив разом.

Для маленьких массивов поведение только в радость: данные в безопасности, никаких сюрпризов с общим состоянием. Но копировать массив из миллиона элементов лишь ради того, чтобы передать его в функцию, — расточительство, и настоящий код так не делает. Именно поэтому в сигнатурах функций массивы больших размеров не водятся: передают срезы — их копия занимает три машинных слова, сколько бы данных за ними ни стояло. Срезам посвящён следующий урок; а сейчас убедимся в копировании руками.

### Песочница: копия или ссылка

Запустите и прочитайте все четыре строки: backup получил полную копию, поэтому правка второго элемента не задела оригинал, а функция raise меняла свою собственную копию. Теперь эксперимент: замените [3]int на []int в двух местах — в объявлении original и в параметре функции — и запустите снова. Правки полезут в оригинал: срез при присваивании копирует не данные, а ссылку на них. Запомните этот контраст — массив значение, срез окно в данные; детали в следующем уроке.

```go
package main

import "fmt"

func raise(arr [3]int) {
        arr[0] = 999
        fmt.Println("внутри функции:", arr)
}

func main() {
        original := [3]int{1, 2, 3}
        backup := original
        backup[1] = 500

        fmt.Println("оригинал:", original)
        fmt.Println("копия:", backup)

        raise(original)
        fmt.Println("после функции:", original)
}
```

### Проверьте себя: тип литерала

**Вопрос:** Какого типа выражение [...]int{7, 8, 9, 1}?

- []int — срез из четырёх элементов
- ✅ [4]int — длину посчитал компилятор
- [...]int — так в типе и останется
- [3]int — лишний элемент отброшен

Три точки — не тип, а просьба посчитать: компилятор видит четыре элемента и подставляет [4]int. Проверить легко функцией len() — она вернёт 4. А вот в полной форме [n]int{...} число элементов должно совпадать с n точно: лишний или недостающий элемент — ошибка компиляции.

### Перебор массива: for и range

Перебирается массив теми же циклами, что и срез. Классический for со счётчиком и len() — из урока про [цикл for](/courses/go-basics/lessons/13-cikl-for/): аккумулятор объявляется до цикла, индекс бегёт от нуля до len(arr) не включительно, внутри читаем arr[i]. Range из [прошлого урока](/courses/go-basics/lessons/15-range/) работает с массивом так же, как со срезом: две переменные — индекс и копия значения, одна переменная — только индекс, подчёркивание пропускает ненужное. Внутри тела работают break и continue с теми же правилами, что в [уроке 14](/courses/go-basics/lessons/14-while-break-continue/).

Одно правило стоит повторить, потому что оно то и дело ловит новичков: значение v из range — копия. Присвоите v = v * 10 — изменится копия, а массив останется прежним; чтобы поменять элементы, пишите по индексу — arr[i] *= 2. Индекс из заголовка после цикла не живёт: переменные цикла умирают вместе с ним. И ещё: перебор массива всегда конечен, вечный цикл на range не написать — границей служит длина, вшитая в тип.

### Перебор на практике

```go
package main

import "fmt"

func main() {
        cups := [7]int{120, 135, 98, 140, 210, 250, 180}

        sum := 0
        for i := 0; i < len(cups); i++ {
                sum += cups[i]
        }
        fmt.Println("сумма:", sum)

        best := cups[0]
        for _, v := range cups {
                if v > best {
                        best = v
                }
        }
        fmt.Println("рекорд:", best)

        for i := range cups {
                cups[i] = cups[i] / 2
        }
        fmt.Println(cups)
}
```

Три перебора подряд — три типовые задачи. Первый — классический for со счётчиком и len(): аккумулятор объявлен до цикла, индекс нужен для чтения cups[i]. Второй — range с подчёркиванием: значение нужно, индекс нет; обратите внимание, поиск максимума начинается с cups[0], а не с нуля — на отрицательных числах нулевой старт дал бы неверный ответ. Третий — range с одной переменной-индексом: деление по месту меняет сам массив. Блок ранимый — прогоните и сверьте вывод.

### Вставьте пропуски: сумма буфера

Допишите классический перебор массива: граница цикла по длине и печать накопленной суммы. Правильная программа печатает «сумма: 150».

```go
buf := [5]int{10, 20, 30, 40, 50}
total := 0
for i := 0; i < ___(buf); i++ {
        total += buf[i]
}
fmt.Println("сумма:", ___)
```

Подсказка: Первый пропуск — функция длины, она же граница классического for. Второй — аккумулятор, который копился в цикле.

Правильные ответы в пропусках: `len`, `total`

### Сравнение массивов через ==

У массивов есть роскошь, которой срезы лишены: их можно сравнивать оператором ==. Сравнение поэлементное: два массива равны, если равны их длины (а они совпадают уже из типа) и каждый элемент первого равен соответствующему элементу второго. Условие if a == b с массивами — законная и частая запись для поиска «не менялась ли конфигурация». Типы слева и справа должны совпадать до последней цифры длины: сравнить [3]int с [4]int компилятор не даст даже при одинаковых значениях.

Ограничение одно: элементы должны быть сравнимы сами по себе — числа, строки, bool, массивы из них. Массив, внутри которого срез или карта, сравнить == не выйдет. Контраст с будущим: для срезов == запрещён вовсе (кроме сравнения с nil), и равенство двух списков на срезах приходится писать циклом руками. На массивах та же мысль — одна строка. Ещё одна причина ценить массив в golang там, где размер действительно фиксирован: сравнение, копирование и передача работают без оговорок.

### Что выведет код: сравнение копий

```go
package main

import "fmt"

func main() {
        a := [3]int{1, 2, 3}
        b := a
        b[0]++
        fmt.Println(a == b)
}
```

**Что выведет программа?**

- true: после b := a массивы равны, инкремент ничего не меняет
- ✅ false: b — копия, инкремент сделал её [2 2 3]
- Ошибку: массивы не сравниваются через ==
- true: сравниваются адреса, а они совпадают

b — независимая копия: b[0]++ поднял её первый элемент до двух, и массивы разъехались — [1 2 3] против [2 2 3]. Сравнение поэлементное, поэтому false. Если убрать строку b[0]++, сравнение вернуло бы true: копии равны, пока никто не трогает.

### Многомерные массивы: сетки [2][3]int

Массив может быть элементом другого массива — так получаются таблицы. Тип [2][3]int читается справа налево: массив из двух элементов, каждый из которых — массив из трёх int. Литерал повторяет вложенность: две строки по три числа в фигурных скобках. Индексация двумя парами скобок — grid[строка][колонка]; len(grid) вернёт число строк, а len(grid[0]) — длину первой строки, то есть число колонок.

Перебор — вложенный range: внешняя пара отдаёт строку целиком (это тоже копия — массив [3]int), внутренняя гуляет по её элементам. В памяти многомерный массив — один сплошной кусок: строки лежат подряд, без указателей между ними, поэтому для игровых полей и матриц это дёшево и предсказуемо. Типичная запись board[0][0] — левый верхний угол; изменение grid[0][1] = 9 трогает только одну ячейку и ни одну строку больше. Проверим всё это в песочнице.

### Песочница: сетка два на три

Запустите: сетка печатается строка за строкой, len(grid) считает строки, len(grid[0]) — колонки. Обратитесь к левому столбцу: grid[0][0] и grid[1][0]. Затем замените литерал на [3][3]int{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}} — единичную матрицу — и допишите печать следа: grid[0][0] + grid[1][1] + grid[2][2], должно выйти 1. Обратите внимание: внутренняя переменная row в range — это [3]int, и её печать даёт обычную строку в скобках.

```go
package main

import "fmt"

func main() {
        grid := [2][3]int{
                {1, 2, 3},
                {4, 5, 6},
        }
        fmt.Println(grid)
        fmt.Println("строк:", len(grid), "колонок:", len(grid[0]))

        for _, row := range grid {
                fmt.Println(row)
        }

        fmt.Println("правый нижний:", grid[1][2])
        grid[0][1] = 9
        fmt.Println(grid)
}
```

### Когда массив в golang уместен: буферы, хеши, сетки

Пришло время честного разговора. В прикладном коде на Go вы почти не встретите массивов в сигнатурах и переменных — их место заняли срезы: они растут через append, копируются дёшево и дружат со всеми коллекциями языка. Правило большого пальца: в 95% случаев вам нужен срез, и следующий урок курса — про них; полный план тем — на странице [курса go-basics](/courses/go-basics/). Но есть ниши, где массив в golang остаётся правильным выбором, и их полезно знать в лицо.

Первая — криптография: хеши живут в массивах фиксированной длины, [16]byte у MD5 и [32]byte у SHA-256; стандартная библиотека объявляет дайджесты именно массивами, потому что размер хеша — часть его смысла. Вторая — буферы известного размера: кольцевой буфер на 64 сообщения, таблица кодов клавиш, палитра из 256 цветов — там, где ёмкость определена задачей, а не данными. Третья — сетки и координаты: доска [8][8], поле игры, матрица преобразования. Четвёртая — таблицы-константы уровня пакета, где литерал с [...] читается как «ровно столько значений, сколько написано».

И последнее, ради чего массивы стоит понимать заранее: срезы устроены поверх них. Срез — это окно в чужой массив: маленький заголовок с указателем, длиной и ёмкостью, глядящий на непрерывный кусок памяти. Когда в следующем уроке срез вдруг «поделится» данными с другим срезом, вспомните массивы-значения — и магии не останется.

### Шпаргалка: операции с массивами

| Запись | Что делает | В песочнице |
| --- | --- | --- |
| a := [3]int{5, 0, 9} | полный литерал: массив из трёх чисел | работает |
| a := [...]int{5, 0, 9} | длину считает компилятор, тип — [3]int | работает |
| var a [3]int | нулевое значение: три нуля | запасной TS-движок даёт длину 0 — проверяйте на настоящем Go |
| a[2] = 30 | запись по индексу, индексы от 0 до len-1 | работает |
| len(a) | длина — константа, часть типа | работает |
| b := a | копия всех элементов, оригинал цел | работает |
| a == b | поэлементное сравнение одинаковых типов | работает |
| grid := [2][3]int{...} | сетка: массив массивов | работает |
| [4]int{1, 2} | частичный литерал, хвост — нули | запасной TS-движок теряет нули — проверяйте на настоящем Go |

> **Честно о песочнице: где массивы ведут себя иначе.** Каждый пример урока прогнан пробником на запасном TS-интерпретаторе — том, что работает и в bun-скриптах. Отличия от настоящего Go, все проверены: (1) var arr [3]int без литерала даёт массив нулевой длины вместо трёх нулей; (2) индексированный литерал [4]int{0: 100, 2: 200} печатается как [100 200] — объявленная длина игнорируется; (3) частичный литерал [4]int{1, 2} — то же самое; (4) fmt.Printf с %T печатает [N]interface {} вместо [3]int; (5) присваивание [3]int переменной типа [4]int не отклоняется, хотя настоящий Go его не компилирует. Основной движок песочницы — настоящий Go — от этих странностей свободен. Поэтому все ранимые примеры урока используют литералы, где число элементов совпадает с длиной: на таких массивах оба движка дают одинаковый результат.

### Соберите программу: итог по размерам

Соберите программу: короткий литерал [...]int, range с подчёркиванием, аккумулятор до цикла и две итоговые строки — «итого: 82» и «элементов: 3».

Правильный порядок строк:

1. `package main`
2. `import "fmt"`
3. `func main() {`
4. `sizes := [...]int{40, 25, 17}`
5. `total := 0`
6. `for _, v := range sizes {`
7. `total += v`
8. `}`
9. `fmt.Println("итого:", total)`
10. `fmt.Println("элементов:", len(sizes))`
11. `}`

### Карточки: массивы

- **Что означает тип [5]string и чем он отличается от []string?** — [5]string — массив ровно из пяти строк, длина — часть типа; []string — срез, размер не фиксирован. [5]string и [6]string — разные типы, присвоить один другому нельзя.
- **Как объявить массив, не считая элементы вручную?** — Короткий литерал arr := [...]int{40, 25, 17} — компилятор посчитает и получит [3]int. Полная форма [3]int{40, 25, 17} — то же самое с явной длиной; в ней число элементов должно совпадать с n.
- **Каково нулевое значение массива?** — Массив, каждый элемент которого — нулевое значение своего типа: для [3]int это три нуля, для [2]string — две пустые строки. Индексы — от 0 до len-1, чтение за границей даёт ошибку «индекс за границами».
- **Что происходит при присваивании b := a, где a — массив?** — Копируются все элементы: массив — значение. Правки в b не задевают a; то же при передаче в функцию. Копия большого массива дорога — поэтому в сигнатурах обычно срезы.
- **Работает ли == для массивов и что сравнивает?** — Да, поэлементно — но только массивы одинакового типа и со сравнимыми элементами (числа, строки, bool, массивы из них). Для срезов == запрещён, кроме сравнения с nil.
- **Как устроен многомерный массив [2][3]int?** — Массив из двух массивов по три int. Индексация grid[строка][колонка], len(grid) — число строк, перебор — вложенным range; строка в range — тоже копия. В памяти — один сплошной кусок.

## Коротко: что запомнить

- Массив — коллекция с фиксированной длиной, вшитой в тип: [3]int и [4]int — разные типы, растянуть или обрезать массив нельзя.
- Литералы: полный [3]int{5, 0, 9}, короткий [...]int с подсчётом длины компилятором, индексированный и частичный — с нулями на пропусках.
- Массив — значение: присваивание и передача в функцию копируют все элементы, оригинал в безопасности.
- Перебор — классическим for с len() или for range: одна переменная — индекс, две — индекс и копия значения.
- Массивы сравнимы через == поэлементно; типы слева и справа должны совпадать до последней цифры длины.
- Многомерный [2][3]int — массив массивов: индекс двумя парами скобок, перебор вложенным range, память — один кусок.
- В прикладном коде массивы редки — срезы закрывают 95% задач; массивы живут в хешах [32]byte, фиксированных буферах и сетках.

## Домашнее задание

Соберите недельный отчёт кофейни: days := [7]string{"пн", "вт", "ср", "чт", "пт", "сб", "вс"} и cups := [7]int{120, 135, 98, 140, 210, 250, 180}. Классическим for с len() посчитайте сумму чашек и напечатайте «итого: 1133». Вторым циклом, range-ом, найдите рекордный день: сравнивайте v с рекордом и запоминайте индекс i, в конце напечатайте days[i] и cups[i] — должно выйти «рекорд: сб 250». Затем сделайте копию planned := cups, положите в её субботнюю ячейку 0 и напечатайте cups — оригинал останется целым: массив копируется целиком.

## Вопросы и ответы по уроку

### Чем массив отличается от среза?

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

### Почему [3]int и [4]int — разные типы?

Потому что длина — часть типа: компилятор по типу знает точный размер в памяти и проверяет каждую передачу массива между переменными, параметрами и возвратами. Присвоить [3]int переменной типа [4]int нельзя — программа просто не скомпилируется. Это цена и выгода фиксированной длины: меньше сюрпризов в рантайме, больше проверок на старте.

### Зачем учить массивы, если в коде почти всегда срезы?

Три причины. Первая: ниши, где массив — правильный выбор, это хеши [32]byte, буферы фиксированного размера и сетки. Вторая: срезы устроены поверх массивов, и без понимания массивов-значений поведение срезов выглядит магией. Третья: range, индексация и границы у них общие — всё, что вы отработали на массивах здесь, переносится на срезы дословно.
