Массивы, срезы, карты
В главе 3 мы изучили базовые типы Go. В этой главе мы рассмотрим еще три встроенных типа: массивы, срезы и карты.
Массивы
Массив — это нумерованная последовательность элементов одного типа с фиксированной длиной. В Go они выглядят так:
var x [5]int
x — это пример массива, состоящего из пяти элементов типа int . Запустим следующую программу:
package main import "fmt" func main()
Вы должны увидеть следующее:
[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 и заполняем его;
- затем мы в цикле считаем общее количество баллов;
- и в конце мы делим общую сумму баллов на количество элементов, чтобы узнать средний балл.
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
Указывать тип не обязательно — Go сам может его выяснить по содержимому массива.
Иногда массивы могут оказаться слишком длинными для записи в одну строку, в этом случае Go позволяет записывать их в несколько строк:
x := [5]float64
Обратите внимание на последнюю , после 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 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 := []intslice2 := append(slice1, 4, 5) fmt.Println(slice1, slice2) >
После выполнения программы slice1 будет содержать [1,2,3] , а slice2 — [1,2,3,4,5] . append создает новый срез из уже существующего (первый аргумент) и добавляет к нему все следующие аргументы.
Пример работы copy :
func main() < slice1 := []intslice2 := 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 — это карта, которая представляет 10 первых химических элементов, индексируемых символами. Это очень частый способ использования карт — в качестве словаря или таблицы. Предположим, мы пытаемся обратиться к несуществующему элементу:
fmt.Println(elements["Un"])
Если вы выполните это, то ничего не увидите. Технически карта вернет нулевое значение хранящегося типа (для строк это пустая строка). Несмотря на то, что мы можем проверить нулевое значение с помощью условия ( elements[«Un»] == «» ), в Go есть лучший способ сделать это:
name, ok := elements["Un"] fmt.Println(name, ok)
Доступ к элементу карты может вернуть два значения вместо одного. Первое значение это результат запроса, второе говорит, был ли запрос успешен. В Go часто встречается такой код:
if name, ok := elements["Un"]; ok
Сперва мы пробуем получить значение из карты, а затем, если это удалось, мы выполняем код внутри блока.
Объявления карт можно записывать сокращенно — так же, как массивы:
elements := map[string]string
Карты часто используются для хранения общей информации. Давайте изменим нашу программу так, чтобы вместо имени элемента хранить какую-нибудь дополнительную информацию о нем. Например его агрегатное состояние:
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) ?
- Дан массив:
Как при вводе с клавиатуры набора чисел в одну строку преобразовать её в массив int
На голанге — ручками. можно получить массив строк и конвертнуть каждый элемент, но лучше создать ридера и вычитывать сразу в нужный тип.
8 сен 2022 в 14:37
2 ответа 2
Сортировка: Сброс на вариант по умолчанию
let str = "2 2 2 2 2"; let intArr = str.split(" ").map(a => parseInt(a)); console.log(intArr);
Отслеживать
ответ дан 8 сен 2022 в 14:20
Danil Apsadikov Danil Apsadikov
1,273 2 2 золотых знака 11 11 серебряных знаков 24 24 бронзовых знака
а есть вариант не использовать строки?
8 сен 2022 в 14:23
а какой по вашему тип данных сможет содержать вот такие данные «2 2 2 2 2», кроме строк ?
8 сен 2022 в 14:25
А какой у вас тип данных у 2 2 2 2 2 может быть, кроме как не строка?
8 сен 2022 в 14:25
а, правда, логично, не подумал (
8 сен 2022 в 14:26
@DanilApsadikov кстати, а вы просто промазали с ЯП или теги не смотрели? это не по JS вопрос 🙂
8 сен 2022 в 14:26
var str = "2 2 2 2 2" var intArr []int strArr := strings.Split(str, " ") for _, v := range strArr < i, _ := strconv.Atoi(v) intArr = append(intArr, i) >fmt.Println(intArr)
Отслеживать
ответ дан 8 сен 2022 в 15:42
1 2 2 бронзовых знака
добро пожаловать на Stack Overflow на русском! пожалуйста, постарайтесь оставлять чуть более развёрнутые ответы. дополнить ответ можно, нажав править
Полное руководство по массивам и срезам в Golang
Поначалу легко воспринимать массивы и срезы как одно и то же, но с разными названиями: и то и другое является структурой данных для представления коллекций. Однако на самом деле они сильно отличаются друг от друга.
В этой статье мы рассмотрим их различия и реализации в Go.
Мы обратимся к примерам, чтобы вы могли принимать более взвешенное решение о том, где их применять.
Массивы
Массив – это коллекция фиксированного размера. Акцент здесь ставится именно на фиксированный размер, поскольку, как только вы зададите длину массива, позже вы уже не сможете ее изменить.
Давайте рассмотрим пример. Мы создадим массив из четырех целых значений:
arr := [4]int
Длина и тип
В примере выше переменная arr определена как массив типа [4]int , это означает, что массив состоит из четырех элементов. Важно обратить внимание на то, что размер 4 включен в определение типа.
Из этого исходит, что на самом деле массивы разной длины — это массивы разных типов. Вы не сможете приравнять друг к другу массивы разной длины и не сможете присвоить значение одного массива другому в таком случае:
longerArr := [5]int longerArr = arr // This gives a compilation error longerArr == arr // This gives a compilation error
Я обнаружил, что о массивах легко рассуждать с точки зрения структур. Если бы вы попытались создать структуру подобную массиву, у вас скорее всего получилось бы следующее:
// Struct equivalent for an array of length 4 type int4 struct < e0 int e1 int e2 int e3 int >// Struct equivalent for an array of length 5 type int5 struct < e0 int e1 int e2 int e3 int e5 int >arr := int4 longerArr := int5
На самом деле так делать не рекомендуется, однако это хороший способ получить представление о том, почему массивы разной длины являются массивами разного типа.
Представление в памяти
Массив хранится в виде последовательности из n блоков определенного типа:
Эта память распределяется в момент, когда вы инициализируете переменную типа массив.
Передача по ссылке
В Go нет такой вещи, как передача по ссылке, вместо этого все передается по значению. Если присвоить значение массива другой переменной, то присваиваемое значение просто будет скопировано.
Если вы хотите передать лишь «ссылку» на массив, используйте указатели:
При распределении памяти и в функции массив на самом деле является простым типом данных и работает во многом аналогично структурам.
Срезы
Срезы можно рассматривать как расширенную реализацию массивов.
Срезы были реализованы в Go, чтобы покрыть некоторые крайне распространенные варианты использования, с которыми разработчики сталкиваются при работе с коллекциями, например, динамическое изменение размера коллекций.
Объявление среза очень похоже на объявление массива, за исключением того, что опускается спецификатор длины:
slice := []int
Если просто смотреть на код, то кажется, что срезы и массивы достаточно похожи, но основное их отличие лежит в реализации и условиях использования.
Представление в памяти
Срез аллоцируется иначе, чем массив, и по сути является модифицированным указателем. Каждый срез содержит в себе три блока информации:
- Указатель на последовательность данных.
- Длину (length), которая определяет количество элементов, которые сейчас содержатся в срезе.
- Объем (capacity), который определяет общее количество предоставленных ячеек памяти.
Из этого следует, что срезы разной длины можно присваивать друг другу. Они имеют один и тот же тип, а указатель, длина и объем могут меняться:
slice1 := []int slice2 := []int // slices of any length can be assigned to other slice types slice1 = slice2
Срез, в отличии от массива, не выделяет память во время инициализации. Фактически, срезы инициализируется с нулевым ( nil ) значением.
Передача по ссылке
Когда вы присваиваете срез другой переменной, вы все еще передаете значение. Здесь значение обращается только к указателю, длине и объему, а не к памяти, занимаемой самими элементами.
Добавление новых элементов
Чтобы добавить новые элементы к срезу, необходимо использовать функцию append .
nums := []int nums = append(nums, 8)
Под капотом это будет выглядеть, как присвоение значения, указанного для нового элемента, и после – возвращение нового среза. Длина нового среза будет на единицу больше.
Если при добавлении элемента длина увеличивается на единицу и тем самым превышает заявленный объем, необходимо предоставить новый объем (в этом случае текущий объем обычно удваивается).
Именно поэтому чаще всего рекомендуется создавать срез с длиной и объемом, указанными заранее (особенно, если вы четко имеете представление какого размера срез вам нужен):
arr := make([]int, 0, 5) // This creates a slice with length 0 and capacity 5
Что использовать: массивы или срезы?
Массивы и срезы – это совершенно разные вещи, и, следовательно, их варианты использования также разнятся.
Давайте рассмотрим несколько примеров с открытыми исходниками и стандартную библиотеку Go, чтобы понять, что и в каких случаях использовать.
Кейс 1: UUID
UUID – это 128-битные фрагменты данных, их часто используют для маркировки объекта или сущности. Обычно они представлены в виде шестнадцатеричных значений, разделенных тире:
e39bdaf4-710d-42ea-a29b-58c368b0c53c
В библиотеке Google UUID, UUID представлен как массив из 16 байт:
type UUID [16]byte
Это имеет смысл, поскольку мы знаем, что UUID состоит из 128 бит (16 байт). Мы не собираемся добавлять или удалять какие-либо байты из UUID, и поэтому использование массива для его представления будет.
Кейс 2: Сортировка целых значений
В этом примере мы будем использовать функцию sort.Ints из sort standard library:
s := []int // unsorted sort.Ints(s) fmt.Println(s) // [1 2 3 4 5 6]
Функция sort.Ints берет срез из целых чисел и сортирует их по возрастанию значений. Срезы здесь использовать предпочтительнее по двум причинам:
- Количество целых чисел не указано (количество целых чисел для сортировки может быть любым);
- Числа нужно отсортировать по возрастанию. Использование массива обеспечит передачу всей коллекции целых чисел в качестве значения, поэтому функция будет сортировать свою собственную копию, а не переданную ей коллекцию.
Заключение
Теперь, когда мы рассмотрели ключевые различия между массивами и срезами, а также их варианты использования, я хочу дать несколько советов, чтобы вам было проще решить, какую конструкцию следует использовать:
- Если сущность описывается набором непустых элементов фиксированной длины – используйте массивы.
- При описании коллекции, к которой вы хотите добавить или из которой удалить элементы – используйте срезы.
- Если коллекция может содержать любое количество элементов, используйте срезы.
- Будете ли вы каким-то образом изменять коллекцию? Если да, то следует использовать срезы.
Как видите, срезы охватывают большинство сценариев для создания приложений на Go. Тем не менее, массивы имеют право на существование, и более того, невероятно полезны, особенно когда появляется подходящий вариант использования.
- Блог компании OTUS
- Программирование
- Go
Читаем ввод с клавиатуры в командной строке
У каждого процесса есть свой стандартный ввод, вывод и дескриптор файла ошибок. stdin выступает в роли процесса ввода данных. Данная инструкция описывает, как читать данные из stdin .
Как читать ввод с клавиатуры в командной строке в Golang?
1. Создайте файл fmt.go со следующим содержимым:
Премиум канал по Golang
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании!
Уроки, статьи и Видео
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
package main
var name string
fmt . Println ( «Как тебя зовут?» )
fmt . Scanf ( «%s\n» , & name )
var age int
fmt . Println ( «Сколько тебе лет?» )
fmt . Scanf ( «%d\n» , & age )
fmt . Printf ( «Привет, %s, твой возраст — %d\n» , name , age )
2. Выполните код через go run fmt.go ;
3. Введите имя Джон и нажмите Enter;
4. Введите возраст 40 и нажмите Enter;
5. Посмотрите на финальный результат:
Как тебя зовут ?
Сколько тебе лет ?
Привет , Джон , твой возраст — 40
6. Создайте файл scanner.go со следующим содержимым:
package main
// Scanner может просканировать построчный ввод
sc : = bufio . NewScanner ( os . Stdin )
for sc . Scan ( ) <
txt : = sc . Text ( )
fmt . Printf ( "Эхо: %s\n" , txt )
7. Выполните код через go run scanner.go ;
8. Введите слово Привет и нажмите Enter;
9. Нажмите CTRL + C для отправки SIGINT ;
10. Посмотрите на результат:
Эхо : Привет
11. Создайте файл reader.go со следующим содержимым:
package main
data : = make ( [ ] byte , 8 )
n , err : = os . Stdin . Read ( data )
if err == nil && n > 0 <
process ( data )
func process ( data [ ] byte ) <
fmt . Printf ( "Получено: %X %s\n" , data , string ( data ) )
12. Выполните код через конвейерный ввод echo ‘Go is awesome!’ | go run reader.go ;
13. Посмотрите на вывод:
echo ‘Go is awesome!’ | go run reader .go
Получено : 6F20697320617765 Go is aw
Получено : 736F6D652127207C esome !
stdin в Go можно получить через Stdin из пакета os . По факту, это тип File , что имплементирует интерфейс Reader . Чтение из Reader очень простое. Предыдущий код показывает три самых частых случая того, как читать из Stdin .
Первая опция показывает, как использовать пакет fmt , что предоставляет функции Scan , Scanf и Scanln . Функция Scanf читает ввод и сохраняет его в переменную (или переменные). Преимущество Scanf в том, что вы можете определить формат сканируемого значения. Функция Scan только читает ввод и сохраняет его в переменную ( без предварительного форматирования ), а Scanln , как можно понять по названию, читает ввод, что заканчивается обрыванием строки \n .
Scanner , из второго примера, предоставляет удобный способ для сканирования более крупного ввода. Scanner содержит функцию Split, через которую можно определить настраиваемую функцию разделения строки. К примеру, для сканирования слов из stdin вы можете использовать bufio.ScanWords и предварительно определенный SplitFunc .
Чтение через API Reader является последним представленным подходом. Он предоставляет вам больше контроля над тем, как читать ввод и как его сохранять.
Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»