Go in a nutshell
  • Go in a nutshell
  • Введение
  • Родословная или Should I Go?
  • Hello, world!
  • Базовые типы данных
  • Go - установка, сборка
  • Алфавит и пакеты
  • Синтаксис
  • Приведение типов
  • Операторы
  • Константы
  • Управление потоком
  • Массивы, срезы, отображения
  • Именованные типы и структуры
  • Указатели и оператор new
  • Функции
  • Работа с параметрами командной строки
  • Завершение работы программы
  • Методы
Powered by GitBook
On this page
  • Именованные типы
  • Структуры
  • Пустые структуры
  • Анонимные структуры
  • Анонимные поля
  • Волшебный местоблюститель
  • Сравнение структур

Was this helpful?

Именованные типы и структуры

Скоро сказка сказывается, да не скоро дело делается. Двигаемся вперёд - медленно, но уверенно. Похоже, в этой главке мне нечем вас будет удивить. Как именованные типы, так и структуры весьма и весьма напоминают таковые в С/С++.

Именованные типы

Именованные типы являются по сути псевдонимами тех или иных существующих примитивов, и используются в основном либо для улучшения семантичности кода, либо с целью дальнейшего применения методов. О методах речь пойдёт в соответствующей главе, а сейчас просто объявим именованный тип.

package main

import (
	"fmt"
)

// именованный тип для хранения дня недели
type weekday int 

// "заполним" неделю при помощи геператора констант
const (
	Sunday = iota
	Mondey
	Wednesday
	Tuesday
	Thursday
	Friday
	Saturday
)

// сделаем ассоциативный массив - отображение
var week map[weekday]string

func main() {

// инициализируем отображение
	week := map[weekday]string{
		Sunday:    "Sunday",
		Mondey:    "Mondey",
		Wednesday: "Wednesday",
		Tuesday:   "Tuesday",
		Thursday:  "Thursday",
		Friday:    "Friday",
		Saturday:  "Saturday",
	}

// для счётчика цикла нам понадобится также переменная типа weekday!
	var i weekday

	for i = 0; i < weekday(len(week)); i++ { // приведение типа!
		fmt.Printf("Index: %v, Value: %v\n", i, week[i]) // распечатка отображения

	}

}
//Результат:
//Index: 0, Value: Sunday
//Index: 1, Value: Mondey
//Index: 2, Value: Wednesday
//Index: 3, Value: Tuesday
//Index: 4, Value: Thursday
//Index: 5, Value: Friday
//Index: 6, Value: Saturday

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

Структуры

Как и в "голом" С, в языке Go нет классов. И, точно так же, структура является единственным составным (агрегированным) типом данных. Синтаксис же таков:

package main

import (
	"fmt"
)

type T struct {
	x, y int
	f    float64
	s    string
	b    bool
}

func main() {

//переменная t типа T
	var t T
	fmt.Println(t)

}
//Результат:
//{0 0 0  false}

Элементы структуры называются полями. Очевидно, непроинициализированная структура имеет для каждого своего поля значение по умолчанию, равное значению по умолчанию для соответствующего примитива.

Часто непроинициализированную структуру объявляют таким образом:

package main

import (
	"fmt"
)

type T struct {
	x, y int
	f    float64
	s    string
	b    bool
}

func main() {

//переменная t типа T
	t := T{}
	fmt.Println(t)

}
//Результат:
//{0 0 0  false}

Доступ к полям структуры осуществляется через оператор "точка" - .

package main

import (
	"fmt"
)

type T struct {
	x, y int
	f    float64
	s    string
	b    bool
}

func main() {

	var t T
	fmt.Println(t)
	t.x, t.y = 1, 2
	t.f = 0.01
	t.s = "qwerty"
	t.b = true
	fmt.Println(t)

}
//Результат:
//{0 0 0  false}
//{1 2 0.01 qwerty true}

Возможна упрощённая инициализация полей (подобная конструкция именуется структурным литералом):

package main

import (
	"fmt"
)

type T struct {
	x, y int
	f    float64
	s    string
	b    bool
}

func main() {

	t := T{x: 1, y: 2, f: 0.01, s: "qwerty", b: true}	
	fmt.Println(t)

}
//Результат:
//{1 2 0.01 qwerty true}

Можно ещё более кратко:

package main

import (
	"fmt"
)

type T struct {
	x, y int
	f    float64
	s    string
	b    bool
}

func main() {

	t := T{1, 2, 0.01, "qwerty", true}
	fmt.Println(t)

}
//Результат:
//{1 2 0.01 qwerty true}

Именование как самой структуры, так и каждого её поля определяет экспортируемость всей структуры или отдельного поля. Как и везде, работает "правило заглавной буквы". Структура, именованная со строчной буквы экспортироваться не будет. В экспортируемой структуре, поля, не определённые с прописной буквы, также экспортироваться не будут. То есть часть полей может быть как бы public, другая - как бы private, переводя на привычный язык ООП. Структуры прекрасно сериализуются и используются в связке с JSON, весьма часто. Сериализации/десериализации подлежат только экспортируемые поля экспортируемых же структур. Если в результируещем JSON мы хотим получить имена атрибутов с маленькой (строчной) буквы, либо получить вовсе некие иные, не соответствующие наименованию исходных полей структуры, имена - для этой цели предусмотрены так называемые JSON tag descriptors. Приведём пример их применения:

