Массивы
Массивы в Go представляют собой фиксированную по размеру коллекцию данных заданного типа. Выделение памяти для массива происходит в момент его инициализации, а хранится он в ней как последовательность блоков указанного типа.
Рассмотрим несколько способов создания массива в Go:
- С помощью ключевого слова
var
и последующего присвоения значений:
- С указанием длины массива и входящих в него элементов:
- С перечислением элементов, но без указания длины, которая будет автоматически подсчитана компилятором:
- Для создания N-мерного массива нужно указать длину каждого измерения, тип и в скобках
{}
перечислить элементы:
Например, так создается двумерный массив с целочисленными элементами:
Стоит помнить, что массивы разной длины имеют разные типы, так как размер массива входит в определение типа:
Значения массива можно изменять в цикле:
Аналогичная конструкция, использующая функцию len()
для вычисления длины массива:
При работе с массивами нужно всегда следить за их длиной и не допускать обращения к посторонней области памяти. При выходе за границы массива компилятор сообщит об ошибке index out of range:
Особенности массивов в Go
В Go массивы являются обычными значениями, поэтому при передаче или присваивании массивов создается копия их содержимого. Альтернативный вариант для избежания копирования заключается в использовании указателей на массив, как это делалось с переменными в предыдущих уроках.
Чтобы лучше понять эту особенность, обратимся к конкретному примеру. В коде ниже массив result
копируется при передаче в функцию changeArray
, поэтому все операции увеличения производятся над массивом arr
, а result
остается без изменения.
Для изменения массива result
следует передать его по указателю в функцию changeArrayPtr
, которая, в отличие от changeArray
, в качестве одного из параметров принимает не просто массив, а указатель на него:
Слайсы
Слайс – это расширенная реализация массива, которая поддерживает динамическое изменение размера. В исходном коде Go он представляет собой структуру с тремя полями:
Из описания структуры можно понять, что слайс не хранит никаких данных, а всего лишь описывает часть базового массива, на который ссылается.
Длина слайса (len) – это количество элементов, содержащихся в нем.
Емкость слайса (cap) – это количество элементов, которые могут быть сохранены в слайсе до того момента, когда произойдет его перераспределение. При этом емкость должна быть строго больше длины, иначе произойдет ошибка компиляции.
Создать слайс можно несколькими способами:
- С помощью ключевого слова
var
создается nil-слайс:
- Короткое объявление:
- С указанием типа данных и размера в функции
make
. В таком случае емкость по умолчанию будет равна заданной длине:
- С указанием типа данных, размера и ёмкости в функции
make
:
- Создание слайса из массива:
- Создать n-мерный слайс можно с помощью добавления измерений в цикле
for
:
Срез
Срез представляет собой слайс, который ссылается только на заданную часть базового массива или слайса и создается с помощью указания начального и конечного индекса в квадратных скобках:
Слайс и срез часто объединяют в одно понятие и подразумевают под ним структуру со ссылкой на массив, длиной и емкостью.
Чтобы избежать путаницы, в данном пособии будем считать слайсом расширенную реализацию массива, созданную одним из ранее рассмотренных способов, а срезом – только часть исходного массива или слайса, полученную с помощью конструкции [:]
.
Особенности слайсов в Go
При работе со слайсами и срезами стоит помнить, что они хранят ссылку на данные, поэтому изменение их элементов приведет к изменению соответствующих элементов базового массива или слайса.
Продемонстрируем эту особенность на следующем примере:
В первом выводе можно заметить, что изменения в слайсах second
и third
коснулись также слайса first
. При добавлении числа 60 в слайс second
он переаллоцировался и теперь перестал ссылаться на first
, так как хранится по новому адресу. Поэтому изменение его нулевого элемента не коснется слайсов first
и third
, что видно во втором выводе.
Передача слайса в функцию
Несмотря на то что слайс содержит указатель, он на самом деле является именно значением структуры, содержащей указатель на базовый источник данных.
Рассмотрим пример, наглядно демонстрирующий эту особенность. Передадим слайс nums
в функцию без возвращаемого значения doubleNumbers
, которая увеличит его элементы двое и попытается изменить длину на 2.
Как нетрудно заметить, элементы nums
действительно увеличились в два раза, а вот длина осталась неизменной.
Теперь добавим возвращаемое значение в функцию doubleNumbers
и присвоим результат её выполнения переменной newNums
:
В данном случае слайс nums
не изменяется, но возвращаемое значение содержит новую длину, которая сохранится в newNums
.
Длина слайса nums
осталась неизменной, так как передалась её копия, а не оригинал. Если необходимо изменить длину, нужно явно указать возвращаемое значение функции и присвоить его переменной при вызове, как это было показано в последнем примере.
Append
Для добавления элементов в слайс используется функция append
со следующей сигнатурой:
Она принимает в качестве параметров слайс и значения, которые необходимо в него добавить. В коде ниже показан пример её применения:
В Go допускается добавление одного слайса в другой. Это делается следующим образом:
Во время выполнения программы функция append
самостоятельно решает, нужно ли создавать новый слайс. Это происходит, например, при превышении имеющейся емкости:
В коде выше при вызове append
будет превышена емкость слайса slc
, что приведет к её увеличению вдвое и изменению адреса slc
на новый.
Функция append
напрямую влияет на использование памяти в программе, поэтому для избежания лишних аллокаций и незапланированного копирования, необходимо следить за емкостью созданных слайсов.
Copy
Копирование слайсов производится с помощью отдельной функции copy
со следующей сигнатурой:
Она копирует элементы из исходного слайса в целевой и возвращает количество скопированных элементов, которое будет минимальным из len(dst)
и len(src)
.
Рассмотрим несколько классических примеров использования copy
:
- Копирование слайса большего размера в слайс с меньшим:
- Копирование слайса меньшего размера в слайс с большим:
- Копирование слайса в самого себя:
- Особый случай – копирование байтов строки в слайс байтов.
Пакет slices
В версии 1.21 разработчики Go добавили новый пакет slices
, облегчающий работу со слайсами. Он позволяет использовать готовые функции для выполнения базовых операций со слайсами, что в разы упрощает работу. Чтобы почувствовать разницу, обратимся к примеру кода для поиска максимального значения в слайсе до и после введения пакета slices
:
Теперь рассмотрим примеры практического применения некоторых функций из пакета slices
:
- Сортировка слайса –
slices.Sort
:
- Сравнение слайсов –
slices.Compare
:
- Поиск элемента в слайсе –
slices.Contains
:
- Удаление элементов в диапазоне [i:j] из слайса –
slices.Delete
:
- Вставка элементов начиная с указанного индекса –
slices.Insert
:
Посмотреть все функции пакета slices и примеры их использования можно на официальном сайте go.
Задачи
Давайте решим несколько классических задач на массивы и слайсы для оттачивания навыков программирования и закрепления материала статьи. Для их решения достаточно применить изученные в этом уроке конструкции, при этом не рекомендуется использовать сторонние пакеты, таких как slices
, math
и другие.
Самый хороший дом
На некоторой улице в произвольном порядке стоят n
домов. Каждый дом можно однозначно определить с помощью его номера и индекса расположения относительно начала улицы (она начинается слева). При этом номера домов могут повторяться. Назовем хорошим дом, имеющий наибольший номер и располагающийся наиболее близко к началу улицы. Ваша задача — вывести номер и индекс такого дома.
Входные данные: на первой строке подается количество чисел – натуральное число n (n < 1000), на второй строке через пробел перечисляются целочисленные номера домов в диапазоне от -10^4 до 10^4.
Выходные данные: два числа – номер и индекс хорошего дома.
Решение: задача заключается в нахождении значения и индекса максимального числа массива.
Гоша ищет редкие числа
Гоша всерьёз увлекся математикой и поставил себе такую задачу: написать на доске n
целых чисел и найти среди них самые редкие, то есть такие, что встречаются только один раз. Ваша задача помочь Гоше проверить свои вычисления, написав код для решения этой интересной задачи.
Входные данные: на первой строке подается количество чисел – натуральное число n (n < 10^6), на второй строке через пробел перечисляются целые числа.
Выходные данные: числа, встречающиеся в последовательности ровно один раз.
Решение:
Перестановка соседей
Напишите функцию shiftNeighbour(nums []int)
для перестановки соседних элементов слайса. Для нечетного количества элементов последний из них остается без изменения.
Пример работы функции:
Решение:
Циклический сдвиг слайса*
Реализуйте функцию sliceShift(nums []int, shift int) []int
для циклического сдвига слайса на shift элементов вправо, если shift > 0, и влево, если shift < 0.
Подсказка: для заполнения нового слайса копируйте в него часть исходного с помощью функции copy()
.
Решение: для начала приведем сдвиг к корректной форме. Если shift > 0, то достаточно взять остаток от деления сдвига на len(nums). В случае shift < 0 заметим, что сдвиг влево равен сдвигу вправо на len(nums) + shift, и преобразуем его к нужному виду. Далее создадим новый слайс и скопируем туда элементы исходного, учитывая индексы:
Подведём итоги
Массивы и слайсы предоставляют разработчикам мощные инструменты для решения широкого спектра задач. Массивы полезны для хранения статической информации, в то время как слайсы обеспечивают гибкость и удобство работы с переменными размерами данных.
В следующей статье цикла погрузимся в изучение строк, байтов, рун, разберем их особенности и познакомимся с хеш-таблицами.
Содержание самоучителя
- Особенности и сфера применения Go, установка, настройка
- Ресурсы для изучения Go с нуля
- Организация кода. Пакеты, импорты, модули. Ввод-вывод текста.
- Переменные. Типы данных и их преобразования. Основные операторы
- Условные конструкции if-else и switch-case. Цикл for. Вложенные и бесконечные циклы
- Функции и аргументы. Области видимости. Рекурсия. Defer
- Массивы и слайсы. Append и сopy. Пакет slices
- Строки, руны, байты. Пакет strings. Хеш-таблица (map)
- Структуры и методы. Интерфейсы. Указатели. Основы ООП
- Наследование, абстракция, полиморфизм, инкапсуляция
- Обработка ошибок. Паника. Восстановление. Логирование
- Обобщенное программирование. Дженерики
- Работа с датой и временем. Пакет time
- Интерфейсы ввода-вывода. Буферизация. Работа с файлами. Пакеты io, bufio, os
- Конкурентность. Горутины. Каналы
- Тестирование кода и его виды. Table-driven подход. Параллельные тесты
- Основы сетевого программирования. Стек TCP/IP. Сокеты. Пакет net
Комментарии