【问题标题】:golang pointers on pointers as function parametersgolang 指针上的指针作为函数参数
【发布时间】:2012-06-21 01:59:32
【问题描述】:

我有以下功能:

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()
}

问题:

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

我假设:

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

【问题讨论】:

    标签: pointers go


    【解决方案1】:

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

    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

    【讨论】:

    • 您好,快问一下,哪种方法效率更高?它是固定在一个用例上,还是我们可以使用指针一个,即 *Whatever 作为我们的默认值。 (问的原因是因为我正在强烈地将 Go 作为一种新语言来阅读,并且很难看到 Go 中的值传递和引用)我已经读过 Go 中的一切都是按值传递的,所以如果效率是游戏,你觉得 * 有帮助吗?
    • 不要从效率的角度考虑指针与值接收器,而是从可变性的角度考虑。如果方法要改变对象,请使用指针。否则使用值接收器。在您真正编写代码并对其进行分析并发现您有效率问题之前,您不会遇到效率问题。
    • 感谢@jorelli 的回复,我其实是在尝试用go 做一个数据库交集代码,假设2 个表非常大,所以我问效率,因为让我们说我怀疑,我按值传递,相同的数据将占用内存到 2 倍,否则指针会更好地帮助。这只是我的观点,我可能错了。
    【解决方案2】:

    引用传递是一种语言,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)时,它会改变原来的内部数据。

    【讨论】:

      【解决方案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 复制存储在接口 value 中的事物。如果 接口值包含一个结构,复制接口值会生成一个 结构的副本。如果接口值持有一个指针,则复制 接口值复制了指针,但又不是 它指向的数据。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-29
        • 2011-02-04
        • 2012-03-07
        • 1970-01-01
        相关资源
        最近更新 更多