Как ввести массив с клавиатуры golang
Перейти к содержимому

Как ввести массив с клавиатуры golang

  • автор:

Массивы, срезы, карты

В главе 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) ?
  • Дан массив:

Golang

Одной из наиболее распространенных особенностей процедурных языков программирования является концепция массива. Массивы кажутся простыми вещами, но есть много вопросов, на которые нужно ответить при добавлении их в язык, таких как:

  • фиксированный размер или переменный размер?
  • размер является частью типа?
  • как выглядят многомерные массивы?
  • имеет ли пустой массив значение?

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

На ранней стадии разработки Go потребовалось около года, чтобы найти ответы на эти вопросы, прежде чем дизайн показался правильным. Ключевым этапом было введение срезов (slice), которые основаны на массивах фиксированного размера, чтобы обеспечить гибкую, расширяемую структуру данных. Тем не менее, по сей день программисты, плохо знакомые с Go, часто спотыкаются о то, как работают срезы, возможно, потому, что опыт других языков окрашивает их мышление.

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

Массивы

Массивы являются важным строительным блоком в Go, но, как и основа здания, они часто скрыты под более видимыми компонентами. Мы должны кратко поговорить о них, прежде чем перейти к более интересной, мощной и выдающейся идее срезов.

Массивы не часто встречаются в программах Go, потому что размер массива является частью его типа, что ограничивает его выразительность.

var buffer [256]byte

объявляет переменную buffer, которая содержит 256 байтов. Тип buffer включает его размер, [256]byte. Массив с 512 байтами будет иметь другой тип — [512]byte.

Данные, связанные с массивом, — это просто массив элементов. Схематически наш буфер выглядит так в памяти,

buffer: byte byte byte . 256 раз . byte byte byte

То есть переменная содержит 256 байтов данных и ничего больше. Мы можем получить доступ к его элементам с помощью знакомого синтаксиса индексации, buffer[0], buffer[1] и т. д. до buffer[255]. (Диапазон индекса от 0 до 255 охватывает 256 элементов.) Попытка индексировать буфер со значением вне этого диапазона приведет к сбою программы.

Существует встроенная функция len, которая возвращает количество элементов массива или среза, а также несколько других типов данных. Для массивов очевидно, что возвращает len. В нашем примере len(buffer) возвращает фиксированное значение 256.

Массивы имеют свое место — они, например, являются хорошим представлением матрицы преобразования — но их наиболее распространенная цель в Go — хранить память для среза.

Срезы: заголовок среза (slice header)

Срезы — это то, где происходит действие, но чтобы правильно их использовать, нужно точно понимать, что они из себя представляют и что они делают.

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

Учитывая нашу переменную массива buffer из предыдущего раздела, мы могли бы создать срез, который описывает элементы со 100 по 150 (точнее, со 100 по 149 включительно), разрезая массив:

var slice []byte = buffer[100:150]

В этом примере мы использовали полное объявление переменной, чтобы быть явными. Переменная slice имеет тип []byte, произносится как «срез байтов» и инициализируется из массива, называемого buffer, путем разбиения элементов от 100 (включительно) до 150 (исключая). Более идиоматический синтаксис отбрасывает тип, который устанавливается инициализирующим выражением:

var slice = buffer[100:150]

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

slice := buffer[100:150]

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

type sliceHeader struct < Length int ZerothElement *byte >slice := sliceHeader

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

До сих пор мы использовали операцию среза в массиве, но мы также можем нарезать срез, например так:

slice2 := slice[5:10]

Как и прежде, эта операция создает новый срез, в данном случае с элементами с 5 по 9 (включительно) исходного среза, что означает элементы с 105 по 109 исходного массива. Базовая структура sliceHeader для переменной slice2 выглядит следующим образом:

slice2 := sliceHeader

Обратите внимание, что этот header по-прежнему указывает на тот же базовый массив, хранящийся в переменной buffer.

Мы также можем создать срез заново, то есть срезать срез и сохранить результат обратно в исходную структуру slice. После

slice = slice[5:10]

