После чтения данного урока вы сможете:
- Добавлять элементы в срезы массивов;
- Разобраться, как устроены длина и вместимость массива.
В массивах находится фиксированное число элементов, а через слайсы мы только смотрим на эти массивы с фиксированной длиной. Программистам часто требуется использовать массив с переменной длиной, что растет по мере необходимости. Через комбинацию срезов со встроенной функцией 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? Какую функцию можно использовать для определения точного числа?
Ответ
В срезе девять карликовых планет. Это можно определить через встроенную функцию len:
|
1 |
fmt.Println(len(dwarfs)) // Выводит: 9 |
Длина и вместимость среза в 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?
Ответ
Плутон Хаумеа Макемаке Эрида предоставляют вместимость 4, хотя длина равна 1.
Разбор функции 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!" |
Ответ
dwarfs3 and dwarfs2 изменятся, но dwarfs1 останется прежним, так как он указывает на другой массив.
Трех-индексный срез в 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?
Ответ
Предварительное выделение через 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?
Ответ
- Для подсчета компилятором Go количества элементов композитного литера для массива;
- Заставить последний параметр вариативной функции зафиксировать ноль или большее число аргументов в качестве среза;
- Расширить элементы среза в аргументы, передаваемые функции.
Заключение
- У срезов есть длина и вместимость;
- Когда вместимости недостаточно, встроенная функция
appendвыделит новый базовый массив; - Вы можете использовать функцию
makeдля предварительного выделения среза; - Вариативные функции принимают несколько аргументов, что помещаются в срез.
Итоговое задание для проверки:
Напишите программу, что использует цикл для продолжающегося добавления элементов в срез. Каждый раз при изменении вместимости среза выводится новое значение. Всегда ли append удваивает вместимость при завершении места в базовом массиве?
Решение
|
1 2 3 4 5 6 7 8 9 10 |
s := []string{} lastCap := cap(s) for i := 0; i < 10000; i++ { s = append(s, "An element") if cap(s) != lastCap { fmt.Println(cap(s)) lastCap = cap(s) } } |

Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»