//Экспортируемая структура + JSON tag descriptors
type ResultEntity struct {
	Merchant string `json:"merchant"`
	PaperState int `json:"paperState"`
	Error bool `json:"error"`
	ErrorDescription string `json:"errorDescription"`
}

Необходимо отметить, что полями структуры могут быть, помимо примитивов, также массивы, срезы, отображения, а также другие, вложенные структуры:

package main

import (
	"fmt"
)

type innerStruct struct {
	b bool
}

type T struct {
	i   int
	arr [5]int
	s   innerStruct
}

func main() {

	var t T

	fmt.Println(t)

}
//Результат:
//{0 [0 0 0 0 0] {false}}

Важное замечание: структурный тип не может содержать поле собственного типа! Формально это правило звучит так: "агрегат­ное значение не должно содержать само себя".

//Ошибка компиляции: invalid recursive type T
type T struct {
	t T
}

//Допустимо
type T1 struct {
	pt *T1
}

Данный приём позволяет нам строить связанные списки и деревья. Эти сущности изучает дисциплина, именуемая структуры данных. Имеется немало алгоритмов, использующих указанные техники. Углубление в эту тему в рамках настоящей главы видится избыточным.

Пустые структуры

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

type Empty struct{}

то есть структура без полей.

В чём примечательность подобной структуры? Главной особенностью является тот факт, что переменная, содержащая пустую структуру занимает в памяти 0 (нуль) байт. Импортируем встроенный пакет unsafe и проверим данное утверждение:

package main

import (
	"fmt"
	"unsafe"
)

type Empty struct{}

func main() {

	var e Empty

	fmt.Printf("Размер %d байт", unsafe.Sizeof(e))
}
//Результат:
//Размер 0 байт

Чем и где это может быть полезно? Да, в общем то, везде, где поможет нам сэкономить память. Обратитесь к своей интуиции! Увы, рассмотреть многие подробности нам пока что не позволяет нехватка знаний. Некоторые полезные приёмы будут рассмотрены позже в главах о методах, интерфейсах и многозадачности. Имея в виду ограниченный объём накопленных нами на данный момент знаний, приведу лишь такой интересный пример: пустые структуры иногда используются в качестве значений в отображениях, в тех случаях, когда программисту могут быть важны ключи а не значения.

package main

import (
	"fmt"
)

//Именованный тип - будет хранить неупорядоченный список значений типа int
type intSet map[int]struct{}

func add(m intSet, i int) {
	m[i] = struct{}{}
}

func main() {
	uniqInts := make(intSet, 0)
	add(uniqInts, 1)
	add(uniqInts, 2)
	add(uniqInts, 1) // повтор
	add(uniqInts, 1) // повтор

	// выведет только 2 значения
	for i, _ := range uniqInts {
		fmt.Println(i)
	}
}

Как демонстрирует данный пример, мы создали именованный тип, в основе которого лежит отображение (map), ключом которого является значение типа int, а значением - пустая структура. Целью в данном случае является хранение множества значений типа int, реализуя определение множества как совокупности уникальных неупорядоченных значений. Применение структуры в данном случае служит для экономии памяти, ибо значение ключей здесь не представляет интереса. Также подвергните разбору функцию add, которая служит для установки значений. Обратите внимание и на форму объявления переменной uniqInts. Всякие полезные наблюдения помогут вам овладевать языком, закреплять соответствующие навыки, вырабатывать стиль и приёмы.

Анонимные структуры

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

package main

import (
	"fmt"
)

func main() {

	var s struct { x, y int}
	fmt.Println(s)
	s.x, s.y = 1, 2
	fmt.Println(s)
	fmt.Printf("Тип s: %T", s)
}
//Результат:
//{0 0}
//{1 2}
//Тип s: struct { x int; y int }

Фактически, сигнатурно, s - самая настоящая структура. Однако, s - это переменная, но вовсе не тип. Поэтому инициализация при помощи структурного литерала будет выглядеть так:

s := struct {
		x int
		y int
	}{
		x: 1,
		y: 2,
	}
//или проще:	
s := struct {x, y int}{1, 2}

Также, поскольку s не является типом, не сработает привычная конструкция вида:

var v s
var v1 = s{3, 4}
//Ошибка: s is not a type

В этом плане возможно лишь прямое присваивание var v = s (создастся копия по значению).

Анонимной структуре может быть без приведения типа присвоено значение "нормальной" структуры, при их сигнатурном совпадении:

package main

import (
	"fmt"
)

type T struct{ x, y int }

func main() {
	var t = T{}
	var s struct{ x, y int }
	s = t
	fmt.Println(s)
}
//Результат:
//{0, 0}

