首页 文章

指针上的golang指针作为函数参数

提问于
浏览
20

我有以下功能:

func addCatsToMap(m map[string][]CatHouse, meowId int, treats Set, dog *Dog) {

//if (complicated thing) add Cat to m

}

其中 Settreats 的类型是具有以下定义的接口:

type Set interface {
  Add(value string)
  Contains(value string) (bool)
  Length() (int)
  RemoveDuplicates()
}

Question:

mtreatsdog 是否通过引用传递, meowId 是否复制了它的值?

我认为:

  • m 是传递引用,因为它是一张 Map

  • dog 是一个结构 . 所以,我应该传递指针以避免复制数据

3 回答

  • 3

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

    type Whatever struct {
        Name string
    }
    

    并定义以下两种方法:

    func (w *Whatever) Foo() {
        ...
    }
    
    func (w Whatever) Bar() {
        ...
    }
    

    然后类型 Whatever 只有方法 Bar() ,而类型 *Whatever 有方法 Foo()Bar() . 这意味着如果您有以下界面:

    type Grits interface {
        Foo()
        Bar()
    }
    

    然后 *Whatever 实现 GritsWhatever 没有,因为 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 实现了 RenableCandy 没有 .

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

  • 6

    通过引用传递是一种语言,Go中的任何内容都不是"pass by reference" . 通过引用传递意味着赋值操作符可以在单独使用时更改原始值 . 但是,有一些引用类型,例如指向某处的 Map 和指针 . 除非使用其他运算符(如map索引和 * 运算符),否则对它们使用赋值运算符将不会修改原始运算符 .

    你的 Map m 是一个引用类型是正确的,因此就像一个指针 . 除替换 Map 外,对 Map 的任何更改都将修改原始 Map .

    m["whatever"] = 2           // Modifies the original map
    m = anothermap              // Does not modify the original map
    

    如果存在真正的“按引用传递”,则第二个示例将修改原始 Map .

    dog 一样传递指针允许您修改原始指针 . 如果您调用任何指针方法或使用 * 运算符,原始将更改 . 在您的示例中,可能不需要指针 . 如果 Dog 很小,则传递副本可能更容易 . 由程序员决定何时使用指针是一个好时机 .

    Set 未通过引用传递 . Interfaces are not references. 虽然在6g编译器内部确实是接口使用指针,但接口本身并不像一个接口 . 传递接口,无论它包含的对象的大小,都与使用6g编译器传递指针一样便宜 . 但是,无法使用指针和贴图修改接口的原始值 .

    虽然您无法修改传递的原始接口,但该接口可以包含指针类型 . 在这种情况下,它将像狗指针一样,调用某些方法可以修改原始 . 对于您的特定 Set 接口,我猜它包含基于方法名称的指针类型 . 因此,当您调用 set.Add(whatever) 时,它将更改原始内部数据 .

  • 38

    调用,Go编程语言规范在函数调用中,函数值和参数按通常顺序计算 . 在评估它们之后,调用的参数通过值传递给函数,并且被调用的函数开始执行 . 当函数返回时,函数的返回参数通过值传递回调用函数 . 函数参数何时通过值传递?常见问题 - Go编程语言 . 与C系列中的所有语言一样,Go中的所有内容都按值传递 . 也就是说,函数总是获取正在传递的东西的副本,就好像有一个赋值语句将值赋给参数 . 例如,将int值传递给函数会生成int的副本,并且传递指针值会生成指针的副本,但不会生成它指向的数据 . (有关它如何影响方法接收器的讨论,请参阅下一节 . )映射和切片值的行为类似于指针:它们是描述符,包含指向底层 Map 或切片数据的指针 . 复制 Map 或切片值不会复制它指向的数据 . 复制接口值会生成存储在接口值中的事物的副本 . 如果接口值包含结构,则复制接口值会生成结构的副本 . 如果接口值包含指针,则复制接口值会生成指针的副本,但同样不会指向它指向的数据 .

相关问题