Сегменты
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
Сегменты — разновидность коллекций, которые могут расширяться для хранения дополнительных элементов; а это как раз то, что нужно! Оказывается, в Go существует структура данных, в которую можно добавлять новые значения, — она называется сегментом. Как и массив, сегмент состоит из нескольких элементов, относящихся к одному типу. В отличие от массивов, существуют функции, позволяющие добавлять новые элементы в конец сегмента. Отличие сегмента от массива: Фактически это уже знакомый синтаксис объявления массива, только без указания размера. var myArray [5]int // Массив — обратите внимание на размер. var mySlice []int // Сегмент — размер не задан В отличие от переменных для массивов, объявление переменной для сегмента не приводит к автоматическому созданию сегмента. Для этого следует вызвать встроенную функцию make. Функции передается тип создаваемого сегмента (он должен соответствовать типу переменной, которой вы собираетесь присвоить сегмент) и длина сегмента при создании. Пример: var notes []string notes = make([]string, 7) notes[0] = "do" notes[1] = "re" notes[2] = "mi" fmt.Println(notes[2]) fmt.Println(notes[0]) Пример: pr := make([]int, 5) pr[0] = 99 pr[4] = 200 fmt.Println("pr[4] - pr[0] = ", pr[4]-pr[0]) Встроенная функция len для сегментов работает так же, как и для массивов. Передайте len сегмент, и функция вернет его длину в виде целого числа. Пример: notes := make([]string, 7) primers := make([]int, 5) fmt.Println(len(notes)) fmt.Println(len(primers)) Циклы "for" и "for...range" работают с сегментами точно так же, как и с массивами: Пример: letters := []string{"a", "b", "c"} for i := 0; i < len(letters); i++ { fmt.Println(letters[i]) } fmt.Println(" ") for _, letter := range letters { fmt.Println(letter) } |
Литералы сегментов
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
Как и с массивами, если вы заранее знаете, какими значениями должен быть заполнен сегмент в исходном состоянии, то можете инициализировать сегмент этими значениями при помощи литерала сегмента. Литерал сегмента очень похож на литерал массива, но если литерал массива содержит длину массива в квадратных скобках, у литерала сегмента квадратные скобки пусты. За пустыми скобками следует тип элементов, которые будут храниться в сегменте, и список исходных значений всех элементов, заключенный в фигурные скобки. Вызывать функцию make необязательно, при использовании литерала сегмента ваш код создаст сегмент и заполнит его. Сегмент: []int{9, 18, 27} | | | | | Список значений, разделенных запятыми. | Тип элементов в сегменте Пустая пара квадратных скобок Пример: notes := []string{"do", "re", "mi", "fa", "so", "la", "ti"} //Значения присваиваются с помощью литерала сегмента. fmt.Println(notes[3], notes[6], notes[0]) primes := []int{ // Многострочный литерал сегмента. 2, 3, 5, } fmt.Println(primes[0], primes[1], primes[2]) Погодите! Похоже, что сегменты могут делать все, что делают массивы, и в них можно добавлять элементы. Тогда почему бы не ограничиться сегментами и не забыть про эту ерунду с массивами? !!! Потому что сегменты построены на основе массивов. !!! И вы не сможете понять, как работают сегменты, не понимая массивы. Каждый массив существует на основе базового массива. Данные сегмента на самом деле хранятся в базовом массиве, а сегмент всего лишь предоставляет «окно» для работы с некоторыми (или всеми) элементами массива. Когда вы используете функцию make или литерал сегмента для создания сегмента, базовый массив при этом создается автоматически (и вы не можете обратиться к нему иначе как через сегмент). Но вы также можете создать массив самостоятельно, а затем создать сегмент на основе этого массива при помощи оператора сегмента. Пример: underlyingArray := [5]string{"a", "b", "c", "d", "e"} slice1 := underlyingArray[0:3] fmt.Println(slice1) Пример: underlyingArray := [5]string{"a", "b", "c", "d", "e"} i, j := 1, 4 slice2 := underlyingArray[i:j] fmt.Println(slice2) У оператора сегмента предусмотрены значения по умолчанию как для начального, так и для конечного индексов. Если начальный индекс не указан, будет использовано значение 0 позиции. Пример: underlyingArray := [5]string{"a", "b", "c", "d", "e"} slice4 := underlyingArray[:3] fmt.Println(slice4) А если не указан конечный индекс, то в сегмент включаются элементы от начального индекса и до конца базового завершается. Пример: underlyingArray := [5]string{"a", "b", "c", "d", "e"} slice5 := underlyingArray[1:] fmt.Println(slice5) |
Базовые массивы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Как упоминалось ранее, сам сегмент не содержит данных, это всего лишь «окно» для просмотра элементов базового массива. Сегмент можно представить себе как микроскоп, направленный на определенную часть предметного стекла (базовый массив). Когда вы берете сегмент базового массива, то «видите» только ту часть элементов массива, которая видна через этот сегмент. Несколько сегментов могут существовать на основе одного базового массива. В этом случае каждый сегмент становится «окном» для отдельного подмножества элементов массива. Сегменты даже могут перекрываться! Присваивание нового значения элементу сегмента приводит к изменению соответствующего элемента в базовом массиве. Если на один и тот же базовый массив указывают несколько сегментов, то и изменения элементов массива будут видны во всех сегментах. Из-за этих потенциальных проблем обычно рекомендуется создавать сегменты с использованием make или литерала сегмента (вместо того, чтобы создать массив и применять к нему оператор сегмента). С make и литералами сегментов вам никогда не приходится иметь дела с базовым массивом. |
Расширение сегментов функцией «append»
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
В Go существует встроенная функция append, которая получает сегмент и одно или несколько значений, которые присоединяются в конец сегмента. Функция возвращает но вый, расширенный сегмент со всеми элементами исходного сегмента и новыми элементами, добавленными в его конец. Пример: slice := []string{"a", "b"} fmt.Println(slice, len(slice)) slice = append(slice, "c") fmt.Println(slice, len(slice)) slice = append(slice, "d", "e") fmt.Println(slice, len(slice)) Вам не нужно следить за тем, по какому индексу присваиваются новые значения, или за чем-нибудь еще! Просто вызовите функцию append и передайте ей сегмент со значениями, которые добавляются в конец сегмента, и вы получите новый расширенный сегмент. Да, так просто! Обратите внимание: возвращаемое значение append во всех случаях присваивается той же переменной сегмента, которая передается append. Таким образом предотвращается возможность непоследовательного поведения сегментов, возвращаемых append. Базовый массив сегмента не может увеличиваться в размерах. Если в массиве не остается места для добавления элементов, все элементы копируются в новый, больший массив, а сегмент обновляется, чтобы он базировался на новом массиве. Но поскольку все это происходит где-то за кулисами внутри функции append, невозможно простым способом определить, имеет ли возвращенный сегмент тот же базовый массив, как и переданный сегмент, или другой. Если в программе будут оставаться оба сегмента, это может привести к непредсказуемому поведению. Пример: s1 := []string{"s1", "s1"} s2 := append(s1, "s2", "s2") s3 := append(s2, "s3", "s3") s4 := append(s3, "s4", "s4") fmt.Println(s1, s2, s3, s4) s4[0] = "XX" fmt.Println(s1, s2, s3, s4) По этой причине при вызове append возвращаемое значение обычно присваивается той же переменной сегмента, которая была передана append. Если в программе хранится только один сегмент, то вам не придется беспокоиться о том, используют ли два сегмента один базовый массив! Пример: s1 := []string{"s1", "s1"} s1 = append(s1, "s2", "s2") s1 = append(s1, "s3", "s3") s1 = append(s1, "s4", "s4") fmt.Println(s1) |
Сегменты и нулевые значения
1 2 3 4 5 6 7 8 9 10 11 |
Как и в случае с массивами, при обращении к элементу сегмента, которому не было присвоено значение, вы получите нулевое значение для этого типа. Пример: floatSlice := make([]float64, 10) boolSlice := make([]bool, 10) fmt.Println(floatSlice[9], boolSlice[5]) Пример: var intSlice []int var stringSlice []string fmt.Printf("intSlice: %#v, stringSlice: %#v\n", intSlice, stringSlice) |