В чем разница между передачей значения или передачей адреса в интерфейс в Go?

# #arrays #go #pointers #interface

Вопрос:

Допустим, у нас есть простой интерфейс и реализация:

 type Vertex struct {
    X int
    Y int
}

type Abser interface {
    Abs() float64
}

func (v Vertex) Abs() float64 {
    sum := float64(v.X   v.Y)
    return math.Sqrt(sum)
}
 

Теперь у меня есть переменная интерфейса:

 var abser Abser
 

Я хочу установить для него вершину. Я могу установить значение одного или адрес одного:

 v := Vertex{1, 1}
abser = v
abser = amp;v
 

В чем разница между ними двумя? Почему установка адреса вершины работает? Как это связано с тем, как интерфейсы работают под капотом? Я все еще новичок, так что буду очень признателен за любую помощь 🙏

Комментарии:

1. вы можете присвоить интерфейсу значение любого типа, которое его реализует. Набор методов *Vertex включает методы, объявленные для типа без указателя Vertex , как указано в спецификациях .

Ответ №1:

В системе типов Go метод, определенный с использованием приемника значений для типа T , определяется для обоих T и *T , то есть:

 func (v Vertex) Abs() float64 {...}
 

Это работает как для v и. *v Сам метод всегда получает копию v .

Метод, определенный для *T , определяется только для *T , а не для T . То есть:

 func (v *Vertex) SetX(newx float64) {v.X=newx}
 

Метод SetX будет работать только в том случае, если получатель адресуем. Это необходимо для того, чтобы вы не писали код, который теряет данные. Например:

 m:=map[string]Vertex{}
m["a"]=Vertex{}
m["a"].SetX(1) // This fails! m["a"] is not addressable
 

Если бы описанный выше случай не удался, то SetX была бы установлена копия m["a"] , и эффекты были бы потеряны, потому что обновленная копия не помещается обратно на карту.

Возвращаясь к интерфейсам: ваш Abser интерфейс реализован любым типом, который реализует Abs() float64 . Основываясь на приведенном выше обсуждении, как Vertex и *Vertex реализуйте Abser .

Допустим, вы определили Vertex.Abs как:

 func (v *Vertex) Abs() float64 {...}
 

Затем, только *Vertex реализует Abser , так что :

 abser = v // This would fail
abser = amp;v // This would work
 

Ответ №2:

Во-первых, в Go все передается по значению.

Указатели передаются по значению — используется копия указателя.

Срезы передаются по значению — копируется указатель на базовый массив, целочисленное значение длины и целочисленное значение емкости.

Интерфейсы также передаются по значению — используется копия значения интерфейса.

Это поможет, если вы подумаете об этом таким образом.

Теперь давайте сосредоточимся на интерфейсах:

Значение интерфейса состоит из двух частей:

  1. Указатель на таблицу интерфейса
  2. Указатель на фактическое значение

Поэтому, когда вы передаете интерфейс, вы, по сути, копируете два указателя.

Теперь, чтобы тип удовлетворял интерфейсу, он должен иметь методы, необходимые для интерфейса. Если какой-либо из методов использует приемник указателя (как func (v *Vertex) Abs() float64 {...} в вашем примере), вам понадобится указатель на значение, чтобы удовлетворить интерфейс, и именно поэтому вам нужно взять его адрес.

Допустим, ни один из методов не требовал приемника указателя (скажем func (v Vertex) Abs() float64 {...} ). Тогда вам не нужно будет указывать адрес, потому что вы просто передадите его, не взяв его адрес.

Я надеюсь, что это ответ на ваш вопрос.

Комментарии:

1. «в Go все передается по значению». Срез-это ссылочный тип, поэтому я не думаю, что этот вывод верен. Подробнее читайте здесь

2. В противном случае, этот ответ хорош.

3. На самом деле срезы тоже передаются по значению. Как и интерфейсы, срезы состоят из частей — указателя на данные, длину и емкость. Когда вы передаете срез, эти 3 части копируются — один указатель на базовый массив и 2 целых числа, как указано на странице, на которую вы ссылаетесь.