为了账号安全,请及时绑定邮箱和手机立即绑定

通过引用传递自定义切片类型

通过引用传递自定义切片类型

Go
忽然笑 2021-06-04 18:09:48
我对指针、切片和接口在 Go 中的交互方式感到困惑。这是我目前编码的内容:type Loader interface {  Load(string, string)}type Foo struct {  a, b string}type FooList []Foofunc (l FooList) Load(a, b string) {  l = append(l, Foo{a, b})  // l contains 1 Foo here}func Load(list Loader) {  list.Load("1", "2")  // list is still nil here}鉴于此设置,我然后尝试执行以下操作:var list FooListLoad(list)fmt.Println(list)但是,列表总是nil在这里。我的 FooList.Load 函数确实向l切片添加了一个元素,但仅此而已。将list在负载继续nil。我想我应该能够将引用传递给我的切片并附加到它。不过,我显然缺少有关如何使其工作的内容。
查看完整描述

3 回答

?
慕哥9229398

TA贡献1877条经验 获得超6个赞

我将简化问题,以便更容易理解。那里正在做的事情与此非常相似,这也不起作用(您可以在此处运行):


type myInt int


func (a myInt) increment() { a = a + 1 }

func increment(b myInt)    { b.increment() }


func main() {

    var c myInt = 42

    increment(c)

    fmt.Println(c) // => 42

}

这不起作用的原因是因为 Go 按值传递参数,如文档所述:


在函数调用中,函数值和参数按通常的顺序计算。在对它们求值后,调用的参数按值传递给函数,被调用的函数开始执行。


实际上,这意味着上面示例中的每个a、b、 和c都指向不同的 int 变量,a并且b是初始c值的副本。


要修复它,我们必须使用指针,以便我们可以引用相同的内存区域(此处可运行):


type myInt int


func (a *myInt) increment() { *a = *a + 1 }

func increment(b *myInt)    { b.increment() }


func main() {

    var c myInt = 42

    increment(&c)

    fmt.Println(c) // => 43

}

Nowa和b都是包含变量地址的指针c,允许它们各自的逻辑改变原始值。注意记录的行为仍持有此:a和b仍然是原始值的副本,但作为一个参数提供的原始值increment函数的地址的c。


切片的情况与此没有什么不同。它们是引用,但引用本身是按值作为参数提供的,因此如果您更改引用,调用站点将不会观察到更改,因为它们是不同的变量。


不过,还有一种不同的方法可以使其工作:实现一个类似于标准append函数的 API 。再次使用更简单的示例,我们可以在increment不改变原始值且不使用指针的情况下通过返回更改后的值来实现:


func increment(i int) int {  return i+1 }

您可以在标准库中的许多地方看到该技术,例如strconv.AppendInt函数。


查看完整回答
反对 回复 2021-06-28
?
隔江千里

TA贡献1906条经验 获得超10个赞

Go 是按值传递的。对于参数和接收器都是如此。如果需要给切片赋值,则需要使用指针。


然后我在某处读到你不应该将指针传递给切片,因为它们已经是引用


这并不完全正确,并且缺少故事的一部分。


当我们说某物是“引用类型”时,包括映射类型、通道类型等,我们的意思是它实际上是一个指向内部数据结构的指针。例如,您可以将地图类型视为基本定义为:


// pseudocode

type map *SomeInternalMapStructure

所以要修改关联数组的“内容”,不需要赋值给map变量;您可以按值传递映射变量,该函数可以更改映射变量指向的关联数组的内容,并且它对调用者可见。当您意识到它是指向某个内部数据结构的指针时,这是有道理的。如果你想改变你只会分配给一个变量地图这你想让它指向内部的关联数组。


但是,切片更复杂。它是一个指针(指向内部数组),加上长度和容量,两个整数。所以基本上,你可以把它想象成:


// pseudocode

type slice struct {

    underlyingArray uintptr

    length int

    capacity int

}

所以它不“只是”一个指针。它是一个相对于底层数组的指针。但是长度和容量是切片类型的“值”部分。


因此,如果您只需要更改切片的元素,那么是的,它就像引用类型一样,因为您可以按值传递切片并让函数更改元素并且它对调用者可见。


但是,当您append()(这就是您在问题中所做的)时,情况就不一样了。首先,append 影响切片的长度,而长度是切片的直接部分之一,而不是在指针后面。其次,append 可能会产生不同的底层数组(如果原始底层数组的容量不够,则分配一个新的);因此切片的数组指针部分也可能会更改。因此有必要改变切片值。(这就是为什么要append()返回一些东西。)从这个意义上说,它不能被视为引用类型,因为我们不仅仅是“改变它指向的东西”;我们直接改变切片。


查看完整回答
反对 回复 2021-06-28
  • 3 回答
  • 0 关注
  • 202 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信