我有以下功能:
func addCatsToMap(m map[string][]CatHouse, meowId int, treats Set, dog *Dog) {
//if (complicated thing) add Cat to m
}
其中Set
treats
的类型是具有以下定义的接口:
type Set interface {
Add(value string)
Contains(value string) (bool)
Length() (int)
RemoveDuplicates()
}
问题:
m
,treats
和dog
传递参考是否属实,而meowId
是否复制了它的值?
我认为:
m
是传递引用,因为它是地图dog
是一个结构。所以,我应该传递指针以避免复制数据答案 0 :(得分:38)
接口类型只是一组方法。请注意,接口定义的成员不指定接收器类型是否为指针。这是因为值类型的方法集是其关联指针类型的方法集的子集。那是满口的。我的意思是,如果您有以下内容:
type Whatever struct {
Name string
}
并定义以下两种方法:
func (w *Whatever) Foo() {
...
}
func (w Whatever) Bar() {
...
}
然后类型Whatever
只有方法Bar()
,而类型*Whatever
则有方法Foo()
和Bar()
。这意味着如果您有以下界面:
type Grits interface {
Foo()
Bar()
}
然后*Whatever
实现Grits
但Whatever
没有,因为Whatever
缺少方法Foo()
。将函数的输入定义为接口类型时,您不知道它是指针还是值类型。
以下示例说明了一种以两种方式接受接口类型的函数:
package main
import "fmt"
type Fruit struct {
Name string
}
func (f Fruit) Rename(name string) {
f.Name = name
}
type Candy struct {
Name string
}
func (c *Candy) Rename(name string) {
c.Name = name
}
type Renamable interface {
Rename(string)
}
func Rename(v Renamable, name string) {
v.Rename(name)
// at this point, we don't know if v is a pointer type or not.
}
func main() {
c := Candy{Name: "Snickers"}
f := Fruit{Name: "Apple"}
fmt.Println(f)
fmt.Println(c)
Rename(f, "Zemo Fruit")
Rename(&c, "Zemo Bar")
fmt.Println(f)
fmt.Println(c)
}
您可以致电Raname(&f, "Jorelli Fruit")
但不能致电Rename(c, "Jorelli Bar")
,因为Fruit
和*Fruit
都会实施Renamable
,而*Candy
会实施Renable
}和Candy
没有。
答案 1 :(得分:6)
通过引用传递是一种语言事物,Go中的任何内容都不是“通过引用传递”。通过引用传递意味着赋值操作符可以在单独使用时更改原始值。但是,有一些引用类型,例如指向某处的地图和指针。除非您使用其他运算符(例如地图索引和*
运算符),否则在它们上使用赋值运算符将不会修改原始运算符。
你的地图m
是引用类型,因此就像指针一样正确。除替换地图外,对地图的任何更改都将修改原始地图。
m["whatever"] = 2 // Modifies the original map
m = anothermap // Does not modify the original map
如果存在真正的“按引用传递”,则第二个示例将修改原始地图。
传递指针,就像使用dog
一样,可以修改原始指针。如果您调用任何指针方法或使用*
运算符,原始文件将更改。在您的示例中,可能不需要指针。如果Dog
很小,则可能更容易传递副本。程序员需要确定何时使用指针。
Set
未通过引用传递。 接口不是引用。虽然在6g编译器内部接口使用指针是正确的,但接口本身并不像一个接口。传递接口,无论它包含的对象的大小,都与使用6g编译器传递指针一样便宜。但是,无法使用指针和贴图修改接口的原始值。
虽然您无法修改传递的原始接口,但该接口可以包含指针类型。在这种情况下,它将像狗指针一样,调用某些方法可以修改原始。对于您的特定Set
接口,我猜它包含基于方法名称的指针类型。因此,当您致电set.Add(whatever)
时,它会更改原始内部数据。
答案 2 :(得分:3)
Calls, The Go Programming Language Specification
在函数调用中,将计算函数值和参数 通常的顺序。在评估它们之后,调用的参数 通过值传递给函数,并且被调用的函数开始 执行。函数的返回参数按值传递 当函数返回时返回调用函数。
When are function parameters passed by value? FAQ - The Go Programming Language.
与C系列中的所有语言一样, Go中的所有内容都按值传递。也就是说,一个函数总是得到一个 被传递的东西的副本,好像有一个任务 语句将值赋给参数。例如,传球 函数的int值生成int的副本,并传递一个 指针值生成指针的副本,但不指向它指向的数据 至。 (有关如何影响方法的讨论,请参阅下一节 接收机。)
Map和slice值的行为类似于指针:它们是描述符 包含指向底层地图或切片数据的指针。复制地图或 切片值不会复制它指向的数据。复制界面 value生成存储在接口值中的东西的副本。如果 接口值包含一个struct,复制接口值使得一个 结构的副本。如果接口值包含指针,则复制 接口值制作指针的副本,但同样不是 它指向的数据。