После чтения данного урока вы сможете:
- Добавлять элементы в срезы массивов;
- Разобраться, как устроены длина и вместимость массива.
В массивах находится фиксированное число элементов, а через слайсы мы только смотрим на эти массивы с фиксированной длиной. Программистам часто требуется использовать массив с переменной длиной, что растет по мере необходимости. Через комбинацию срезов со встроенной функцией append
Go позволяет менять длину массивов. В данном уроке будет рассмотрено, как именно это работает.
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
Содержание статьи
- Функция append в Go
- Длина и вместимость среза в Golang
- Разбор функции append в Go
- Трех-индексный срез в Golang
- Предварительное выделение срезов через make в Go
- Объявление вариативных функций в Golang
Возможно, вам знакома ситуация, когда новые книги уже не помещаются на полку? Или кому-то в семьи приходиться делить комнату, потому что отдельной комнаты на каждого не хватает?
Как и у книжной полки, у массива есть определенная вместимость. Слайс может сфокусироваться на части массива для книг, и потом расти до тех пор, пока не достигнет предела вместимости полки. Если полка заполняется, ее можно заменить полкой побольше или переместить книги в другое место. Затем поместить срез с книгами на новой полке большей вместимости.
Функция append в Go
У Международного астрономического союза (МАС) есть данные о пяти карликовых планетах в нашей Солнечной системе, но их может быть больше. Для добавления новых элементов к срезу dwarfs
используйте встроенную функцию append
, как показано в примере ниже:
1 2 3 4 |
dwarfs := []string{"Церера", "Плутон", "Хаумеа", "Макемаке", "Эрида"} dwarfs = append(dwarfs, "Оркус") fmt.Println(dwarfs) // Выводит: [Церера Плутон Хаумеа Макемаке Эрида Оркус] |
Функция append
является вариативной, как и Println. Вы можете передать сразу несколько элементов для добавления:
1 2 |
dwarfs = append(dwarfs, "Салация", "Квавар", "Седна") fmt.Println(dwarfs) // Выводит: [Церера Плутон Хаумеа Макемаке Эрида Оркус Салация Квавар Седна] |
Срез dwarfs
стартует как вид внутрь массива из пяти элементов, но предыдущий код добавляет к нему еще четыре элемента. Как такое возможно? Чтобы разобраться, сперва разберем такое понятие, как вместимость массива, а также встроенную функцию cap
.
Вопрос для проверки:
Сколько карликовых планет значится в Листинге 1? Какую функцию можно использовать для определения точного числа?
Длина и вместимость среза в Golang
Число элементов, что видны через срез, определяется его длиной. Если у слайса есть базовый массив, что большего размера, у среза остается вместимость для роста.
В следующем примере объявляется функция для вывода длины и вместимости среза:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package main import "fmt" // dump для длины, вместимости и содержимого среза func dump(label string, slice []string) { fmt.Printf("%v: длина %v, вместимость %v %v\n", label, len(slice), cap(slice), slice) } func main() { dwarfs := []string{"Церера", "Плутон", "Хаумеа", "Макемаке", "Эрида"} dump("dwarfs", dwarfs) // Выводит: dwarfs: длина 5, вместимость 5 [Церера Плутон Хаумеа Макемаке Эрида] dump("dwarfs[1:2]", dwarfs[1:2]) // Выводит: dwarfs[1:2]: длина 1, вместимость 4 [Плутон] } |
У среза, созданного через dwarfs[1:2]
, длина равна 1, и вместимость для 4 элементов.
Вопрос для проверки:
Почему вместимость среза dwarfs[1:2]
равна 4?
Разбор функции append в Go
Через использование функции dump
из Листинга 2 в следующем примере показано, как функция append
влияет на вместимость.
1 2 3 |
dwarfs1 := []string{"Церера", "Плутон", "Хаумеа", "Макемаке", "Эрида"} // Длина 5, вместимость 5 dwarfs2 := append(dwarfs1, "Оркус") // Длина 6, вместимость 10 dwarfs3 := append(dwarfs2, "Салация", "Квавар", "Седна") // Длина 9, вместимость 10 |
В массиве, поддерживающем dwarfs1
, недостаточно места (вместимости) для добавления элемента "Оркус"
, поэтому append
копирует содержимое dwarfs1
к новому перемещенному массиву с двойной вместимостью. Это показано на Схеме 1. Срез dwarfs2
указывает на новый перемещенный массив. Добавленная вместимость нужна для создания места, что потребуется для следующей функции append
.
Схема 1: Функция append
перемещает новый массив, увеличивая его вместимость в случае необходимости.
Показать что, dwarfs2
и dwarfs3
ссылаются на иной массив, нежели dwarfs1
, можно через изменение какого-то элемента и последующего вывода указанных трех срезов.
Задание для проверки:
При модификации элемента среза dwarfs3
из Листинга 3 изменятся ли срезы dwarfs2
и dwarfs1
?
1 |
dwarfs3[1] = "Pluto!" |
Трех-индексный срез в Golang
С появлением версии Go 1.2 был введен трех-индексный срез, что нужен для ограничения вместимости итогового среза. В следующем примере у terrestrial
длина и вместимость 4. Добавление элемента "Церера"
приводит к перемещению нового массива, оставляя массив planets
не измененным.
1 2 3 4 5 6 7 8 9 |
planets := []string{ "Меркурий", "Венера", "Земля", "Марс", "Юпитер", "Сатурн", "Уран", "Нептун", } terrestrial := planets[0:4:4] // Длина 4, вместимость 4 worlds := append(terrestrial, "Церера") fmt.Println(planets) // Выводит: [Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун] |
Если третий индекс не уточняется, вместимость terrestrial
равна 8. Добавление элемента "Церера"
не перемещает новый массив, а вместо этого переписывает "Юпитер"
:
1 2 3 4 |
terrestrial = planets[0:4] // Длина 4, вместимость 8 worlds = append(terrestrial, "Церера") fmt.Println(planets) // Выводит: [Меркурий Венера Земля Марс Церера Сатурн Уран Нептун] |
Только если вы не хотите переписать элемент "Юпитер"
, нужно, чтобы при каждом срезе значился по умолчанию трех-индексный срез.
Вопрос для проверки:
Когда нужно использовать трех-индексный срез?
Предварительное выделение срезов через make в Go
Если для append
недостаточно вместимости, Go должен выделить новый массив и скопировать туда содержимое старого массива. Можно избежать лишних распределений и копий через предварительное выделение среза с помощью использования встроенной функции make
.
Функция make
в следующем примере уточняет длину (0) и вместимость (10) среза dwarfs
. Перед заполнением dwarfs
можно добавить до 10 элементов, после чего append
должен будет выделить новый массив.
1 2 |
dwarfs := make([]string, 0, 10) dwarfs = append(dwarfs, "Церера", "Плутно", "Хаумеа", "Макемаке", "Эрида") |
Аргумент вместимости опционален. Начиная с длины и вместимости 10, вы можете использовать make([]string, 10)
. Каждый из 10 элементов содержит нулевое значение собственного типа, в данном случае это пустая строка. Встроенная функция append
позволит добавить 11-й элемент.
Вопрос для проверки:
В чем преимущество создания среза через make
?
Объявление вариативных функций в Golang
Printf
и append
являются вариативными функциями, так как они принимают переменное число аргументов. Для объявления вариативной функции используется многоточие (...
) с последним параметром, как показано в примере ниже:
1 2 3 4 5 6 7 8 |
func terraform(prefix string, worlds ...string) []string { newWorlds := make([]string, len(worlds)) // Создает новый срез вместо прямого изменения worlds for i := range worlds { newWorlds[i] = prefix + " " + worlds[i] } return newWorlds } |
Параметр worlds
является срезом строк, что содержит ноль или более аргументов, передаваемых в terraform
:
1 2 |
twoWorlds := terraform("Нью", "Венера", "Марс") fmt.Println(twoWorlds) // Выводит: [Нью Венера Нью Марс] |
Для передачи среза вместо множества аргументов, расширьте срез через многоточие:
1 2 3 |
planets := []string{"Венера", "Марс", "Юпитер"} newPlanets := terraform("Нью", planets...) fmt.Println(newPlanets) // Выводит: [Нью Венера Нью Марс Нью Юпитер] |
Если бы terraform
модифицировала элементы параметра worlds
, срез planets
также зафиксировал бы эти изменения. Через использование newWorlds
функция terraform
избегает изменения и передачи аргументов.
Вопрос для проверки:
Назовите три случая использования многоточия (...
) в Go?
Заключение
- У срезов есть длина и вместимость;
- Когда вместимости недостаточно, встроенная функция
append
выделит новый базовый массив; - Вы можете использовать функцию
make
для предварительного выделения среза; - Вариативные функции принимают несколько аргументов, что помещаются в срез.
Итоговое задание для проверки:
Напишите программу, что использует цикл для продолжающегося добавления элементов в срез. Каждый раз при изменении вместимости среза выводится новое значение. Всегда ли append
удваивает вместимость при завершении места в базовом массиве?
Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»