如果 resp 是指向响应对象的指针,为什么我们不必使用 resp* 来访问它的值?

If resp is a pointer to a response object, why don't we have to use resp* to access its value?

我最近开始研究 Golang 和随附的文档。在 Golang net/http documentation 中,Get 方法是:

func Get(url string) (resp *Response, err error)

据我了解,此方法 returns 指向响应对象或错误对象(如果发生错误)的指针。如果 resp 是指向响应对象的指针,为什么可以使用以下代码访问 resp 值:

func main() {
    resp, err := http.Get("http://google.com")
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(1)
    }
    fmt.Println(resp)
}

不应该是fmt.Println(*resp)吗?整个文档中还有许多其他类似的示例。我以为我理解指针,但我显然遗漏了一些东西。如果能帮助澄清这一点,我们将不胜感激。

If resp is a pointer to a response object, why can the [object itself] be accessed using [fmt.Println(resp)] ... Should it not be fmt.Println(*resp) instead?

如果您向 fmt.Println 发送一个指向对象的指针,fmt.Println 可以使用该指针到达对象本身(即访问它——甚至修改它,但是 fmt.Println 不修改它)。

如果您向 fmt.Println 发送对象的 副本fmt.Println 可以使用该对象的副本,即访问它(并且不能修改原文)。

所以从这个意义上说,给 fmt.Println 指针值严格来说比传递对象的副本更强大,因为它可以修改对象。 fmt 代码不 使用 这种能力,但它存在于您也可以传递指针的任何其他地方。但是只要fmt.Println:

  1. 注意到这是一个指针,然后
  2. 跟随指针访问底层对象,

然后 fmt.Println 可以 在指向对象的指针和对象的副本上以相同的方式 表现。

事实上,fmt.Print* 函数族与对象指针和对象副本的行为方式并不完全相同:

package main

import (
    "fmt"
)

type T struct {
    Name string
    Value int
}

func main() {
    obj := T{Name: "bob", Value: 42}
    fmt.Println(&obj, obj)
    fmt.Printf("%#v %#v\n", &obj, obj)
}

当这是 运行 (try it on the Go Playground) 时,它会打印:

&{bob 42} {bob 42}
&main.T{Name:"bob", Value:42} main.T{Name:"bob", Value:42}

也就是说,您使用 %vfmt.Println 获得的默认格式打印:

{bob 42}

(对象的副本)或:

&{bob 42}

(指向对象的指针)。使用 %#v 获得的替代格式添加了类型,因此您要么得到:

main.T{Name:"bob", Value:42}

(对象的副本)或:

&main.T{Name:"bob", Value:42}

我们在这里看到的是fmt.Println,它取一个interface{}值,经过以下过程:

  1. 检查值的类型。它是一个指针吗?如果是这样,请记住它是一个指针。打印 <nil> 并且如果它是一个 nil 指针就不要再往前走;否则,获取指针指向的对象。

  2. 既然它不是指针:值有什么类型?如果是 struct 类型,则打印出它的类型名称(%#v)或不打印(%v),如果步骤 1 跟随指针,则以 & 为前缀,然后打开大括号和结构内部事物值的列表,然后是一个右括号来结束整个事物。

    当使用 %#v 时,以适合用作 Go 源代码的格式打印字段名称和值。否则,只打印字符串和 ints 等的内容。

其他指针类型并不总是得到相同的处理!例如,添加一个 int 变量,将其设置为某个值,然后调用 fmt.Println(&i, i)。请注意,这次您没有得到 &42 42 或类似的东西,而是 0x40e050 42 或类似的东西。用 fmt.Printf%#v 试试这个。所以输出取决于类型和格式化动词。

如果您调用 必须 修改其对象的函数(例如 fmt 中的 scan 系列),您 必须 传递一个指针,因为他们需要访问对象才能修改它们。

每个可以采用无约束 interface{} 类型值的函数(包括这里的 Print*Scan* 系列中的所有内容)都必须记录它们对每种实际类型所做的操作。如果他们说,就像 Print* 家族所做的那样,当给定一个指向结构类型的指针时,他们会跟随指针(如果不是 nil),这让你知道你可以发送指针而不是对象。

(某些库中的某些函数在记录它们所做的事情时犯了错误,您必须进行实验。这通常不是一个好情况,因为实验的结果可能是当前实现的意外,而不是未来不会改变的承诺行为。这是谨慎使用 interface{} 的原因之一:这意味着您必须编写大量文档。)