# Массивы, срезы, отображения

## Массивы

Пожалуй, массивы в языке Go не имеют ярких особенностей, что отличали бы в этом плане Go от прочих языков в ту или иную сторону. Итак, вспомним, что такое массив. В классическом понимании, массив есть индексированная последовательность однотипных элементов фиксированного размера. Конечно, есть языки, наподобие PHP или JavaScript, где массив - это сущность, которая позволяет с собою более вольное обращение, к языку Go это никак не относится. Объявляется массив вот так:

```go
package main

import (
	"fmt"
)

func main() {

	const size int = 5
	var array [size]int
	array = [size]int{0, 1, 2, 3, 4}

	for i := 0; i < size; i++ {
		fmt.Println(array[i])
	}

}
```

Мы объявили массив размерностью 5, присвоили каждому элементу значения, и, в завершение, вывели значения каждого элемента в цикле **for**. Как видите, ничего необычного. Как и в языке C, размерность массива задаётся при помощи константы. Нумерация элементов начинается с нуля. Разумеется, вместо константы можно использовать литерал и отказаться от использования отдельной переменной для хранения размерности массива, если это вам необходимо.

```go
package main

import (
	"fmt"
)

func main() {
	//указание типа в левой части можно опустить.
	var array = [5]int{0, 1, 2, 3, 4}

	for i := 0; i < len(array); i++ {
		fmt.Println(array[i])
	}

}
```

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

Как и всякая другая переменная, при объявлении внутри функции, массив может быть объявлен кратко, при помощи оператора **:=**

```go
package main

func main() {
	
	array := [5]int{0, 1, 2, 3, 4}	

}
```

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

```go
package main

func main() {
	
	array := [15]int{0, 1, 2, 3, 4, 5,
		6, 7, 8, 9, 10,
		11, 12, 13, 14, //не забудьте поставить запятую!
	}	

}
```

По массиву (и по срезу тоже) возможна своеобразная "навигация" путём указания значений в квадратных скобках. Например вот так:

```go
package main

import (
	"fmt"
)

func main() {

	const size int = 5
	var array [size]int
	array = [size]int{0, 1, 2, 3, 4}

	for _, val := range array[1:3] {
		fmt.Println(val)
	}

}
//Результат:
//1
//2
```

Как видите, всё просто. Указываем в квадратных скобках желаемый начальный и конечный индекс (включительно). Если начальный индекс - 0, его можно не указывать, вот так: **\[:3]**. Точно так же поступают и с последним индексом: **\[1:]**.

Массивы одного типа могут присваиваться и сравниваться. Под типом в данном случае понимается совокупность: размерность + тип элемента.

```go
package main

import (
	"fmt"
)

func main() {

	array := [5]int{0, 1, 2, 3, 4}
	array1 := [5]int{5, 6, 7, 8, 9}

	fmt.Println(array)
	fmt.Println(array1)
	fmt.Println(array == array1)//сравнение

	array1 = array// и присвоение

	fmt.Println(array1)
	fmt.Println(array == array1)

}
//Результат:
//[0 1 2 3 4]
//[5 6 7 8 9]
//false
//[0 1 2 3 4]
//true
```

Для упрощения дела, я вывожу здесь значения массивов при помощи функции **Println** пакета **fmt** в "сыром" виде, чтобы не загромождать код циклами.

Этим, пожалуй, исчерпывается необходимая информация о массивах.

## Срезы

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

Объявить срез просто:

```go
package main

import (
	"fmt"
)

func main() {
	var slice []int
	fmt.Println(slice)
	if slice == nil {
		fmt.Println("Value is nil!")
	}
	fmt.Println(len(slice))

}
//Результат:
//[]
//Value is nil!
//0
```

Значение **nil** является нулевым указателем. Оно аналогично понятию **NULL** в С-подобных языках. Это - зарезервированное слово в языке Go. Как вы уже поняли, приведенное выше объявление создаёт "нулевой" срез. Это, однако, не мешает работать с таким срезом, что мы покажем далее. А сейчас инициализируем срез.

