#15 golang Структуры - композиция структур, именнованная, встраивание, перекрытие полей

Структуры в Go — это составные типы данных с набором полей, похожие на классы в PHP или Python.

Подобно объявлению простых пользовательских типов мы используем ключевое слово type для определения конкретного типа структуры:

type Person struct {
   Id       int
   Name     string
   IsActive bool
}

-- где Person - имя объявленного типа. Тут у нас имеются типа поля разных типов.

Инициализация структур

Создавать структуры мы можем, используя явное указание имен полей, или же только задавая значения (строго в порядке объявления):

type Person struct {
	Id       int
	Name     string
	IsActive bool
}

func main() {
	// явное задание полей:
	Petya := Person{
		Id:       1,
		Name:     "Петя",
		IsActive: true,
	}
	fmt.Println(Petya)

	// перечисляем только значения
	Vasya := Person{
		1,
		"Вася",
		true,
	}
	fmt.Println(Vasya)
}

-- когда мы явно указываем поля, мы можем задавать не все значения, тогда незаданные поля получат значения по умолчанию.

А вот для способа с передачей только значений уже потребуется указывать вообще все значения.

Обращение к полям структуры

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

type Person struct {
	Id       int
	Name     string
	IsActive bool
}

func main() {
	Petya := Person{
		Id:       1,
		Name:     "Петя",
		IsActive: true,
	}
	fmt.Println(Petya.Name) // распечатаем поле имени
}

Видимость полей

Первая буква имени поля подчиняется общему правилу видимости: с заглавной буквы — поле экспортируемое, то есть доступное и из других пакетов, со строчной — неэкспортируемое, видимое только внутри своего пакета:

type Account struct {
	Login   string // с заглавной - экспортируемое поле
	balance int    // со строчной - видно только внутри своего пакета
}

из другого пакета можно читать и менять acc.Login, а обращение к acc.balance просто не скомпилируется.

Во всех примерах этого урока поля объявлены с заглавной буквы не случайно — такие структуры удобно объявить в одном пакете и использовать в другом (например, в main).

Копозиция структур

В Go нет ООП (!) в классическом понимании, однако вся работа с объектами построена на композиции.

Именованная композиция

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

type Person struct {
	Id       int
	Name     string
	IsActive bool
}

// копозиция через свойство
type Address struct {
	Address string
	Owner   Person // тоже структура
}

func main() {
	myAddress := Address{
		Address: "Улица Лизюкова",
		Owner: Person{
			Id:       1,
			Name:     "Петя",
			IsActive: true,
		}}
	fmt.Println(myAddress) // {Улица Лизюкова {1 Петя true}}
	// Обращение к полю поля:
	fmt.Println(myAddress.Owner.Name) // Петя
}

Композиция "встраиванием" (embedding)

Либо композицию можно провести "слиянием" структур, для этого тип указывается тоже как очередное поле, но без указание имени поля:

type Person struct {
	Id       int
	Name     string
	IsActive bool
}

// копозиция через встраивание (embedding)
type Address struct {
	Address string
	Person  // тоже структура, но встроенная
}

func main() {
	myAddress := Address{
		Address: "улица генерала Лизюкова",
		Person: Person{
			Id:       1,
			Name:     "Петя",
			IsActive: true,
		},
	}

	fmt.Println(myAddress) // {улица генерала Лизюкова {1 Петя true}}
	// Обращение к полю как к своему:
	fmt.Println(myAddress.Name) // Петя
}

-- при этом, не смотря на то, что поля дочерней структуры становятся "напрямую" родительскими литерально присвоить их на первом уровнем мы не можем, такой код для примера выше не сработает:

myAddress := Address{
	Address:  "улица генерала Лизюкова",
	Id:       1,      // "неизвестное" поле
	Name:     "Петя", // "неизвестное" поле
	IsActive: true,   // "неизвестное" поле
}

-- дело в том, что прямое присваивание не явно своих полей на первом уровне (а вообще говоря - на том же уровне, что и своих) запрещено, так как встроенная структура — это отдельный тип (а в Go всё с типами строго), а не просто набор полей, что предотвращает конфликты имен и делает структуру данных явной.

В т.ч. при объединении структур встраиванием (как в примере выше)

Выбор значения при встраивании -объединении (слиянии) структур с одинаковыми именами полей

Бывает так, что мы пытаемся объединить встраиванием структуры, у которых есть поля с одинаковым названием.

type Person struct {
	Id       int
	Name     string
	IsActive bool
}

type Address struct {
	Address string
	Name    string // еще одно поле с именем Name
	Person         // встроенная структура
}

func main() {
	myAddress := Address{
		Address: "улица генерала Лизюкова",
		Name:    "Мой адрес",
		Person: Person{
			Id:       1,
			Name:     "Петя",
			IsActive: true,
		},
	}

	fmt.Println(myAddress) // Тут получим:
	// {улица генерала Лизюкова Мой адрес {1 Петя true}}

	// -- видим, что оба поля сохранениы, но при
	// обращении с первого уроня
	// выводится будет поле из родительской структуры, не вложенной:
	fmt.Println(myAddress.Name) // Мой адрес

	// но мы всё равно можем достать вложенное поле
	// Name через полный селектор ()
	fmt.Println(myAddress.Person.Name) // Петя
}

-- в таком случае никакого конфликта не будет, и оба поля сохраняются, а при обращении к этому полю приоритет будет отдан наиболее "верхнему" (по вложенности структур одной в другую) полю структуры.

То есть при выполнении fmt.Println(myAddress.Name) выше будет выведено поле структуры Address, а не Person, но, как показано выше, через упоминание названия типа вложенной струкутры, мы можем достать и вложенное поле Name ;)

Видео-материалы

Дополнительные материалы

  • Структуры как поля других структур: https://metanit.com/go/tutorial/4.3.php
vedro-compota's picture

разберем этот вопрос выше

_____________
матфак вгу и остальная классика =)

vedro-compota's picture

Либо называем все поля либо нет:


func main() {
	// либо называя поля
	myAddress := Address{
		Address: "улица генерала Лизюкова",
		Person: Person{
			1,
			"Петя",
			true,
		},
	}

	//либо не называя их
	myAddress2 := Address{
		"улица генерала Лизюкова",
		Person{
			1,
			"Петя",
			true,
		},
	}

	fmt.Println(myAddress, myAddress2) 
}

-- оба эти варианта рабочие, а вот так не сработает:

myAddress := Address{
	Address: "улица генерала Лизюкова",
	Person{ // нет названия поля, а для предыдущего есть
		1,
		"Петя",
		true,
	},
}

_____________
матфак вгу и остальная классика =)