21

我有以下功能:

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,treatsdog通过引用传递的,并且meowId它的值是否被复制?

我假设:

  • m是按引用传递,因为它是一张地图
  • dog是一个结构。所以,我应该传递指针以避免复制数据
4

3 回答 3

43

接口类型只是一组方法。请注意,接口定义的成员不指定接收器类型是否为指针。这是因为值类型的方法集是其关联指针类型的方法集的子集。那是一口。我的意思是,如果您有以下情况:

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实现*FruitRenamable*Candy实现RenableCandy不实现。

http://play.golang.org/p/Fb-L8Bvuwj

于 2012-06-21T04:06:16.863 回答
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),它会改变原来的内部数据。

于 2012-06-21T03:30:23.043 回答
3

调用,Go 编程语言规范

在函数调用中,函数值和参数按通常的顺序计算。在它们被评估之后,调用的参数按值传递给函数,被调用的函数开始执行。函数的返回参数在函数返回时按值传回调用函数。

函数参数什么时候传值?FAQ - Go 编程语言。

与 C 家族中的所有语言一样,Go 中的所有内容都是按值传递的。也就是说,一个函数总是得到一个被传递的东西的副本,就好像有一个赋值语句将值分配给参数一样。例如,将 int 值传递给函数会生成 int 的副本,传递指针值会生成指针的副本,但不会复制它指向的数据。(有关这如何影响方法接收器的讨论,请参阅下一节。)

映射和切片值的行为类似于指针:它们是包含指向底层映射或切片数据的指针的描述符。复制地图或切片值不会复制它指向的数据。复制接口值会复制存储在接口值中的事物。如果接口值包含一个结构,则复制接口值会复制该结构。如果接口值包含一个指针,则复制接口值会复制指针,但不会复制它指向的数据。

于 2012-06-21T17:28:00.140 回答