```go
package main

import (
	"fmt"
)

func main() {
	//указание типа в левой части можно опустить.
	var slice = []int{0, 1, 2, 3, 4}
	fmt.Println(slice)
	fmt.Println(len(slice)) // срез из 5 элементов

}
//Результат:
//[0 1 2 3 4]
//5
```

Существует встроенная функция **make**, применяемая для инициализации срезов и отображений. Покажем, как она работает:

```go
package main

import (
	"fmt"
)

func main() {
	var slice = make([]int, 5)
	fmt.Println(slice)
	fmt.Println(len(slice))

}
//Результат:
//[0 0 0 0 0]
//5
```

Не трудно заметить, в данном случае мы создали снова-таки пустой срез, но в данном случае он не равен значению **nil**,  а получил определённую размерность. К тому же каждый из элементов среза был проинициализирован значением типа по умолчанию. Поскольку в нашем случае тип - integer, то соответственно, все элементы были проинициализированы значением 0.

Важно! В отличие от массивов, срез имеет свойство сравнимости лишь со значением **nil**, но не другим срезом.

```go
package main

import (
	"fmt"
)

func main() {

	slice1 := []int{1, 2, 3}
	slice2 := []int{1, 2, 3}	
	fmt.Println(slice1==slice2)

}
//Ошибка!
//invalid operation: slice1 == slice2 (slice can only be compared to nil)
```

Для разрешения проблемы сравнения двух срезов, я написал такую функцию:

```go
//TestSliceEq is a func to compare slices if they are equal or not 
func TestSliceEq(a, b []int) bool {

	if a == nil && b == nil {
		return true
	}

	if a == nil || b == nil {
		return false
	}

	if len(a) != len(b) {
		return false
	}

	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}

	return true
}
```

Рекомендую вам также ею воспользоваться. Начните создавать собственную библиотеку функций. Напомню, в разделе о пакетах мы создали корневой каталог **fqdn.org** для хранения библиотек нашего кода. В нём мы создали папку **common-utils** для хранения файлов пакета **utils**. В пакете находился файл **utils.go** непосредственно с кодом. Полагаю, самое время добавить в этот файл новую функцию.

Замечу, что данная функция обрабатывает срезы одного типа - **\[]int**. Для обработки срезов иного типа необходимо заменить тип параметров функции на соответствующий - **\[]string**, **\[]float64**, **\[]byte** и так далее. Для создания же универсальной функции, нам потребуются более сложные инструменты, и, следовательно, больше знаний. Нам повезло. Так много нового впереди!

## Append и copy - встроенные функции

Массивы неповоротливы, как испанские галеоны. Всё что можно сделать с массивом - присвоить один другому при условии однотипности, да сравнить. Срезы же гораздо гибче! Специально применительно к срезам заведены встроенные функции append и copy, призванные причинять всяческую пользу. Разберёмся же в них!

#### append

Эта встроенная функция позволяет добавить элемент в конец данного среза. Вот так:

```go
package main

import (
	"fmt"
)

func main() {

	slice := []int{0, 1, 2, 3, 4}
	fmt.Println(slice)
	slice = append(slice, 5)
	fmt.Println(slice)

}
//Результат:
//[0 1 2 3 4]
//[0 1 2 3 4 5]
```

Но что если нам понадобится объединить подобным образом два среза? Нет проблем! С помощью небольшой синтаксической хитрости вы можете сделать это.

```go
package main

import (
	"fmt"
)

func main() {

	slice := []int{0, 1, 2, 3, 4}
	slice1 := []int{5, 6, 7, 8, 9}
	fmt.Println(slice)
	slice = append(slice, slice1...)//обратите внимание на многоточие!
	fmt.Println(slice)

}
//Результат:
//[0 1 2 3 4]
//[0 1 2 3 4 5 6 7 8 9]
```

Как это стало возможным? Резонный вопрос. Дело в том, что append - это так называемая вариативная функция. Знак многоточия означает, что количество параметров данного типа не определено. Таким образом, передаваемый в качестве аргумента срез рассматривается функцией как последовательность аргументов данного типа. Но не будем слишком отвлекаться и забегать вперёд. Предлагаю о функциях побеседовать подробнее в соответствующей главе. А сейчас - просто запомните данный кейс.

