如果 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
:
- 注意到这是一个指针,然后
- 跟随指针访问底层对象,
然后 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}
也就是说,您使用 %v
或 fmt.Println
获得的默认格式打印:
{bob 42}
(对象的副本)或:
&{bob 42}
(指向对象的指针)。使用 %#v
获得的替代格式添加了类型,因此您要么得到:
main.T{Name:"bob", Value:42}
(对象的副本)或:
&main.T{Name:"bob", Value:42}
我们在这里看到的是fmt.Println
,它取一个interface{}
值,经过以下过程:
检查值的类型。它是一个指针吗?如果是这样,请记住它是一个指针。打印 <nil>
并且如果它是一个 nil 指针就不要再往前走;否则,获取指针指向的对象。
既然它不是指针:值有什么类型?如果是 struct
类型,则打印出它的类型名称(%#v
)或不打印(%v
),如果步骤 1 跟随指针,则以 &
为前缀,然后打开大括号和结构内部事物值的列表,然后是一个右括号来结束整个事物。
当使用 %#v
时,以适合用作 Go 源代码的格式打印字段名称和值。否则,只打印字符串和 int
s 等的内容。
其他指针类型并不总是得到相同的处理!例如,添加一个 int
变量,将其设置为某个值,然后调用 fmt.Println(&i, i)
。请注意,这次您没有得到 &42 42
或类似的东西,而是 0x40e050 42
或类似的东西。用 fmt.Printf
和 %#v
试试这个。所以输出取决于类型和格式化动词。
如果您调用 必须 修改其对象的函数(例如 fmt
中的 scan
系列),您 必须 传递一个指针,因为他们需要访问对象才能修改它们。
每个可以采用无约束 interface{}
类型值的函数(包括这里的 Print*
和 Scan*
系列中的所有内容)都必须记录它们对每种实际类型所做的操作。如果他们说,就像 Print*
家族所做的那样,当给定一个指向结构类型的指针时,他们会跟随指针(如果不是 nil),这让你知道你可以发送指针而不是对象。
(某些库中的某些函数在记录它们所做的事情时犯了错误,您必须进行实验。这通常不是一个好情况,因为实验的结果可能是当前实现的意外,而不是未来不会改变的承诺行为。这是谨慎使用 interface{}
的原因之一:这意味着您必须编写大量文档。)
我最近开始研究 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 befmt.Println(*resp)
instead?
如果您向 fmt.Println
发送一个指向对象的指针,fmt.Println
可以使用该指针到达对象本身(即访问它——甚至修改它,但是 fmt.Println
不修改它)。
如果您向 fmt.Println
发送对象的 副本,fmt.Println
可以使用该对象的副本,即访问它(并且不能修改原文)。
所以从这个意义上说,给 fmt.Println
指针值严格来说比传递对象的副本更强大,因为它可以修改对象。 fmt
代码不 使用 这种能力,但它存在于您也可以传递指针的任何其他地方。但是只要fmt.Println
:
- 注意到这是一个指针,然后
- 跟随指针访问底层对象,
然后 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}
也就是说,您使用 %v
或 fmt.Println
获得的默认格式打印:
{bob 42}
(对象的副本)或:
&{bob 42}
(指向对象的指针)。使用 %#v
获得的替代格式添加了类型,因此您要么得到:
main.T{Name:"bob", Value:42}
(对象的副本)或:
&main.T{Name:"bob", Value:42}
我们在这里看到的是fmt.Println
,它取一个interface{}
值,经过以下过程:
检查值的类型。它是一个指针吗?如果是这样,请记住它是一个指针。打印
<nil>
并且如果它是一个 nil 指针就不要再往前走;否则,获取指针指向的对象。既然它不是指针:值有什么类型?如果是
struct
类型,则打印出它的类型名称(%#v
)或不打印(%v
),如果步骤 1 跟随指针,则以&
为前缀,然后打开大括号和结构内部事物值的列表,然后是一个右括号来结束整个事物。当使用
%#v
时,以适合用作 Go 源代码的格式打印字段名称和值。否则,只打印字符串和int
s 等的内容。
其他指针类型并不总是得到相同的处理!例如,添加一个 int
变量,将其设置为某个值,然后调用 fmt.Println(&i, i)
。请注意,这次您没有得到 &42 42
或类似的东西,而是 0x40e050 42
或类似的东西。用 fmt.Printf
和 %#v
试试这个。所以输出取决于类型和格式化动词。
如果您调用 必须 修改其对象的函数(例如 fmt
中的 scan
系列),您 必须 传递一个指针,因为他们需要访问对象才能修改它们。
每个可以采用无约束 interface{}
类型值的函数(包括这里的 Print*
和 Scan*
系列中的所有内容)都必须记录它们对每种实际类型所做的操作。如果他们说,就像 Print*
家族所做的那样,当给定一个指向结构类型的指针时,他们会跟随指针(如果不是 nil),这让你知道你可以发送指针而不是对象。
(某些库中的某些函数在记录它们所做的事情时犯了错误,您必须进行实验。这通常不是一个好情况,因为实验的结果可能是当前实现的意外,而不是未来不会改变的承诺行为。这是谨慎使用 interface{}
的原因之一:这意味着您必须编写大量文档。)