Понимание массивов и фрагментов в Go

Вступление

В Goarrays иslices - этоdata structures, состоящие из упорядоченной последовательности элементов. Эти коллекции данных отлично подходят для работы со многими связанными значениями. Они позволяют вам объединять данные, которые принадлежат друг другу, сокращать код и выполнять одни и те же методы и операции с несколькими значениями одновременно.

Хотя массивы и срезы в Go являются упорядоченными последовательностями элементов, между ними есть существенные различия. array в Go - этоdata structure, который состоит из упорядоченной последовательности элементов, емкость которой определена во время создания. Как только массив выделил свой размер, размер больше не может быть изменен. С другой стороны,slice - это версия массива переменной длины, обеспечивающая большую гибкость для разработчиков, использующих эти структуры данных. Срезы представляют собой то, что вы думаете о массивах на других языках.

Учитывая эти различия, существуют определенные ситуации, когда вы будете использовать один над другим. Если вы новичок в Go, определение того, когда их использовать, может вводить в заблуждение: хотя универсальность срезов делает их более подходящим выбором в большинстве ситуаций, существуют конкретные случаи, в которых массивы могут оптимизировать производительность вашей программы.

Эта статья подробно расскажет о массивах и срезах, которые предоставят вам необходимую информацию, чтобы сделать правильный выбор при выборе между этими типами данных. Кроме того, вы рассмотрите наиболее распространенные способы объявления и работы с массивами и срезами. Учебное пособие сначала предоставит описание массивов и как ими манипулировать, затем объяснение срезов и их различия.

Массивы

Массивы - это структуры данных коллекции с заданным количеством элементов. Поскольку размер массива является статическим, структура данных должна выделять память только один раз, в отличие от структуры данных переменной длины, которая должна динамически выделять память, чтобы она могла увеличиваться или уменьшаться в будущем. Хотя фиксированная длина массивов может сделать их несколько жесткими для работы, одноразовое выделение памяти может увеличить скорость и производительность вашей программы. Из-за этого разработчики обычно используют массивы при оптимизации программ в тех случаях, когда структуре данных никогда не потребуется переменное количество элементов.

Определение массива

Массивы определяются путем объявления размера массива в скобках[ ], за которым следует тип данных элементов. В массиве в Go все элементы должны быть одинаковымиdata type. После типа данных можно объявить отдельные значения элементов массива в фигурных скобках{ }.

Ниже приведена общая схема объявления массива:

[capacity]data_type{element_values}

[.note] #Note: Важно помнить, что каждое объявление нового массива создает отдельный тип. Итак, хотя и[2]int, и[3]int имеют целочисленные элементы, их разная длина делает их типы данных несовместимыми.
#

Если вы не объявляете значения элементов массива, по умолчанию используется нулевое значение, что означает, что элементы массива будут пустыми. Для целых чисел это представлено0, а для строк - пустой строкой.

Например, следующий массивnumbers имеет три целочисленных элемента, которые еще не имеют значения:

var numbers [3]int

Если вы напечатаетеnumbers, вы получите следующий вывод:

Output[0 0 0]

Если вы хотите присвоить значения элементов при создании массива, поместите значения в фигурные скобки. Массив строк с заданными значениями выглядит так:

[4]string{"blue coral", "staghorn coral", "pillar coral", "elkhorn coral"}

Вы можете сохранить массив в переменной и распечатать его:

coral := [4]string{"blue coral", "staghorn coral", "pillar coral", "elkhorn coral"}
fmt.Println(coral)

Запуск программы с предыдущими строками даст вам следующий вывод:

Output[blue coral staghorn coral pillar coral elkhorn coral]

Обратите внимание, что при выводе на печать нет элементов между элементами в массиве, что затрудняет определение того, где заканчивается один элемент и начинается другой. Из-за этого иногда полезно использовать вместо этого функциюfmt.Printf, которая может форматировать строки перед их выводом на экран. С помощью этой команды укажите глагол%q, чтобы функция могла заключать значения в кавычки:

fmt.Printf("%q\n", coral)

Это приведет к следующему:

Output["blue coral" "staghorn coral" "pillar coral" "elkhorn coral"]

Теперь каждый элемент цитируется. Глагол указывает программе форматирования добавить возврат строки в конце.

Имея общее представление о том, как объявлять массивы и из чего они состоят, вы можете перейти к изучению того, как указывать элементы в массиве с индексным номером.

