【问题标题】:Why should constructor of Go return address?为什么 Go 的构造函数要返回地址?
【发布时间】:2015-11-03 03:36:40
【问题描述】:

我知道 Go 没有任何构造函数,并且使用 New func 代替它,但根据 this example

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

他们总是返回&amp;f。为什么仅仅返回File 是不够的?

更新

我已经尝试为一个简单的结构返回创建的对象,这很好。所以,我想知道返回地址是否是构造函数或其他东西的标准方式。

谢谢。

【问题讨论】:

  • 函数返回一个File*,所以不能“直接”返回File。在前面的例子中,new(File) 用于以 File* 开头。
  • 所以,除非我使用文件,否则我不必将地址返回给变量,对吧?
  • 两个例子都使用“文件”..我不知道如何解释这个问题。
  • 在go中,返回一个变量的地址和C中调用malloc()是一样的,如果不需要就不用了。
  • 没有关于语言要求函数返回指针,这是定义函数的人做出的选择。您的问题只是概括为“我什么时候应该使用指针,什么时候应该使用普通结构?”的问题。答案是“视情况而定”。您可以在 Internet 上进行一些研究,了解人们通常何时选择在 Go 中使用指针。

标签: pointers go constructor


【解决方案1】:

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

何时返回指针?

通常,如果您返回的值作为指针“更有用”。什么时候更有用?

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

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 是一个光辉的例子。它很大,通常会被大量传递给其他处理程序,并且它具有带有指针接收器的方法。

如果您返回一个指针,这通常表明返回的值如果作为指针存储和传递会更好。

【讨论】:

    【解决方案2】:

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

    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 类型的结构,它会维护通道或其他一些对该值唯一的指针类型。复制值(由指针提供给您)将导致难以发现错误,因为复制的值最终可能会写入或读取原始值的指针类型。

    【讨论】:

      猜你喜欢
      • 2023-01-25
      • 2011-03-21
      • 1970-01-01
      • 1970-01-01
      • 2019-02-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多