Tutorial

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

GoDevelopment

Введение

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

При запуске этого кода вы увидите следующий вывод:

output
Sammy the Shark

Сначала мы определяем структуру Creature в данном примере, которая содержит поле Name типа string. Внутри тела main мы создадим экземпляр Creature, поместив пару скобок после имени типа, Creature, а затем указав значения для полей этого экземпляра. Экземпляр в c будет иметь в поле Name значение “Sammy the Shark”. Внутри вызова функции 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)
}

Вывод будет таким же, как в последнем примере:

output
Sammy the Shark

Мы добавили дополнительное поле в Creature для отслеживания типа создания в виде строки. При создании экземпляра 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)
}

Результат будет выглядеть так:

output
Sammy 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)
}

Вывод для данного примера будет выглядеть следующим образом:

output
Sammy the Shark

Вместо того, чтобы определять новый тип, описывающий структуру с ключевым словом type, в данном примере вложенная структура определяется сразу же после короткого оператора присваивания :=. Мы определяем поля структуры, как и в предыдущих примерах, но теперь мы должны немедленно предоставить другую пару скобок и значения, которые будут присваиваться каждому полю. Использование структуры остается прежним, мы можем обратиться к именам полей с помощью записи с точкой. Чаще всего вы будете видеть вложенные структуры в тестах, так как часто используемые один раз структуры определяются для хранения данных и ожиданий для конкретного тестового случая.“”“

Заключение

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

Creative Commons License