структура sliceHeader для переменной slice выглядит так же, как и для переменной slice2. Вы увидите, что часто используется повторение, например, для усечения среза. Это утверждение удаляет первый и последний элементы нашего среза:

slice = slice[1:len(slice)-1]

Вы часто будете слышать, как опытные программисты на Go говорят о «slice header» (заголовок среза), потому что это именно то, что хранится в переменной среза. Например, когда вы вызываете функцию, которая принимает срез в качестве аргумента, например bytes.IndexRune, этот заголовок — это то, что передается в функцию. В этом вызове

slashPos := bytes.IndexRune(slice, '/')

аргумент slice, который передается в функцию IndexRune, фактически является «slice header».

В заголовке среза есть еще один элемент данных, о котором мы поговорим ниже, но сначала давайте посмотрим, что означает существование заголовка среза, когда вы программируете срезами.

Передача срезов в функции

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

Это имеет значение.

Когда мы вызывали IndexRune в предыдущем примере, ему была передана копия заголовка среза. Такое поведение имеет важные последствия.

Рассмотрим эту простую функцию:

func AddOneToEachElement(slice []byte) < for i := range slice < slice[i]++ >>

Он делает то, что подразумевает его имя, перебирая индексы среза (используя цикл for), увеличивая его элементы.

func main() < slice := buffer[10:20] for i := 0; i < len(slice); i++ < slice[i] = byte(i) >fmt.Println("before", slice) AddOneToEachElement(slice) fmt.Println("after", slice) >
before [0 1 2 3 4 5 6 7 8 9] after [1 2 3 4 5 6 7 8 9 10]

Несмотря на то, что заголовок среза передается по значению, заголовок содержит указатель на элементы массива, поэтому как исходный заголовок среза, так и копия заголовка, переданная функции, описывают один и тот же массив. Поэтому, когда функция возвращается, измененные элементы можно увидеть через исходную переменную slice.

Аргумент функции действительно является копией, как показано в следующем примере:

func SubtractOneFromLength(slice []byte) []byte < slice = slice[0 : len(slice)-1] return slice >func main() < fmt.Println("Before: len(slice) =", len(slice)) newSlice := SubtractOneFromLength(slice) fmt.Println("After: len(slice) =", len(slice)) fmt.Println("After: len(newSlice) background-color:#ededed;padding:10px;overflow:auto;">Before: len(slice) = 50 After: len(slice) = 50 After: len(newSlice) = 49

Здесь мы видим, что содержимое аргумента slice может быть изменено функцией, но его заголовок не может. Длина, хранящаяся в переменной slice, не изменяется при вызове функции, поскольку функции передается копия заголовка slice, а не оригинал. Таким образом, если мы хотим написать функцию, которая изменяет заголовок, мы должны вернуть его в качестве параметра результата, как мы это сделали здесь. Переменная slice не изменяется, но возвращаемое значение имеет новую длину, которая затем сохраняется в newSlice,

Указатели на срезы: получатели методов

Другой способ заставить функцию изменить заголовок среза — передать на него указатель. Вот вариант нашего предыдущего примера, который делает это:

func PtrSubtractOneFromLength(slicePtr *[]byte) < slice := *slicePtr *slicePtr = slice[0 : len(slice)-1] >func main() < fmt.Println("Before: len(slice) =", len(slice)) PtrSubtractOneFromLength(&slice) fmt.Println("After: len(slice) background-color:#ededed;padding:10px;overflow:auto;">Before: len(slice) = 50 After: len(slice) = 49

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

Допустим, мы хотели иметь метод на срезе, который усекает его на последнем слэше. Мы могли бы написать это так:

type path []byte func (p *path) TruncateAtFinalSlash() < i := bytes.LastIndex(*p, []byte("/")) if i >= 0 < *p = (*p)[0:i] >> func main() < // Преобразование из строки в путь. pathName := path("/usr/bin/tso") pathName.TruncateAtFinalSlash() fmt.Printf("%s\n", pathName) >

Если вы запустите этот пример, вы увидите, что он работает правильно, обновляя срез в вызывающей стороне.

/usr/bin

С другой стороны, если мы хотим написать метод для пути, который в верхнем регистре записывает буквы ASCII в пути (местно игнорируя неанглийские имена), метод может быть значением, поскольку получатель значения все еще будет указывать на тот же базовый массив.

type path []byte func (p path) ToUpper() < for i, b := range p < if 'a' > > func main() < pathName := path("/usr/bin/tso") pathName.ToUpper() fmt.Printf("%s\n", pathName) >
/USR/BIN/TSO

Здесь метод ToUpper использует две переменные в конструкции for range для захвата элемента index и slice. Эта форма цикла позволяет избежать записи p[i] несколько раз в теле.

Емкость (capacity)

Посмотрите на следующую функцию, которая расширяет свой аргумент slice срез целых чисел на один элемент:

func Extend(slice []int, element int) []int

func main() < var iBuffer [10]int slice := iBuffer[0:0] for i := 0; i < 20; i++ < slice = Extend(slice, i) fmt.Println(slice) >>
[0] [0 1] [0 1 2] [0 1 2 3] [0 1 2 3 4] [0 1 2 3 4 5] [0 1 2 3 4 5 6] [0 1 2 3 4 5 6 7] [0 1 2 3 4 5 6 7 8] [0 1 2 3 4 5 6 7 8 9] panic: runtime error: slice bounds out of range [:11] with capacity 10

Посмотрите, как срез растет, пока . это не так.

Пришло время поговорить о третьем компоненте заголовка среза: его емкости. Помимо указателя и длины массива, заголовок среза также хранит свою емкость:

type sliceHeader struct

Поле Capacity записывает, сколько места на самом деле имеет базовый массив; это максимальное значение, которое может достигать длина. Попытка вырастить срез за пределы его емкости выйдет за пределы массива и вызовет panic.

После того, как наш пример slice создан

slice := iBuffer[0:0]

его заголовок выглядит так:

slice := sliceHeader

Поле Capacity равно длине базового массива минус индекс в массиве первого элемента среза (в данном случае ноль). Если вы хотите узнать, какая емкость для среза, используйте встроенную функцию cap:

if cap(slice) == len(slice)

Встроенная функция make

Что если мы хотим вырастить срез сверх его возможностей? Это невозможно! По определению, емкость — это предел роста. Но вы можете получить эквивалентный результат, выделив новый массив, скопировав данные и изменив срез, чтобы описать новый массив.

Начнем с выделения. Мы могли бы использовать встроенную функцию new, чтобы выделить больший массив и затем нарезать результат, но вместо этого проще использовать встроенную функцию make. Она выделяет новый массив и создает заголовок среза, чтобы описать его, все сразу. Функция make принимает три аргумента: тип среза, его начальную длину и его емкость, то есть длину массива, который make выделяет для хранения данных среза. Этот вызов создает фрагмент длиной 10 с местом для еще 5 (15-10), как вы можете увидеть, запустив его:

slice := make([]int, 10, 15) fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice))
len: 10, cap: 15

Этот пример удваивает емкость нашего int среза, но сохраняет его длину такой же:

slice := make([]int, 10, 15) fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice)) newSlice := make([]int, len(slice), 2*cap(slice)) for i := range slice < newSlice[i] = slice[i] >slice = newSlice fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice))
len: 10, cap: 15 len: 10, cap: 30

После запуска этого кода у среза будет гораздо больше места для роста, прежде чем потребуется другое перераспределение.

При создании срезов часто верно, что длина и емкость будут одинаковыми. У встроенной команды make есть сокращение для этого общего случая. По умолчанию аргумент длины равен емкости, поэтому вы можете не указывать им одинаковое значение. После

gophers := make([]Gopher, 10)

срез gophers имеет длину и емкость 10.

Встроенная функция copy

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

newSlice := make([]int, len(slice), 2*cap(slice)) copy(newSlice, slice)

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

