Массивы, срезы, карты
В главе 3 мы изучили базовые типы Go. В этой главе мы рассмотрим еще три встроенных типа: массивы, срезы и карты.
Массивы
Массив — это нумерованная последовательность элементов одного типа с фиксированной длинной. В 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
— это длина массива, на который указывает срез:
Другой способ создать срез — использовать выражение[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]
.
Для удобства мы также можем опуститьlow
,high
или и то, и другое.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, }