Структури¶
Структура (struct) — це типізований набір іменованих полів, склеєних в одне значення. Це основний інструмент Go для моделювання «речі з кількома атрибутами» — точки, користувача, HTTP-запиту. Немає ні класів, ні успадкування, ні ключового слова для конструктора: структура — це просто дані, розкладені в пам'яті, а поведінку навколо них ви будуєте окремо (за допомогою функцій та методів).
Майже завжди структурі дають ім'я через ключове слово type з
власних типів:
Це оголошує новий тип Point, значення якого несуть два поля типу int,
X та Y. Поля одного типу можна записати в один рядок:
Створення значень структури¶
Створити Point можна кількома способами, і різниця між ними має
значення.
Літерал з ключами — називаємо поля. Саме цю форму варто використовувати майже завжди: вона не залежить від порядку й переживе додавання нового поля кимось пізніше.
Позиційний літерал — значення в порядку оголошення полів, без імен. Крихкий: ламається, щойно структура отримає нове поле або поля переставлять місцями, і він вимагає значення для кожного поля.
Нульове значення — оголошуємо без ініціалізації, і кожне поле отримує
нульове значення свого типу (0, "", nil, false, …). У структури
немає окремого «неініціалізованого» стану; нульова структура — це
повноцінне, придатне до використання значення.
У літералі з ключами можна пропускати поля; ті, що ви опустили, набувають нульового значення:
З погляду Python: немає
__init__. Нульове значення і є вашим конструктором за замовчуванням. Коли нуль не є розумним значенням за замовчуванням, за домовленістю використовують звичайну функцію з ім'ямNewPoint(...) Point— звичайну функцію, а не спеціальний синтаксис.
Читання та запис полів¶
Крапкова нотація, і поля є адресовними, тож їм можна присвоювати значення напряму:
Структури — це типи-значення¶
Присвоєння структури, передавання її у функцію чи повернення з функції копіює кожне поле. Копія незалежна від оригіналу.
a := Point{X: 1, Y: 2}
b := a // повна копія
b.X = 99
fmt.Println(a.X) // output: 1 — a лишається незмінною
Це найважливіше, що потрібно засвоїти. Якщо ви хочете, щоб функція змінювала структуру викликача, передавайте вказівник:
func moveRight(p *Point) {
p.X++ // p.X — це скорочення для (*p).X — Go розіменовує автоматично
}
a := Point{X: 1, Y: 2}
moveRight(&a)
fmt.Println(a.X) // output: 2
Зверніть увагу на p.X для *Point: Go автоматично розіменовує вказівник
на структуру для доступу до поля, тож вам ніколи не доводиться писати
(*p).X. Базове правило дивіться у вказівниках.
Порівняння структур¶
Структуру можна порівнювати через ==, якщо всі її поля є
порівнюваними. Порівняння відбувається поле за полем.
Це також робить порівнювані структури придатними для використання як
ключі map. Але якщо структура містить непорівнюване поле — slice, map чи
функцію — вся структура стає непорівнюваною, і == є помилкою
компіляції:
type Bag struct {
items []int
}
b1 := Bag{}
b2 := Bag{}
_ = b1 == b2 // compile error: struct containing []int cannot be compared
Вкладені та вбудовані структури¶
Поле саме може бути структурою:
type Line struct {
Start Point
End Point
}
l := Line{
Start: Point{0, 0},
End: Point{3, 4},
}
fmt.Println(l.End.Y) // output: 4
Якщо оголосити поле без імені — лише тип — таке поле є вбудованим (embedded), а його поля підвищуються (promoted), тож до них можна звертатися напряму:
type Circle struct {
Point // вбудоване: без імені поля, лише тип
Radius int
}
c := Circle{Point: Point{X: 1, Y: 2}, Radius: 5}
fmt.Println(c.X) // output: 1 — підвищене з вбудованого Point
fmt.Println(c.Point.Y) // output: 2 — явний шлях теж працює
Вбудовування — це механізм композиції в Go; він заступає той бік успадкування, що стосується даних, у інших мовах. Бік методів у вбудовуванні (підвищення методів) розглядається в методах.
Анонімні структури¶
Можна створити значення структури, так і не оголосивши іменований тип.
Зручно для одноразового групування — рядка таблиці тестів, швидкої форми
JSON — де type верхнього рівня був би зайвим шумом.
config := struct {
Host string
Port int
}{
Host: "localhost",
Port: 8080,
}
fmt.Println(config.Host, config.Port) // output: localhost 8080
Теги структур¶
Кожне поле може нести тег: сирий рядковий літерал після типу. Теги —
це метадані: компілятор їх ігнорує, але бібліотеки читають їх під час
виконання через рефлексію. Канонічне застосування — керування тим, як
encoding/json називає поля:
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
}
u := User{Name: "Ada"}
b, _ := json.Marshal(u)
fmt.Println(string(b)) // output: {"name":"Ada"}
Тут Email відкидається через omitempty і його нульове (порожнє)
значення. Без тегів ключами були б "Name" та "Email" — імена полів
Go. Теги за домовленістю — це пари key:"value" в зворотних лапках;
кілька ключів розділяються пробілами.
Порожня структура struct{}¶
Структура без полів займає нуль байтів. Вона не несе жодних даних — її використовують суто як сигнал. Два поширені застосування — це множина (map, значення якої вас не цікавлять) та канал, що сигналізує «подія сталася», не надсилаючи корисного навантаження:
seen := map[string]struct{}{}
seen["go"] = struct{}{}
_, ok := seen["go"]
fmt.Println(ok) // output: true
struct{}{} спершу читається дивно: внутрішнє struct{} — це тип
(порожня структура), зовнішнє {} — це літерал (значення цього типу).
Швидка довідка¶
| Форма | Значення |
|---|---|
type T struct { X, Y int } |
оголосити іменований тип-структуру |
T{X: 1, Y: 2} |
літерал з ключами (бажано) |
T{1, 2} |
позиційний літерал (прив'язаний до порядку) |
var t T |
нульове значення — усі поля обнулені |
t.X |
доступ до поля (авто-розіменування через *T) |
a == b |
поле за полем, лише якщо всі поля порівнювані |
| вбудоване поле (тип без імені) | підвищує внутрішні поля |
`json:"name"` |
тег поля, який бібліотеки читають через рефлексію |
struct{}{} |
значення порожньої структури на нуль байтів |
Джерела¶
- Struct types — go.dev/ref/spec#Struct_types
- Composite literals — go.dev/ref/spec#Composite_literals
- Comparison operators — go.dev/ref/spec#Comparison_operators
- Struct tags — pkg.go.dev/reflect#StructTag
- encoding/json#Marshal — pkg.go.dev/encoding/json#Marshal
- Effective Go: embedding — go.dev/doc/effective_go#embedding