Структуры и Go

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

Вот основы работы со структурами:

Структуры и Go Golang, Программирование, Структура, IT, Длиннопост

package main

import (

"fmt"

)

// Этот код определяет структуру Person с 3-мя полями: Name (строка), Age (целое число) и hidden (булево значение).

// Приватные поля в Go определяются с помощью нижнего регистра первой буквы имени поля в структуре.

// Они не доступны за пределами пакета, в котором объявлена структура.

type Person struct {

hidden bool

Name string

Age  int

}

// К структурам можно привязывать методы, используя получатель (receiver):

func (p *Person) Greet() string {

return "Hello, " + p.Name

}

// Методы структуры могут иметь доступ к её приватным полям, что позволяет вам управлять доступом к этим полям через публичные методы.

func (e *Person) SetPrivateField(value bool) {

e.hidden = value

}

func (e *Person) GetPrivateField() bool {

return e.hidden

}

// В Go поддерживается встраивание структур что позволяет создавать сложные структуры данных и реализовывать наследование в стиле композиции

// Экземпляр Employee наследует поля и методы Person, к ним можно обращаться так, как если бы они были объявлены напрямую в Employee.

type Employee struct {

Person

Position string

}

func main() {

// Экземпляр структуры может быть создан следующим образом

p := Person{false, "John Doe", 30}


// или с использованием именованных полей для повышения читаемости

p = Person{Name: "John Doe", Age: 30}


// Доступ к полям структуры осуществляется через оператор точки

fmt.Println(p.Name) // Выведет: John Doe


// Вызов метода осуществляется так же, как и доступ к полям

message := p.Greet()

fmt.Println(message) // Выведет: Hello, John Doe


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

var user = struct {

ID  int

Name string

}{ID: 1, Name: "John Doe"}

fmt.Println(user) // Выведет: {1 John Doe}

}

В Go методы структур могут иметь два типа приемников (receiver): по значению (копия структуры) и по указателю. Выбор между ними влияет на возможность метода изменять саму структуру и на производительность. Вот основные различия:

Приемник по значению (копия)

Когда метод принимает структуру по значению, он работает с копией этой структуры. Изменения, сделанные внутри метода, не отражаются на оригинальной структуре.

Структуры и Go Golang, Программирование, Структура, IT, Длиннопост

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

Приемник по указателю

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

Структуры и Go Golang, Программирование, Структура, IT, Длиннопост

Использование приемника по указателю рекомендуется, когда:

  • Необходимо модифицировать саму структуру в методе.

  • Структура достаточно большая, и копирование её значений могло бы отрицательно сказаться на производительности.

Подводные камни

Структуры и Go Golang, Программирование, Структура, IT, Длиннопост

package main

import "fmt"

type Example struct {

Count int

Name string

}

type Derived struct {

Example

Name string

}

type ExampleP struct {

Example *Example

}

func main() {

// Значение по умолчанию для структур

var ex Example

fmt.Println(ex.Count) // Выведет 0, значение по умолчанию для int

fmt.Println(ex.Name)  // Выведет "", значение по умолчанию для string

// Встраивание структур и теневая реализация

d := Derived{}

d.Name = "Derived"

d.Example.Name = "Base"

fmt.Println(d.Name, d.Example.Name) // Чтобы получить доступ к полю Name из встроенной структуры Example, необходимо явно указать путь к нему

// Неинициализированные указатели в структурах

exp := ExampleP{}

fmt.Println(exp.Example.Name) // Приведет к панике, так как ex.Nested == nil

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

// Не будет работать для структур, содержащих срезы!

ex1 := Example{Count: 5}

ex2 := Example{Count: 5}

fmt.Println(ex1 == ex2) // Выведет "true", так как все поля совпадают

}

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

  1. Срезы (slices): Как вы уже заметили, срезы не поддерживают операцию сравнения. Попытка сравнить структуры, содержащие срезы, приведет к компиляционной ошибке.

  2. Мапы (maps): Также как срезы, мапы не поддерживают операцию сравнения. Структуры с полями типа мапа не могут быть сравнены с помощью ==.

  3. Функции (functions): Функции в Go также не могут быть сравнены напрямую, кроме сравнения на равенство или неравенство с nil.

  4. Каналы (channels): Хотя каналы могут быть сравнены на равенство или неравенство, включая сравнение с nil, сложные сравнения структур, содержащих каналы, могут быть неоднозначными в зависимости от конкретной логики.

  5. Интерфейсы с динамическими типами, не поддерживающими сравнение: Если поле интерфейса содержит значение динамического типа, который сам по себе не поддерживает сравнение, то структура, содержащая такое поле, также не может быть сравнена.

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

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

Структуры и Go Golang, Программирование, Структура, IT, Длиннопост