Интересный момент: срез - аргумент можно задать без создания переменной,"на лету":

```go
package main

import (
	"fmt"
)

func main() {

	slice := []int{0, 1, 2, 3, 4}
	fmt.Println(slice)
	slice = append(slice, []int{5, 6, 7, 8, 9}...)
	fmt.Println(slice)

}
//Результат:
//[0 1 2 3 4]
//[0 1 2 3 4 5 6 7 8 9]
```

Непроинициализированный, "пустой" срез, значение которого nil, не помеха тому, чтобы в него успешно "аппендить":

```go
package main

import (
	"fmt"
)

func main() {

	var slice []int //значение nil
	fmt.Println(slice)
	slice = append(slice, []int{0, 1, 2, 3, 4}...)
	fmt.Println(slice)

}
//Результат:
//[]
//[0 1 2 3 4]
```

#### copy

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

```go
package main

import (
	"fmt"
)

func main() {

	slice1 := []int{0, 1, 2, 3, 4}
	slice2 := []int{5, 6, 7, 8, 9}
	copy(slice1, slice2)
	fmt.Println(slice1)
	fmt.Println(slice2)

}
//Результат:
//[5 6 7 8 9]
//[5 6 7 8 9]
```

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

```go
package main

import (
	"fmt"
)

func main() {

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

}
//Результат:
//[5 6 7 8 9 10 11 12]
```

В противоположной ситуации, то есть при меньшей размерности первого среза, избыточные элементы второго среза будут проигнорированы (отброшены):

```go
package main

import (
	"fmt"
)

func main() {

	slice1 := make([]int, 2)
	slice2 := []int{5, 6, 7, 8, 9}
	copy(slice1, slice2)// элементы 7, 8, 9 "не помещаются"
	fmt.Println(slice1)

}
//Результат:
//[5 6]
```

## Приём "работа на месте"

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

```go
func reverse(s []byte) {
	for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
		s[i], s[j] = s[j], s[i]
	}
}
```

Эта функция делает реверс "на месте" элементов заданного среза. Соберите информацию по теме  **endianness**, и вам сразу станет интересно приведенное выше. Впрочем, мы, скорее всего, ещё коснёмся данного вопроса. Скучно не будет!

## Срезы: а что под капотом?

Практически, мы использовали срезы как динамические массивы. Это допустимо, но необходимо понимать, что это лишь удобное упрощение, редукция. Настало время разобраться, что же такое представляет собою срез в языке Go фактически. Суть дела заключается в том, что всякий срез привязан к подлежащему так называемому базовому массиву. Он содержит такие понятия, как указатель, длину и ёмкость. Указатель "глядит" на первый элемент массива, доступный через срез. Благодаря этому, изменение среза, приводит и к изменению базового массива, в том числе и при передаче среза в функцию в качестве аргумента. Длина берётся при помощи уже знакомой нам встроенной функции **len**. В свою очередь, функция **cap** даёт нам значение ёмкости, которая есть количество элементов между началом среза и концом базового массива. Таким образом, когда мы создаём срез, "под капотом" на деле создаётся массив, с которого и берётся собственно "срез". С другой стороны, можно сначала явно создать массив, а затем "привязать" к нему срез, или несколько срезов, в том числе, возможно, перекрывающих друг друга. А ещё, при использовании функции **make**, можно третьим параметром указать емкость базового массива, отличную от создаваемого среза. Попытаемся проиллюстрировать сказанное.

Простое объявление среза. Что делает компилятор на самом деле?

```go
package main

import (
	"fmt"
)

func main() {
	//Объявляя срез, "за кулисами" на деле создаётся базовый массив
	//Объявленный срез указывает на нулевой элемент массива
	//и равен его ёмкости
	slice := []int{0, 1, 2, 3, 4}
	fmt.Println(len(slice))//длина среза
	fmt.Println(cap(slice))//ёмкость	

}
//Результат:
//5
//5
```

Явное объявление базового массива с последующим взятием с него срезов:

```go
package main

import (
	"fmt"
)

func main() {
	//Создаём явно базовый массив
	var week = [7]string{"Понедельник", "Вторник", "Среда", "Четверг", "Пятница",
	 "Суббота", "Воскресенье"}
	//Делаем с него срезы
	workDays := week[:5]
	weekend := week[5:]
	fmt.Println(week)
	fmt.Println(workDays)
	fmt.Println(weekend)
	fmt.Println(len(workDays))
	fmt.Println(cap(workDays))
	fmt.Println(len(weekend))
	fmt.Println(cap(weekend))

}
//Результат:
//[Понедельник Вторник Среда Четверг Пятница Суббота Воскресенье]
//[Понедельник Вторник Среда Четверг Пятница]
//[Суббота Воскресенье]
//5
//7
//2
//2
```

Важная деталь! При взятии среза из элементов базового массива в диапазоне X...Y указывается значение Y+1. Как следует из примера выше, срез **workDays** содержит элементы с 0 по 4 включительно, однако записывается это как **workDays := week\[:5]**! Будьте внимательны!

И, наконец, продемонстрируем третий параметр функции **make**:

```go
package main

import (
	"fmt"
)

func main() {	
	
	slice := make([]int, 5, 10)
	fmt.Println(len(slice))//длина среза
	fmt.Println(cap(slice))//ёмкость	

}
//Результат:
//5
//10
```

По факту, создаётся базовый массив ёмкостью 10 элементов и с него берётся срез, являющийся указателем на нулевой элемент этого массива, и имеющий длину 5 элементов.

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

**One more thing...**

Для вывода на экран содержимого среза, правильнее всего использовать цикл **for**. При работе с большим количеством однотипных срезов, во избежание загромождения кода циклами и следуя концепции "The **DRY Principle**: Don't Repeat Yourself", рекомендую добавить в свою библиотеку такую функцию:

```go
//PrintBuf func prints elements of the slice
func PrintBuf(buffer []int) {
	for _, val := range buffer {
		fmt.Printf("%v ", val)
	}
}
```

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

Для особо любознательных и нетерпеливых читателей сообщу, что для работы с байтовыми массивами существует встроенный пакет **bytes.** Строки также можно трактовать как разновидность массивов, и для работы с ними существует пакет **strings**, во многом аналогичный брату-близнецу **bytes**. Конечно, это будет большим забеганием вперёд.

## Отображения

Во всякой науке первейшая вещь - дефиниция. Отображения - как раз тот случай, когда следует об этом поговорить. В английском оригинале эта сущность определяется как **maps**. Поэтому некоторые не мудрствуя лукаво обзывают это просто "мапами", или "картами", на худой конец. В серьёзных же источниках в основном принят термин "отображения", который мы и будем употреблять, как наиболее академический. Что же по факту представляют собой **maps**? Отображения - это ассоциативные массивы по типу ключ - значение, где как ключ, так и значение могут быть любого типа (это может быть даже другое отображение). Очень близки к отображениям словари (dictionary) из C#. С математической точки зрения отображения - это истинные множества, так как это есть неупорядоченные совокупности уникальных данных.

Объявим отображение. Отметим, что в отличие от срезов, мы обязаны сразу проинициализировать наше отображение. Это делается при помощи встроенной функции **make,** либо в результате сокращённой инициализации. В противном случае отображение всё же будет объявлено (со значением **nil**), однако дальнейшая работа с таким отображением вызовет исключение:

```go
package main

import (
	"fmt"
)

func main() {
	//Создадим отображение типа [int]string
	//то есть ключ имеет тип int, а значение -  string
	var didgits map[int]string
	fmt.Println(didgits)
	if didgits == nil {
		fmt.Println("NIL")
	}
	//Добавляем элемент
	didgits[0] = "Zero" // вызовет типичное исключение

}
//Результат:
//map[]
//NIL
//panic: assignment to entry in nil map
```

Вот так правильно:

```go
package main

import (
	"fmt"
)

func main() {

	var didgits = make(map[int]string)
	fmt.Println(didgits)

	didgits[0] = "Zero" // теперь исключения нет
	//Добавляем элемент
	fmt.Println(didgits[0])

}
//Результат:
//map[]
//Zero
```