Индексирование массивов (и фрагментов)

Каждый элемент в массиве (а также срез) может быть вызван индивидуально посредством индексации. Каждому элементу соответствует порядковый номер, который представляет собой значениеint, начиная с порядкового номера0 и считая его вверх.

Мы будем использовать массив в следующих примерах, но вы также можете использовать срез, так как они идентичны в том, как вы индексируете их обоих.

Для массиваcoral разбивка индекса выглядит так:

«Синий коралл» «Коралл оленьего рога» «Коралловый столб» «Коралловый лосось»

0

1

2

3

Первый элемент, строка"blue coral", начинается с индекса0, а срез заканчивается с индексом3 с элементом"elkhorn coral".

Поскольку каждый элемент в срезе или массиве имеет соответствующий порядковый номер, мы можем обращаться к ним и манипулировать ими так же, как и с другими последовательными типами данных.

Теперь мы можем вызвать дискретный элемент среза, ссылаясь на его порядковый номер:

fmt.Println(coral[1])
Outputstaghorn coral

Номера индексов для этого среза варьируются от0-3, как показано в предыдущей таблице. Поэтому для вызова любого из элементов по отдельности мы будем ссылаться на номера индексов, например:

coral[0] = "blue coral"
coral[1] = "staghorn coral"
coral[2] = "pillar coral"
coral[3] = "elkhorn coral"

Если мы вызовем массивcoral с любым номером индекса, превышающим3, он выйдет за пределы допустимого диапазона:

fmt.Println(coral[18])
Outputpanic: runtime error: index out of range

При индексации массива или фрагмента вы всегда должны использовать положительное число. В отличие от некоторых языков, которые позволяют индексировать в обратном направлении отрицательным числом, выполнение этого в Go приведет к ошибке:

fmt.Println(coral[-1])
Outputinvalid array index -1 (index must be non-negative)

Мы можем объединить строковые элементы в массиве или срезе с другими строками с помощью оператора+:

fmt.Println("Sammy loves " + coral[0])
OutputSammy loves blue coral

Мы смогли объединить строковый элемент с номером индекса0 со строкой"Sammy loves ".

С индексными номерами, которые соответствуют элементам в массиве или срезе, мы можем получить доступ к каждому элементу дискретно и работать с этими элементами. Чтобы продемонстрировать это, мы рассмотрим, как изменить элемент с определенным индексом.

Модифицирующие элементы

Мы можем использовать индексирование для изменения элементов в массиве или слайсе, установив элемент с пронумерованным индексом равным другому значению. Это дает нам больший контроль над данными в наших срезах и массивах и позволит нам программно манипулировать отдельными элементами.

Если мы хотим изменить строковое значение элемента с индексом1 массиваcoral с"staghorn coral" на"foliose coral", мы можем сделать это следующим образом:

coral[1] = "foliose coral"

Теперь, когда мы печатаемcoral, массив будет другим:

fmt.Printf("%q\n", coral)
Output["blue coral" "foliose coral" "pillar coral" "elkhorn coral"]

Теперь, когда вы знаете, как управлять отдельными элементами массива или среза, давайте рассмотрим несколько функций, которые дадут вам больше гибкости при работе с типами данных коллекции.

Подсчет элементов сlen()

В Golen() - это встроенная функция, предназначенная для помощи в работе с массивами и срезами. Как и в случае со строками, вы можете рассчитать длину массива или фрагмента, используяlen() и передав массив или фрагмент в качестве параметра.

Например, чтобы узнать, сколько элементов находится в массивеcoral, вы должны использовать:

len(coral)

Если вы распечатаете длину массиваcoral, вы получите следующий результат:

Output4

Это дает длину массива4 в типе данныхint, что правильно, потому что массивcoral состоит из четырех элементов:

coral := [4]string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral"}

Если вы создадите массив целых чисел с большим количеством элементов, вы также можете использовать функциюlen():

numbers := [13]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
fmt.Println(len(numbers))

Это приведет к следующему выводу:

Output13

Хотя в этих примерах массивов относительно мало элементов, функцияlen() особенно полезна при определении количества элементов в очень больших массивах.

Далее мы рассмотрим, как добавить элемент в тип данных коллекции, и покажем, как из-за фиксированной длины массивов добавление этих статических типов данных приведет к ошибке.

Добавление элементов с помощьюappend()