В обоих случаях сигнатура структуры - struct { x int; y int }. Внимание! Значение имеет не только тип, но и наименование полей. При несовпадении имён полей даже приведение типов окажется невозможным.

И последнее к вопросу об анонимных структурах. С ними нельзя использовать методы. О методах в контексте анонимных структур мы поговорим в своё время.

Анонимные поля

Говоря о структурах, в языке Go возможен такой фокус, как анонимные поля. И, поскольку имена отсутствуют, будет нетрудным заключение о том, что в такой структуре позволительно лишь одно поле с данными каждого типа (один int, один bool итак далее), ибо именно названия вообще позволяют нам различать однотипные вещи. Выглядеть подобная структура будет как-то так:

package main

import (
	"fmt"
)

type T struct {
	int
	bool
}

func main() {
	t := T{0, true}	
	fmt.Println(t.int, t.bool)
}

Как можно видеть, для доступа к полям вместо привычных имён будут использоваться названия их типов. Объявление "микса" из именованных и неименованных полей допустимо. Неименованные поля всегда закрыты (не экспортируются). Применение методов возможно:

package main

import (
	"fmt"
)

type T struct {
	int
	bool
}

func (t T) f() int {
	return t.int * 2
}

func main() {
	t := T{2, true}
	fmt.Println(t.f())
}
//Результат: 4
package main

import (
	"fmt"
)

type htmlElement struct {
	width, height int
}

//Внимание, методы! Nota bene.
func (w *htmlElement) setHeight(height int) { w.height = height }
func (w *htmlElement) setWidth(width int)   { w.width = width }

type button struct {
	htmlElement
	text string
}

type input struct {
	htmlElement
	type_ string
}

func main() {
	b := new (button)
	i := new (input)
	b.setWidth(3)
	b.setHeight(5)
	b.text = "submit"
	i.setWidth(10)	
	i.setHeight(20)
	i.type_ = "text"
	fmt.Println(*b, *i)
}

Здесь тип htmlElement описывает общие для всех элементов HTML формы поля и методы. Путём включения, типы button и input получили как нужные поля, так и методы, с ними работающие. Отдельное имя для поля типа htmlElement в данном случае не принципиально, им можно пренебречь. Данный тип нам понадобился в первую очередь для того, чтобы "протащить" в конструкцию нужные методы.

Волшебный местоблюститель

Сравнение структур

Здесь необходимо выделить несколько основных моментов. Во-первых, структуры с разными сигнатурами - принципиально несравниваемы.

package main

import (
	"fmt"
)

type T struct{ a, b int }
type T1 struct{ a1, b1 int }

func main() {
	t := T{1, 2}
	t1 := T1{1, 2}
	fmt.Println(t == t1)
}
//Результат:
//invalid operation: t == t1 (mismatched types T and T1)

Приведение типов T(t1) также невозможно. А вот при совпадении сигнатур возможны варианты. Обратите внимание на применение анонимной структуры - тут даже не потребовалось приведение типов:

package main

import (
	"fmt"
)

type T struct{ a, b int }
type T1 struct{ a, b int }

func main() {
	t := T{1, 2}
	t1 := T1{1, 2}
	s := struct{a, b int}{1, 2}
	fmt.Println(t == T(t1))
	fmt.Println(t == s)
}
//Результат:
//true
//true

Во-вторых, структуры, имеющие в качестве одного или нескольких полей несравниваемые типы (например, срезы) - принципиально несравниваемы.

package main

import (
	"fmt"
)

type T struct {
	x, y int
	b    []byte
}

func main() {
	t := T{1, 2, []byte{3, 4, 5}}
	t1 := T{1, 2, []byte{3, 4, 5}}
	fmt.Println(t == t1)
}
//Результат:
//invalid operation: t == t1 (struct containing []byte cannot be compared)

И, наконец, в-третьих, переменные, представляющие собой структуры одного типа и не содержащие несравниваемых полей, а также сигнатурно идентичные им анонимные структуры, всегда сравниваемы.

package main

import (
	"fmt"
)

type T struct{ x, y int }

func main() {
	t := T{1, 2}
	t1 := T{1, 2}
	t2 := T{3, 4}
	s := struct{ x, y int }{3, 4}
	fmt.Println(t == t1)
	fmt.Println(t1 == t2)
	fmt.Println(t2 == s)
}
//Результат:
//true
//false
//true

Краткое заключение

На данном этапе посчитаем эти сведения о структурах и именованных типах достаточными. Прибегнем к разумному редукционизму. Впереди же нас ждёт продолжение этой важной и интересной темы в главах о методах и интерфейсах, указателях и работе с JSON.

Раунд!

PreviousМассивы, срезы, отображенияNextУказатели и оператор new

Last updated 4 years ago

Was this helpful?

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

Для чего всё это нужно? Резонный вопрос. С одной стороны это в своём роде пустая синтаксическая опция. С другой стороны, это один из способов реализации принципа композиции в Go. Для понимания сказанного, нам придётся немного забежать вперёд - в . Пример:

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

указатель
методы
методы