Golang: struct – структуры в примерах

Автор: | 28/11/2018

Структура – это коллекция типизированных полей, т.е. у кождого поля в структуре есть свой тип данных.

Создать структуру можно следующим образом:

type Example struct {
    FirstField string
    SecondField string
    ThirdField int
}

Тут у структуры с именем Example есть коллекция полей – поля FirstField и SecondField типа string, и поле ThirdField типа integer.

Если различные поля имеют одинаковый тип – можно упростить объявление до такого вида:

type Example struct {
    FirstField, SecondField string
    ThirdField              int
}

Объявление и инициализация структуры

Объявление переменной типа struct

Можно выполнить объявление структуры через переменную:

var st = Example

В таком случае поля структуры будут инициализированы с нулями:

package main

import (
    "fmt"
)

type Example struct {
    FirstField, SecondField string
    ThirdField              int
}   

var st Example

func main() {

    fmt.Println(st)
}

Результат:

[simterm]

$ go run structs.go 
{  0}

[/simterm]

Либо выполнить объявление и проиницилизировать сразу с данными:

package main

import (
    "fmt"
)   

type Example struct {
    FirstField, SecondField string
    ThirdField              int
}   

var st1 Example
var st2 = Example{"FirstValue", "SecondValue", 3}

func main() {

    fmt.Println(st1)
    fmt.Println(st2)
}

Обратите внимание, что при объявлении переменной st2 с иницилизацией данных в полях структуры – используется “=“.

Проверяем:

[simterm]

$ go run structs.go 
{  0}
{FirstValue SecondValue 3}

[/simterm]

Инициализацию можно выполнить, используя именованные поля. В таком случае поля, для которых значение не задано, будут проинициализированы со значением 0.

Для удобства чтения – значения можно разбивать построчно, разделяя их запятыми (в том числе после последнего элемента), как в примере с st3:

package main

import (
    "fmt"
)   

type Example struct {
    FirstField, SecondField string
    ThirdField              int
}   

var st1 Example

var st2 = Example{"FirstValue", "SecondValue", 3}

var st3 = Example{
    FirstField: "FirstValue", 
    ThirdField: 3,
}

func main() {

    fmt.Println(st1)
    fmt.Println(st2)
    fmt.Println(st3)
}

Результат:

[simterm]

$ go run structs.go 
{  0}
{FirstValue SecondValue 3}
{FirstValue  3}

[/simterm]

И, как и с обычными переменными, внутри функций можно пропустить использование var, и использовать “:“:

package main

import (
    "fmt"
)   

type Example struct {
    FirstField, SecondField string
    ThirdField              int
}   

var st1 Example
var st2 = Example{"FirstValue", "SecondValue", 3}

func main() {

    st3 := Example{
        FirstField: "FirstValue",
        ThirdField: 3,
    }

    fmt.Println(st1)
    fmt.Println(st2)
    fmt.Println(st3)
}

Доступ к данным

Доступ к полям структуры

К отдельным полям структуры можно получить доступ с помощью точки:

package main

import (
    "fmt"
)
    
type Example struct {       
    FirstField, SecondField string
    ThirdField              int
}

func main() {
        
    st := Example{
        FirstField: "FirstValue",
        ThirdField: 3,
    }
    
    fmt.Println(st.FirstField)

}

Результат:

[simterm]

$ go run structs.go 
FirstValue

[/simterm]

Переопределение значений полей

Что бы изменить уже проиницилизированное значение поля – достаточно его указать ещё раз:

...
func main() {
        
    st := Example{
        FirstField: "FirstValue",
        ThirdField: 3,
    }
    
    st.FirstField = "AnotherValue"
   
    fmt.Println(st.FirstField)

}

Результат:

[simterm]

$ go run structs.go 
AnotherValue

[/simterm]

Указатели к структуре

Для доступа к стуктуре и полям можно использовать указатели (см. C: указатели – подробный разбор).

По умолчанию Go использует передачу аргументов в функцию по значению, а не по ссылке.

Что бы продемонстрировать разницу – добавим новую функцию, и выполним передачу по значению:

package main

import (
    "fmt"
)

type Example struct {
    FirstField, SecondField string
    ThirdField              int
}

func changeVal(changed_st Example) {

    // change passed struct's 'FirstField' field value
    changed_st.FirstField = "ChangedValue"

    // print value
    fmt.Println("\nchanged_st.FirstField value:", changed_st.FirstField)
    // print changed_st.FirstField address
    fmt.Printf("changed_st.FirstField address: %p\n", &changed_st.FirstField)

}

