使用指向数组的指针

39

我在玩谷歌的Go语言,遇到了一些在C语言中很基础但目前看到的文档并没有涵盖的问题。

当我将切片的指针传递给函数时,我以为我们可以通过以下方式访问它:

func conv(x []int, xlen int, h []int, hlen int, y *[]int)

    for i := 0; i<xlen; i++ {
        for j := 0; j<hlen; j++ {
            *y[i+j] += x[i]*h[j]
        }
    }
 }

但是Go编译器不喜欢这个:

sean@spray:~/dev$ 8g broke.go
broke.go:8: invalid operation: y[i + j] (index of type *[]int)

好的,我只是猜测而已。我有一个相当简单的解决方法:

func conv(x []int, xlen int, h []int, hlen int, y_ *[]int) {
    y := *y_

    for i := 0; i<xlen; i++ {
        for j := 0; j<hlen; j++ {
            y[i+j] += x[i]*h[j]
        }
    }
}

但是肯定有更好的方法。令人烦恼的是,当搜索大部分与Go有关的术语时,搜索引擎会混杂出现各种C/C++/不相关的结果,所以这种方式并不很有用。


如果您想要问一个关于Go语言的问题,请使用go标签进行标记,这样更多的人会来回答;Vatine已经为您这样做了。golang是一个过时的标签;正如你所看到的,有117个问题被标记为go,而只有3个错误地标记为golang - peterSO
1
请注意,您不需要将xlen和hlen作为参数传递;您可以使用len(x)和len(h)代替。 - Arkku
2
只需将*y放到() 中 => (*y)[index],然后尽情玩乐,参见play ground - Ivan Black
6个回答

34

谷歌Go文档说明传递数组的方法 - 它们说通常您希望传递一个切片(而不是指针?):

已更新:

如@Chickencha的评论所示,数组切片是引用类型,这就是为什么它们高效传递的原因。因此,您可能希望使用切片机制而不是“原始”指针。

来自谷歌Effective Go文档http://golang.org/doc/effective_go.html#slices

切片是引用类型,


原文

它在标题下面

关于类型的插曲

[...snip...] 当将数组传递给函数时, 你几乎总是想要声明形式参数为 切片。当你调用该函数时, 取数组的地址,Go 将创建(高效地)一个切片引用并传递。

编辑注:这已不再适用

使用切片可以编写此函数(来自sum.go):

09    func sum(a []int) int {   // returns an int
10        s := 0
11        for i := 0; i < len(a); i++ {
12            s += a[i]
13        }
14        return s
15    }

并像这样调用它:

19        s := sum(&[3]int{1,2,3})  // a slice of the array is passed to sum    

也许可以将整个数组作为切片传递。谷歌表明Go语言能够高效地处理切片。这是对问题的另一种回答,也许更好。

我同意这听起来是正确的方法。切片基本上是指向数组的指针,并且是通过引用传递数组的首选方式。 - Evan Shaw
1
追加操作没有星号就无法工作。请参见playground - Ivan Black
@John K,即使是指针这样的引用类型,也不能混淆使用。你提供的调用“sum”函数的示例代码将无法编译,因为你不能将一个指针传递给需要切片(引用)的函数。调用“sum”函数的代码s := sum(&[3]int{1,2,3})可以更改为arr := [3]int{1,2,3} s := sum(arr[:]) 完整示例可在playground中找到。 - Rader

21

[]类型(例如[]int)实际上是切片而不是数组。在Go中,数组的大小是类型的一部分,因此要实际拥有数组,您需要像[16]int这样的东西,并且指向它的指针会是*[16]int。所以,您实际上已经在使用切片了,而切片的指针*[]int是不必要的,因为切片已经通过引用传递了。

还要记住,只要切片的元素类型与数组匹配,就可以轻松地传递引用整个数组的切片&array(现在不行了)。

示例:

package main
import "fmt"

func sumPointerToArray(a *[8]int) (sum int) {
    for _, value := range *a { sum += value }
    return
}
func sumSlice (a []int) (sum int) {
    for _, value := range a { sum += value }
    return
}
func main() {
    array := [...]int{ 1, 2, 3, 4, 5, 6, 7, 8 }
    slice := []int{ 1, 2, 3, 4 }
    fmt.Printf("sum arrray via pointer: %d\n", sumPointerToArray(&array))
    fmt.Printf("sum slice: %d\n", sumSlice(slice))
    slice = array[0:]
    fmt.Printf("sum array as slice: %d\n", sumSlice(slice))
}

编辑: 更新以反映自首次发布以来的Go更改。


哦,另外需要注意的是,对于数组长度的[...]符号只能应用于数组字面量中,以便让编译器为您计算元素数量。例如,不能将*[...]int作为函数的形式参数。 - Arkku
O_o 这不起作用:无法在函数参数中使用 &array(类型为 *[8]int)作为 []int 类型。 - Doug
@Doug 有趣的是,当我发布它时,它确实编译通过了,并且旧文档也使用&array语法将数组作为切片传递。但是,现在似乎不再起作用。 - Arkku
@Doug 已更新为与当前版本的Go编译。 - Arkku
折腾一番后,结果发现 []int 仍然可用,但是在传递参数时必须手动将数组转换为切片(sumPointerToArray(array[:]) 而不是直接传递 &array)。 - Doug

5

分号和星号被添加和移除。

*y[i+j] += x[i]*h[j]

被解释为

(*y)[i+j] += x[i] * h[j];

编辑:请阅读评论。答案可能已经不再有效了,而且我已经有一段时间没有接触过Go语言了,甚至无法再阅读此内容。


在Go中,您可以跳过分号,星号是我认为应该用于指向数组的指针。 - Sean
从2009-12-22版本开始:自上一个版本以来,语言已经有了一个重大的语法变化,在这个列表上已经广泛讨论:在语句结束标记和换行符之间现在隐含了分号。详情请参见http://groups.google.com/group/golang-nuts/t/5ee32b588d10f2e9。请删除分号。 - peterSO
请请请修复MD:)-现在看起来像外星字形哈哈(我本来会自己改的,但由于某种原因编辑队列已满) - AthulMuralidhar

2

数组的长度是其类型的一部分,您可以使用len()内置函数获取数组的长度。因此,您不需要传递xlen、hlen参数。

在Go中,当将数组传递给函数时,几乎总是可以使用切片。在这种情况下,您不需要指针。 实际上,您不需要传递y参数。这是C语言输出数组的方式。

按照Go的风格:

func conv(x, h []int) []int {
    y := make([]int, len(x)+len(h))
    for i, v := range x { 
        for j, u := range h { 
            y[i+j] = v * u 
        }   
    }   
    return y
}

调用函数:

conv(x[0:], h[0:])

i的值为0到(len(x)-1),j的值为0到(len(h)-1)。因此,(i+j)的值为0到((len(x)-1)+(len(h)-1))。由于数组索引从零开始,所以我们不需要为y准备((len(x)-1)+(len(h)-1)+1)个数组元素吗? - peterSO
对于 var s []int,引用ss[0:len(s)]s[0:]都是同一件事,即切片s。因此,更简洁的写法是 conv(x, h),而不是conv(x[0:], h[0:]) - peterSO

2

这是一个可运行的Go程序。

package main

import "fmt"

func conv(x, h []int) []int {
    y := make([]int, len(x)+len(h)-1)
    for i := 0; i < len(x); i++ {
        for j := 0; j < len(h); j++ {
            y[i+j] += x[i] * h[j]
        }
    }
    return y
}

func main() {
    x := []int{1, 2}
    h := []int{7, 8, 9}
    y := conv(x, h)
    fmt.Println(len(y), y)
}

为了避免错误的猜测,请阅读Go文档:Go编程语言

0

这个问题的其他答案几乎都谈到了使用切片而不是数组指针,但没有一个回答解决错误的方法,所以我想写下这个答案。错误提示我们无法访问y的索引,因为它是无效操作。

你的第一种方法是错误的,因为Go编译器会报错。第一种方法的问题在于*y[i+j]是错误的语法。这是因为从技术上讲,你正在做这个*(y[i+j]),你不能这样做,因为在你的情况下y是一个指向int数组的指针。如果你打印y,它会打印出数组的内存地址。

你试图获取y+j个索引,但这个索引并不存在,因为y不是一个数组。你可以通过给语句添加括号来修复你的代码,这将表明你正在尝试获取y指向的数组的i+j个索引。使用(*y)[i+j]代替*y[i+j]。在进行更改后,函数将如下所示:

func conv(x []int, xlen int, h []int, hlen int, y *[]int) {
    for i := 0; i<xlen; i++ {
        for j := 0; j<hlen; j++ {
            (*y)[i+j] += x[i]*h[j]
        }
    }
}

1
你的代码使用了指向切片的指针,而不是像你的解释所暗示的那样使用数组。 - Pizza lord
@Pizzalord 哦,对了,抱歉我不小心用了“数组”这个词,而不是“切片”,因为问题中不小心使用了“数组”这个词。我会在我的答案中将“数组”替换为“切片”,但代码对于切片和数组都可以工作。 - Hargunbeer Singh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接