Главная / Курсы / Golang / Срезы
# Глава 5. Срезы ## Понятие среза и его представление в памяти Массив переменной длины в Go реализуется с помощью среза (slice). Вместо массива всегда лучше использовать срез, если только вам не нужно хранить фиксированное количество элементов. Срезы передаются в функции по ссылке, то есть в функцию передается не копия среза (как в случае с массивом), а адрес этого среза. Изменения, внесенные в срез внутри функции, не будут потеряны по выходу из этой функции. Кроме того, такой прием существенно повышает быстродействие. Срез предоставляет доступ к подпоследовательности элементов массива. Чтобы объявить срез, необязательно для этого заводить массив, однако массив все равно создается неявным образом. Этот массив называется **базовым массивом.** Срез состоит из указателя, длины и емкости. Указатель есть адрес первого элемента среза, который не обязан быть первым элементом массива. Длина — это количество элементов среза. Длина не превышает емкость. Емкость — это количество элементов между началом среза и концом базового массива. Кстати, в Go строка `string` представляет собой срез байтов, доступный только для чтения. ## Объявление и инициализация среза Вот так можно объявить и инициализировать срез: ```go {.example_for_playground .example_for_playground_001} slice := []int{1, 2, 3, 4, 5} ``` В отличие от массива, размер среза не является частью его типа. Таким образом, тип переменной `slice` — `[]int`. Вот так можно объявить пустой срез из 10 элементов: ```go {.example_for_playground .example_for_playground_002} slice := make([]int, 10) ``` ## Функции работы со срезами Встроенная функция `make(T, args)` создает срезы, каналы и карты, возвращая инициализированное значение типа `T`. Для срезов `args` — это длина и емкость, для них полная форма данной функции — `make(T, len, cap)`, причем емкость может быть опущена, как в примере выше. В этом случае емкость равна длине. Очистить срез можно следующим образом: ```go slice = nil ``` `nil` представляет собой нулевое значение для указателей, интерфейсов, отображений, срезов, каналов, а также функций и является значением по умолчанию для них. Срез со значением `nil` не имеет базового массива. Его длина и емкость равны нулю. Стоит иметь в виду, что пустой срез необязательно равен `nil`: ```go {.example_for_playground .example_for_playground_003} slice := []int{} if slice == nil { fmt.Println("slice is nil") } else { fmt.Println("slice is NOT nil") } ``` ``` slice is NOT nil ``` Поэтому для того чтобы проверить, является ли срез пустым, задействуйте длину среза `len(slice) == 0`, а не сравнение с нулевым значением `slice == nil`. Чтобы добавить элемент к срезу, можно воспользоваться встроенной функцией `append`: ```go {.example_for_playground .example_for_playground_004} slice = append(slice, 125) ``` ## Срезы срезов Срезы, как и массивы, могут быть многомерными: ```go {.example_for_playground .example_for_playground_005} slice := [][]int{{1, 2, 3}, {4, 5, 6}} ``` Доступ к элементу с индексом `i` осуществляется также, как для массива. Через нотацию `[:]` можно выбрать несколько последовательных элементов среза. Вот так можно напечатать срез из элементов со значениями `2` и `3`: ```go {.example_for_playground .example_for_playground_006} slice := []int{1, 2, 3, 4, 5} fmt.Println(slice[1:3]) ``` ``` [2 3] ``` Первый индекс `1` включает данный элемент, второй индекс `3` не включает этот элемент. Таким образом, будут выбраны элементы с индексами `1` и `2`. Такое соглашение принято в языке Go всюду. Если не указать первое число в нотации `[:]`, то вместо него подставится нуль: ```go {.example_for_playground .example_for_playground_007} slice := []int{1, 2, 3, 4, 5} fmt.Println(slice[:3]) ``` ``` [1 2 3] ``` Если не указать последнее число, то вместо него подставится длина среза: ```go {.example_for_playground .example_for_playground_008} slice := []int{1, 2, 3, 4, 5} fmt.Println(slice[3:]) ``` ``` [4 5] ``` ## Вторичный срез Важно! Когда мы используем нотацию `[:]`, то создается так называемый вторичный срез. Такой срез не является самостоятельным, он ссылается на исходный срез. Поэтому при изменении элементов вторичного среза изменятся также и элементы исходного. Рассмотрим следующий пример. ```go {.example_for_playground .example_for_playground_009} slice := []int{1, 2, 3, 4, 5} slice2 := slice[1:3] slice2[1] = 10 fmt.Println(slice) ``` Программа выведет следующий срез: ``` [1 2 10 4 5] ``` Аналогично массивам, перебрать все элементы среза можно с помощью ключевого слова `range`. Узнать длину среза можно с помощью функции `len()`. Узнать емкость среза можно с помощью функции `cap()`. Функция `Split` пакета `strings` позволяет разбить текст по некоторому разделителю и получить срез: ```go {.example_for_playground .example_for_playground_010} s := "1,2,3" res := strings.Split(s, ",") fmt.Println(res) ``` Код выведет следующий срез: ``` [1 2 3] ``` В переменной `s` типа `string` содержится некоторый текст. Допишите функцию `count`, которая подсчитает число слов в этом тексте. Словом считать любую последовательность символов, разделяемую пробелами. {.task_text} ```go {.task_source #golang_chapter_0050_task_0010} package main import "fmt" func count(s string) int{ // ваш код здесь } func main() { s := "Язык Go является регистрозависимым языком" fmt.Println(count(s)) } ``` Воспользуйтесь функцией `strings.Split`. {.task_hint} ```go {.task_answer} package main import ( "fmt" "strings" ) func count(s string) int { if s == "" { return 0 } res := strings.Split(s, " ") return len(res) } func main() { s := "Язык Go является регистрозависимым языком" fmt.Println(count(s)) } ``` В переменной `slice` типа `[]uint` содержится некоторый срез. Реализуйте тело функции `maxEl`, которая принимает на вход срез данного типа и возвращает максимальный элемент. {.task_text} ```go {.task_source #golang_chapter_0050_task_0020} package main import "fmt" func maxEl(slice []uint) uint{ // ваш код здесь } func main() { slice := []uint{9, 10, 4, 2} fmt.Println(maxEl(slice)) } ``` Объявите переменную типа `uint`. По умолчанию в ней лежит значение `0`. Это минимальное из возможных значений для переменной данного типа. Реализуйте цикл по всем элементам среза. Если внутри цикла найдется элемент больше, чем значение данной переменной, присвойте переменной это значение. По окончанию цикла верните значений переменной. {.task_hint} ```go {.task_answer} package main import ( "fmt" ) func maxEl(slice []uint) uint { var res uint for _, el := range slice { if el > res { res = el } } return res } func main() { slice := []uint{9, 10, 4, 2} fmt.Println(maxEl(slice)) } ``` В переменной `slice` типа `[]uint16` содержится некоторый срез. Реализуйте тело функции `diff`, которая принимает на вход срез данного типа и возвращает разницу между максимальным и минимальным элементами среза. {.task_text} ```go {.task_source #golang_chapter_0050_task_0030} package main import "fmt" func diff(slice []uint16) uint16 { // ваш код здесь } func main() { slice := []uint16{9, 10, 4, 2} fmt.Println(diff(slice)) } ``` Максимальное значение для переменной типа `uint16` равно `65535`. {.task_hint} ```go {.task_answer} package main import ( "fmt" ) func diff(slice []uint16) uint16 { var maxEl uint16 var minEl uint16 = 65535 for _, el := range slice { if el > maxEl { maxEl = el } if el < minEl { minEl = el } } return maxEl - minEl } func main() { slice := []uint16{9, 10, 4, 2} fmt.Println(diff(slice)) } ``` ## Проблема утечки памяти Как упоминалось ранее, срез — это всего лишь указатель, длина и емкость. **Срез не создает копию базового массива или его части.** Рассмотрим следующий пример: ```go {.example_for_playground} package main import "fmt" func f() []int { const size = 100500 // постоянное значение arr := [size]int{} for i := 0; i < size; i++ { arr[i] = i } slice := arr[100:110] return slice } func main() { s := f() fmt.Println(s) } ``` Функция `f` создает массив размером `100500` и заполняет его, после чего возвращает срез длиной `10` элементов. Код работает, как ожидается: ``` [100 101 102 103 104 105 106 107 108 109] ``` Однако поскольку создан срез на базовый массив размером `100500`, этот базовый массив не может быть очищен сборщиком мусора по выходу из функции. Емкость среза составляет `100400`. Чтобы убедиться в том, что базовый массив по-прежнему в памяти, мы можем расширить срез внутри функции `main` при условии, что не выходим за границу емкости: ```go {.example_for_playground .example_for_playground_011} func main() { s := f() fmt.Println(s[:100400]) } ``` Исправить ситуацию с утечкой памяти можно с помощью встроенной функции `copy`, которая позволяет скопировать интересующие элементы одного среза в другой: ```go {.example_for_playground} package main import ( "fmt" ) func f() []int { const size = 100500 arr := [size]int{} for i := 0; i < size; i++ { arr[i] = i } slice := arr[100:110] c := make([]int, len(slice)) copy(c, slice) return c } func main() { s := f() fmt.Println(s) } ``` ## Резюме 1. В Go используются срезы, которые реализуют массивы переменной длины. Срезы могут быть как одномерными, так и многомерными. 2. Срез представляет собой указатель, длину и емкость. 3. Срез всегда ссылается на базовый массив, кроме среза со значением `nil`. 4. Для обхода среза можно использовать ключевое слово `range`. 5. Срезы обладают множеством достоинств по сравнению с массивами, поэтому их стоит использовать всюду, где заранее неизвестно хранимое количество элементов.
Отправка...
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!