Passing an array as an argument in golang

huangapple go评论96阅读模式
英文:

Passing an array as an argument in 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

问题出在函数nameReader的参数类型上。在main函数中,我们定义了一个长度为3的数组a,其中元素的类型是name。然后我们将数组a作为参数传递给了nameReader函数。但是,nameReader函数的参数类型是[]name,表示切片类型,而不是数组类型。

要解决这个问题,我们需要将nameReader函数的参数类型改为切片类型。可以使用切片操作符[:]将数组转换为切片,如下所示:

nameReader(a[:])

这样就可以将数组a转换为切片类型,并将其作为参数传递给nameReader函数了。

英文:

Why does this not work?

package main

import &quot;fmt&quot;

type name struct {
    X string
}

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

    nameReader(a)
} 

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

Error:

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

答案1

得分: 75

你已经定义了一个接受切片作为参数的函数,而你在调用该函数时尝试传递一个数组。你可以通过以下两种方式解决这个问题:

  1. 在调用函数时,将数组转换为切片。只需将调用改为以下形式即可:

    nameReader(a[:])
    
  2. 修改函数签名,接受一个数组而不是切片。例如:

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

    这种解决方案的缺点是函数现在只能接受长度为3的数组,并且在调用函数时会复制数组。

你可以在这里找到有关数组和切片的更多详细信息,以及在使用它们时常见的陷阱。

英文:

You have defined your function to accept a slice as an argument, while you're trying to pass an array in the call to that function. There are two ways you could address this:

  1. Create a slice out of the array when calling the function. Changing the call like this should be enough:

    nameReader(a[:])
    
  2. Alter the function signature to take an array instead of a slice. For instance:

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

    Downsides of this solution are that the function can now only accept an array of length 3, and a copy of the array will be made when calling it.

You can find a more details on arrays and slices, and common pitfalls when using them here

答案2

得分: 40

由于@james-henstridge的回答已经涵盖了如何使其工作的内容,我不会重复他所说的,但我会解释为什么他的回答有效。

在Go语言中,数组与大多数其他语言有些不同(是的,有数组和切片。我稍后会讨论切片)。在Go中,数组是固定大小的,就像你在代码中使用的一样(因此,[3]int是与[4]int不同的类型)。此外,数组是。这意味着如果我从一个地方复制一个数组到另一个地方,我实际上是复制了数组的所有元素(而不是像大多数其他语言那样,只是创建对同一数组的另一个引用)。例如:

a := [3]int{1, 2, 3} // 数组字面量
b := a               // 将a的内容复制到b
a[0] = 0
fmt.Println(a)       // 输出"[0 2 3]"
fmt.Println(b)       // 输出"[1 2 3]"

然而,正如你注意到的,Go还有切片。切片与数组类似,但有两个关键区别。首先,它们是可变长度的(因此[]int是任意数量整数的切片类型)。其次,切片是引用。这意味着当我创建一个切片时,会分配一块内存来表示切片的内容,并且切片变量本身实际上只是指向该内存的指针。然后,当我复制该切片时,实际上只是复制了指针。这意味着如果我复制切片然后更改其中一个值,我会为所有人都更改该值。例如:

a := []int{1, 2, 3} // 切片字面量
b := a              // a和b现在指向同一块内存
a[0] = 0
fmt.Println(a)      // 输出"[0 2 3]"
fmt.Println(b)      // 输出"[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中,你可以进行所谓的切片操作。这基本上是在旧切片的基础上创建一个新切片,该新切片只表示旧切片的一部分。你可以使用slc[a:b]语法来引用从索引a开始并在索引b之前结束的子切片。例如:

a := [5]int{1, 2, 3, 4, 5}
b := a[1:4]
fmt.Println(b) // 输出"[2 3 4]"

在底层,切片操作的实现方式是复制与a对应的结构体,并将指针编辑为在内存中向前移动1个整数(因为新切片从索引1开始),并将长度编辑为比之前短2个(因为旧切片的长度为5,而新切片的长度为3)。那么现在内存中的情况是怎样的呢?嗯,如果我们可以将整数可视化排列,它可能是这样的:

  开始     结束  // a
  v         v
[ 1 2 3 4 5 ]
    ^     ^
    开始 结束    // b

注意,b的结束后面仍然有一个整数?那就是容量。看,只要内存还在供我们使用,我们可能会用到它的所有空间。因此,即使你只有一个长度很小的切片,它也会记住还有更多的容量,以防你将来需要它。例如:

a := []int{1, 2, 3}
b := a[0:1]
fmt.Println(b) // 输出"[1]"
b = b[0:3]
fmt.Println(b) // 输出"[1 2 3]"

看到我们最后那里的b[0:3]了吗?此时,b的长度实际上小于3,所以我们之所以能够这样做,是因为Go已经跟踪了在底层内存中,我们实际上保留了更多的容量。这样,当我们要求一部分容量时,它可以愉快地满足我们的要求。

英文:

Since @james-henstridge's answer already covered how you could make it work, I won't duplicate what he said, but I will explain why his answer works.

In Go, arrays work a bit differently than in most other languages (yes, there are arrays and slices. I'll discuss slices later). In Go, arrays are fixed-size, as you use in your code (so, [3]int is a different type than [4]int). Additionally, arrays are values. What this means is that if I copy an array from one place to another, I'm actually copying all of the elements of the array (instead of, as in most other languages, just making another reference to the same array). For example:

a := [3]int{1, 2, 3} // Array literal
b := a               // Copy the contents of a into b
a[0] = 0
fmt.Println(a)       // Prints &quot;[0 2 3]&quot;
fmt.Println(b)       // Prints &quot;[1 2 3]&quot;

However, as you noticed, Go also has slices. Slices are similar to arrays, except in two key ways. First, they're variable length (so []int is the type of a slice of any number of integers). Second, slices are references. What this means is that when I create a slice, a piece of memory is allocated to represent the contents of the slice, and the slice variable itself is really just a pointer to that memory. Then, when I copy that slice around, I'm really just copying the pointer. That means that if I copy the slice and then change one of the values, I change that value for everybody. For example:

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 &quot;[0 2 3]&quot;
fmt.Println(b)      // Prints &quot;[0 2 3]&quot;

Implementation

If that explanation was pretty easily understandable, then you might also be curious to know how this is implemented (if you had trouble understanding that, I'd stop reading here because the details will probably just be confusing).

Under the hood, Go slices are actually structs. They have a pointer to the allocated memory, like I mentioned, but they also have two other key components: length and capacity. If it were described in Go terms, it'd look something like this:

type int-slice struct {
    data *int
    len  int
    cap  int
}

The length is the length of the slice, and it's there so that you can ask for len(mySlice), and also so that Go can check to make sure you're not accessing an element that's not actually in the slice. The capacity, however, is a bit more confusing. So let's dive a bit deeper.

When you first create a slice, you give a number of elements that you want the slice to be. For example, calling make([]int, 3) would give you a slice of 3 ints. What this does is allocate space in memory for 3 ints, and then give you back a struct with a pointer to the data, the length of 3, and the capacity of 3.

However, in Go, you can do what's called slicing. This is basically where you create a new slice out of an old slice that represents only part of the old slice. You use the slc[a:b] syntax to refer to the sub-slice of slc starting at index a and ending just before index b. So, for example:

a := [5]int{1, 2, 3, 4, 5}
b := a[1:4]
fmt.Println(b) // Prints &quot;[2 3 4]&quot;

What this slicing operation does under the hood is to make a copy of the struct that corresponds to a, and to edit the pointer to point 1 integer forward in memory (because the new slice starts at index 1), and edit the length to be 2 shorter than before (because the old slice had length 5, while the new one has length 3). So what does this look like in memory now? Well, if we could visualize the integers laid out, it'd look something like this:

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

Notice how the there's still one more int after the end of b? Well that's the capacity. See, so long as the memory's going to be sticking around for us to use, we might as well be able to use all of it. So even if you only have a slice whose length is small, it will remember that there's more capacity in case you ever want it back. So, for example:

a := []int{1, 2, 3}
b := a[0:1]
fmt.Println(b) // Prints &quot;[1]&quot;
b = b[0:3]
fmt.Println(b) // Prints &quot;[1 2 3]&quot;

See how we do b[0:3] at the end there? The length of b is actually less than 3 at this point, so the only reason we're able to do that is that Go has kept track of the fact that, in the underlying memory, we've actually got more capacity reserved. That way, when we ask for some of it back, it can happily oblige.

答案3

得分: 11

一种替代方法

可以在切片上调用可变参数函数,将名称列表作为单独的参数传递给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)
    }
}
英文:

An alternative approach

One could invoke a variadic function on the a slice to input the list of names as individual arguments to the nameReader function, eg:

package main

import &quot;fmt&quot;

type name struct {
	X string
}

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

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

答案4

得分: 2

不要回答我要翻译的问题。以下是要翻译的内容:

不要声明一个固定大小的数组,而是声明一个切片。为其分配内存并填充数据。一旦完成这些步骤,原始引用仍然指向切片,可以将其传递给函数。考虑下面这个简单的例子:

package main

import "fmt"

func main() {
  // 声明一个切片
  var i []int

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

  // 检查值 - 应该是 3
  fmt.Printf("Val - %d\n", i[0])

  // 调用函数
  a(i)

  // 再次检查值 - 应该是 33
  fmt.Printf("Val - %d\n", i[0])
}

func a(i []int) {
  // 检查值 - 应该是 3
  fmt.Printf("Val - %d\n", i[0])

  // 修改值
  i[0] = 33

  // 再次检查值 - 应该是 33
  fmt.Printf("Val - %d\n", i[0])
}

如你所见,数组作为引用传递给函数后,可以被修改。

输出结果如下:

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

完整的程序也可以在这里找到:http://play.golang.org/p/UBU56eWXhJ

英文:

Instead of declaring a sized array, declare a slice. Allocate memory to it and fill it in. Once you have that, the original reference is still to the slice, which can be passed around to a function. Consider this simple example

package main

import &quot;fmt&quot;

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(&quot;Val - %d\n&quot;, i[0])

  // call the function  
  a(i)

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

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

  // change the value
  i[0] = 33

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

As you can see the array got passed (as a reference) and can be modified by the corresponding function.

Output looks like this:

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

The whole program can also be found at: http://play.golang.org/p/UBU56eWXhJ

答案5

得分: 1

将数组作为参数传递。

数组值被视为单个单元。数组变量不是指向内存中某个位置的指针,而是表示包含数组元素的整个"内存块"。
这意味着当重新分配数组变量或将其作为函数参数传递时,会创建数组值的新副本。
《学习Go编程,作者:Vladimir Vivien》

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

不要这样做:

var numbers [1024*1024]int

而应该这样做:

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

请记住:

> https://golang.org/pkg/builtin/#new
> > new内置函数分配内存。第一个参数是类型,而不是值,返回的值是指向该类型的新分配的零值的指针。

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

nums[0] = 10
doSomething(nums)

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

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

英文:

Passing Arrays as parameters.

Arrays values are treated as a single unit. An array variable is not a pointer to a location in memory, but rather represents the entire "Block of Memory" containing the array elements.
This has the implications of creating a new copy of an array value when the array variable is reassigned or passed in as a function parameter.
Learning Go Programming, By Vladimir Vivien

This could have unwanted side effects on memory consumption for a program. You can fix this using "pointer types" to reference array values. For instance:

instead do this:

var numbers [1024*1024]int

you must do:

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

Remember that:

> https://golang.org/pkg/builtin/#new
> > The new built-in function allocates memory. The first argument is a
> > type, not a value, and the value returned is a pointer to a newly
> > allocated zero value of that type.

Now you could pass the array pointer to the function without the side effect of memory consumption and use it as you want.

nums[0] = 10
doSomething(nums)

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

One thing to keep on mind is that array type is a low-level storage construct in Go and is used as the basis for storage primitives, where there are strict memory allocation requirements to minimize space consumption. For those cases where your requirement rely on the performance your must choose to work with arrays (like the previous example) instead of slices.

答案6

得分: 0

我们可以使用切片。在这里运行代码: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: Traps, Gotchas, and Common Mistakes for New Golang Devs

英文:

We can just use slices. Run code here: http://play.golang.org/p/WtcOvlQm01

Need to remember [3]name is an array. []name is a slice.

package main

import &quot;fmt&quot;

type name struct {
    X string
}

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

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

Further reading: 50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs

huangapple
  • 本文由 发表于 2014年2月12日 13:52:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/21719769.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定