Ответ 2
Так как ответ @james-henstridge уже рассмотрел, как вы могли бы заставить его работать, я не буду дублировать сказанное, но я объясню, почему его ответ работает.
В Go массивы работают немного иначе, чем на большинстве других языков (да, есть массивы и срезы. Я расскажу об срезах позже). В Go массивы имеют фиксированный размер, как вы используете в своем коде (так что [3]int
- это другой тип, чем [4]int
). Кроме того, массивы являются значениями. Это означает, что если я копирую массив из одного места в другое, я фактически копирую все элементы массива (вместо того, чтобы, как и на большинстве других языков, просто ссылаться на один и тот же массив). Например:
a := [3]int{1, 2, 3} // Array literal
b := a // Copy the contents of a into b
a[0] = 0
fmt.Println(a) // Prints "[0 2 3]"
fmt.Println(b) // Prints "[1 2 3]"
Однако, как вы заметили, Go также имеет срезы. Срезы похожи на массивы, за исключением двух ключевых способов. Во-первых, они являются переменной длиной (поэтому []int
- это тип среза любого числа целых чисел). Во-вторых, срезы являются ссылками. Это означает, что когда я создаю срез, выделяется часть памяти для представления содержимого среза, а сама переменная среза - это просто указатель на эту память. Затем, когда я копирую этот фрагмент, я просто копирую указатель. Это означает, что если я скопирую срез, а затем измените одно из значений, я изменил это значение для всех. Например:
a := []int{1, 2, 3} // Slice literal
b := a // a and b now point to the same memory
a[0] = 0
fmt.Println(a) // Prints "[0 2 3]"
fmt.Println(b) // Prints "[0 2 3]"
Реализация
Если это объяснение было довольно легко понятным, вам также может быть интересно узнать, как это реализовано (если у вас возникли проблемы с пониманием этого, я бы прекратил читать здесь, потому что детали, вероятно, просто запутывают).
Под капотом, Go ломтиками на самом деле являются структуры. У них есть указатель на выделенную память, как я уже упоминал, но у них также есть другие ключевые компоненты: длина и емкость. Если бы это было описано в терминах Go, это выглядело бы примерно так:
type int-slice struct {
data *int
len int
cap int
}
Длина - это длина фрагмента, и она там, чтобы вы могли запросить len(mySlice)
, а также чтобы Go мог проверить, чтобы убедиться, что вы не получаете доступ к элементу, который фактически не находится на срезе. Способность, однако, немного запутанна. Поэтому дайте погрузиться немного глубже.
Когда вы сначала создаете срез, вы даете несколько элементов, которые вы хотите использовать срез. Например, вызов make([]int, 3)
даст вам фрагмент из 3 целых чисел. Это означает выделение пространства в памяти для 3-х целых чисел, а затем возвращает структуру с указателем на данные, длиной 3 и емкостью 3.
Однако в Go вы можете делать то, что называется slicing. Это в основном, когда вы создаете новый срез из старого фрагмента, который представляет только часть старого фрагмента. Синтаксис slc[a:b]
используется для обозначения суб-среза slc
, начинающегося с индекса a
и заканчивающегося непосредственно перед индексом b
. Итак, например:
a := [5]int{1, 2, 3, 4, 5}
b := a[1:4]
fmt.Println(b) // Prints "[2 3 4]"
Эта операция разрезания под капотом состоит в том, чтобы сделать копию структуры, которая соответствует a
, и отредактировать указатель на целое число целых чисел вперед в памяти (поскольку новый срез начинается с индекса 1) и отредактируйте длину на 2 короче, чем раньше (потому что старый срез имел длину 5, а новый - длина 3). Так что же теперь это выглядит в памяти? Ну, если бы мы могли визуализировать вычисленные целые числа, это выглядело бы примерно так:
begin end // a
v v
[ 1 2 3 4 5 ]
^ ^
begin end // b
Обратите внимание, как там еще один int после окончания b
? Хорошо, что емкость. Понимаете, пока память будет использоваться для использования, мы могли бы также использовать все это. Поэтому, даже если у вас есть только срез, длина которого мала, он будет помнить, что в случае, если вы когда-либо захотите, вы получите больше возможностей. Итак, например:
a := []int{1, 2, 3}
b := a[0:1]
fmt.Println(b) // Prints "[1]"
b = b[0:3]
fmt.Println(b) // Prints "[1 2 3]"
Посмотрите, как мы делаем b[0:3]
в конце? Длина b
на самом деле на данный момент меньше 3, поэтому единственная причина, по которой мы можем это сделать, - это то, что Go отслеживает тот факт, что в основной памяти мы фактически получили больше резервных копий, Таким образом, когда мы попросим некоторые из них вернуться, он может с радостью согласиться.
Ответ 5
Передача массивов в качестве параметров.
Значения массивов обрабатываются как единое целое. Переменная массива не является указателем на местоположение в памяти, а представляет собой весь блок памяти, содержащий элементы массива.
Это имеет значение для создания новой копии значения массива, когда переменная массива переназначается или передается как параметр функции.
Обучение программированию, Владимир Вивьен
Это может иметь нежелательные побочные эффекты для потребления памяти для программы. Вы можете исправить это, используя "типы указателей" для ссылки на значения массива. Например:
вместо этого сделайте следующее:
var numbers [1024*1024]int
вы должны сделать:
type numbers [1024*1024]int
var nums *numbers = new(numbers)
Помните, что:
https://golang.org/pkg/builtin/#new
Новая встроенная функция выделяет память. Первый аргумент - это тип, а не значение, а возвращаемое значение - указатель на новый выделенное нулевое значение этого типа.
Теперь вы можете передать указатель массива на функцию без побочного эффекта потребления памяти и использовать ее по своему усмотрению.
nums[0] = 10
doSomething(nums)
func doSomething(nums *numbers){
temp := nums[0]
...
}
Следует помнить, что тип массива - это низкоуровневая конструкция хранилища в Go и используется в качестве основы для примитивов хранения, где существуют жесткие требования к распределению памяти , чтобы свести к минимуму потребление пространства, В тех случаях, когда ваше требование зависит от производительности, вы должны выбрать работу с массивами (например, предыдущий пример) вместо срезов.