Для добавления элемента используется простое присваивание значения по ключу.

```go
package main

import (
	"fmt"
)

func main() {

	var didgits = make(map[int]string)

	//Добавляем элементы
	didgits[0] = "Zero"
	didgits[1] = "One"
	didgits[2] = "Two"
	didgits[3] = "Three"
	didgits[4] = "Four"
	didgits[5] = "Five"
	didgits[6] = "Six"
	didgits[7] = "Seven"
	didgits[8] = "Eight"
	didgits[9] = "Nine"

	for key, val := range didgits {
		fmt.Println("Key: ", key)
		fmt.Println("Val: ", val)
	}

}
//Результат: (последовательность принципиально не упорядочена!)
//Key:  7
//Val:  Seven
//Key:  8
//Val:  Eight
//Key:  2
//Val:  Two
//Key:  3
//Val:  Three
//Key:  5
//Val:  Five
//Key:  6
//Val:  Six
//Key:  0
//Val:  Zero
//Key:  1
//Val:  One
//Key:  4
//Val:  Four
//Key:  9
//Val:  Nine
```

Отображения позволяется инициализировать сокращённо:

```go
package main

import (
	"fmt"
)

func main() {

	didgits := map[int]string{
		0: "Zero",
		1: "One",
		2: "Two",
		3: "Three",
		4: "Four",
		5: "Five",
		6: "Six",
		7: "Seven",
		8: "Eight",
		9: "Nine",
	}

	for key, val := range didgits {
		fmt.Println("Key: ", key)
		fmt.Println("Val: ", val)
	}

}
```

Для удаления элементов отображения используется встроенная функция **delete**.

```go
package main

import (
	"fmt"
)

func main() {

	var didgits = make(map[int]string)

	fmt.Println(didgits)

	didgits[0] = "Zero"

	fmt.Println(didgits)
	fmt.Println(didgits[0])

	delete(didgits, 0) //передаём имя отображения + ключ

	fmt.Println(didgits)
	fmt.Println(didgits[0])// нет такого элемента

}
//Результат:
//map[]
//map[0:Zero]
//Zero
//map[]
```

Это немного необычно, но попытка вывести на экран или удалить при помощи функции **delete** несуществующий элемент, не вызовет никакой ошибки! И всё же, вероятно, прежде чем приступить к каким-либо действиям, вы можете захотеть проверить наличие того или иного элемента в данном отображении. Такая возможность существует. Доступ к элементу отображения по ключу может опционально возвращать не только один элемент - собственно значение, но и булев флаг, означающий принадлежность данного ключа множеству, составляющему это отображение.

```go
package main

import (
	"fmt"
)

func main() {

	var didgits = make(map[int]string)	

	didgits[0] = "Zero"
	//Если элемент существует, выведем его на экран
	if value, ok := didgits[0]; ok{
		fmt.Println(value)
	}	

}
//Результат:
//Zero
```

&#x20;В завершение, приведу пример, когда значением отображения является другое отображение:

```go
package main

import (
	"fmt"
)

func main() {

	//Ключ - int, значение - map[string]string
	weekdays := map[int]map[string]string{
		1: map[string]string{"name": "Sunday", "type": "weekend"},
		2: map[string]string{"name": "Monday", "type": "workDay"},
		3: map[string]string{"name": "Wednesday", "type": "workDay"},
		4: map[string]string{"name": "Tuesday", "type": "workDay"},
		5: map[string]string{"name": "Thursday", "type": "workDay"},
		6: map[string]string{"name": "Friday", "type": "workDay"},
		7: map[string]string{"name": "Saturday", "type": "weekend"},
	}

	if value, ok := weekdays[1]; ok {
		fmt.Println(value["name"], value["type"])
	}

}
//Результат:
//Sunday weekend
```

Пожалуй, самое последнее... Отображения отлично сериализуются! Благодаря своей гибкости и полиморфности, они отлично зарекомендовали себя при работе с JSON в качестве полей структур. Но об этом - в своё время.

**Раунд!**


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.golang.org.ua/massivy-srezy-otobrazheniya.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
