Перейти к содержанию
Educora
Средний16 мин6 / 10

Структуры, методы и указатели

Создавай свои типы с помощью структур, пойми, зачем нужны указатели, пиши методы с получателями-значениями и указателями, используй композицию.

Проверь себя
В этом уроке ты узнаешь
  • Объявлять структуру, создавать её литералом и выводить через %+v
  • Работать с указателями через & и *
  • Правильно выбирать между получателем-значением и получателем-указателем
  • Работать с функциями-конструкторами NewX и встраиванием структур

Сведения об ученике — имя, возраст, средний балл — должны храниться вместе. Держать их в трёх отдельных переменных неудобно. В Go нет классов, вместо них есть структуры (struct): они объединяют связанные поля в один тип, а методы добавляют этому типу поведение. В этом уроке ты также изучишь указатели — без них функция не может изменить исходные данные.

Структуры

Структура объявляется как type Имя struct { ... }, внутри перечисляются поля и их типы. Чтобы создать значение, пишут литерал структуры: Student{Name: "Aysel", Age: 15}. К полям обращаются через точку: s.Name. Незаданное поле получает нулевое значение, поэтому даже пустая структура — вполне рабочее значение.

Go
package main

import "fmt"

type Student struct {
	Name  string
	Age   int
	Score float64
}

func main() {
	s := Student{Name: "Aysel", Age: 15, Score: 92.5}
	var empty Student
	s.Age++
	fmt.Println(s)
	fmt.Printf("%+v\n", s)
	fmt.Printf("%+v\n", empty)
	fmt.Println(s.Name, s.Score)
}
Ожидаемый результат
{Aysel 16 92.5}
{Name:Aysel Age:16 Score:92.5}
{Name: Age:0 Score:0}
Aysel 92.5

%v (и Println) показывают только значения, а %+v добавляет имена полей — это очень удобно при отладке. Структуры, все поля которых сравнимы, можно сравнивать через ==: два значения Student равны, если равны все их поля.

Указатели

В Go всё, что передаётся в функцию, копируется. Если функция меняет свой параметр, переменная вызывающей стороны остаётся нетронутой. Чтобы изменить оригинал, нужен указатель — адрес переменной в памяти. &x берёт адрес, а *p читает или меняет значение по этому адресу. Тип *int означает «указатель на int». Арифметики указателей (как p++ в C) в Go нет, поэтому указатели безопасны.

Go
package main

import "fmt"

func doubleValue(n int) {
	n *= 2
}

func doublePointer(n *int) {
	*n *= 2
}

func main() {
	x := 5
	p := &x
	*p = 7
	fmt.Println(x, *p, p == &x)
	doubleValue(x)
	fmt.Println(x)
	doublePointer(&x)
	fmt.Println(x)
}
Ожидаемый результат
7 7 true
7
14
Определение
Указатель

Значение, хранящее адрес другой переменной в памяти. Его нулевое значение — nil. & берёт адрес, а * обращается к значению по этому адресу.

Методы: получатели-значения и указатели

Метод — это функция с особым параметром, получателем (receiver). Получатель записывается между словом func и именем метода: func (a Account) Summary() string. Если получатель — значение (a Account), метод работает с копией объекта. Если указатель (a *Account), метод может изменить исходный объект.

Go
package main

import "fmt"

type Account struct {
	Owner   string
	Balance float64
}

func (a Account) Summary() string {
	return fmt.Sprintf("%s: %.2f AZN", a.Owner, a.Balance)
}

func (a *Account) Deposit(amount float64) {
	a.Balance += amount
}

func (a Account) BrokenDeposit(amount float64) {
	a.Balance += amount
}

func main() {
	acc := Account{Owner: "Murad", Balance: 100}
	acc.Deposit(50)
	acc.BrokenDeposit(1000)
	fmt.Println(acc.Summary())
	p := &acc
	p.Deposit(25.5)
	fmt.Println(p.Summary())
}
Ожидаемый результат
Murad: 150.00 AZN
Murad: 175.50 AZN

BrokenDeposit пытался добавить 1000 манатов, но баланс не изменился, потому что метод работал с копией. Обрати внимание: acc — не указатель, но acc.Deposit(50) работает — Go автоматически вызывает (&acc).Deposit(50). Точно так же через указатель p можно вызвать метод Summary с получателем-значением.

ПолучательКогда выбирать
func (a Account)Метод не меняет объект, а тип маленький (несколько полей, как time.Time).
func (a *Account)Метод меняет поля, тип большой или содержит поле, которое нельзя копировать, например sync.Mutex.
Ошибка: изменение теряется
type Counter struct {
	value int
}

func (c Counter) Increment() {
	c.value++ // changes a copy
}
Исправлено: получатель-указатель
type Counter struct {
	value int
}

func (c *Counter) Increment() {
	c.value++ // changes the original
}

Конструкторы и встраивание

Особых конструкторов в Go нет. Вместо них пишут обычную функцию, имя которой начинается с New: NewTeacher(...) создаёт объект и возвращает указатель на него. Наследования тоже нет — Go предпочитает композицию. Одну структуру можно встроить (embedding) в другую как поле без имени: тогда поля и методы встроенной структуры становятся доступны прямо во внешней.

Go
package main

import "fmt"

type Person struct {
	Name string
}

func (p Person) Greet() string {
	return "Hi, I am " + p.Name
}

type Teacher struct {
	Person
	Subject string
}

func NewTeacher(name, subject string) *Teacher {
	return &Teacher{Person: Person{Name: name}, Subject: subject}
}

func main() {
	t := NewTeacher("Leyla", "Math")
	fmt.Println(t.Greet())
	fmt.Println(t.Name, "teaches", t.Subject)
	fmt.Printf("%+v\n", *t)
}
Ожидаемый результат
Hi, I am Leyla
Leyla teaches Math
{Person:{Name:Leyla} Subject:Math}

Главное

  • Структура объединяет связанные поля в один тип; %+v выводит её с именами полей.
  • Go передаёт всё по значению; чтобы изменить оригинал, нужен указатель (&x, *p).
  • Получатель-значение работает с копией, получатель-указатель — с оригиналом; & Go подставляет сам.
  • Обращение к полю через nil-указатель вызывает panic.
  • Вместо конструкторов используют функции NewX, вместо наследования — встраивание структур (композицию).

Проверь себя

Вопросов: 10. Каждый правильный ответ приносит XP.

1 / 10
Что выведет fmt.Printf("%+v", Point{X: 1, Y: 2})?