Функция copy также делает все правильно, когда источник и пункт назначения перекрываются, что означает, что ее можно использовать для перемещения элементов в одном срезе. Вот как использовать copy для вставки значения в середину среза.

// Insert вставляет значение в срез по указанному индексу, // который должен быть в диапазоне. // Срез должен иметь место для нового элемента. func Insert(slice []int, index, value int) []int < // Вырастить срез на один элемент. slice = slice[0 : len(slice)+1] // Используем copy, чтобы переместить // верхнюю часть среза в сторону и открыть отверстие. copy(slice[index+1:], slice[index:]) // Сохраняем новое значение. slice[index] = value // Возвращаем результат. return slice >

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

slice[i:]

означает тоже самое что и

slice[i:len(slice)]

Кроме того, хотя мы еще не использовали этот трюк, мы можем также пропустить первый элемент выражения среза; по умолчанию это ноль. Таким образом

slice[:]

просто означает сам срез, который полезен при разрезании массива. Это выражение является кратчайшим способом сказать «срез, описывающий все элементы массива»:

array[:]

Теперь это не так, давайте запустим нашу функцию Insert.

// Отметьте емкость > длины: // пространство для добавления элемента. slice := make([]int, 10, 20) for i := range slice < slice[i] = i >fmt.Println(slice) slice = Insert(slice, 5, 99) fmt.Println(slice)
[0 1 2 3 4 5 6 7 8 9] [0 1 2 3 4 99 5 6 7 8 9]
Append: пример

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

func Extend(slice []int, element int) []int < n := len(slice) if n == cap(slice) < // Срез полон; должен расти. // Мы удваиваем его размер и добавляем 1, // поэтому, если размер равен нулю, // мы все равно наращиваем. newSlice := make([]int, len(slice), 2*len(slice)+1) copy(newSlice, slice) slice = newSlice >slice = slice[0 : n+1] slice[n] = element return slice >

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

slice := make([]int, 0, 5) for i := 0; i

len=1 cap=5 slice=[0] address of 0th element: 0x456000 len=2 cap=5 slice=[0 1] address of 0th element: 0x456000 len=3 cap=5 slice=[0 1 2] address of 0th element: 0x456000 len=4 cap=5 slice=[0 1 2 3] address of 0th element: 0x456000 len=5 cap=5 slice=[0 1 2 3 4] address of 0th element: 0x456000 len=6 cap=11 slice=[0 1 2 3 4 5] address of 0th element: 0x454030 len=7 cap=11 slice=[0 1 2 3 4 5 6] address of 0th element: 0x454030 len=8 cap=11 slice=[0 1 2 3 4 5 6 7] address of 0th element: 0x454030 len=9 cap=11 slice=[0 1 2 3 4 5 6 7 8] address of 0th element: 0x454030 len=10 cap=11 slice=[0 1 2 3 4 5 6 7 8 9] address of 0th element: 0x454030

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

С надежной функцией Extend в качестве руководства мы можем написать еще более удобную функцию, которая позволяет нам расширять срез несколькими элементами. Для этого мы используем способность Go превращать список аргументов функции в срез при вызове функции. То есть мы используем вариационную (variadic) возможность функции Go.

Давайте назовем функцию Append. Для первой версии мы можем просто вызывать Extend несколько раз, чтобы механизм функции variadic был понятен. Сигнатура Append является следующей:

func Append(slice []int, items . int) []int

Это говорит о том, что Append принимает один аргумент, slice, за которым следует ноль или более аргументов int. Эти аргументы представляют собой срез int, если говорить о реализации Append, как вы можете видеть:

// Append добавляет элементы к срезу. // Первая версия: просто вызов Extend в цикле. func Append(slice []int, items . int) []int < for _, item := range items < slice = Extend(slice, item) >return slice >

Обратите внимание на цикл for, проходящий по элементам аргумента items, который подразумевает тип []int. Также обратите внимание на использование пустого идентификатора _ для отбрасывания индекса в цикле, который нам не нужен в этом случае.

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

