Структуры и методы
Структуры и методы в Go — объявление структур, методы, value- и pointer-получатели, встраивание как композиция и идиома конструктора.
8 вопросов
JuniorТеорияОчень частоЧто такое struct в Go?
Что такое struct в Go?
struct — это составной тип, группирующий именованные поля, каждое со своим типом. Это значимый тип: присваивание или передача копирует все поля. struct поддерживает встраивание (композицию), методы и теги полей. Его нулевое значение имеет каждое поле в собственном нулевом значении, а две struct сравнимы через ==, когда все их поля сравнимы.
Типичные ошибки
- ✗Думать, что struct — ссылочный тип: присваивание или передача копирует все поля
- ✗Считать, что struct не может иметь методы или встраивать другие типы
- ✗Полагать, что любая struct сравнима через
==независимо от типов её полей
Уточняющие вопросы
- →Чем встраивание отличается от наследования в других языках?
- →Какие типы полей делают struct несравнимой и почему?
JuniorТеорияЧастоКак Go инициализирует struct без конструкторов?
Как Go инициализирует struct без конструкторов?
В Go нет конструкторов. Идиома — это обычная фабричная функция, по соглашению NewT(...), которая принимает входные данные, проверяет их и возвращает инициализированный *T или T. Это рядовая функция, а не особый синтаксис — ничто не заставляет вызывающих её использовать. Часто нулевое значение struct уже пригодно к работе, так что конструктор вообще не нужен; NewT пишут лишь тогда, когда при создании надо проверить входы или задать ненулевые умолчания.
Типичные ошибки
- ✗Считать, что Go автоматически запускает метод-конструктор
New/Init - ✗Думать, что нулевое значение никогда не пригодно и всегда нужна фабрика
- ✗Полагать, что вызывающих заставляют использовать
NewT, а не голый литерал
Уточняющие вопросы
- →Когда нулевое значение struct достаточно пригодно, чтобы обойтись без конструктора?
- →Почему фабрика
NewTвозвращает*T, а неT?
JuniorТеорияЧастоЧто выведет программа?
Что выведет программа?
1 9
Открыть задачу →Типичные ошибки
- ✗Ждать, что
qтоже изменится:q := pкопирует все поля структуры, поэтомуq— отдельнаяPoint, у которойXостаётся1. - ✗Считать, что
r.X = 9требует(*r).X: Go сам разыменовывает указатель на структуру, поэтомуr.Xпишет в поле самойp.
Уточняющие вопросы
- →Что напечатает
fmt.Println(r.X)в конце программы? - →Как передать
Pointв функцию, чтобы функция могла её изменить?
JuniorТеорияЧастоЧто выведет программа?
Что выведет программа?
dog pet
Открыть задачу →Типичные ошибки
- ✗Ждать
dog dog:Say— методAnimal, и егоa.Kind()вызываетAnimal.Kind; встраивание — не наследование, до методаDogдело не доходит. - ✗Ждать
pet petили ошибку компиляции: собственныйKindуDogзатеняет продвинутый, поэтомуDog{}.Kind()вызывает его без конфликта.
Уточняющие вопросы
- →Как всё же добраться до встроенного поля после того, как его затенило собственное?
- →Если два встроенных типа продвигают один и тот же метод, что произойдёт?
JuniorТеорияЧастоЧто выведет программа?
Что выведет программа?
6 5
Открыть задачу →Типичные ошибки
- ✗Ждать ошибку компиляции: имя метода принадлежит его типу, поэтому метод
Rect.Areaи функцияAreaне конфликтуют. - ✗Ждать, что оба вызова попадут в одну функцию:
r.Area()вызывает метод с получателемr, аArea(2, 3)— обычную функцию.
Уточняющие вопросы
- →Почему тип получателя метода должен быть объявлен в том же пакете?
- →Как получатель позволяет вызывать метод как
c.Inc(), а неInc(c)?
JuniorТеорияЧастоЧто выведет программа?
Что выведет программа?
1 1
Открыть задачу →Типичные ошибки
- ✗Ждать
1 2: получатель-значение получает копиюcпри каждом вызове, поэтому каждый вызов увеличивает свою копию с0до1, аc.nостаётся0. - ✗Считать получатель-значение доступным только для чтения: метод вправе менять свою копию, и
return c.nвозвращает её новое значение1.
Уточняющие вопросы
- →Что напечатает программа, если дать
Incполучатель-указатель(c *Counter)? - →Чему равно
c.nпосле двух вызовов в этой программе?
MiddleТеорияЧастоКогда метод должен использовать pointer-получатель вместо value-получателя?
Когда метод должен использовать pointer-получатель вместо value-получателя?
Используйте pointer-получатель, когда метод должен изменять получателя, ведь value-получатель получает лишь копию, и изменение теряется. Используйте его и когда struct большая, чтобы не копировать каждое поле при каждом вызове, или ради единообразия, когда часть методов типа уже требует указателя — смешивать виды получателей не рекомендуется. Value-получатель подходит для маленьких неизменяемых типов, где копия дёшева и безопасна, и оставляет значение пригодным при конкурентном чтении.
Типичные ошибки
- ✗Брать value-получатель для метода, который должен изменять получателя
- ✗Копировать большую struct при каждом вызове вместо взятия указателя
- ✗Непоследовательно смешивать value- и pointer-получатели на одном типе
Уточняющие вопросы
- →Почему смешивать pointer- и value-получатели на одном типе не рекомендуют?
- →Как выбор получателя влияет на то, какой набор методов удовлетворяет интерфейсу?
JuniorКодИногдаПочему мутация в этом методе не сохраняется, и как это исправить?
Почему мутация в этом методе не сохраняется, и как это исправить?
Печатает foo. Вызов метода — это сахар для передачи получателя аргументом, а у Set значимый получатель, поэтому b — копия: b.Value = s меняет копию, а оригинал не трогается. Даже передача &bs всё равно напечатает foo, ведь Set копирует значение в любом случае. Исправление: сделать Set с указательным получателем, func (b *BasicStruct) Set(s string), и передавать &bs.
Типичные ошибки
- ✗Думать, что значимый получатель меняет структуру вызывающего, а не копию
- ✗Считать, что одна лишь передача
&bsчинит это без смены получателя - ✗Полагать, что код не компилируется, а не просто печатает неизменённое значение
Уточняющие вопросы
- →Почему метод со значимым получателем входит в набор методов и
T, и*T? - →После смены
Setна указательный получатель, почему надо передавать&bs, а неbs?