Подтвердить что ты не робот

Передача массива в качестве аргумента в golang

Почему это не работает?

package main

import "fmt"

type name struct {
    X string
}

func main() {
    var a [3]name
    a[0] = name{"Abbed"}
    a[1] = name{"Ahmad"}
    a[2] = name{"Ghassan"}

    nameReader(a)
} 

func nameReader(array []name) {
    for i := 0; i < len(array); i++ {
        fmt.Println(array[i].X)
    }
}

Ошибка:

.\structtest.go:15: cannot use a (type [3]name) as type []name in function argument
4b9b3361

Ответ 1

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

  • Создайте срез из массива при вызове функции. Изменение такого вызова должно быть достаточно:

    nameReader(a[:])
    
  • Измените сигнатуру функции, чтобы взять массив вместо среза. Например:

    func nameReader(array [3]name) {
        ...
    }
    

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

Ответ 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 отслеживает тот факт, что в основной памяти мы фактически получили больше резервных копий, Таким образом, когда мы попросим некоторые из них вернуться, он может с радостью согласиться.

Ответ 3

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

package main

import "fmt"

func main() {
  // declare a slice
  var i []int

  i = make([]int, 2)
  i[0] = 3
  i[1] = 4

  // check the value - should be 3
  fmt.Printf("Val - %d\n", i[0])

  // call the function  
  a(i)

  // check the value again = should be 33
  fmt.Printf("Val - %d\n", i[0])  
}

func a(i []int) {
  // check the value - should be 3
  fmt.Printf("Val - %d\n", i[0])  

  // change the value
  i[0] = 33

  // check the value again = should be 33
  fmt.Printf("Val - %d\n", i[0])  
}

Как вы можете видеть, массив прошел (как ссылку) и может быть изменен соответствующей функцией.

Результат выглядит следующим образом:

Val - 3
Val - 3
Val - 33
Val - 33

Вся программа также может быть найдена по адресу: http://play.golang.org/p/UBU56eWXhJ

Ответ 4

Мы можем просто использовать срезы. Выполнить код здесь: http://play.golang.org/p/WtcOvlQm01

Необходимо запомнить [3]name - это массив. []name - это срез.

package main

import "fmt"

type name struct {
    X string
}

func main() {
    a := []name{name{"Abbed"}, name{"Ahmad"}, name{"Ghassan"}}
    nameReader(a)
} 

func nameReader(array []name) {
    for i := 0; i < len(array); i++ {
        fmt.Println(array[i].X)
    }
}

Дальнейшее чтение: 50 Shades of Go: Ловушки, Gotchas и распространенные ошибки для новых разработчиков Golang

Ответ 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 и используется в качестве основы для примитивов хранения, где существуют жесткие требования к распределению памяти , чтобы свести к минимуму потребление пространства, В тех случаях, когда ваше требование зависит от производительности, вы должны выбрать работу с массивами (например, предыдущий пример) вместо срезов.