何时将指针返回给结构体是个好主意?

12

我正在学习Go语言,但仍然有点困惑什么时候需要使用指针。具体来说,当从函数返回一个struct时,什么情况下适合返回结构体实例本身,什么情况下适合返回结构体的指针?

示例代码:

type Car struct {
  make string
  model string
}

func Whatever() {
  var car Car

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

  // ...

  return car
}

在什么情况下我会想要返回指针,在什么情况下我不想要?是否有一个好的经验法则?


同样的规则不适用吗? - Carson
2
不同的语言有不同的规则。每种语言都有其注意事项,我个人不了解Go语言,所以无法代表它发言。但是我知道,在C语言中,返回指向堆栈上分配的对象的指针是绝对不可取的。 - Richard J. Ross III
我对答案感到困惑:我认为灰色区域与小对象的性能有关,我看不出除了分析之外估计最快解决方案的方法。传递小于64位的值对象是否简单地更快? - Denys Séguret
3个回答

21

有两件事情你需要记在心里,性能和API。

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

  1. hash/crc32: crc32.NewIEEE()函数返回一个指针类型(实际上,是一个接口,但底层类型是指针)。哈希函数的实例具有状态。当您将信息写入哈希时,它会对数据进行求和,因此当您调用Sum()方法时,它将为您提供该实例的状态。

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

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

我回答了你的问题吗?可能没有。这是一个设计决策,您需要根据具体情况来确定。在设计自己的库时,我会参考标准库。这真的取决于判断力和您希望客户端代码如何使用您的类型。


1
我不得不读几遍才意识到这是一个非常好的答案。谢谢你。 - Carson
很棒的回答。我是Go的新手,是在IRC的#go-nuts频道被Stephen推荐来这里的。 - Mark Richman

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
}

那么,使用Go语言时,我不应该使用new关键字吗?我应该像你所示的那样返回一个引用吗?你的例子让我感到困惑。 - Carson
@Carson:好的,你可以使用 new,但是 new 会将值初始化为该类型的零值,对于结构体,所有字段都将初始化为零值。这可能不是所有类型的有效初始化值。如果零值适用于初始化您的类型,则可以只使用 new;但是,如果您的类型需要自定义“构造函数”,则应像我展示的那样定义自己的函数。 - newacct

1

通俗来说,异常情况通常会在特定情况下出现:

  • 当返回值非常小(不超过几个单词)时,可以返回一个值。
  • 当复制开销会显著影响性能时(大小为很多单词),可以返回指针。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接