# #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 все передается по значению.
Указатели передаются по значению — используется копия указателя.
Срезы передаются по значению — копируется указатель на базовый массив, целочисленное значение длины и целочисленное значение емкости.
Интерфейсы также передаются по значению — используется копия значения интерфейса.
Это поможет, если вы подумаете об этом таким образом.
Теперь давайте сосредоточимся на интерфейсах:
Значение интерфейса состоит из двух частей:
- Указатель на таблицу интерфейса
- Указатель на фактическое значение
Поэтому, когда вы передаете интерфейс, вы, по сути, копируете два указателя.
Теперь, чтобы тип удовлетворял интерфейсу, он должен иметь методы, необходимые для интерфейса. Если какой-либо из методов использует приемник указателя (как func (v *Vertex) Abs() float64 {...}
в вашем примере), вам понадобится указатель на значение, чтобы удовлетворить интерфейс, и именно поэтому вам нужно взять его адрес.
Допустим, ни один из методов не требовал приемника указателя (скажем func (v Vertex) Abs() float64 {...}
). Тогда вам не нужно будет указывать адрес, потому что вы просто передадите его, не взяв его адрес.
Я надеюсь, что это ответ на ваш вопрос.
Комментарии:
1. «в Go все передается по значению». Срез-это ссылочный тип, поэтому я не думаю, что этот вывод верен. Подробнее читайте здесь
2. В противном случае, этот ответ хорош.
3. На самом деле срезы тоже передаются по значению. Как и интерфейсы, срезы состоят из частей — указателя на данные, длину и емкость. Когда вы передаете срез, эти 3 части копируются — один указатель на базовый массив и 2 целых числа, как указано на странице, на которую вы ссылаетесь.