Go: Массивы и слайсы, глубокое погружение.
В этой статье мы обсудим все, что нужно знать о массивах и слайсах (срезах) в Go, подробно поговорим об их внутренней структуре и о том, почему они ведут себя совершенно по-разному, несмотря на то, что выполняют схожие функции.
Массивы в Golang
Массивы в Golang — это просто последовательности или однородные коллекции элементов со схожими типами данных в памяти.
Значения массива называются элементами или элементами. Массивы могут содержать ноль или более нулевых значений.
Доступ к элементу массива осуществляется по индексу.
Рассмотрим сценарий, в котором у нас есть числа до 100, и мы хотим сохранить их в переменной.
В отличие от хранения их по отдельности как num0, num1 до num99, сохранение их в массиве с именем num и доступ к каждому числу как num[0], num[1] и т. д. гораздо проще.
Объявление массива
В Go массивы можно объявлять двумя методами.
- Использование ключевого слова var
- Использование знака := (метод сокращенного объявления)
а) Использование ключевого слова var:
var array_name = [length]datatype{values} // here length is defined or var array_name = [...]datatype{values} // here length is inferred
Объявление массива
б) Использование знака :=:
array_name := [length]datatype{values} // here length is defined or array_name := [...]datatype{values} // here length is inferred
Примеры массивов Golang:
package main import ( "fmt" ) func main() { // an array with datatype string declared using keyword var var monitoring_tools = [3]string{"apm", "rum", "infra"} //an array with datatype integers declared using := sign num := [5]int{1,5,44,89,12} fmt.Println(monitoring_tools) fmt.Println(num) }
Пример массива с определенной длиной
Вывод:
Вывод примера массива с определенной длиной
package main import ( "fmt" ) func main() { var odd_num = [...]int{1,3,9} even_num := [...]int{4,8,16,72,80} fmt.Println(odd_num) fmt.Println(even_num) }
Пример массива с предполагаемой длиной
Вывод примера массива с предполагаемой длиной
Доступ к массиву Go
Доступ к элементам массива намного проще, поскольку память располагается последовательно.
Для доступа к элементам мы можем использовать оператор [].
// Declare an integer array of five elements. // Initialize each element with a specific value. array := [5]int{10, 20, 30, 40, 50} // Change the value at index 2. array[4] = 55
Многомерный массив
Многомерный массив — это массив с более чем двумя измерениями, которые представлены строками и столбцами.
Общая форма объявления многомерного массива:
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type //example var threedim [5][10][4]int
Пример двумерного массива:
// Declare a two dimensional integer array of four elements // by two elements. var array [4][2]int // Use an array literal to declare and initialize a two // dimensional integer array. array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}} // Declare and initialize index 1 and 3 of the outer array. array := [4][2]int{1: {20, 21}, 3: {40, 41}} // Declare and initialize individual elements of the outer // and inner array. array := [4][2]int{1: {0: 20}, 3: {1: 41}}
Доступ к элементам двумерного массива:
// Declare a two dimensional integer array of two elements. var array [2][2]int // Set integer values to each individual element. array[0][0] = 10 array[0][1] = 20 array[1][0] = 30 array[1][1] = 40
Вы можете скопировать многомерный массив в другой массив, если они имеют тот же тип данных.
Присвоение многомерных массивов одного типа:
// Declare two different two dimensional integer arrays. var array1 [2][2]int var array2 [2][2]int // Add integer values to each individual element. array2[0][0] = 10 array2[0][1] = 20 array2[1][0] = 30 array2[1][1] = 40 // Copy the values from array2 into array1. array1 = array2
Поскольку массив — это значение, вы также можете скопировать отдельные измерения.
// Copy index 1 of array1 into a new array of the same type. var array3 [2]int = array1[1] // Copy the integer found in index 1 of the outer array // and index 0 of the interior array into a new variable of // type integer. var value int = array1[1][0]
Пример многомерного массива:
package main import ( "fmt") func printarray(a [3][2]string) { for _, v1 := range a { for _, v2 := range v1 { fmt.Printf("%s ", v2) } fmt.Printf("\n") } } func main() { companies := [3][2]string{ {"apple", "microsoft"}, {"meta", "netflix"}, {"atatus", "amazon"}, } printarray(companies) var monitoring_tools [3][2]string monitoring_tools[0][0] = "apm" monitoring_tools[0][1] = "rum" monitoring_tools[1][0] = "synthetics" monitoring_tools[1][1] = "logs" monitoring_tools[2][0] = "api analytics" monitoring_tools[2][1] = "error monitoring" fmt.Printf("\n") printarray(monitoring_tools) }
Вывод:
Перейти к многомерному массиву
Передача массивов между функциями
Массив, передаваемый между функциями, может потребовать слишком много памяти и производительности, поскольку переменные всегда передаются по значению при передаче между функциями, тогда как когда переменная передается как массив, независимо от ее размера, она копируется.
// Declare an array var array []int // Pass the array to the function foo. foo(array) func foo(array []int) { ... return array }
Что следует помнить при работе с массивом Go
Массивы в Golang изменяемы, а это означает, что значения можно легко изменить с помощью синтаксиса, подробно описанного ниже.
var array_name[index] = element
Пример:
package main import ("fmt") func main() { var monitoring_tools = [3]string{"apm", "rum", "infra"} var monitoring_tools[1] = "synthetic" fmt.Println(monitoring_tools) }
- Доступ к элементам массива можно получить, используя значение индекса или цикл for.
Пример:
package main import ("fmt") func main() { var monitoring_tools = [3]string{"apm", "rum", "infra"} // Using index value fmt.Println(monitoring_tools[1]) // Using for loop for i:= 0; i < len(monitoring_tools); i++{ fmt.Println(monitoring_tools[i]) } }
- Повторяющиеся элементы могут храниться в массиве.
- Массивы в Go обычно одномерны.
- В Golang массив имеет тип значения, а не ссылочный тип.
Пример:
package main import ("fmt") func main() { // Creating an array with inferred array length tools:= [...]string{"apm", "rum", "synthetic", "infra", "logs"} fmt.Println("Original tools array(Before):", tools) // Creating a new variable and initialize with tools monitoring_tools := tools fmt.Println("New monitoring tools array(before):", monitoring_tools) // Change the value at index 0 to application performance monitoring monitoring_tools[0] = "application performance monitoring" fmt.Println("New monitoring tools array(After):", monitoring_tools) fmt.Println("Original tools array(After):", tools) }
- Тип массива также сопоставим, когда его элементы сравниваются с помощью оператора == .
Программы Go, как правило, не используют массивы из-за их фиксированного размера, что ограничивает их выразительные возможности. К счастью, срезы могут помочь в этом отношении. Прокрутите вниз, чтобы узнать больше о Slices.
Слайсы в Голанге
Слайсы (Срезы) — это легкая структура данных Go с последовательностью переменной длины, которая более мощна, гибка и удобна, чем массивы.
Слайсы также поддерживают хранение нескольких элементов одного типа в одной переменной, как это делают массивы. С другой стороны, срезы позволяют вам изменять длину в любое время.
Проблема с необработанными массивами заключается в том, что размер нельзя изменить после создания, размеры элементов фиксированы.
Например, представьте, что у вас есть массив, содержащий адреса каждого сотрудника вашей компании. Вы не можете изменить адреса в массиве после того, как добавили в компанию нового человека, вместо этого вам необходимо добавить в массив нового человека.
Чтобы эффективно использовать срезы, вам необходимо понимать, что они из себя представляют и что они делают.
Создание слайса
Слайсы объявляются как массивы, но без указания их размера. Поэтому размер можно изменить при необходимости.
Тип T среза представлен как []T.
Вы можете создать слайс тремя способами.
- Создание среза с использованием литералов.
- Создание среза из массива.
- Создание слайса из другого слайса.
Создание среза с использованием литералов
Как и в случае с массивами, литералы срезов могут быть объявлены с использованием ключевого слова var без упоминания размера в квадратных скобках.
// Creating a slice using a slice literal var num = []int{1, 2, 3, 4, 5}
Полный пример создания среза с литералами.
package main import ("fmt") func main() { // Creating a slice using a slice literal var odd_num = []int{3, 5, 7, 9, 11, 13, 17} // Usng := sign even_num := []int{2, 4, 8, 16, 32, 64} fmt.Println("Odd numbers are = ", odd_num) fmt.Println("Even numbers are = ", even_num) }
Вывод:
Пример перехода на слайс
Создание среза с использованием массивов
Срезы — это сегменты массива, поэтому их можно объявить, используя нижнюю границу (low) и верхнюю границу (up). Это делается путем разделения двух значений двоеточием.
arr[low:up]
Пример:
package main import ("fmt") func main() { // Initializing an array var odd_num = [7]int{3, 5, 7, 9, 11, 13, 17} // Creating a slice from the array var odd_slice []int = odd_num[1:4] fmt.Println("Array of odd numbers = ", odd_num) fmt.Println("Slice = ", odd_slice) }
Вывод:
Срез с использованием массивов:
Значение индексов low и high по умолчанию равно 0 и длине среза соответственно.
Ниже приведен пример среза со строками.
package main import ("fmt") func main() { monitoring := [5]string{"APM", "RUM", "Synthetics", "Infra", "Logs"} slice1 := monitoring[1:4] slice2 := monitoring[:3] slice3 := monitoring[2:] slice4 := monitoring[:] fmt.Println("Array = ", monitoring) fmt.Println("slice1 = ", slice1) fmt.Println("slice2 = ", slice2) fmt.Println("slice3 = ", slice3) fmt.Println("slice4 = ", slice4) }
Создание слайса из другого слайса
Существующий слайс можно разрезать для создания нового слайс.
Пример:
package main import ("fmt") func main() { cities := []string{"New York", "London", "Chicago", "Beijing", "Delhi", "Mumbai", "Bangalore", "Hyderabad", "Hong Kong"} asianCities := cities[3:] indianCities := asianCities[1:5] fmt.Println("cities = ", cities) fmt.Println("asianCities = ", asianCities) fmt.Println("indianCities = ", indianCities) }
Вывод:
Компоненты в слайсе
Срез состоит из трех элементов:
- Pointers - это специальные переменные в срезе, которые используются для хранения адреса памяти другой переменной. Это используется для указания определенных элементов массива.
- Length - это общее количество элементов, присутствующих в массиве.
- Capacity - определяет размер, до которого массив может расширяться или сжиматься.
Примеры компонентов:
var a = [6]int{100, 200, 300, 400, 500, 600} var s = [1:4]
Переменная s создается из индексов от 1 до 4. Следовательно, длина a равна 6 , b равна 5 , а емкость a равна 6 и b равна 5 , поскольку переменная s была создана из переменной a с индексом 1.
Пример использования строки:
// Golang program to illustrate // the working of the slice components package main import ("fmt") func main() { // Creating an array arr := [7]string{"Atatus", "offers", "full-stack", "monitoring", "tools"} // Display array fmt.Println("Array:", arr) // Creating a slice myslice := arr[0:6] // Display slice fmt.Println("Slice:", myslice) // Display length of the slice fmt.Printf("Length of the slice: %d", len(myslice)) // Display the capacity of the slice fmt.Printf("\nCapacity of the slice: %d", cap(myslice)) }
При повторной нарезке длину среза можно увеличить до полной емкости. Срез, длина которого превышает его длину, вызовет ошибку времени выполнения.
Ниже приведен пример для понимания концепции повторной нарезки.
package main import ("fmt") func main() { s := []int{2, 7, 3, 9, 10, 56, 78, 23, 89, 100} fmt.Println("Original Slice") fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s)) s = s[1:5] fmt.Println("\nSlicing index 2 through 8") fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s)) s = s[:8] fmt.Println("\nAfter extending the length") fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s)) s = s[2:] fmt.Println("\nAfter removing the first two elements") fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s)) }
Создание слайса с использованием встроенной функции make().
Golang также предлагает встроенную функцию make(), которую можно использовать для создания срезов.
func make([]T, len, cap) []T
Функция make принимает три параметра: тип, длину и необязательную емкость. Срезы относятся к массиву с базовой емкостью, равной заданному размеру.
Пример:
slice := make([]string, 2, 3)
Пример создания среза с помощью функции make().
package main import ("fmt") func main() { i := make([]int, 5, 5) fmt.Println(i) }
Значения среза, созданного с помощью функции make(), будут равны [0 0 0 0 0] соответственно.
Slice of Slices
Это просто вложенные слайсы. Можно создавать различные типы срезов, которые могут содержать срезы внутри срезов, используя один единственный срез.
package main import ("fmt") func main() { monitoring_tools := [][]string{ {"rum", "apm"}, {"synthetic", "logs"}, {"analytics", "infra"}, } fmt.Println("Slice monitoring_tools = ", monitoring_tools) fmt.Println("length = ", len(monitoring_tools)) fmt.Println("capacity = ", cap(monitoring_tools)) }
Перебор слайсов
Процесс разработки приложения или выполнения какой-либо задачи обычно включает итерации для выполнения чего-либо в коллекции.
Срезы Golang можно повторять одним из следующих двух способов:
Использование цикла for...range
package main import ("fmt") func main() { numbers := []int{1, 13, 120, 345, 1902} for index, value := range numbers { fmt.Printf("Index: %d, Value: %d\n", index, value) } }
Индекс можно инициализировать только одной переменной, если значение не является необходимым.
for index := range numbers { fmt.Printf("Indexes are: %d", index) }
Если вам нужно только значение, вы можете инициализировать его следующим образом:
package main import ("fmt") func main() { numbers := []float64{2.5, 7.3, 14.8, 54.4} sum := 0.0 for _, number := range numbers { sum += number } fmt.Printf("Total Sum = %.2f\n", sum) }
Использование цикла for
package main import ("fmt") func main() { countries := []string{"India", "America", "Russia", "England"} for i := 0; i < len(countries); i++ { fmt.Println(countries[i]) } }
Функции в слайсе
- Copy (Копировать)
- Append (Добавить)
- Remove (Удалять)
Копирование слайса
С помощью встроенной функции copy() в Go вы можете легко скопировать исходный срез в целевой срез. Чтобы скопировать слайс в другой слайс, не затрагивая оригинал, используется функция копирования.
Пример:
func main() { slice1 := []int{1, 2, 3, 4, 5} slice2 := make([]int, len(s1)) copy(slice2, slice1) fmt.Println(slice1, slice2) // [1 2 3 4 5] [1 2 3 4 5] slice2[1] = 10 // changing s2 does not affect s1 fmt.Println(slice1, slice2) // [1 2 3 4 5] [1 10 3 4 5] }
При использовании функции копирования первым аргументом является целевой срез, а вторым аргументом — исходный срез. Оба среза должны иметь одинаковый тип данных.
Во время копирования слайсов длина обоих слайсов может быть одинаковой, а может и не быть одинаковой. Копирование в нулевой слайс не приведет к копированию ничего, поскольку копируется только наименьшее количество элементов.
Пример:
func main() { s1 := []int{1, 2, 3, 4, 5} // s1 is length 5 s2 := make([]int, 3) // s2 is length 3 copy(s2, s1) fmt.Println(s2) // [1 2 3] var s3 []int // nil slice of length 0 copy(s3, s1) fmt.Println(s3) // [] }
Добавить слайс
Как мы все знаем, невозможно изменить или увеличить длину массива, но в срезах он является динамическим, и мы можем добавлять дополнительные элементы в существующие срезы.
Чтобы добавить или добавить слайс в конец другого слайса, вы можете использовать функцию add() в Go.
slice = append(slice, elem1, elem2, ...)
Срезы можно присоединять непосредственно друг к другу с помощью оператора .... В функции add() срез является первым параметром, а следующий параметр может быть одним или несколькими добавляемыми значениями.
package main import ("fmt") func main() { slice1 := []string{"Kiwi", "Orange", "Mango"} slice2 := []string{"Berry", "Cherry", "Grapes"} slice3 := append(slice1, slice2...) fmt.Println("slice1 = ", slice1) fmt.Println("slice2 = ", slice2) fmt.Println("After appending slice1 & slice2 = ", slice3) }
Слайс Удалить
Go не предоставляет встроенной функции для удаления элемента в срезе. Существует несколько стратегий удаления элемента.
Пример № 1. Если порядок срезов важен
Когда мы хотим сохранить порядок срезов после удаления элемента, мы перемещаем элементы слева от удаленного элемента на один.
package main import ("fmt") func remove(slice []int, s int) []int { return append(slice[:s], slice[s+1:]...) } func main() { var Slice1 = []int{1, 2, 3, 4, 5} fmt.Printf("slice1: %v\n", Slice1) Slice2 := remove(Slice1, 2) fmt.Printf("slice2: %v\n", Slice2) }
Вывод:
Пример № 2. Если порядок срезов не важен
package main import ("fmt") func remove(s []int, i int) []int { s[i] = s[len(s)-1] return s[:len(s)-1] } func main() { var Slice1 = []int{1, 2, 3, 4, 5} fmt.Printf("slice1: %v\n", Slice1) Slice2 := remove(Slice1, 2) fmt.Printf("slice2: %v\n", Slice2) }
Вывод:
Удаление элементов, если порядок срезов не важен
Пример №3. Удаление элементов из среза во время итерации
Несколько элементов можно удалить из среза, перебирая его. Однако вам придется выполнять итерацию от конца к началу, а не наоборот.
Поскольку длина среза продолжает уменьшаться при удалении элемента, вы столкнетесь с ошибкой индекса за пределами границ, если начнете итерацию с самого начала.
package main import ("fmt") func main() { num := []int{1, 2, 3, 4, 5, 6} for i := len(num) - 1; i >= 0; i-- { // this checks whether the integer is even // before removing it from the slice if num[i]%2 == 0 { num = append(num[:i], num[i+1:]...) } } fmt.Println(num) // [1,3,5] }
Вывод:
Удаление элементов из среза во время итерации
Передача Slice между функциями
Переменная-указатель в аргументе среза будет ссылаться на тот же базовый массив, даже если срез передается по значению. Таким образом, когда срез передается функции, изменения, сделанные внутри функции, видны и за ее пределами.
package main import ("fmt") // A function called change in which slice // is passed by value func change(element []string) { // Modifying the given slice element[2] = "api analytics" fmt.Println("Modified slice: ", element) } // Main function func main() { // Creating slice monitoring_tools := []string{"apm", "rum", "logs", "synthetics"} fmt.Println("Initial slice: ", monitoring_tools) // Passing the slice to the function change(monitoring_tools) fmt.Println("Final slice:", monitoring_tools) }
Вывод:
Передача Go Slice между функциями
Независимо от того, сколько функций они передают, указатель среза всегда указывает на одну и ту же ссылку.
Например, когда мы меняем журналы значений на аналитику API со значением индекса 2, указатель среза указывает на ссылку 2. После этого изменения также отражается срез вне функции, поэтому последний срез имеет вид [apm rum api Analytics Synthics] .
Заключение
Подводя итог, можно сказать, что срезы — это простой способ для программистов создавать динамические структуры. Они также позволяют программистам создавать очень эффективные структуры.
Ниже приведены несколько соображений при использовании массивов и срезов.
- Массивы используются, когда сущность представлена коллекцией непустых элементов.
- Чтобы описать общую коллекцию, в которую можно добавлять или удалять элементы, следует рассмотреть срезы.
- Вы можете определить срезы для описания коллекций, содержащих неограниченное количество элементов.
- Нужно ли каким-либо образом модифицировать коллекцию? Если да, используйте слайсы.
Теперь у вас есть четкое представление о том, как работают срезы и массивы в Go.
Срезы станут намного проще в использовании, как только вы поймете, как они работают, особенно благодаря встроенным функциям копирования и добавления.
Go срез был создан как альтернатива массивам, но его можно использовать и как альтернативу указателям, и как простой способ облегчить начинающим программистам управление памятью.