什么时候返回指向结构体的指针是一个好主意?

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

When is it a good idea to return a pointer to a struct?

问题

我正在学习Go语言,对于何时使用指针感到有些困惑。具体来说,在从函数返回struct时,何时适合返回结构体实例本身,何时适合返回指向结构体的指针?

示例代码:

type Car struct {
  make string
  model string
}

func Whatever() {
  var car Car

  car := Car{"honda", "civic"}

  // ...

  return car
}

在哪些情况下我会希望返回指针,而在哪些情况下我不希望?有没有一个好的经验法则?

英文:

I'm learning Go, and I'm a little confused about when to use pointers. Specifically, when returning a struct from a function, when is it appropriate to return the struct instance itself, and when is it appropriate to return a pointer to the struct?

Example code:

type Car struct {
  make string
  model string
}

func Whatever() {
  var car Car

  car := Car{"honda", "civic"}

  // ...

  return car
}

What are the situations where I would want to return a pointer, and where I would not want to? Is there a good rule of thumb?

答案1

得分: 20

有两件事情你需要记住,性能和API。

汽车是如何使用的?它是一个有状态的对象吗?它是一个大的结构体吗?不幸的是,如果我不知道汽车是什么,就无法回答这个问题。说实话,最好的方法是看看别人是怎么做的,然后模仿他们。最终,你会对这种事情有一种感觉。现在我将描述标准库中的三个例子,并解释为什么我认为他们使用了它们。

  1. hash/crc32crc32.NewIEEE() 函数返回一个指针类型(实际上是一个接口,但底层类型是指针)。哈希函数的实例具有状态。当你向哈希中写入信息时,它会对数据进行求和,所以当你调用 Sum() 方法时,它会给出该实例的状态。

  2. timetime.Date 函数返回一个 Time 结构体。为什么?时间就是时间。它没有状态。它就像一个整数,你可以比较它们,对它们进行数学运算等等。API 设计者决定,对时间的修改不会改变当前时间,而是创建一个新的时间。作为库的用户,如果我想要一个月后的时间,我会希望得到一个新的时间对象,而不是改变我已经有的当前时间。时间只有3个字长。换句话说,它很小,在使用指针时不会有性能提升。

  3. math/bigbig.NewInt() 是一个有趣的例子。我们可以基本上认为,当你修改一个 big.Int 时,你通常会想要一个新的 big.Intbig.Int 没有内部状态,那为什么它是一个指针呢?答案很简单,就是性能。程序员意识到大整数是很大的。每次进行数学运算时不断分配内存可能不太实际。所以,他们决定使用指针,并允许程序员决定何时分配新的空间。

我回答了你的问题吗?可能没有。这是一个设计决策,你需要根据具体情况来解决。在设计自己的库时,我会以标准库作为指南。最终,这完全取决于判断力和你期望客户端代码如何使用你的类型。

英文:

There are two things you want to keep in mind, performance and API.

How is a Car used? Is it an object which has state? Is it a large struct? Unfortunately, it is impossible to answer when I have no idea what a Car is. Truthfully, the best way is to see what others do and copy them. Eventually, you get a feeling for this sort of thing. I will now describe three examples from the standard library and explain why I think they used what they did.

  1. hash/crc32: The crc32.NewIEEE() function returns a pointer type (actually, an interface, but the underlying type is a pointer). An instance of a hash function has state. As you write information to a hash, it sums up the data so when you call the Sum() method, it will give you the state of that one instance.

  2. time: The time.Date function returns a Time struct. Why? A time is a time. It has no state. It is like an integer where you can compare them, preform maths on them, etc. The API designer decided that a modification to a time would not change the current one but make a new one. As a user of the library, if I want the time one month from now, I would want a new time object, not to change the current one I have. A time is also only 3 words in length. In other words, it is small and there would be no performance gain in using a pointer.

  3. math/big: big.NewInt() is an interesting one. We can pretty much agree that when you modify a big.Int, you will often want a new one. A big.Int has no internal state, so why is it a pointer? The answer is simply performance. The programmers realized that big ints are … big. Constantly allocating each time you do a mathematical operation may not be practical. So, they decided to use pointers and allow the programmer to decide when to allocate new space.

Have I answered your question? Probably not. It is a design decision and you need to figure it out on a case by case basis. I use the standard library as a guide when I am designing my own libraries. It really all comes down to judgement and how you expect client code to use your types.

答案2

得分: 2

经常情况下,当你想要模仿面向对象的风格时,你会有一个“对象”来存储状态和可以改变对象的“方法”,那么你会有一个“构造函数”来返回一个指向结构体的指针(将其视为其他面向对象语言中的“对象引用”)。为了改变“对象”的字段,改变器方法必须是指向结构体类型的方法,而不是结构体类型本身,因此拥有指向结构体的指针而不是结构体值本身是很方便的,这样所有的“方法”都将在其方法集中。

例如,要在Java中模仿这样的东西:

class Car {
  String make;
  String model;
  public Car(String myMake) { make = myMake; }
  public setMake(String newMake) { make = newMake; }
}

在Go中,你经常会看到类似这样的代码:

type Car struct {
  make string
  model string
}
func NewCar(myMake string) *Car {
  return &Car{myMake, ""}
}
func (self *Car) setMake(newMake string) {
  self.make = newMake
}
英文:

Often, when you want to mimic an object-oriented style, where you have an "object" that stores state and "methods" that can alter the object, then you would have a "constructor" function that returns a pointer to a struct (think of it as the "object reference" as in other OO languages). Mutator methods would have to be methods of the pointer-to-the-struct type instead of the struct type itself, in order to change the fields of the "object", so it's convenient to have a pointer to the struct instead of a struct value itself, so that all "methods" will be in its method set.

For example, to mimic something like this in Java:

class Car {
  String make;
  String model;
  public Car(String myMake) { make = myMake; }
  public setMake(String newMake) { make = newMake; }
}

You would often see something like this in Go:

type Car struct {
  make string
  model string
}
func NewCar(myMake string) *Car {
  return &Car{myMake, ""}
}
func (self *Car) setMake(newMake string) {
  self.make = newMake
}

答案3

得分: 1

非常宽泛地说,异常通常会在特定情况下出现:

  • 当返回值非常小(不超过几个词)时,返回一个值。
  • 当复制开销会严重影响性能时(大小为很多词),返回一个指针。
英文:

Very losely, exceptions are likely to show up in specific circumstances:

  • Return a value when it is really small (no more than few words).
  • Return a pointer when the copying overhead would substantially hurt performance (size is a lot of words).

huangapple
  • 本文由 发表于 2012年6月12日 04:13:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/10986906.html
匿名

发表评论

匿名网友

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

确定