Если resp - указатель на объект ответа, почему мы не должны использовать resp * для доступа к его значению? - PullRequest
0 голосов
/ 16 января 2020

Я недавно начал изучать Golang и сопроводительную документацию. В документации Golang net / http метод Get:

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

Насколько я понимаю, этот метод возвращает указатель на объект ответа или объект ошибки (должен произошла ошибка). Если 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) вместо этого? В документации много других подобных примеров. Я думал, что понял указатели, но я явно что-то упускаю. Любая помощь в разъяснении этого, безусловно, будет оценена.

1 Ответ

1 голос
/ 16 января 2020

Если resp является указателем на объект ответа, почему к самому [объекту] можно обратиться, используя [fmt.Println(resp)] ... Разве это не должно быть fmt.Println(*resp) вместо этого?

Если вы отправите на 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)
}

При запуске ( попробуйте на 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{}, проходит следующий процесс:

  1. Проверьте тип значения. Это указатель? Если это так, помните, что это был указатель. Напечатайте <nil> и больше не go, если это нулевой указатель; в противном случае получите объект, на который указывает указатель.
  2. Теперь, когда это не указатель: какой тип имеет значение? Если это тип struct, распечатайте его имя типа (%#v) или нет (%v), с префиксом &, если за шагом 1 следует указатель, а затем открывающая фигурная скобка и список значений вещи внутри структуры, а затем закрывающая скобка для завершения всего.

    При использовании %#v выведите имена полей и напечатайте значения в формате, подходящем для использования в качестве исходного кода Go , В противном случае просто напечатайте содержимое строк и int с и т. Д.

Другие типы указателей не всегда обрабатываются одинаково! Например, добавьте переменную int, установите для нее какое-либо значение и вызовите fmt.Println(&i, i). Обратите внимание, что в этот раз вы получите не &42 42 или что-то в этом роде, а 0x40e050 42 или что-то в этом роде. Попробуйте это с fmt.Printf и %#v. Поэтому вывод зависит от типа и глагола форматирования.

Если вы вызываете функции, которые должны изменить их объекты (например, семейство scan в fmt), вы должен передать указатель, так как им нужен доступ к объектам для их изменения.

Каждая функция, которая может принимать значения неограниченных типов interface{} (включая все в Print* и Scan* семья здесь) должны документировать, что они делают с каждым фактическим типом. Если они говорят, как это делает семья Print*, то, когда они получают указатель на тип структуры, они следуют за указателем (если не ноль), что позволяет вам знать, что вы можете отправить указатель вместо объекта.

(Некоторые функции в некоторых библиотеках виновны в недостаточном документировании того, что они делают, и вам приходится экспериментировать. В общем, это не очень хорошая ситуация, потому что результаты эксперимента могут быть случайностью текущей реализации, а не обещанное поведение, которое не изменится в будущем. Это одна из причин, по которой стоит позаботиться о использовании interface{}: это означает, что вам нужно написать много документации.)

...