Урок курса
Массивы и срезы: создание, индексирование, len, cap, append и range
Golang с нуляДо этого урока вы работали с отдельными переменными и функциями, которые их принимают. Как только данных становится больше одного — нужна коллекция. Go предлагает два инструмента: массив с фиксированной длиной и срез, который умеет расти. Они похожи внешне, но устроены по-разному, и путать их дорого.
Массив [N]T: тип, литерал, индексирование, len и cap
В Go тип массива записывается как [N]T, где N — числовая константа, а T — тип элементов. Важная деталь: N — это часть самого типа. [3]int и [5]int — два разных типа, точно так же как int и string. Присвоить одно другому или передать в функцию, ожидающую [3]int, массив [5]int — ошибка компиляции.
Создаётся массив литералом:
a := [3]int{10, 20, 30}
Здесь a имеет тип [3]int. Элементы нумеруются с нуля, последний доступный индекс — N−1, то есть для [3]int это индексы 0, 1, 2.
Чтение и запись — стандартные:
fmt.Println(a[0]) // 10
a[2] = 99
fmt.Println(a[2]) // 99
Что произойдёт, если выйти за границу? Здесь важно различать два случая.
Константный индекс — компилятор знает значение во время сборки и отклоняет программу сразу:
fmt.Println(a[3]) // ошибка компиляции: invalid argument: index 3 out of bounds [0:3]
Эта программа не соберётся вообще.
Переменный индекс — компилятор не может проверить его заранее, поэтому проверка переносится в момент выполнения. Если значение окажется вне диапазона, возникает panic:
i := 3
fmt.Println(a[i]) // runtime panic: index out of range [3] with length 3
Программа скомпилируется, но упадёт при запуске. Это не исключение, которое можно перехватить в обычном коде, — выполнение прекращается.
len(a) возвращает 3. cap(a) тоже возвращает 3 — у массива нет отдельной ёмкости, она совпадает с длиной и зафиксирована навсегда. Это принципиальное отличие от среза.
По синтаксису типа легко отличить массив от среза: [3]int — массив, []int — срез. Квадратные скобки без числа означают срез.
Срез []T: литерал, make([]T, len, cap), len, cap и nil-срез
Срез []T — динамическая последовательность. В отличие от массива, у него нет числа в скобках, и его длина не является частью типа: все []int — один и тот же тип, сколько бы элементов в них ни было.
Два способа создать срез — литерал и make.
Литерал:
s := []int{1, 2, 3}
fmt.Println(len(s), cap(s)) // 3 3
Здесь len и cap совпадают, потому что срез создан ровно под три элемента.
make даёт больше контроля:
s := make([]int, 3, 5)
fmt.Println(len(s), cap(s)) // 3 5
Доступны только те индексы, что меньше len: s[0], s[1], s[2] — всё, эти три элементы равны нулю. Попытка обратиться к s[3] или s[4] вызовет панику, потому что они выходят за пределы длины. Правило простое: допустимый диапазон индексов — от 0 до len(s)−1, и никак иначе. append возвращает срез с увеличенной длиной, открывая тем самым новые индексы — но об этом в следующей секции.
Если третий аргумент опустить, cap равен len:
s := make([]int, 4)
fmt.Println(len(s), cap(s)) // 4 4
Инвариант всегда один: len(s) <= cap(s). Это гарантия языка, нарушить её через обычный код невозможно.
Отдельный случай — nil-срез. Это нулевое значение типа []T:
var s []int
fmt.Println(s == nil) // true
fmt.Println(len(s), cap(s)) // 0 0
nil-срез — не ошибка. Он валиден, его можно передавать в функции и применять к нему append. Просто в нём пока ноль элементов и ноль ёмкости.
append с обязательным присваиванием и for range по срезу
append — единственный стандартный способ добавить элемент в срез. Он принимает срез и один или несколько новых значений, а возвращает обновлённый срез:
s := []int{1, 2, 3}
s = append(s, 4)
fmt.Println(s) // [1 2 3 4]
Обычная форма в этом уроке — s = append(s, v). Главное правило: используйте возвращённый срез. Его длина увеличена на число добавленных элементов, а базовый массив мог сохраниться или смениться — это зависит от текущей ёмкости и остаётся деталью реализации.
Результат append можно также присвоить другой переменной, вернуть из функции или сразу передать следующему вызову. Что недопустимо — полностью игнорировать возвращённое значение:
append(s, 4) // ошибка компиляции: append(s, 4) evaluated but not used
Программа не соберётся. Go требует, чтобы возвращённый срез был использован.
append отлично работает с nil-срезом — вы видели его в предыдущей секции:
var s []int
s = append(s, 10, 20)
fmt.Println(s) // [10 20]
Для перебора среза используйте цикл for range:
s := []string{"go", "is", "fun"}
for i, v := range s {
fmt.Println(i, v)
}
// 0 go
// 1 is
// 2 fun
i — индекс, v — копия элемента. Это важно. Если написать:
for _, v := range s {
v = "changed" // не меняет s
}
— срез останется нетронутым. v — локальная переменная итерации, изменение которой не отражается на оригинале. Чтобы изменить элемент, нужно присваивание по индексу:
for i := range s {
s[i] = "changed"
}
fmt.Println(s) // [changed changed changed]
Форма for i := range s даёт только индекс — используйте её, когда значение элемента в теле цикла не нужно. Если нужно только значение без индекса, отбросьте индекс через _:
for _, v := range s {
fmt.Println(v)
}
Самопроверка: append не гарантирует отдельный массив
Создайте срез и его подсрез, измените элемент подсреза и посмотрите на исходный. Затем добавьте элемент через append. Если capacity хватает, добавление может использовать прежний базовый массив, а при нехватке памяти выделяется новый. Поэтому вывод «после append срезы всегда независимы» неверен.
Для копирования создайте целевой срез подходящей длины и вызовите copy. Проверьте именно length, а не только capacity: copy записывает не больше минимальной длины источника и назначения. Указатель на общий массив объясняет поведение лучше, чем запоминание отдельных примеров. Разберите это до задач, где функция изменяет полученный slice.
Продолжите разбор: Разделяемый базовый массив, copy и подсрезы.
Задача 1
Чем тип [4]int отличается от типа []int в Go?
Задача 2
Срез создан как s := make([]int, 3, 7). Каковы len(s) и cap(s) сразу после создания?
Задача 3
Что выведет следующая программа? Введите ровно те строки, которые появятся в stdout.
package main
import "fmt"
func main() {
s := []int{10, 20, 30}
for i, v := range s {
v = v * 2
_ = i
}
fmt.Println(s)
}
Задача 4
Фильтр положительных значений
Контроллер получает целые показания от датчика. По правилам этой системы в итоговый отчёт попадают только значения больше нуля; ноль и отрицательные значения нужно отбросить.
Показания уже собраны в последовательность. Нужно отобрать положительные значения, сохранив тот порядок, в котором они пришли.
Что нужно сделать
Допишите keepPositive. Функция должна вернуть новый срез только с положительными элементами входного среза, сохранив их порядок. Ноль положительным не считается. Создайте результат через make, обойдите вход через range и сохраняйте результат append.
Формат ввода
Сначала число n, затем n целых чисел.
Формат вывода
В первой строке количество оставшихся элементов. Во второй строке — сами элементы через пробел. Если элементов нет, вторая строка пустая.
Пример
Ввод: 6 -3 5 0 -1 8 2 Вывод: 3 5 8 2
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 5
Сводка температур за неделю
Метеорологический журнал давно стал привычным инструментом для тех, кто хочет понять, каким выдался минувший отрезок времени. Семь дней подряд фиксируются показания термометра, и к концу недели набирается полный ряд цифр, каждая из которых отражает один конкретный день — без пропусков и без перестановок.
Когда неделя закрыта, возникает естественное желание подвести итог: не листать записи поочерёдно, а получить единую картину. Для этого нужно выяснить, какой день оказался самым холодным, какой — самым тёплым, и насколько типичной была температура в целом. Именно эти три ориентира дают ощущение недели как единого целого.
Программа последовательно проходит по всем семи значениям, не исключая ни одного, и определяет среди них наименьшее и наибольшее. Затем она вычисляет среднее по всей семёрке, представляя его целым числом. На выходе пользователь видит минимум, максимум и среднее — лаконичную сводку, которая в трёх числах описывает прошедшую неделю.
Что нужно сделать
Реализуйте функцию WeeklyTemperatureSummary(temperatures [7]int) []int. Она должна обойти все семь элементов массива и вернуть срез из трёх чисел в строгом порядке: минимальная температура, максимальная температура, целое среднее. Среднее вычисляется обычным целочисленным делением суммы на 7.
Ввод и вывод
Функция получает один массив ровно из семи целых чисел. Она возвращает срез [минимум, максимум, среднее]. Читать stdin и печатать результат не нужно.
Пример
WeeklyTemperatureSummary([7]int{-3, 1, 4, 2, 0, 5, -2}) возвращает []int{-3, 5, 1}.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задачи к уроку
- Go: обход массива [7]int и возврат среза []int
Найдите минимум, максимум и целое среднее в массиве из семи чисел, верните результаты срезом в заданном порядке.
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
