英文:
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。
汽车是如何使用的?它是一个有状态的对象吗?它是一个大的结构体吗?不幸的是,如果我不知道汽车是什么,就无法回答这个问题。说实话,最好的方法是看看别人是怎么做的,然后模仿他们。最终,你会对这种事情有一种感觉。现在我将描述标准库中的三个例子,并解释为什么我认为他们使用了它们。
-
hash/crc32
:crc32.NewIEEE()
函数返回一个指针类型(实际上是一个接口,但底层类型是指针)。哈希函数的实例具有状态。当你向哈希中写入信息时,它会对数据进行求和,所以当你调用Sum()
方法时,它会给出该实例的状态。 -
time
:time.Date
函数返回一个Time
结构体。为什么?时间就是时间。它没有状态。它就像一个整数,你可以比较它们,对它们进行数学运算等等。API 设计者决定,对时间的修改不会改变当前时间,而是创建一个新的时间。作为库的用户,如果我想要一个月后的时间,我会希望得到一个新的时间对象,而不是改变我已经有的当前时间。时间只有3个字长。换句话说,它很小,在使用指针时不会有性能提升。 -
math/big
:big.NewInt() 是一个有趣的例子。我们可以基本上认为,当你修改一个big.Int
时,你通常会想要一个新的big.Int
。big.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.
-
hash/crc32
: Thecrc32.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 theSum()
method, it will give you the state of that one instance. -
time
: Thetime.Date
function returns aTime
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. -
math/big
: big.NewInt() is an interesting one. We can pretty much agree that when you modify abig.Int
, you will often want a new one. Abig.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).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论