Еще одна новая техника в этом примере заключается в том, что мы инициализируем срез, записывая составной литерал, который состоит из типа среза, за которым следуют его элементы в фигурных скобках:

slice := []int

Функция Append интересна по другой причине. Мы можем не только добавлять элементы, мы можем добавить целый второй срез, «разорвав» его на аргументы, используя обозначение . на месте вызова:

slice1 := []int slice2 := []int fmt.Println(slice1) slice1 = Append(slice1, slice2. ) // '. ' важно! fmt.Println(slice1)
[0 1 2 3 4] [0 1 2 3 4 55 66 77]

Конечно, мы можем сделать Append более эффективной, производя выделение не более одного раза, основываясь на внутренностях Extend:

// Append добавляет элементы к срезу. // Эффективная версия. func Append(slice []int, elements . int) []int < n := len(slice) total := len(slice) + len(elements) if total >cap(slice) < // Перераспределяем. // Выращиваем в 1,5 раза новый размер, // чтобы мы могли расти. newSize := total*3/2 + 1 newSlice := make([]int, total, newSize) copy(newSlice, slice) slice = newSlice >slice = slice[:total] copy(slice[n:], elements) return slice >

Здесь обратите внимание на то, как мы используем copy дважды, один раз, чтобы переместить данные среза во вновь выделенную память, а затем скопировать добавляемые элементы в конец старых данных.

Выполним; поведение такое же, как и раньше:

slice1 := []int slice2 := []int fmt.Println(slice1) slice1 = Append(slice1, slice2. ) // '. ' важно! fmt.Println(slice1)
[0 1 2 3 4] [0 1 2 3 4 55 66 77]
Append: встроенная функция

И вот мы приходим к мотивации для разработки встроенной функции append. Она делает именно то, что делает наш пример Append, с эквивалентной эффективностью, но работает для любого типа среза.

Слабым местом Go является то, что любые операции универсального типа должны предоставляться средой выполнения. Когда-нибудь это может измениться, но сейчас, чтобы упростить работу со срезами, Go предоставляет встроенную универсальную функцию append. Она работает так же, как наша версия int среза, но для любого типа среза.

Помните, поскольку заголовок среза всегда обновляется вызовом append, вам необходимо сохранить возвращенный срез после вызова. Фактически, компилятор не позволит вам вызывать append без сохранения результата.

Вот примеры использования append:

// Создаем пару новых срезов. slice := []int slice2 := []int fmt.Println("Start slice: ", slice) fmt.Println("Start slice2:", slice2) // Добавляем элемент в срез. slice = append(slice, 4) fmt.Println("Add one item:", slice) // Добавляем один срез в другой. slice = append(slice, slice2. ) fmt.Println("Add one slice:", slice) // Делаем копию среза (int). slice3 := append([]int(nil), slice. ) fmt.Println("Copy a slice:", slice3) // Копируем срез в свой конец. fmt.Println("Before append to self:", slice) slice = append(slice, slice. ) fmt.Println("After append to self:", slice)
Start slice: [1 2 3] Start slice2: [55 66 77] Add one item: [1 2 3 4] Add one slice: [1 2 3 4 55 66 77] Copy a slice: [1 2 3 4 55 66 77] Before append to self: [1 2 3 4 55 66 77] After append to self: [1 2 3 4 55 66 77 1 2 3 4 55 66 77]
Nil

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

sliceHeader

sliceHeader<>

Ключевым моментом является то, что указатель элемента также равен nil. Срез, созданный

array[0:0]

имеет нулевую длину (и, возможно, даже нулевую емкость), но его указатель не равен nil, поэтому это не нулевой срез.

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

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

Строки

Теперь краткий раздел о строках в Go в контексте срезов.

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

Поскольку они доступны только для чтения, нет необходимости в емкости (вы не можете увеличить их), но в большинстве случаев вы можете обращаться с ними так же, как с байтами только для чтения.

Для начала мы можем проиндексировать их для доступа к отдельным байтам:

slash := "/usr/ken"[0] // возвращает значение байта '/'.

Мы можем нарезать строку, чтобы получить подстроку:

usr := "/usr/ken"[0:4] // возвращает строку "/usr"

Теперь должно быть очевидно, что происходит за кулисами, когда мы нарезаем строку.

Мы также можем взять обычный срез байтов и создать из него строку с простым преобразованием:

str := string(slice)

или обратное преобразование:

slice := []byte(usr)

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

Важным следствием этого срезовидного дизайна для строк является то, что создание подстроки очень эффективно. Все, что должно произойти, — это создание заголовка строки из двух слов. Поскольку строка доступна только для чтения, исходная строка и строка, полученная в результате операции среза, могут безопасно использовать один и тот же массив.

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

Заключение

Чтобы понять, как работают срезы, полезно понять, как они реализованы. Существует небольшая структура данных, заголовок среза, то есть элемент, связанный с переменной среза, и этот заголовок описывает раздел отдельно выделенного массива. Когда мы передаем значения среза, заголовок копируется, но массив, на который он указывает, всегда используется совместно.

Как только вы оцените, как они работают, срезы становятся не только простыми в использовании, но и мощными и выразительными, особенно с помощью встроенных функций copy и append.

  • Основы Go: размер и вместимость среза
  • Эффективный Go: встроенная функция append
  • Эффективный Go: срезы (slices)

Как ввести массив с клавиатуры golang

В Go имеется объект os.Stdin , который реализует интерфейс io.Reader и позволяет считывать данные с консоли. Например, мы можем использовать функцию fmt.Fscan() для считывания с консоли с помощью os.Stdin:

package main import ( «fmt» «os» ) func main()

При запуске программы мы сможем вводить данные с консоли, и они перейдут в переменные name и age:

Введите имя: Tom Введите возраст: 24 Tom 24

Однако также для получения ввода с консоли мы можем использовать встроенные функции fmt.Scan() , fmt.Scanln() и fmt.Scanf() , которые аналогичны соответственно функциям fmt.Fscan() , fmt.Scanln() и fmt.Scanf() :

func Scan(a . interface<>) (n int, err error) func Scanf(format string, a . interface<>) (n int, err error) func Scanln(a . interface<>) (n int, err error)

Все эти функции уже по умолчанию считывают данные с потока os.Stdin:

package main import ( «fmt» «os» ) func main()

package main import ( "fmt" "os" ) func main() < var name string var age int fmt.Print("Введите имя и возраст: ") fmt.Scan(&name, &age) fmt.Println(name, age) // альтернативный вариант //fmt.Println("Введите имя и возраст:") //fmt.Scanf("%s %d", &name, &age) //fmt.Println(name, age) >

В случае если вводятся сразу несколько значений, то разделителем между ними является пробел. Хотя теоретически строка может включать внутренние пробелы, тем не менее данные функции считывают значение строки и других типов данных до пробела:

Введите имя и возраст: Tom 34 Tom 34

The Go Playground

The Go Playground is a web service that runs on go.dev’s servers. The service receives a Go program, vets, compiles, links, and runs the program inside a sandbox, then returns the output.

If the program contains tests or examples and no main function, the service runs the tests. Benchmarks will likely not be supported since the program runs in a sandboxed environment with limited resources.

There are limitations to the programs that can be run in the playground:

  • The playground can use most of the standard library, with some exceptions. The only communication a playground program has to the outside world is by writing to standard output and standard error.
  • In the playground the time begins at 2009-11-10 23:00:00 UTC (determining the significance of this date is an exercise for the reader). This makes it easier to cache programs by giving them deterministic output.
  • There are also limits on execution time and on CPU and memory usage.

The article «Inside the Go Playground» describes how the playground is implemented. The source code is available at https://go.googlesource.com/playground.

The playground service is used by more than just the official Go project (Go by Example is one other instance) and we are happy for you to use it on your own site. All we ask is that you contact us first (note this is a public mailing list), that you use a unique user agent in your requests (so we can identify you), and that your service is of benefit to the Go community.

Any requests for content removal should be directed to security@golang.org. Please include the URL and the reason for the request.

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *