Определение структур в Go

Вступление

Построение абстракций вокруг конкретных деталей - это величайший инструмент, который язык программирования может дать разработчику. Структуры позволяют разработчикам Go описывать мир, в котором работает программа Go. Вместо того чтобы рассуждать о строках, описывающихStreet,City илиPostalCode, структуры позволяют нам вместо этого говорить оAddress. Они служат естественным связующим звеном дляdocumentation в наших усилиях по информированию будущих разработчиков (включая нас самих), какие данные важны для наших программ Go и как будущий код должен использовать эти данные надлежащим образом. Структуры могут быть определены и использованы несколькими различными способами. В этом уроке мы рассмотрим каждый из этих методов.

Определение структур

Структуры работают как бумажные формы, которые вы можете использовать, например, для подачи налогов. Бумажные формы могут содержать поля для текстовой информации, например, ваши имена и фамилии. Помимо текстовых полей, формы могут иметь флажки для указания логических значений, таких как «замужем» или «не замужем», или полей даты для даты рождения. Точно так же структуры собирают разные фрагменты данных и организуют их под разными именами полей. Когда вы инициализируете переменную с новой структурой, создается впечатление, что вы скопировали форму и сделали ее готовой для заполнения.

Чтобы создать новую структуру, вы должны сначала дать Go план, описывающий поля, которые содержит структура. Это определение структуры обычно начинается с ключевого словаtype, за которым следует имя структуры. После этого используйте ключевое словоstruct, за которым следует пара скобок{}, где вы объявляете поля, которые будет содержать структура. После того как вы определили структуру, вы можете объявить переменные, которые используют это определение структуры. Этот пример определяет структуру и использует ее:

package main

import "fmt"

type Creature struct {
    Name string
}

func main() {
    c := Creature{
        Name: "Sammy the Shark",
    }
    fmt.Println(c.Name)
}

Когда вы запустите этот код, вы увидите этот вывод:

outputSammy the Shark

Сначала мы определяем структуруCreature в этом примере, содержащую полеName типаstring. В телеmain мы создаем экземплярCreature, помещая пару фигурных скобок после имени типаCreature, а затем задавая значения для полей этого экземпляра. Экземпляр вc будет иметь полеName, установленное на «Акула Сэмми». При вызове функцииfmt.Println мы получаем значения поля экземпляра, помещая точку после переменной, в которой был создан экземпляр, за которым следует имя поля, к которому мы хотим получить доступ. Например,c.Name в этом случае возвращает полеName.

Когда вы объявляете новый экземпляр структуры, вы обычно перечисляете имена полей с их значениями, как в последнем примере. В качестве альтернативы, если каждое значение поля будет предоставлено во время создания экземпляра структуры, вы можете опустить имена полей, как в этом примере:

package main

import "fmt"

type Creature struct {
    Name string
    Type string
}

func main() {
    c := Creature{"Sammy", "Shark"}
    fmt.Println(c.Name, "the", c.Type)
}

Вывод такой же, как в предыдущем примере:

outputSammy the Shark

Мы добавили дополнительное поле кCreature для отслеживанияType существа какstring. При создании экземпляраCreature в телеmain мы решили использовать более короткую форму создания экземпляра, указав значения для каждого поля по порядку и опустив их имена. В объявленииCreature{"Sammy", "Shark"} полеName принимает значениеSammy, а полеType принимает значениеShark, потому чтоName появляется первым в объявление типа, за которым следуетType.

Эта более короткая форма объявления имеет несколько недостатков, которые заставили сообщество Go в большинстве случаев предпочесть более длинную форму. Вы должны предоставить значения для каждого поля в структуре при использовании краткого объявления - вы не можете пропустить поля, которые вам не нужны. Это быстро приводит к путанице в коротких объявлениях для структур со многими полями. По этой причине объявление структур с помощью краткой формы обычно используется с структурами, которые имеют несколько полей.

Имена полей в примерах до сих пор начинались с заглавных букв. Это более важно, чем стилистическое предпочтение. Использование заглавных или строчных букв для имен полей влияет на то, будут ли имена ваших полей доступны для кода, выполняемого в других пакетах.

Экспорт поля структуры

Поля структуры следуют тем же правилам экспорта, что и другие идентификаторы в языке программирования Go. Если имя поля начинается с заглавной буквы, оно будет доступно для чтения и записи с помощью кода вне пакета, в котором была определена структура. Если поле начинается со строчной буквы, только код в пакете этой структуры сможет читать и писать это поле. В этом примере определяются поля, которые экспортируются, и те, которые не являются:

package main

import "fmt"

type Creature struct {
    Name string
    Type string

    password string
}

func main() {
    c := Creature{
        Name: "Sammy",
        Type: "Shark",

        password: "secret",
    }
    fmt.Println(c.Name, "the", c.Type)
    fmt.Println("Password is", c.password)
}

Это выведет:

outputSammy the Shark
Password is secret

Мы добавили дополнительное поле к нашим предыдущим примерам,secret. secret - это неэкспортированное полеstring, что означает, что любой другой пакет, который пытается создать экземплярCreature, не сможет получить доступ или установить его полеsecret. В том же пакете мы можем получить доступ к этим полям, как в этом примере. Посколькуmain также находится в пакетеmain, он может ссылаться наc.password и извлекать хранимое там значение. Нередко в структурах есть неэкспортированные поля с доступом к ним, опосредованным экспортируемыми методами.

Встроенные структуры

Помимо определения нового типа для представления структуры, вы также можете определить встроенную структуру. Эти определения структуры на лету полезны в ситуациях, когда создание новых имен для типов структуры будет напрасной тратой усилий. Например, тесты часто используют структуру, чтобы определить все параметры, которые составляют конкретный тестовый пример. Было бы обременительно придумать новые имена, такие какCreatureNamePrintingTestCase, когда эта структура используется только в одном месте.

Определения встроенных структур отображаются справа от присваивания переменной. Вы должны сразу же создать их экземпляр, предоставив дополнительную пару фигурных скобок со значениями для каждого поля, которое вы определяете. В следующем примере показано определение встроенной структуры:

package main

import "fmt"

func main() {
    c := struct {
        Name string
        Type string
    }{
        Name: "Sammy",
        Type: "Shark",
    }
    fmt.Println(c.Name, "the", c.Type)
}

Выход из этого примера будет:

outputSammy the Shark

Вместо того, чтобы определять новый тип, описывающий нашу структуру, с помощью ключевого словаtype, этот пример определяет встроенную структуру, помещая определениеstruct сразу после оператора короткого присваивания,:=. Мы определяем поля структуры, как в предыдущих примерах, но затем мы должны немедленно предоставить другую пару фигурных скобок и значения, которые примет каждое поле. Использование этой структуры теперь точно так же, как и раньше - мы можем ссылаться на имена полей, используя точечные обозначения. Самое распространенное место, где вы увидите объявленные встроенные структуры, - это тесты, так как часто определяются одноразовые структуры, содержащие данные и ожидания для конкретного тестового примера.

Заключение

Структуры - это коллекции разнородных данных, определенных программистами для организации информации. Большинство программ работают с огромными объемами данных, и без структур было бы трудно вспомнить, какие переменныеstring илиint принадлежат друг другу, а какие - разные. В следующий раз, когда вы обнаружите, что манипулируете группами переменных, спросите себя, не лучше ли было бы эти переменные сгруппировать вместе с помощьюstruct. Эти переменные, возможно, все время описывали какую-то концепцию более высокого уровня.