Структура в Go представляет собой тип данных с заданным набором атрибутов (полей), использующийся для описания составных объектов. Структура имеет близкие аналоги в других языках программирования:
- C —
struct
; - С++ —
class
,struct
; - Python —
class
,tuple
; - PHP —
class
; - Lua —
table
.
Посмотрите, как выглядит описание типа Person
:
Здесь описан тип структуры, отображающий информацию о некотором человеке: его имя, электронную почту, дату рождения. Поля структуры могут быть любого типа, доступного в языке.
Могут быть и указателями на саму структуру. Классический пример — структура данных «дерево»:
Инициализация
Существует несколько подходов к созданию экземпляра объекта.
1. Пустой объект
Все поля структуры при таком подходе принимают значения по умолчанию.
Подход применяют:
- когда экземпляр не требует специальной инициализации и может быть использован дальше по коду;
- когда для инициализации полей нужны дополнительные условия и данные, то есть выставление значений конкретных полей будет следовать ниже по коду.
2. Неявное указание значений полей
При таком подходе перечисляют значения для всех полей структуры, используя литералы либо значения внешних переменных.
Требования:
- Нужно перечислить все поля объекта.
- Порядок следования аргументов инициализатора должен совпадать с порядком описания полей структуры. Если поставить поле
Email
на первое место в описанииtype Person struct
, инициализация экземпляра выше будет некорректна (с точки зрения логики, но не компилятора).
Подход применяют:
- когда нужно явно указать значения всех полей объекта;
- когда вы уверены, что спецификация типа не будет меняться часто, иначе придётся вносить правки для каждого инициализатора объекта в коде.
3. Явное указание значений полей
При таком подходе явно указывают имена полей и их значения.
Особенности:
- этот подход отличается от первого опциональным указанием полей;
- порядок указания полей не важен;
- значения полей, которые не были использованы в инициализаторе (
dateOfBirth
в примере), примут значения по умолчанию.
Для повышения читабельности кода такую инициализацию часто описывают в несколько строк, что справедливо и для второго подхода:
p := Person{
Name: "Иван",
Email: "ivan@yandex.ru",
}
Note
Обратите внимание, что последняя строка в многострочной записи литерала тоже заканчивается запятой. Это делается для того, чтобы можно было вставлять и удалять строки, не заботясь о запятой в конце.
Подход применяют:
- почти всегда, так как он лишён ограничений, описанных выше.
На практике обычно применяется явное указание имён, потому что оно снижает количество возможных ошибок.
4. Конструктор
Учитывая тонкости при инициализации сложного объекта, разработчики применяют конструкторы.
В Go нет синтаксиса конструкторов и деструкторов, но часто можно встретить аналог:
Вот некоторые правила, одобренные Go-сообществом:
- имя функции конструктора пишут с префиксом
New
; - если конструктор производит валидацию аргументов, функция должна возвращать ошибку последним аргументом.
Можем вернуться к нашему примеру, чтобы добавить проверку корректности email
и числовых компонент даты, тогда декларация функции примет вид:
Подход применяют:
- когда нужно производить валидацию аргументов, чтобы построить логически правильный объект;
- когда построение экземпляра объекта требует дополнительных действий, например, подключения к базе данных.
Note
Пример выше не идеален, так как изменение спецификации
Person
потребует изменения прототипа конструктора или создания новой версии (скажем,NewPersonWithPhone()
). Идиоматичные Go-подходы к созданию объектов рассмотрим в следующих темах.
Доступ к полям
Для доступа к полям структуры используется точка (p.Name
):
Иван ivan@yandex.ru
Пётр
Область видимости
Как вы уже знаете, в Go есть понятия экспортируемых и неэкспортируемых типов. Код разделяется на пакеты, и, чтобы тип, функция или глобальная переменная были доступны в другом пакете, их имена должны начинаться с заглавной буквы. Это же правило работает для полей и методов структуры.
В примере выше Person
— экспортируемый тип (публичный). Другие пакеты могут создавать экземпляры этого типа и иметь доступ к публичным полям Name
и Email
. А поле dateOfBirth
— неэкспортируемое (приватное).
Note
Неэкспортируемый тип можно использовать в другом пакете, если есть соответствующий конструктор типа и экспортируемая функция конструктора. Такой трюк встречается в Go-коде, однако чаще всего для сокрытия реализации используют интерфейсы.
Приведём пример экспортирования приватного типа:
some data
Теги
У каждого поля структуры может быть набор аннотаций, которые называются тегами (tags):
Теги не влияют на представление или работу с данными напрямую, но могут использоваться пакетами для получения дополнительной информации о конкретном поле.
Набор тегов с их значениями можно представить как набор ключей и значений, где ключи разделяются пробелами, а значения ключей — запятой.
В примере выше встречаются следующие теги:
json
— используется пакетом encoding/json для сериализации/десерилизации структур в JSON;yaml
— похож наjson
, но используется внешними библиотеками для работы с форматом YAML;format
иexample
— могут быть как подсказкой для разработчика, так и аннотацией для генерации Swagger-описания (к примеру, библиотекой swag).
Применяемые аннотации чаще всего зависят от используемой библиотеки. Возможные ключи и значения стоит искать в документации пакета (в худшем случае — в коде).
Разработчик может вводить свои теги и работать с ними через пакет reflect
стандартной библиотеки.
Пример
Для сериализации используется функция json.Marshal()
пакета json
. Дана структура:
Напишите код, который будет сериализовывать структуру в json
-строку следующего вида:
Решение:
Анонимные структуры
Анонимные структуры объявляются и используются непосредственно в коде. Отдельный тип для них не описывают, потому что анонимные структуры применяются однократно, и описание имеет смысл только для конкретной части кода: например, при сериализации/десериализации сообщений. Чаще всего анонимные структуры используют в тестах для описания тестовых структур.
Проще всего понять концепцию анонимных структур из следующего соображения:
Конструкция type Person ...
на самом деле не описывает, а создаёт тип на основе существующего и называет его. То есть по факту тип создаёт именно конструкция struct{}
.
Получив такой анонимный тип, можно сразу же создать переменную этого типа.
Приведём пример использования анонимной структуры при построении REST-запроса:
Здесь мы описали анонимную структуру, инициализировали её экземпляр, произвели JSON-сериализацию и вывели результат в виде строки.
struct{}
0
0x11d46e8
Размер struct{}
равен 0
, при этом объект c
имеет адрес. Такую лазейку можно использовать для оптимизации кода по памяти, а в дальнейшем разберём это на практике.
Встраивание структур (struct embedding)
Go supports embedding of structs and interfaces to express a more seamless composition of types. This is not to be confused with //go:embed
which is a go directive introduced in Go version 1.16+ to embed files and folders into the application binary.
Reference: https://gobyexample.com/struct-embedding
Сравнение структур и их аналогов в популярных языках программирования
В таблице приведены особенности структур языка Go в сравнении с другими языками.
Пример: Реверс-инжиниринг JSON
Есть пример API-вызова в формате JSON:
На входе есть строка с сырыми данными, требуется написать функцию её десериализации:
Решение:
📂 Go | Последнее изменение: 11.11.2024 20:26