append() - это встроенный в Go метод, который добавляет элементы в тип данных коллекции. Однако этот метод не будет работать при использовании с массивом. Как упоминалось ранее, основной способ, которым массивы отличаются от слайсов, состоит в том, что размер массива нельзя изменить. Это означает, что хотя вы можете изменять значения элементов в массиве, вы не можете сделать массив больше или меньше после того, как он был определен.

Рассмотрим ваш массивcoral:

coral := [4]string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral"}

Допустим, вы хотите добавить в этот массив элемент"black coral". Если вы попытаетесь использовать функциюappend() с массивом, набрав:

coral = append(coral, "black coral")

Вы получите сообщение об ошибке:

Outputfirst argument to append must be slice; have [4]string

Чтобы исправить это, давайте узнаем больше о типе данных среза, как определить срез и как преобразовать массив в срез.

Ломтики

slice - это тип данных в Go, который представляет собойmutable, или изменяемую упорядоченную последовательность элементов. Поскольку размер среза является переменным, при их использовании становится гораздо больше гибкости; при работе с коллекциями данных, которые могут понадобиться в будущем для расширения или сжатия, использование среза гарантирует, что в вашем коде не возникнут ошибки при попытке манипулировать длиной коллекции. В большинстве случаев эта изменчивость стоит возможного перераспределения памяти, иногда требуемого срезами по сравнению с массивами. Когда вам нужно сохранить много элементов или перебрать элементы, и вы хотите иметь возможность легко изменять эти элементы, вы, вероятно, захотите работать с типом данных среза.

Определение среза

Срезы определяются путем объявления типа данных, которому предшествуют пустые квадратные скобки ([]) и список элементов между фигурными скобками ({}). Вы заметите, что в отличие от массивов, которым для объявления определенной длины требуется символint между скобками, в срезе между скобками ничего нет, что представляет его переменную длину.

Давайте создадим фрагмент, содержащий элементы типа данных string:

seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp", "anemone"}

Когда мы распечатываем срез, мы можем видеть элементы, которые находятся в срезе:

fmt.Printf("%q\n", seaCreatures)

Это приведет к следующему:

Output["shark" "cuttlefish" "squid" "mantis shrimp" "anemone"]

Если вы хотите создать срез определенной длины без заполнения элементов коллекции, вы можете использовать встроенную функциюmake():

oceans := make([]string, 3)

Если вы напечатаете этот фрагмент, вы получите:

Output["" "" ""]

Если вы хотите предварительно выделить память для определенной емкости, вы можете передать третий аргументmake():

oceans := make([]string, 3, 5)

Это сделает обнуленный срез длиной3 и предварительно выделенной емкостью элементов5.

Теперь вы знаете, как объявить срез. Однако это еще не решает ошибку, которая была у нас с массивомcoral ранее. Чтобы использовать функциюappend() сcoral, вам сначала нужно научиться вырезать части массива.

Нарезка массивов на кусочки

Используя индексы для определения начальной и конечной точек, вы можете вызвать подраздел значений в массиве. Это называется массивомslicing, и вы можете сделать это, создав диапазон номеров индексов, разделенных двоеточием, в форме[first_index:second_index]. Однако важно отметить, что при разрезании массива результатом является срез, а не массив.

Допустим, вы хотите просто распечатать средние элементы массиваcoral, без первого и последнего элементов. Вы можете сделать это, создав срез, начинающийся с индекса1 и заканчивающийся непосредственно перед индексом3:

fmt.Println(coral[1:3])

Запуск программы с этой строкой приведет к следующему:

Output[foliose coral pillar coral]

При создании среза, как в[1:3], первое число - это место, где начинается срез (включительно), а второе число - это сумма первого числа и общего количества элементов, которые вы хотите получить:

array[starting_index : (starting_index + length_of_slice)]

В этом случае вы назвали второй элемент (или индекс 1) в качестве отправной точки и в итоге назвали два элемента. Вот как будет выглядеть расчет:

array[1 : (1 + 2)]

Вот как вы пришли к этой записи:

coral[1:3]

Если вы хотите установить начало или конец массива в качестве начальной или конечной точки среза, вы можете опустить одно из чисел в синтаксисеarray[first_index:second_index]. Например, если вы хотите распечатать первые три элемента массиваcoral, то есть"blue coral","foliose coral" и"pillar coral", вы можете сделать это, набрав:

