Массивы, срезы, карты в GO программировании

1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (7 оценок, среднее: 4,00 из 5)
Загрузка...

Массивы

Массив — это нумерованная последовательность элементов одного типа с фиксированной длиной. В Go они выглядят так:

var x [5]int

x — это пример массива, состоящего из пяти элементов типа int. Запустим следующую программу:

package main

import "fmt"

func main() {
    var x [5]int
    x[4] = 100
    fmt.Println(x)
}

Вы должны увидеть следующее:

[0 0 0 0 100]

x[4] = 100 должно читаться как «присвоить пятому элементу массива x значение 100». Может показаться странным то, что x[4] является пятым элементом массива, а не четвертым, но, как и строки, массивы нумеруются с нуля. Доступ к элементам массива выглядит так же, как у строк. Вместо fmt.Println(x) мы можем написать fmt.Println(x[4]) и в результате будет выведено 100.

Пример программы, использующей массивы:

func main() {
    var x [5]float64
    x[0] = 98
    x[1] = 93
    x[2] = 77
    x[3] = 82
    x[4] = 83

    var total float64 = 0
    for i := 0; i < 5; i++ {
        total += x[i]
    }
    fmt.Println(total / 5)
}

Эта программа вычисляет среднюю оценку за экзамен. Если вы выполните её, то увидите 86.6. Давайте рассмотрим её внимательнее:

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

Эта программа работает, но её всё еще можно улучшить. Во-первых, бросается в глаза следующее: i < 5 и total / 5. Если мы изменим количество оценок с 5 на 6, то придется переписывать код в этих двух местах. Будет лучше использовать длину массива:

var total float64 = 0
for i := 0; i < len(x); i++ {
    total += x[i]
}
fmt.Println(total / len(x))

Напишите этот кусок кода и запустите программу. Вы должны получить ошибку:

$ go run tmp.go
# command-line-arguments
.\tmp.go:19: invalid operation: total / len(x) (mismatched types float64 and int)

Проблема в том, что len(x) и total имеют разный тип. total имеет тип float64, а len(x) — int. Так что, нам надо конвертировать len(x) в float64:

fmt.Println(total / float64(len(x)))

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

Другая вещь, которую мы можем изменить в нашей программе - это цикл:

var total float64 = 0
for i, value := range x {
    total += value
}
fmt.Println(total / float64(len(x)))

В этом цикле i представляет текущую позицию в массиве, а value будет тем же самым что и x[i]. Мы использовали ключевое слово range перед переменной, по которой мы хотим пройтись циклом.

Выполнение этой программы вызовет другую ошибку:

$ go run tmp.go
# command-line-arguments
.\tmp.go:16: i declared and not used

Компилятор Go не позволяет вам создавать переменные, которые никогда не используются в коде. Поскольку мы не используем i внутри нашего цикла, то надо изменить код следующим образом:

var total float64 = 0
for _, value := range x {
    total += value
}
fmt.Println(total / float64(len(x)))

Одиночный символ подчеркивания _ используется, чтобы сказать компилятору, что переменная нам не нужна (в данном случае нам не нужна переменная итератора).

А еще в Go есть короткая запись для создания массивов:

x := [5]float64{ 98, 93, 77, 82, 83 }

Указывать тип не обязательно — Go сам может его выяснить по содержимому массива.

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

x := [5]float64{
    98,
    93,
    77,
    82,
    83,
}

Обратите внимание на последнюю , после 83. Она обязательна и позволяет легко удалить элемент из массива просто закомментировав строку:

x := [4]float64{
    98,
    93,
    77,
    82,
    // 83,
}

Срезы

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

var x []float64

Единственное отличие объявления среза от объявления массива — отсутствие указания длины в квадратных скобках. В нашем случае x будет иметь длину 0.

Срез создается встроенной функцией make:

x := make([]float64, 5)

Этот код создаст срез, который связан с массивом типа float64, длиной 5. Срезы всегда связаны с каким-нибудь массивом. Они не могут стать больше чем массив, а вот меньше — пожалуйста. Функция make принимает и третий параметр:

x := make([]float64, 5, 10)

10 — это длина массива, на который указывает срез:

Массивы, срезы, карты в GO программировании

Другой способ создать срез — использовать выражение [low : high]:

arr := [5]float64{1,2,3,4,5}
x := arr[0:5]

low - это позиция, с которой будет начинаться срез, а high - это позиция, где он закончится. Например: arr[0:5] вернет [1,2,3,4,5]arr[1:4] вернет [2,3,4].

Для удобства мы также можем опустить lowhigh или и то, и другое. arr[0:] это то же самое что arr[0:len(arr)]arr[:5] то же самое что arr[0:5] и arr[:] то же самое что arr[0:len(arr)].

Функции срезов

В Go есть две встроенные функции для срезов: append и copy. Вот пример работы функции append:

func main() {
    slice1 := []int{1,2,3}
    slice2 := append(slice1, 4, 5)
    fmt.Println(slice1, slice2)
}

После выполнения программы slice1 будет содержать [1,2,3], а slice2 — [1,2,3,4,5]append создает новый срез из уже существующего (первый аргумент) и добавляет к нему все следующие аргументы.

Пример работы copy:

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

После выполнения этой программы slice1 будет содержать [1,2,3], а slice2 — [1,2]. Содержимое slice1 копируется в slice2, но поскольку в slice2 есть место только для двух элементов, то только два первых элемента slice1 будут скопированы.

Карта

Карта (также известна как ассоциативный массив или словарь) — это неупорядоченная коллекция пар вида ключ-значение. Пример:

var x map[string]int

Карта представляется в связке с ключевым словом map, следующим за ним типом ключа в скобках и типом значения после скобок. Читается это следующим образом: «x — это карта string-ов для int-ов».

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

