- Объявлять структуру, создавать её литералом и выводить через
%+v - Работать с указателями через
&и* - Правильно выбирать между получателем-значением и получателем-указателем
- Работать с функциями-конструкторами
NewXи встраиванием структур
Сведения об ученике — имя, возраст, средний балл — должны храниться вместе. Держать их в трёх отдельных переменных неудобно. В Go нет классов, вместо них есть структуры (struct): они объединяют связанные поля в один тип, а методы добавляют этому типу поведение. В этом уроке ты также изучишь указатели — без них функция не может изменить исходные данные.
Структуры
Структура объявляется как type Имя struct { ... }, внутри перечисляются поля и их типы. Чтобы создать значение, пишут литерал структуры: Student{Name: "Aysel", Age: 15}. К полям обращаются через точку: s.Name. Незаданное поле получает нулевое значение, поэтому даже пустая структура — вполне рабочее значение.
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 нет, поэтому указатели безопасны.
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), метод может изменить исходный объект.
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) в другую как поле без имени: тогда поля и методы встроенной структуры становятся доступны прямо во внешней.
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.
fmt.Printf("%+v", Point{X: 1, Y: 2})?