fmt.Println(coral[:3])

Это напечатает:

Output[blue coral foliose coral pillar coral]

Это напечатало начало массива, остановившись прямо перед индексом3.

Чтобы включить все элементы в конец массива, вы должны изменить синтаксис:

fmt.Println(coral[1:])

Это дало бы следующий фрагмент:

Output[foliose coral pillar coral elkhorn coral]

В этом разделе обсуждались вызовы отдельных частей массива путем вырезания подразделов. Далее вы узнаете, как использовать срезы для преобразования целых массивов в срезы.

Преобразование из массива в срез

Если вы создаете массив и решаете, что он должен иметь переменную длину, вы можете преобразовать его в фрагмент. Чтобы преобразовать массив в срез, используйте процесс среза, который вы изучили на шагеSlicing Arrays into Slices этого руководства, за исключением того, что на этот раз выберите весь срез, пропустив оба номера индекса, которые будут определять конечные точки:

coral[:]

Имейте в виду, что вы не можете преобразовать переменнуюcoral в сам срез, так как после того, как переменная определена в Go, ее тип нельзя изменить. Чтобы обойти это, вы можете скопировать все содержимое массива в новую переменную в виде среза:

coralSlice := coral[:]

Если вы напечатаетеcoralSlice, вы получите следующий вывод:

Output[blue coral foliose coral pillar coral elkhorn coral]

Теперь попробуйте добавить элементblack coral, как в разделе массива, используяappend() с только что преобразованным срезом:

coralSlice = append(coralSlice, "black coral")
fmt.Printf("%q\n", coralSlice)

Это выведет срез с добавленным элементом:

Output["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral"]

Мы также можем добавить более одного элемента в один операторappend():

coralSlice = append(coralSlice, "antipathes", "leptopsammia")
Output["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia"]

Чтобы объединить два фрагмента вместе, вы можете использоватьappend(), но вы должны расширить второй аргумент, чтобы добавить его, используя синтаксис раскрытия...:

moreCoral := []string{"massive coral", "soft coral"}
coralSlice = append(coralSlice, moreCoral...)
Output["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]

Теперь, когда вы узнали, как добавить элемент в свой фрагмент, мы рассмотрим, как его удалить.

Удаление элемента из среза

В отличие от других языков, Go не предоставляет никаких встроенных функций для удаления элемента из слайса. Элементы должны быть удалены из ломтика, нарезая их.

Чтобы удалить элемент, вы должны вырезать элементы перед этим элементом, вырезать элементы после этого элемента, а затем добавить эти два новых фрагмента вместе без элемента, который вы хотите удалить.

Еслиi - это индекс удаляемого элемента, то формат этого процесса будет выглядеть следующим образом:

slice = append(slice[:i], slice[i+1:]...)

ИзcoralSlice удалим элемент"elkhorn coral". Этот элемент находится в позиции индекса3.

coralSlice := []string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral", "black coral", "antipathes", "leptopsammia", "massive coral", "soft coral"}

coralSlice = append(coralSlice[:3], coralSlice[4:]...)

fmt.Printf("%q\n", coralSlice)
Output["blue coral" "foliose coral" "pillar coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]

Теперь элемент в позиции индекса3, строка"elkhorn coral", больше не находится в нашем срезеcoralSlice.

Мы также можем удалить диапазон тем же подходом. Допустим, мы хотели удалить не только элемент"elkhorn coral", но также"black coral" и"antipathes". Мы можем использовать диапазон в выражении, чтобы выполнить это:

coralSlice := []string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral", "black coral", "antipathes", "leptopsammia", "massive coral", "soft coral"}

coralSlice = append(coralSlice[:3], coralSlice[6:]...)

fmt.Printf("%q\n", coralSlice)

Этот код извлечет индекс3,4 и5 из среза:

Output["blue coral" "foliose coral" "pillar coral" "leptopsammia" "massive coral" "soft coral"]

Теперь, когда вы знаете, как добавлять и удалять элементы из слайса, давайте посмотрим, как измерить объем данных, которые слайс может хранить в любой момент времени.

Измерение емкости среза с помощьюcap()

Поскольку срезы имеют переменную длину, функцияlen()+`method is not the best option to determine the size of this data type. Instead, you can use the `+cap() определяет емкость среза. Это покажет вам, сколько элементов может содержать слайс, что определяется тем, сколько памяти уже выделено для слайса.

[.note] #Note: Поскольку длина и емкость массива всегда одинаковы, функцияcap() не будет работать с массивами.
#

Обычноcap() используется для создания среза с заранее заданным количеством элементов, а затем программно заполнять эти элементы. Это позволяет избежать потенциальных ненужных выделений, которые могут произойти при использованииappend() для добавления элементов сверх той емкости, для которой в настоящее время выделена емкость.

Возьмем сценарий, в котором мы хотим составить список чисел от0 до3. Для этого мы можем использоватьappend() в цикле, или мы можем предварительно выделить срез и использоватьcap() для прохождения цикла для заполнения значений.

Во-первых, мы можем взглянуть на использованиеappend():

numbers := []int{}
for i := 0; i < 4; i++ {
    numbers = append(numbers, i)
}
fmt.Println(numbers)
Output[0 1 2 3]

В этом примере мы создали срез, а затем создали циклfor, который будет повторяться четыре раза. Каждая итерация добавляла текущее значение переменной циклаi в индекс срезаnumbers. Однако это может привести к ненужному выделению памяти, что может замедлить работу вашей программы. При добавлении к пустому слайсу, каждый раз, когда вы делаете вызов для добавления, программа проверяет емкость слайса. Если добавленный элемент заставляет срез превышать эту емкость, программа выделит дополнительную память для его учета. Это создает дополнительные издержки в вашей программе и может привести к замедлению выполнения.

Теперь давайте заполним срез без использованияappend(), предварительно выделив определенную длину / емкость:

numbers := make([]int, 4)
for i := 0; i < cap(numbers); i++ {
    numbers[i] = i
}

fmt.Println(numbers)
Output[0 1 2 3]

В этом примере мы использовалиmake() для создания среза и предварительно распределили элементы4. Затем мы использовали функциюcap() в цикле для итерации по каждому обнуленному элементу, заполняя каждый, пока он не достигнет заранее выделенной емкости. В каждом цикле мы поместили текущее значение переменной циклаi в индекс срезаnumbers.

Хотя стратегииappend() иcap() функционально эквивалентны, примерcap() позволяет избежать любых дополнительных выделений памяти, которые могли бы потребоваться при использовании функцииappend().

Построение многомерных ломтиков

Вы также можете определить срезы, которые состоят из других срезов, в качестве элементов, причем каждый список в квадратных скобках заключен в большие скобки родительского среза. Такие наборы срезов называютсяmultidimensional slices. Их можно представить как изображающие многомерные координаты; например, набор из пяти срезов, каждый из которых имеет длину шесть элементов, может представлять собой двумерную сетку с горизонтальной длиной пять и вертикальной высотой шесть.

Давайте рассмотрим следующий многомерный срез:

seaNames := [][]string{{"shark", "octopus", "squid", "mantis shrimp"}, {"Sammy", "Jesse", "Drew", "Jamie"}}

Чтобы получить доступ к элементу в этом срезе, нам нужно будет использовать несколько индексов, по одному для каждого измерения конструкции:

fmt.Println(seaNames[1][0])
fmt.Println(seaNames[0][0])

В предыдущем коде мы сначала идентифицируем элемент с индексом0 среза с индексом1, затем мы указываем элемент с индексом0 среза с индексом0 . Это приведет к следующему:

OutputSammy
shark

Ниже приведены значения индекса для остальных отдельных элементов:

seaNames[0][0] = "shark"
seaNames[0][1] = "octopus"
seaNames[0][2] = "squid"
seaNames[0][3] = "mantis shrimp"

seaNames[1][0] = "Sammy"
seaNames[1][1] = "Jesse"
seaNames[1][2] = "Drew"
seaNames[1][3] = "Jamie"

При работе с многомерными слайсами важно иметь в виду, что вам нужно ссылаться на более чем один индексный номер, чтобы получить доступ к определенным элементам в соответствующем вложенном слайсе.

Заключение

В этом уроке вы узнали основы работы с массивами и слайсами в Go. Вы прошли несколько упражнений, чтобы продемонстрировать, как массивы фиксированы по длине, тогда как срезы имеют переменную длину, и обнаружили, как это различие влияет на ситуационное использование этих структур данных.

Чтобы продолжить изучение структур данных в Go, ознакомьтесь с нашей статьей оUnderstanding Maps in Go или изучите всю сериюHow To Code in Go.