【问题标题】:Are channels passed by reference implicitly通道是否通过引用隐式传递
【发布时间】:2013-05-11 11:55:21
【问题描述】:

go tour 有这个频道示例:https://tour.golang.org/concurrency/2

package main

import "fmt"

func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    c <- sum // send sum to c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
    x, y := <-c, <-c // receive from c

    fmt.Println(x, y, x+y)
}

在 sum 函数中修改了通道 c,并且在函数终止后更改仍然存在。显然 c 是通过引用传递的,但没有创建指向 c 的指针。在 go 中,channel 是通过引用隐式传递的吗?

【问题讨论】:

  • 是的,Go 中的引用类型是slicemapchannel。传递这些时,您正在制作参考的副本。 (字符串也被实现为引用类型,尽管它们是不可变的。)

标签: concurrency go channels pass-by-reference


【解决方案1】:

从技术上讲,它们是被复制的,因为当你使用make 时,你在堆上分配了一些东西,所以它在技术上是一个幕后的指针。但是指针类型没有暴露出来,所以可以认为是引用类型。

编辑:来自规范:

内置函数 make 采用类型 T,它必须是切片、映射或通道类型,可选地后跟特定类型的表达式列表。它返回一个 T 类型的值(不是 *T)。内存按照初始值一节中的说明进行初始化。

必须先初始化通道,然后才能使用它。 make() 这样做了,所以它可以用作引用类型。

这基本上意味着您可以将它传递给一个函数并对其进行写入或读取。一般的经验法则是,如果您使用 makenew&amp;,您可以将其传递给另一个函数,而无需复制基础数据。

因此,以下是“引用”类型:

  • 切片
  • 地图
  • 频道
  • 指针
  • 功能

在传递给函数时,仅复制数据类型(数字、布尔值和结构等)。字符串很特殊,因为它们是不可变的,但不是按值传递的。这意味着以下内容将无法按预期工作:

type A struct {
    b int
}
func f(a A) {
    a.b = 3
}
func main() {
    s := A{}
    f(s)
    println(s.b) // prints 0
}

【讨论】:

  • @tjameson:make 并不意味着堆分配,切片实际上是作为结构实现的,并在传递时复制。
  • @squint - 对。 array,我的意思是make([]int, 5),但我刚刚意识到这在技术上更像是一个切片。我的错。
  • 指针并不是真正的引用类型,它们是碰巧充当引用的值类型。
  • slice 的行为不像 mapchan 那样的“不透明指针”。如果使用参数a []string 调用的函数将其更改为a = append(a, x),则调用者将看不到长度的变化。对预先存在的索引的更改是可见的(除非同一函数中的 append 在它之前导致重新分配)。
  • @Subtubes 正确,通道不需要作为指针传递,因为 chan 类型已经是指向结构的指针(尝试c := make(chan int, 0); log.Printf("%v", c))。调用函数时,此指针被复制并传递给函数。而切片类型由具有(指针、长度、容量)值的结构组成。当调用一个函数时,这个(指针、长度和容量)结构被复制并传递给函数。有关更详细的说明,请参阅Go is pass-by-value — but it might not always feel like it
【解决方案2】:

Go 中的所有内容都是按值传递和分配的。某些内置类型,包括通道类型和映射类型,表现为指向某些隐藏内部结构的不透明指针。并且可以通过对通道或地图的操作来修改该内部结构。它们以nil 开头,类似于nil 指针。

【讨论】:

  • 一切?与所选答案相矛盾。
  • @TheRealFakeNews:是的,一切。当然,如果您对按值传递有不同的定义,您可能会不同意。我的定义是,如果在被调用函数中对参数变量的简单赋值对调用范围内的传递变量没有影响,则它是按值传递。此语义定义与 Java 仅具有按值传递的普遍接受的声明(在此站点和其他地方)一致。 Go 中的传递语义是相同的。
【解决方案3】:

您可以说是,但是说“通道 c 在 sum 函数中被修改”并不是真正正确的术语。通道发送和接收实际上并不被视为修改。

请注意,切片和映射的行为方式相似,有关详细信息,请参阅 http://golang.org/doc/effective_go.html

此外,“通过引用传递”意味着可以对 sum 中的 c 进行分配,这将在 sum 之外更改其值(而不是基础数据),但事实并非如此。

【讨论】:

    【解决方案4】:

    频道变量是引用,但这取决于您对“引用”的定义。 Language specification 从未提及引用类型。

    sum 函数中没有“修改”任何通道(变量)。发送到频道会更改其状态。

    换句话说,是的,通道被实现为指向某个运行时结构的指针。请注意,这对于引用语义是绝对必要的。

    编辑:上面的句子的意思是:“请注意,对于引用语义,这不是严格必要的。”,即。 “不”这个词去了MIA。对于最终造成的任何混淆,我们深表歉意。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-04-01
      • 1970-01-01
      • 2021-05-20
      • 2010-10-10
      • 1970-01-01
      相关资源
      最近更新 更多