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

18

我理解Go语言没有任何构造函数,而是使用New函数代替,但根据这个示例

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

他们总是返回&f。为什么不能简单地返回File呢?

更新

我已经尝试过为一个简单结构创建对象并且可以正常工作。 所以,我想知道返回地址是否是构造函数的标准方式或其他原因。

谢谢。


1
该函数返回一个 File*,因此不可能直接返回 File。在之前的情况下,使用 new(File) 来开始一个 File* - user2864740
所以,除非我在使用文件,否则我不必将地址返回给变量,对吗? - Bhoomtawath Plinsut
这两个示例都使用了“File”.. 我不知道如何解释那个问题。 - user2864740
2
在Go语言中,返回变量的地址与在C语言中调用malloc()函数是相同的。如果你不需要它,就不必使用它。 - slebetman
2
语言本身并没有要求函数返回指针,这是定义函数的人做出的选择。你的问题可以概括为,“何时应该使用指针,何时应该使用普通结构体?”答案是,“这取决于情况。”你可以在互联网上进行一些研究,了解人们通常在什么情况下选择在Go中使用指针。 - Amit Kumar Gupta
2个回答

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() // Error!

由于"无法获取m[0]的地址",所以您不能这样做。但是下面的方法可以实现:

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

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

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


0
指针接收器可以接受指针和值类型,只要它们匹配数据类型即可。
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)

}

除了icza提到的大结构体被传递之外。指针值是一种表明指针语义正在发挥作用并且使用特定类型的人不应该复制由指针共享的值的方式。

如果您查看File或http类型的结构,它将维护通道或其他某些指针类型,这对于该值是唯一的。复制该值(由指针提供给您)会导致难以找到的错误,因为复制的值可能最终会写入或读取原始值的指针类型。


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