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

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

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

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

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 нет классов. И, точно так же, структура является единственным составным (агрегированным) типом данных. Синтаксис же таков:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Раунд!

Last updated