What is the correct go idiom for calling functions on a struct?

huangapple go评论98阅读模式
英文:

What is the correct go idiom for calling functions on a struct?

问题

我是你的中文翻译助手,以下是翻译好的内容:

我是Go语言的新手(之前使用Python和Ruby),想知道在结构体上调用函数的惯用方式是什么?

我主要想知道是否应该使用点运算符来调用函数,还是将我的类型作为参数之一。另外,使用指针是否更好?

  • 指针 vs 非指针?
  • 点运算符 vs 参数?

我可以这样做吗?

package main

import "fmt"

func main() {
    me := Person{firstname: "John", lastname: "Doe", age: 40}
    fmt.Println(me.fullname())
}

type Person struct {
    firstname string
    lastname  string
    age       int
}

func (p Person) fullname() string {
    return p.firstname + p.lastname
}

或者这样做:

package main

import "fmt"

func main() {
    me := Person{firstname: "John", lastname: "Doe", age: 40}
    fmt.Println(fullname(&me))
}

type Person struct {
    firstname string
    lastname  string
    age       int
}

func fullname(p *Person) string {
    return p.firstname + p.lastname
}
英文:

###I am new to go (coming from python and ruby) and want to know what is the idiomatic way of calling functions on a struct?
Mostly I want to know if I should use the dot operator to call functions or use my type as one of the arguments. Also is it better to use pointers or not?

  • pointer vs no pointer?
  • dot vs argument?
    *

I can do it this way?

package main

import "fmt"

func main() {
	me := Person{firstname: "John", lastname: "Doe", age: 40}
	fmt.Println(me.fullname())
}

type Person struct {
	firstname string
	lastname  string
	age       int
}

func (p Person) fullname() string {
	return p.firstname + p.lastname
}

or this way

package main

import "fmt"

func main() {
	me := Person{firstname: "John", lastname: "Doe", age: 40}
	fmt.Println(fullname(&me))
}

type Person struct {
	firstname string
	lastname  string
	age       int
}

func fullname(p *Person) string {
	return p.firstname + p.lastname
}

答案1

得分: 2

这并没有一个正确的方法。重要的是要理解两者之间的区别,并在适当的情况下应用每种方法。此外,你的函数并不真正可比较。在第一种情况中,你定义了一个带有类型为Person的接收器的函数,并且它是按值传递的,这意味着会创建实例的副本并将其推送到堆栈上。而在另一种情况下,函数是独立的,你将一个Person的引用传递给它。你还可以通过将接收类型设置为指针来进行引用传递。

所以在第一种情况下:

func (p Person) fullname() string {
    return p.firstname + p.lastname
}

你可以像这样调用函数 p.fullname(),并且p的副本会被推送到堆栈上。如果你在这个函数中进行赋值操作,p不会被修改,作用域中的实例会被修改。所以对于设置器或任何意图改变对象状态的函数来说,这根本不是一个选项。

为了详细说明我所说的另一种选择,你可以这样做:

func (p *Person) fullname() string {
    return p.firstname + p.lastname
}

这仍然允许你像 p.fullname() 这样在实例上调用它,但是传递给函数的是一个 *Person 类型的引用。所以这将是实现在结构体上设置值的典型选择。

现在来看看你的第二个例子:

func fullname(p *Person) string {
    return p.firstname + p.lastname
}

它没有接收器并且传递了一个指针。这意味着你可以这样调用它 fullname(p)。这个函数没有被导出,所以它只能在声明它的包中使用,这里是main包。为了与(可能)更熟悉的语言进行类比,这就像在C++中定义一个函数,而另外两个则定义了一个“方法”或类中的函数。而另外两个则类似于“按值传递”或“按引用传递”。这是按引用传递,但与任何类型无关。

在你的例子中,出于性能原因,我总是会使用 func (p *Person) fullname() string。在我看来,使用按值传递的替代方案 func (p Person) fullname() string 最合适的时机是当你想要强制不可变性时。如果你想要执行操作,最好生成一个新对象而不是修改现有对象(许多集合库都是这样操作的,例如C#中的LINQ总是生成一个新集合,而在查询期间尝试修改集合将导致异常),那么你可能需要一个值类型的接收器。

希望这有所帮助。如果其中任何内容仍然令人困惑,我可以提供更多信息。

英文:

There isn't one correct way of doing this. It's important to understand the differences between the two and apply each where appropriate. Further more, your functions aren't really comparable. In the one case you've defined a function with a receiver of type Person and it is passed by value, meaning a copy of the instance is made and pushed onto the stack. In the other instance the function is stand alone and you pass a Person reference to it. You can also, do pass by reference with an 'instance method' by making the receiving type a pointer.

So in the first case;

func (p Person) fullname() string {
    return p.firstname + p.lastname
}

you call the function like p.fullname() and a copy of p is pushed onto the stack. If you did assignment in this function, p would not be modified, the instance in the scope would be modified. So for setters, or any function that is intended to change the objects state, this isn't an option at all.

To elaborate on the alternative I was talking about, you could instead do this;

func (p *Person) fullname() string {
    return p.firstname + p.lastname
}

Which still allow you to call it on an instance like p.fullname() however, what's passed to the function is a reference of type *Person. So this would be the typical choice for implementing a function that sets a value on a struct.

Now your second example;

func fullname(p *Person) string {
    return p.firstname + p.lastname
}

has no receiver and passes a pointer. Meaning you call it like this fullname(p). The function is not exported so it's only available in the package where it was declared, in this case main. To draw parallels to (potentially) more familiar languages, this is like defining a function in C++ where the other two are defining a 'method' or a function in a class. And the other two would be compared to 'passing by value' or 'passing by reference'. This passes by reference by is no associated to any type.

In your example I would always use func (p *Person) fullname() string for performance reasons. In my opinion the most appropriate time to use the pass by value alternative func (p Person) fullname() string is when you want to enforce immutability. If you want to do operations where it's better to produce a new object rather than modifying an existing one (a lot of collection libraries operate like this, for example LINQ in C# always produces a new collection and an attempt to modify the collection during a query will result in an exception) then you probably want a value type receiver.

Hope that helps. I can update with more information if any of this is still the cause of confusion.

huangapple
  • 本文由 发表于 2016年5月26日 03:57:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/37446501.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定