Структуры
Чему вы научитесь:
- объявлять свой тип-структуру и работать с его полями;
- добавлять типу методы и выбирать между получателем-значением и получателем-указателем;
- писать конструктор и решать, какие имена видны другим пакетам;
- встраивать один тип в другой и отличать встраивание от наследования.

Представьте бумажную анкету. В ней есть графы «Имя», «Возраст», «Город», и у каждой графы своя подпись. Всё, что вы о себе написали, лежит на одном листе. Анкету можно отдать целиком, а не диктовать каждую графу по отдельности.
Без такой анкеты связанные значения расползаются по разным переменным. Точка на плоскости — это два числа, и их можно хранить в двух переменных x и y. Но тогда возникают неудобства:
- функция, которая принимает точку, берёт два параметра;
- список точек превращается в два среза, которые нужно держать в согласии.
В Go связанные значения собирают в один тип:
package main
import "fmt"
type Point struct {
X int
Y int
}
func main() {
p := Point{X: 1, Y: 2}
p.X = 10
fmt.Println(p, p.X, p.Y) // {10 2} 10 2
}Давайте разберём пример по строкам:
type Point structописывает бланк анкеты с графамиXиY.p := Point{X: 1, Y: 2}заполняет такую анкету: вXпишет1, вY—2.p.X = 10исправляет графуX.fmt.Println(p, p.X, p.Y)печатает всю структуру как{10 2}, а затем каждое поле отдельно.
Объявление типа
Структура — тип, который хранит несколько значений под одним именем. Каждое такое значение — поле со своим именем и типом. Объявление type Point struct { … } состоит из частей:
type— ключевое слово: «объявляю новый тип»;Point— имя типа;struct— вид типа: набор полей;X intиY intв фигурных скобках — поля, по одному в строке: имя, затем тип.
Поля одного типа можно перечислить через запятую: struct{ X, Y int }. К полю обращаются через точку: p.X читает его, а p.X = 10 записывает.
Здесь картинка с анкетой расходится с Go. На бумаге можно дописать графу на полях, а у структуры набор полей раз и навсегда задаёт её тип.
Литерал структуры
Значение структуры создают одним из трёх способов. Давайте разберём каждый.
Литерал с именами полей
Запись Point{X: 1, Y: 2} называет каждое поле и его значение через двоеточие. Порядок полей в такой записи не важен.
Поле, которого нет в литерале, получает нулевое значение — как пустая графа анкеты. Так, Point{X: 1} печатается как {1 0}.
Эту форму пишут чаще всего. Она понятнее и не ломается, когда в тип добавляют новое поле.
Литерал без имён
Запись Point{1, 2} перечисляет значения без имён полей. Она тоже компилируется, но у неё два ограничения:
- нужны все поля, и строго в порядке объявления;
- если пропустить поле,
Point{1}не скомпилируется:too few values in struct literal of type Point.
Когда в тип добавляют новое поле, все такие литералы перестают компилироваться. Поэтому форму без имён оставляют для маленьких типов, которые не меняются.
Объявление через var
Запись var p Point создаёт структуру с нулевыми значениями во всех полях. Такая p печатается как {0 0} — это чистый бланк анкеты.
Присваивание копирует
Структура — такое же значение, как int или массив. Присваивание q := p копирует все поля, и дальше у q своя память — как ксерокопия анкеты, на которой можно писать, не трогая оригинал:
p := Point{X: 1, Y: 2}
q := p
q.X = 9
fmt.Println(p.X, q.X) // 1 9Разберём по строкам:
p := Point{X: 1, Y: 2}создаёт оригинал.q := pснимает с него копию всех полей.q.X = 9пишет только в копию.fmt.Println(p.X, q.X)печатает1 9: оригинал не изменился.
Структуру, переданную в функцию, Go копирует так же: параметр получает свою копию всех полей.
Сравнение структур
Этот вопрос часто задают на собеседовании: «Можно ли сравнить две структуры?» Ответ состоит из трёх частей:
- можно, оператором
==, если сравнимы все поля: он сравнивает их по порядку; - сразу после
q := pвыражениеp == qдаётtrue; - если в структуре есть поле-срез или поле
map, сравнение не компилируется:struct containing []string cannot be compared.
Поле через указатель
Чтобы изменить p через другую переменную, нужен указатель на неё. В теме об указателях это была бумажка с адресом дома — здесь по ней приходят к самой анкете.
Писать (*r).X не обязательно: для указателя на структуру Go сам подставляет разыменование. Запись r.X обращается к полю той структуры, на которую указывает r.
p := Point{X: 1, Y: 2}
r := &p
r.X = 9
fmt.Println(p.X, r.Y) // 9 2Разберём по строкам:
r := &pкладёт вrадресp.r.X = 9меняет полеXв самойp.fmt.Println(p.X, r.Y)печатает9 2:p.Xуже изменено, аr.Yчитает полеYтой жеp.
Частая ошибка
Поля элементов среза меняют через переменную цикла range:
pts := []Point{{1, 2}, {3, 4}}
for _, p := range pts {
p.X = 0 // ✗
}
fmt.Println(pts) // [{1 2} {3 4}]Программа компилируется, но срез не меняется: p получает копию очередного элемента, и запись уходит в эту копию. Чтобы изменить сам элемент, обращайтесь к нему по индексу: pts[i].X = 0 в цикле for i := range pts. Внутри литерала среза имя типа Point у элементов можно не писать.
Главное
- Структура собирает поля с именами и типами в один тип; к полю обращаются через точку.
- Присваивание и передача в функцию копируют все поля структуры.
- Структуры сравнивают через
==, только если сравнимы все их поля. - Через указатель на структуру поле читают и меняют как
r.X, без(*r).X.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.