为什么Go语言的构造函数应该返回地址?

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

Why should constructor of Go return address?

问题

我理解到Go语言没有构造函数,而是使用New func来代替,但根据这个例子,它们总是返回&f。为什么不简单地返回File就可以了呢?

更新

我尝试返回一个简单结构体的创建对象,结果是可以的。所以,我想知道返回一个地址是不是构造函数的标准方式或者其他什么。

谢谢。

英文:

I understand that Go doesn't have any constructors and a New func is used in its place, but according to this example.

func NewFile(fd int, name string) *File {
  if fd < 0 {
    return nil
  }
  f := File{fd, name, nil, 0}
  return &f
}

They always return &f. Why just simply returning File isn't suffice?

Update

I've tried returning the created object for a simple struct and it's fine. So, I wonder if returning an address is a standard way of constructor or something.

Thanks.

答案1

得分: 26

如前所述,是的,规范允许你返回值(作为非指针)或指针。这只是你需要做出的决定。

何时返回指针?

通常情况下,如果你返回的值作为指针更有用,那么就返回指针。什么时候返回指针更有用呢?

例如,如果它具有许多使用指针接收器的方法。是的,你可以将返回值存储在变量中,这样它就是可寻址的,你仍然可以调用其具有指针接收器的方法。但是如果立即返回一个指针,你可以"链式"调用方法。看看这个例子:

type My int

func (m *My) Str() string { return strconv.Itoa(int(*m)) }

func createMy(i int) My { return My(i) }

现在写:

fmt.Println(createMy(12).Str())

会导致错误:cannot call pointer method on createMy(12)

但是如果返回一个指针,就可以正常工作:

func createMy(i int) *My { return (*My)(&i) }

另外,如果你将返回的值存储在一个不可寻址的数据结构中(例如map),你无法通过索引映射来调用值的方法,因为映射的值是不可寻址的。

看看这个例子:My.Str()具有指针接收器。所以如果你尝试这样做:

m := map[int]My{0: My(12)}
m[0].Str() // 错误!

你不能这样做,因为_"无法取得m[0]的地址"_。但是下面的代码可以正常工作:

m := map[int]*My{}
my := My(12)
m[0] = &my // 在映射中存储一个指针

m[0].Str() // 你可以调用它,不需要取`m[0]`的地址
           // 因为它已经是一个指针

另一个指针有用的例子是如果它是一个"大"的结构体,会被频繁传递http.Request就是一个很好的例子。它很大,通常会被传递给其他处理程序,并且它具有使用指针接收器的方法。

如果你返回一个指针,通常意味着返回的值最好作为指针存储和传递。

英文:

As mentioned, yes, the spec allows you to return either values (as non-pointers) or pointers. It's just a decision you have to make.

When to return pointer?

Usually if the value you return is "more useful" as a pointer. When is it more useful?

For example if it has many methods with pointer receiver. Yes, you could store the return value in a variable and so it will be addressable and you can still call its methods that have pointer receivers. But if a pointer is returned right away, you can "chain" method calls. See this example:

type My int

func (m *My) Str() string { return strconv.Itoa(int(*m)) }

func createMy(i int) My { return My(i) }

Now writing:

fmt.Println(createMy(12).Str())

Will result in error: cannot call pointer method on createMy(12)

But if works if you return a pointer:

func createMy(i int) *My { return (*My)(&i) }

Also if you store the returned value in a data structure which is not addressable (map for example), you cannot call methods on values by indexing a map because values of a map are not addressable.

See this example: My.Str() has pointer receiver. So if you try to do this:

m := map[int]My{0: My(12)}
m[0].Str() // Error!

You can't because "cannot take the address of m[0]". But the following works:

m := map[int]*My{}
my := My(12)
m[0] = &my // Store a pointer in the map

m[0].Str() // You can call it, no need to take the address of m[0]
           // as it is already a pointer

And another example for pointers being useful is if it is a "big" struct which will be passed around a lot. http.Request is a shining example. It is big, it is usually passed around a lot to other handlers, and it has methods with pointer receiver.

If you return a pointer, that usually suggests that the returned value is better if stored and passed around as a pointer.

答案2

得分: 0

指针接收器接受指针类型和值类型,只要它们与数据类型匹配。

type User struct {
	name  string
	email string
	age   int
}

// NewUserV 返回值类型... 理想情况下,对于 User,我们不应该返回值类型
func NewUserV(name, email string, age int) User {
	return User{name, email, age}
}

// NewUserP 返回指针类型...
func NewUserP(name, email string, age int) *User {
	return &User{name, email, age}
}

// ChangeEmail ...
func (u *User) ChangeEmail(newEmail string) {
	u.email = newEmail
}

func main() {
	// 使用值类型
	usr1 := NewUserV("frank", "frank@camero.com", 22)
	fmt.Println("修改前:", usr1)
	usr1.ChangeEmail("frank@gmail.com")
	fmt.Println("修改后:", usr1)

	// 使用指针类型
	usr2 := NewUserP("john", "john@liliput.com", 22)
	fmt.Println("修改前:", usr2)
	usr2.ChangeEmail("john@macabre.com")
	fmt.Println("修改后:", usr2)
}

除了icza提到的传递大型结构体的问题外,指针值还表示指针语义正在起作用,使用特定类型的人不应该复制由指针共享的值。

如果你看一下 File 或 http 类型的结构体,它们维护着通道或其他指针类型,这些类型对于该值是唯一的。复制该值(由指针给出)会导致难以发现的错误,因为复制的值可能最终会读取或写入原始值的指针类型。

英文:

Pointer receiver accepts both pointer and value types, as long as it matches the data type.

type User struct {
	name  string
	email string
	age   int
}

// NewUserV returns value ... ideally for a User we should not be 
// returning value
func NewUserV(name, email string, age int) User {
	return User{name, email, age}
}

// NewUserP returns pointer ...
func NewUserP(name, email string, age int) *User {
	return &User{name, email, age}
}

// ChangeEmail ...
func (u *User) ChangeEmail(newEmail string) {
	u.email = newEmail
}

func main() {
	// with value type
	usr1 := NewUserV("frank", "frank@camero.com", 22)
	fmt.Println("Before change: ", usr1)
	usr1.ChangeEmail("frank@gmail.com")
	fmt.Println("After change: ", usr1)

	// with pointer type
	usr2 := NewUserP("john", "john@liliput.com", 22)
	fmt.Println("Before change: ", usr2)
	usr2.ChangeEmail("john@macabre.com")
	fmt.Println("After change: ", usr2)

}

In addition to what icza mentioned about the big struct being passed around. Pointer values are a way of saying that pointer semantics are at play and who ever uses the particular type should not make copy of the value which is being shared by the pointer.

If you look at the struct of File or http type, it maintains channels or some other pointer types which is unique to that value. Make a copy of the value (given to you by the pointer) would lead to hard to find bugs since the copied value might end up writing or reading to the pointer types of the original value.

huangapple
  • 本文由 发表于 2015年8月11日 11:42:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/31932822.html
匿名

发表评论

匿名网友

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

确定