После изучения данного урока вы сможете:
- Представить координаты Марса в виде структуры;
- Закодировать структуры в популярный формат данных JSON.
Транспортные средства состоят из множества деталей, у которых могут быть свои значения (или состояние). Двигатель запущен, колеса крутятся, бензина достаточно. Использование отдельных переменных для каждого значения сродни разобранному на складе магазина транспорту. В здании могут быть открытые окна и двери, осталось собрать. Для сбора деталей или создания структуры в Go используется тип structure
.
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
Содержание статьи
- Объявление структуры
- Повторное использование структур с типами
- Инициализация структур через композитные литералы
- Копирование структур
- Пример среза структур
- Кодирование структур в JSON
- Изменение JSON через теги struct
В то время как в коллекции используется один тип, структуры дают возможность группировать элементы разных типов. Осмотритесь вокруг. Какие из ближайших к вам предметов можно представить как структуру?
Объявление структуры в Golang
Пара координат станет хорошим примером для разбора принципа работы структуры. Широта и долгота всегда используются вместе. Функция для подсчета расстояния между двумя локациями потребуют две пары координат:
1 |
func distance(lat1, long1, lat2, long2 float64) float64 |
Хотя это работает, при передачи независимых друг от друга координат высока вероятность возникновения ошибок, к тому же это очень утомительно. Широта и долгота представляют собой одну единицу, и структура позволяет их использовать именно так.
Структура curiosity
в следующем примере объявляется с полями вещественного типа для широты и долготы. Для присваивания значения полю или получения к нему доступа используется запись с точкой, то есть указывается название переменной, затем точка и в конце название поля. Пример показан ниже.
1 2 3 4 5 6 7 8 9 10 11 |
var curiosity struct { lat float64 long float64 } // Присваивание значений полям структуры curiosity.lat = -4.5895 curiosity.long = 137.4417 fmt.Println(curiosity.lat, curiosity.long) // Выводит: -4.5895 137.4417 fmt.Println(curiosity) // Выводит: {-4.5895 137.4417} |
На заметку: Функции семейства
Марсоход Curiosity начал свое путешествие в кратере Брэдбери, координаты которого 4°35’22.2” S, 137°26’30.1” E. В Листинге 1 широта и долгота для кратера Брэдбери представлена в десятичных значениях с положительной северной шириной и положительной восточной долготой, как показано на Фигуре 1.
Фигура 1: Широта и долгота в десятичных значениях
Вопросы для проверки:
- В чем преимущество структур в сравнении с отдельными переменными?
- Кратер Брэдбери расположен на 4400 метра ниже марсианского «уровня моря». Если бы у
curiosity
было поле высоты уровня моря, как можно было бы присвоить ему значение -4400.
Повторное использование структур с типами в Go
Если вам нужно несколько структур с одинаковыми полями, вы можете определить тип наподобие того, как мы это делали с типом celsius
в уроке о методах. Тип location
, объявленный в следующем примере, используется для помещения марсохода Спирит на Мемориальной Станции шаттла Колумбия и марсохода Оппортьюнити на Мемориальной Станции Челленджера.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
type location struct { lat float64 long float64 } var spirit location // Повторное использование типа location spirit.lat = -14.5684 spirit.long = 175.472636 var opportunity location // Повторное использование типа location opportunity.lat = -1.9462 opportunity.long = 354.4734 fmt.Println(spirit, opportunity) // Выводит: {-14.5684 175.472636} {-1.9462 354.4734} |
Вопрос для проверки:
Как можно адаптировать код из Листинга 1 для использования типа location
для марсохода Curiosity в кратере Брэдбери?
Инициализация структур через композитные литералы Golang
Для инициализации структур композитные литералы используются в двух различных формах. В Листинге 3 переменные opportunity
и insight
инициализируются через использование пар «поле-значение». Поля могут значиться в любом порядке, а поля, что не в списке, сохранят нулевое значение для своего типа. Данная форма принимает изменения и продолжит корректную работу даже при добавлении в структуру полей или при изменении порядка полей. Если location
достигает поля высоты уровня моря, то opportunity
и insight
принимают по умолчанию нулевое значение высоты уровня моря.
1 2 3 4 5 6 7 8 9 |
type location struct { lat, long float64 } opportunity := location{lat: -1.9462, long: 354.4734} fmt.Println(opportunity) // Выводит: {-1.9462 354.4734} insight := location{lat: 4.5, long: 135.9} fmt.Println(insight) // Выводит: {4.5 135.9} |
Композитный литерал в Листинге 4 не уточняет названия полей. Вместо этого значение для каждого поля должно предоставляться в том же порядке, в котором они указаны в определении структуры. Данная форма лучше всего подходит для стабильных типов, у которых только несколько полей. Если тип location
достигает поля высоты уровня моря, spirit
должен уточнить значение высоты уровня моря для компиляции программы. Смешивание порядка lat
и long
вызовет ошибки компиляции, однако программа не сможет вывести правильные результаты.
1 2 |
spirit := location{-14.5684, 175.472636} fmt.Println(spirit) // Выводит: {-14.5684 175.472636} |
Неважно, как вы инициализируете структуру, вы можете модифицировать специальный символ %v
со знаком плюса +
для вывода названий полей, как показано в следующем примере. Это особенно полезно для крупных структур.
1 2 3 |
curiosity := location{-4.5895, 137.4417} fmt.Printf("%v\n", curiosity) // Выводит: {-4.5895 137.4417} fmt.Printf("%+v\n", curiosity) // Выводит: {lat:-4.5895 long:137.4417} |
Вопрос для проверки:
В чем преимущества синтаксиса композитного литерала с «полем-значением» перед формой «только со значениями»?
Копирование структур
Когда марсоход достигает востока кратера Брэдбери, бухты Йеллоунайф, место кратера Брэдбери в реальной жизни не меняется, как и в следующем листинге. Переменная curiosity
инициализируется с копией значений, находящихся в bradbury
, поэтому изменения происходят независимо.
1 2 3 4 5 6 |
bradbury := location{-4.5895, 137.4417} curiosity := bradbury curiosity.long += 0.0106 // Направляется на восток к бухте Йеллоунайф fmt.Println(bradbury, curiosity) // Выводит: {-4.5895 137.4417} {-4.5895 137.4523} |
Вопрос для проверки:
При передачи curiosity
функции, что манипулирует lat
или long
, будут ли изменения видны в месте вызова?
Пример среза структур в Golang
Срез структур []struct
представляет собой коллекцию нуля или более значений (срез), где каждое значение базируется на структуре вместо примитивного типа вроде float64
.
Если программе требуется коллекция мест высадки марсоходов, не нужно создавать отдельные срезы для широты и долготы, как показано в примере ниже.
1 2 |
lats := []float64{-4.5895, -14.5684, -1.9462} longs := []float64{137.4417, 175.472636, 354.4734} |
Это плохой код, особенно если учитывать структуры для локаций, что ранее были введены в уроке. Теперь представьте большее количество срезов, добавленных для высоты уровня моря и так далее. Ошибка при редактировании предыдущего листинга может с легкостью привести к данных, сдвинутым через срезы или даже к срезам с разными длинами.
Лучшим решением станет создание одного среза, где каждое значение является структурой. Тогда каждая локация будет единственной единицей, что можно расширить с названием места высадки или другого необходимого поля, как показано в коде ниже:
1 2 3 4 5 6 7 8 9 10 11 |
type location struct { name string lat float64 long float64 } locations := []location{ {name: "Bradbury Landing", lat: -4.5895, long: 137.4417}, {name: "Columbia Memorial Station", lat: -14.5684, long: 175.472636}, {name: "Challenger Memorial Station", lat: -1.9462, long: 354.4734}, } |
Вопрос для проверки:
В чем опасность использования нескольких связанных между собой срезов?
Кодирование структур в JSON
JavaScript Object Notation, или JSON является стандартным форматом данных, введенным Дугласом Крокфордом. Он основан на подмножестве языка JavaScript, а также поддерживается другими языками программирования. JSON широко используется для веб API (Application Programming Interfaces), включая MAAS API, что предоставляет данные о погоде от марсохода Curiosity.
Функция Marshal
из пакета json
используется в Листинге 9 для кодирования данных из location
в формат JSON. Marshal
возвращает данные JSON в виде байтов, что можно отправить или конвертировать в строки для отображения на экране. В результате также может выйти ошибка. Об этом поговорим в одном из следующих уроков.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package main import ( "encoding/json" "fmt" "os" ) func main() { type location struct { Lat, Long float64 // Поля должны начинаться с большой буквы } curiosity := location{-4.5895, 137.4417} bytes, err := json.Marshal(curiosity) exitOnError(err) fmt.Println(string(bytes)) // Выводит: {“Lat”:-4.5895,“Long”:137.4417} } // exitOnError выводит любые ошибки и выходит. func exitOnError(err error) { if err != nil { fmt.Println(err) os.Exit(1) } } |
Обратите внимание, что ключи JSON совпадают с названиями полей структуры location
. Для работы, пакет json
требует экспорта полей. Если Lat
и Long
начинаются с маленькой буквы, выводом будет {}
.
Вопрос для проверки:
Как расшифровывается аббревиатура JSON?
Изменение JSON через теги struct в Golang
В Go, пакет json
требует, чтобы первые буквы полей были в верхнем регистре, то же самое касается составных названий в стиле CamelCase
. Возможно, вам понадобятся ключи JSON в snake_case
стиле, особенно при взаимодействии с Python или Ruby. Поля структуры могут быть помечены тегами с названиями полей, которые бы вы хотели, чтобы использовал пакет json
.
Единственное изменение между Листингом 9 и Листингом 10 в использовании тегов struct
, что изменяют вывод функции Marshal
. Обратите внимание, что поля Lat
и Long
все еще должны экспортироваться, чтобы пакет json
увидел их.
1 2 3 4 5 6 7 8 9 10 11 12 |
type location struct { // Теги в struct меняют вывод Lat float64 `json:"latitude"` Long float64 `json:"longitude"` } curiosity := location{-4.5895, 137.4417} bytes, err := json.Marshal(curiosity) exitOnError(err) fmt.Println(string(bytes)) // Выводит: {“latitude”:-4.5895,“longitude”:137.4417} |
Теги структур являются обыкновенными строками, что связаны с полями структуры. Предпочтительны литералы необработанной строки (
) , потому что кавычкам не нужно экранировать через обратный слеш, как в случае с менее читабельным "json:\"latitude\""
.
Теги структуры форматируются как key:"value"
, где ключом обычно является название пакета. Для настройки поля Lat
для JSON и XML тегом структуры был бы
.json:"latitude" xml:"latitude"
Как предполагает название, теги struct
нужны только для полей структур, хотя json.Marshal
закодирует и другие типы.
Вопрос для проверки:
Почему поля Lat
и Long
начинаются с букв в верхнем регистре при кодировании JSON?
Заключение
- Структуры группируют значения вместе в одну единицу;
- Структуры являются значениями, что копируются во время присваивания или передачи функциям;
- Композитные литералы предоставляют удобные средства для инициализации структур;
- Теги
struct
декорируют экспортируемые поля с дополнительными информацией, что пакеты могут использовать; - Пакет
json
использует теги вstruct
для контролирования вывода названий полей.
Итоговое задание для проверки:
Напишите программу для отображения закодированных данных в JSON трех мест высадки марсохода в Листинге 8. JSON должен содержать название каждого места высадки и использовать теги структуры, как показано в Листинге 10.
Чтобы сделать вывод приятнее, используйте функцию MarshalIndent
из пакета json.
Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»