Структура — это коллекция типизированных полей, т.е. у кождого поля в структуре есть свой тип данных.
Создать структуру можно следующим образом:
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)
}
Тут:
- в
st.FirstField = "AnotherValue"задаём значение поля FirstField структуры Example равным AnotherValue changeVal(st)— передаём стуктуру и её поля в функциюchangeVal()по значению- выводим значение поля 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, которая ссылается на Examplefunc 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)
}
Тут:
- инициализируем поле структуры Example, присваиваем её переменной st
- используя
TypeOf()выполняем рефлексию структуры - используя
FieldByName()— получаем поле «Field» структуры «Example«, типStructTag - выводим все содержимое поля из типа
StructTag - выводим только содержимое его тега(ов)
- получаем только значение тега, используя
Get() - и аналогично, но используя
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]