func main() {

    // initialize the 'st' variable with default value 'FirstValue'
    st := Example{
        FirstField: "FirstValue",
        ThirdField: 3,
    }

    // redefine the st's FirstField with the new value 'AnotherValue'
    st.FirstField = "AnotherValue"

    // execute the changeVal() function and pass the 'st' variable/struct to change the 'FirstField's value
    changeVal(st)

    // print value
    fmt.Println("\nst.FirstField value:", st.FirstField)
    // print variable's address
    fmt.Printf("st.FirstField address: %p\n\n", &st.FirstField)
    
}

Тут:

  1. в st.FirstField = "AnotherValue" задаём значение поля FirstField структуры Example равным AnotherValue
  2. changeVal(st) – передаём стуктуру и её поля в функцию changeVal() по значению
  3. выводим значение поля FirstField стуктуры Example

Проверяем:

[simterm]

$ go run structs.go 

changed_st.FirstField value: ChangedValue
changed_st.FirstField address: 0xc00005c180

st.FirstField value: AnotherValue
st.FirstField address: 0xc00005c150

[/simterm]

В результате – в выводе значения st.FirstField мы получаем то же, что было задано в рамках функции main(), хотя в функции changeVal() мы его изменили.

При этом – обращаем внимание на адреса переменных в обеих функциях:

  • в main() – 0xc00005c150
  • в changeVal() – 0xc00005c180

Теперь изменим код, и выполним передачу аргумента в функцию changeVal() по ссылке:

...
func changeVal(changed_st *Example) {

    // change passed struct's 'FirstField' field value
    changed_st.FirstField = "ChangedValue"

    // print value
    fmt.Println("\nchanged_st.FirstField value:", changed_st.FirstField)
    // print changed_st.FirstField address
    fmt.Printf("changed_st.FirstField address: %p\n", &changed_st.FirstField)

}

func main() {

    // initialize the 'st' variable with default value 'FirstValue'
    st := Example{
        FirstField: "FirstValue",
        ThirdField: 3,
    }

    // redefine the st's FirstField with the new value 'AnotherValue'
    st.FirstField = "AnotherValue"

    // execute the changeVal() function and pass the 'st' variable/struct to change the 'FirstField's value
    changeVal(&st)

    // print value
    fmt.Println("\nst.FirstField value:", st.FirstField)
    // print variable's address
    fmt.Printf("st.FirstField address: %p\n\n", &st.FirstField)
    
}

Тут мы поменяли:

  • changeVal(&st) – выполняем передачу адреса переменной st, которая ссылается на Example
  • func changeVal(changed_st *Example) – присваиваем переменной changed_st переданный адрес структуры Example

Результат:

[simterm]

$ go run structs.go 

changed_st.FirstField value: ChangedValue
changed_st.FirstField address: 0xc00005c150

st.FirstField value: ChangedValue
st.FirstField address: 0xc00005c150

[/simterm]

Теперь обе функции – и main(), и changeVal() – ссылаются на один и тот же адрес в памяти – 0xc00005c150.

Функция changeVal() выполнила изменение в самой структуре, а не локальной копии changed_st.FirstField, и в функции main() мы получаем значение ChangedValue, т.к. в локальной копии st.FirstField = "AnotherValue" изменение выполняется до того, как в changeVal() было выполнено изменение поля самой структуры.

Указатели и функция new()

Можно создать указатель на структуру с помощью функции new().

Изменим наш код:

...
func changeVal(changed_st *Example) {

    // change passed struct's 'FirstField' field value
    changed_st.FirstField = "ChangedValue"

    // print value
    fmt.Println("\nchanged_st.FirstField value:", changed_st.FirstField)
    // print changed_st.FirstField address
    fmt.Printf("changed_st.FirstField address: %p\n", &changed_st.FirstField)

}

func main() {

    // create a new memory object
    // the "st"'s variable's value will be this object's address
    st := new(Example)
    fmt.Printf("\nThe st's variable type: %T\n", st)

    // init fileds values
    st.FirstField = "FirstValue"
    st.ThirdField = 3 

    // execute the changeVal() function and pass the "st" variable/struct to change the "FirstField"'s value
    changeVal(st)

    // print value
    fmt.Println("\nst.FirstField value:", st.FirstField)
    // print variable's address
    fmt.Printf("st.FirstField address: %p\n\n", &st.FirstField)

}

