【问题标题】:I don't understand when to use pointers on go [duplicate]我不明白何时使用指针 [重复]
【发布时间】:2021-06-28 20:28:09
【问题描述】:

我正在游览go语言,我有一个关于指针的问题。

示例代码(https://tour.golang.org/methods/19):

package main

import (
    "fmt"
    "time"
)

type MyError struct {
    When time.Time
    What string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("at %v, %s",
        e.When, e.What)
}

func run() error {
    return &MyError{
        time.Now(),
        "it didn't work",
    }
}

func main() {
    if err := run(); err != nil {
        fmt.Println(err)
    }
}

在这种情况下,它使用 *MyError 和 &MyError,但我尝试删除 * 和 & 并且它可以正常工作。为什么他们在这个例子中使用指针?与正常变量有什么区别?什么时候应该使用指针?

【问题讨论】:

    标签: go


    【解决方案1】:

    “我什么时候应该使用指针?”是一个非常大的问题,没有简单的答案。指针是一种传递对值的引用而不是值本身的方法,允许您修改原始值或“查看”对该值的修改。它还可以防止复制,这可以在非常有限的情况下提高性能(不要一直传递指针,因为它可能会提高性能)。最后,指针还可以让您表示“虚无”,每个指针都可以是nil。这既是福也是祸,因为您必须在访问它之前检查每个指针是否为nil

    在您的具体示例中,返回&MyError 起作用的原因是因为您的Error() 函数对*MyError 的值(指向MyError 的指针)而不是MyError 本身的值进行操作.这意味着*MyError 实现了Error 接口,因此可以分配给error 类型,因此它可以从任何期望error 作为返回值的函数返回。

    返回MyError 不会单独工作,因为MyError 不是*MyError。 Go 在处理函数接收者时做了一些有用的事情:如果接收者是 *MyError,它将允许您调用 MyError*MyError 上的任何方法,但它允许您调用方法如果类型是 *MyError,则在 *MyError 上 - 也就是说,Go 不会凭空为您“创建”指针。

    如果你要从 func (e* MyError) 中删除 *,你会告诉 Go Error() 适用于 MyError 的任何实例,这意味着 *MyErrorMyError 都将履行该合同.这就是为什么当你不使用指针接收器时以下两个都有效的原因:

    func (e MyError) Error() string {}
    
    var _ error = MyError{} // Valid
    var _ error = &MyError {}
    

    【讨论】:

      【解决方案2】:

      在这种特殊情况下,使用指针不会产生影响。这是看待它的一种方式:

      在 Go 中,所有变量都是按值传递的。这意味着:

      type T struct {...}
      
      func f(value T) {..}
      
      f(t)
      

      在上面,t 作为值传递。这意味着当调用f 时,编译器会创建t 的副本并将其传递给ff 对该副本所做的任何修改都不会影响用于调用ft

      如果你使用指针:

      func f(value *T) {...}
      
      f(&t)
      

      在上面,编译器将创建一个指向t 的指针,并将其副本传递给f。如果fvalue 进行更改,这些更改将在用于调用ft 实例上进行。换句话说:

      type T struct {
        x int
      }
      
      func f(value T) {
         value.x=1
      }
      
      func main() {
         t:=T{}
         f(t)
         fmt.Println(t.x)
      }
      

      这将打印 0,因为 f 所做的修改是在 t 的副本上完成的。

      func f(value *T) {
         value.x=1
      }
      
      func main() {
         t:=T{}
         f(&t)
         fmt.Println(t.x)
      }
      

      上面,它将打印 1,因为对 f 的调用更改了 t

      同样的想法也适用于方法和接收器:

      type T struct {
         x int
      }
      
      func (t T) f() {
         t.x=1
      }
      
      func main() {
         t:=T{}
         t.f()
         fmt.Println(t.x)
      }
      

      上面的程序会打印0,因为该方法修改了t的副本。

      func (t *T) f() {
         t.x=1
      }
      
      func main() {
         t:=T{}
         t.f()
         fmt.Println(t.x)
      }
      

      上面的程序会打印1,因为方法的接收者是用指针声明的,调用t.f()相当于f(&t)

      因此,如果您想修改对象,或者如果复制对象成本太高,请在传递参数或声明方法时使用指针。

      这只是关于指针参数的一小部分。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-04-25
        • 2018-07-21
        • 2019-06-24
        • 2016-03-31
        • 1970-01-01
        • 2016-02-29
        • 2022-01-15
        相关资源
        最近更新 更多