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

Posted

技术标签:

【中文标题】在golang中将数组作为参数传递【英文标题】:Passing an array as an argument in golang 【发布时间】:2014-03-10 07:07:10 【问题描述】:

为什么这不起作用?

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

【问题讨论】:

【参考方案1】:

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

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

    nameReader(a[:])
    

    更改函数签名以采用数组而不是切片。例如:

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

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

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

【讨论】:

有没有办法让函数接受一个数组? 这就是我概述的第二个选项的作用。但正如我所说,缺点是它只接受长度为 3 的数组进行更改:如果你想接受任意长度的序列,你需要使用切片。 赞成,但a := []name"Abbed", "Ahmad", "Ghassan" 是我最初将a 定义为切片的方式。 该死的我们应该在 func 签名中包含 [...]type 在下一个版本中:( @YasirG.:目前尚不清楚如何安全地做到这一点,因为数组是按值传递的并且不存储它们的大小(它是隐含的类型)。而且我们已经有了切片来处理任意长度的序列。【参考方案2】:

由于@james-henstridge 的回答已经涵盖了如何让它发挥作用,我不会重复他所说的话,但我会解释为什么他的回答有效。

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

a := [3]int1, 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 := []int1, 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]int1, 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 := []int1, 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 的长度实际上小于,所以我们能够做到这一点的唯一原因是 Go 一直在跟踪这样一个事实,即在底层内存中,我们'实际上保留了更多容量。这样,当我们要求返还一些时,它可以很高兴地答应。

【讨论】:

或者人们可以直接阅读 Rob 的博客文章:Arrays, slices (and strings): The mechanics of 'append'。 啊,是的,这似乎更合理......我忘记了。 你说分片b := a[1:4]指针加1,长度减2,但是容量不变?不应该也减1吗? @StefanPochmann 是的,b 的容量减少了1,根据b := a[1: 4] 的声明,b 的长度应为 3,容量应为 4。答案中的图表和一些信息令人困惑和误导,尽管第一部分很有趣【参考方案3】:

另一种方法

可以在 a 切片上调用 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)
    

【讨论】:

【参考方案4】:

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

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

【讨论】:

【参考方案5】:

将数组作为参数传递。

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

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

改为这样做:

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 中的一种低级存储结构,被用作存储原语的基础,其中有严格的内存分配要求以尽量减少空间消耗 .对于那些您的要求依赖于性能的情况,您必须选择使用数组(如前面的示例)而不是切片。

【讨论】:

【参考方案6】:

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

需要记住[3]name是一个数组。 []name 是一个切片。

package main

import "fmt"

type name struct 
    X string


func main() 
    a := []namename"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

【讨论】:

是的,这是最好的方法,但我认为提出的问题不是解决此方法。

以上是关于在golang中将数组作为参数传递的主要内容,如果未能解决你的问题,请参考以下文章

在 PHP 中将数组作为参数而不是数组传递

在 C++ 中将数组作为函数参数传递

如何在Java中将对象数组作为参数传递

我们可以在 PHP 中的任何函数中将数组作为参数传递吗?

在 JavaScript 中将数组作为函数参数传递

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