changeVal() оставляем без изменений, а переменной st присваиваем значение, которое вернёт функция new(), которой мы передаём структуру Example – это будет указатель на созданный объект:

[simterm]

$ go run structs.go 

The st's variable type: *main.Example

changed_st.FirstField value: ChangedValue
changed_st.FirstField address: 0xc000070150

st.FirstField value: ChangedValue
st.FirstField address: 0xc000070150

[/simterm]

Експорт структур и полей

Если имя структуры или поля в ней начинается с заглавной буквы – его можно использовать как экпортированное в других модулях.

Проверяем $GOPATH:

[simterm]

$ go env GOPATH
/home/setevoy/Scripts/Go/goprojects

[/simterm]

Переходим туда, каталог src, и создаём пакет example:

[simterm]

$ cd /home/setevoy/Scripts/Go/goprojects/src && mkdir example

[/simterm]

Создаём модули – modename и main:

[simterm]

$ cd example && mkdir {modename,main}

[/simterm]

Создаём файлы, пока пустые:

[simterm]

$ touch main/main.go && touch modename/example.go

[/simterm]

Проверяем структуру каталогов и файлов:

[simterm]

$ tree ~/Scripts/Go/goprojects/src/example/
/home/setevoy/Scripts/Go/goprojects/src/example/
├── main
│   └── main.go
└── modename
    └── example.go

[/simterm]

Создаём модуль modename – в файле example.go создаём две структуры:

package modename

type Exported struct {
    ExportedField1   string
    unexportedField1 string
}

type unexported struct {
    ExportedField1   string
    unexportedField2 string
}

И файл main/main.go:

package main

import (
    "example/modename"
    "fmt"
)

func main() {

    fmt.Println(modename.Exported{ExportedField1: "Value"})
}

Проверяем:

[simterm]

$ go run main/main.go 
{Value }

[/simterm]

Пробуем использовать поле unexportedField1:

func main() {

    fmt.Println(modename.Exported{unexportedField1: "Value"})
}

Аналогично – попытка использовать структуру unexported тоже приведёт к ошибкам:

...
func main() {

    fmt.Println(modename.unexported{ExportedField1: "Value"})
}

Результат:

[simterm]

$ go run main/main.go 
# command-line-arguments
main/main.go:10:14: cannot refer to unexported name modename.unexported
main/main.go:10:14: undefined: modename.unexported

[/simterm]

Поле unexportedField2 структуры unexported вернёт такую же ошибку.

Теги в структурах

Собственно, весь этот пост появился из-за того, что мне стало интересно – что за “`” в некоторых структурах, например – в go-flags.

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

Для примера возьмём такой код:

package main

import (
    "fmt"
    "reflect"
)

type Example struct {

    // declare field with the "tagname" tag and its "tagvalue" value
    Field string `tagname:"tagvalue"`
}

func main() {

    // initialize Example struct's fields
    st := Example{Field: "Value1"}

    // access tags via the "reflect" package
    tp := reflect.TypeOf(st)

    // set the Field's tag and value to the "res" variable
    res, _ := tp.FieldByName("Field")

    // all field's data
    fmt.Println("All filed's data:", res)
    fmt.Printf("Data type: %T\n", res)

    // tag's name and value
    fmt.Println("Field's tag data: ", res.Tag)

    // tag's value only using the Get()
    fmt.Println("Tag's value with Get(): ", res.Tag.Get("tagname"))

    // tag's value only using the Lookup()
    value, ok := res.Tag.Lookup("tagname")
    fmt.Printf("Tag's value and return code with Lookup(): %s, %t\n", value, ok)
}

Тут:

  1. инициализируем поле структуры Example, присваиваем её переменной st
  2. используя TypeOf() выполняем рефлексию структуры
  3. используя FieldByName() – получаем поле “Field” структуры “Example“, тип StructTag
  4. выводим все содержимое поля из типа StructTag
  5. выводим только содержимое его тега(ов)
  6. получаем только значение тега, используя Get()
  7. и аналогично, но используя Lookup()

Результат:

[simterm]

$ go run struct_tags.go 
All filed's data: {Field  string tagname:"tagvalue" 0 [0] false}
Data type: reflect.StructField
Field's tag data:  tagname:"tagvalue"
Tag's value with Get():  tagvalue
Tag's value and return code with Lookup(): tagvalue, true

[/simterm]

Ссылки по теме

Go Tour – Structs

Go by Example: Structs

Golang Structs Tutorial with Examples

Tags in Golang

Pass by pointer vs pass by value in Go