var x map[string]int
x["key"] = 10
fmt.Println(x)

Вы должны увидеть ошибку, похожую на эту:

panic: runtime error: assignment to entry in nil map

goroutine 1 [running]:
main.main()
  main.go:7 +0x4d

goroutine 2 [syscall]:
created by runtime.main
        C:/Users/ADMINI~1/AppData/Local/Temp/2/bindi
t269497170/go/src/pkg/runtime/proc.c:221
exit status 2

До этого момента мы имели дело только с ошибками во время компиляции. Сейчас мы видим ошибку исполнения.

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

x := make(map[string]int)
x["key"] = 10
fmt.Println(x["key"])

Если выполнить эту программу, то вы должны увидеть 10. Выражение x["key"] = 10 похоже на те, что использовались при работе с массивами, но ключ тут не число, а строка (потому что в карте указан тип ключа string). Мы также можем создать карты с ключом типа int:

x := make(map[int]int)
x[1] = 10
fmt.Println(x[1])

Это выглядит очень похоже на массив, но существует несколько различий. Во-первых, длина карты (которую мы можем найти так: len(x)) может измениться, когда мы добавим в нее новый элемент. В самом начале при создании длина 0, после x[1] = 10 она станет равна 1. Во-вторых, карта не является последовательностью. В нашем примере у нас есть элемент x[1], в случае массива должен быть и первый элемент x[0], но в картах это не так.

Также мы можем удалить элементы из карты используя встроенную функцию delete:

delete(x, 1)

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

package main

import "fmt"

func main() {
    elements := make(map[string]string)
    elements["H"] = "Hydrogen"
    elements["He"] = "Helium"
    elements["Li"] = "Lithium"
    elements["Be"] = "Beryllium"
    elements["B"] = "Boron"
    elements["C"] = "Carbon"
    elements["N"] = "Nitrogen"
    elements["O"] = "Oxygen"
    elements["F"] = "Fluorine"
    elements["Ne"] = "Neon"

    fmt.Println(elements["Li"])
}

В данном примере elements - это карта, которая представляет 10 первых химических элементов, индексируемых символами. Это очень частый способ использования карт — в качестве словаря или таблицы. Предположим, мы пытаемся обратиться к несуществующему элементу:

fmt.Println(elements["Un"])

Если вы выполните это, то ничего не увидите. Технически карта вернет нулевое значение хранящегося типа (для строк это пустая строка). Несмотря на то, что мы можем проверить нулевое значение с помощью условия (elements["Un"] == ""), в Go есть лучший способ сделать это:

name, ok := elements["Un"]
fmt.Println(name, ok)

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

if name, ok := elements["Un"]; ok {    
    fmt.Println(name, ok)
}

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

Объявления карт можно записывать сокращенно - так же, как массивы:

elements := map[string]string{
    "H": "Hydrogen",
    "He": "Helium",
    "Li": "Lithium",
    "Be": "Beryllium",
    "B": "Boron",
    "C": "Carbon",
    "N": "Nitrogen",
    "O": "Oxygen",
    "F": "Fluorine",
    "Ne": "Neon",
}

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

func main() {
    elements := map[string]map[string]string{
            "H": map[string]string{
            "name":"Hydrogen",
            "state":"gas",
        },
        "He": map[string]string{
            "name":"Helium",
            "state":"gas",
        },
        "Li": map[string]string{
            "name":"Lithium",
            "state":"solid",
        },
        "Be": map[string]string{
            "name":"Beryllium",
            "state":"solid",
        },
        "B":  map[string]string{
            "name":"Boron",
            "state":"solid",
        },
        "C":  map[string]string{
            "name":"Carbon",
            "state":"solid",
        },
        "N":  map[string]string{
            "name":"Nitrogen",
            "state":"gas",
        },
        "O":  map[string]string{
            "name":"Oxygen",
            "state":"gas",
        },
        "F":  map[string]string{
            "name":"Fluorine",
            "state":"gas",
        },
        "Ne":  map[string]string{
            "name":"Neon",
            "state":"gas",
        },
    }

    if el, ok := elements["Li"]; ok {    
        fmt.Println(el["name"], el["state"])
    }
}

Заметим, что тип нашей карты теперь map[string]map[string]string. Мы получили карту строк для карты строк. Внешняя карта используется как поиск по символу химического элемента, а внутренняя — для хранения информации об элементе. Не смотря на то, что карты часто используется таким образом, в главе 9 мы узнаем лучший способ хранения данных.

Задачи

  • Как обратиться к четвертому элементу массива или среза?

  • Чему равна длина среза, созданного таким способом: make([]int, 3, 9)?

  • Дан массив:

    x := [6]string{"a","b","c","d","e","f"}
    

    что вернет вам x[2:5]?

  • Напишите программу, которая находит самый наименьший элемент в этом списке:

    x := []int{ 48,96,86,68, 57,82,63,70, 37,34,83,27, 19,97, 9,17, }

    Это тоже интересно:

    Дорогой читатель! Добавьте этот сайт в закладки своего браузера и поделитесь с друзьями ссылкой на этот сайт! Мы стараемся показывать здесь всякие хитрости и секреты. Пригодится точно.

    Пишем первую программу на Go.

    Типы в языке программирования GO.

    Управление потоком в Go.

Похожие статьи

  • Отзывы о статье0
  1. 18.11.2019

    […] Массивы, срезы, карты в GO программировании. […]

  2. 21.06.2020

    […] Массивы, срезы, карты в GO программировании. […]

  3. 20.09.2020

    […] Массивы, срезы, карты в GO программировании. […]

Добавить комментарий

Ваш адрес email не будет опубликован.

Adblock
detector