在golang中将数组作为参数传递

时间:2014-02-12 05:52:59

标签: arrays go arguments

为什么这不起作用?

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

6 个答案:

答案 0 :(得分:41)

您已经将函数定义为接受切片作为参数,而您尝试在对该函数的调用中传递数组。有两种方法可以解决这个问题:

  1. 调用函数时从数组中创建一个切片。像这样改变呼叫就足够了:

    nameReader(a[:])
    
  2. 更改函数签名以获取数组而不是切片。例如:

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

    此解决方案的缺点是该函数现在只能接受长度为3的数组,并且在调用时将生成该数组的副本。

  3. 您可以在此处找到有关阵列和切片的更多详细信息,以及常见的陷阱:http://openmymind.net/The-Minimum-You-Need-To-Know-About-Arrays-And-Slices-In-Go/

答案 1 :(得分:25)

由于@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个int的空间,然后返回一个带有指向数据的指针,长度为3,容量为3的结构。

但是,在Go中,您可以执行所谓的切片。这基本上是从旧切片创建新切片的位置,该切片仅表示旧切片的一部分。您可以使用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整数的指针(因为新切片从索引1开始) ,并将长度编辑为比以前短2(因为旧切片的长度为5,而新切片的长度为3)。那么现在在内存中看起来是什么样的呢?好吧,如果我们可以想象出布局的整数,它看起来像这样:

  begin     end  // a
  v         v
[ 1 2 3 4 5 ]
    ^     ^
    begin end    // b

注意b结束后还有一个int还有一个吗?那就是容量。看,只要记忆力将继续供我们使用,我们也可以使用它。因此,即使您只有一个长度很小的切片,它也会记住,如果您想要它的话,还有更多的容量。所以,例如:

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已经跟踪了在底层内存中的事实,我们实际上有更多的容量保留。这样,当我们要求退还一些时,它可以很高兴。

答案 2 :(得分:6)

另一种方法

可以在一个切片上调用variadic function来输入名称列表作为nameReader函数的单独参数,例如:

package main

import "fmt"

type name struct {
    X string
}

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

func nameReader(a ...name) {
    for _, n := range a {
        fmt.Println(n.X)
    }
}

答案 3 :(得分:2)

声明切片,而不是声明一个大小的数组。为它分配内存并填充它。一旦你有了,原始引用仍然是切片,可以传递给一个函数。考虑这个简单的例子

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 :(得分:0)

我们可以使用切片。在此处运行代码:http://play.golang.org/p/WtcOvlQm01

需要记住Element.update("FrontDeskEventTable", "\u003Cform accept-charset=\"UTF-8\" action=\"/intranet/admin/front_desk_dates/1/front_desk_date_events/2\" class=\"edit_front_desk_date_event, etc, etc 是一个数组。 [3]name是一个切片。

[]name

进一步阅读:50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs

答案 5 :(得分:0)

将数组作为参数传递。

数组值被视为一个单元。数组变量不是指向内存中某个位置的指针,而是表示包含数组元素的整个“内存块”。 这具有在重新分配数组变量或作为函数参数传入时创建数组值的新副本的含义。 学习编程,由Vladimir Vivien

这可能会对程序的内存消耗产生不必要的副作用。您可以使用“指针类型”来修复此问题以引用数组值。例如:

而是这样做:

var numbers [1024*1024]int

你必须这样做:

type numbers [1024*1024]int
var nums *numbers = new(numbers)

请记住:

  

https://golang.org/pkg/builtin/#new

     
    

新的内置函数分配内存。第一个参数是a     type,而不是值,返回的值是指向新的值     分配了该类型的零值。

  

现在你可以将数组指针传递给函数,而不会产生内存消耗的副作用,并可以根据需要使用它。

nums[0] = 10
doSomething(nums)

func doSomething(nums *numbers){
  temp := nums[0]
  ...
}

要记住的一件事是,数组类型是Go中的低级存储构造,并用作存储原语的基础,其中存在严格的内存分配要求以最小化空间消耗 。对于那些需求依赖于性能的情况,您必须选择使用数组(如前面的示例)而不是切片。