- Пример
- Зачем нужны структуры
- Пример
- Как без структур
- Как со структурами
- Объекты и структуры в Go
- Синтаксис структур и базовые практики
- Итоги
В языке Go одним из ключевых инструментов для работы с данными являются структуры. Если переменные позволяют хранить отдельные значения (числа, строки, булевы значения), а массивы и срезы — наборы однотипных данных, то структуры дают возможность объединять в одном месте разнородные данные, которые логически связаны между собой.
Структуры можно представить как собственные типы данных, которые мы конструируем под задачу. Они помогают упорядочивать код и делать его более читаемым и удобным в поддержке.
Пример
Представим, что нам нужно описать студента: у него есть имя, возраст, список курсов и средний балл. Если бы мы хранили все в отдельных переменных, получилось бы громоздко:
var name string
var age int
var courses []string
var gpa float64
При этом сразу видно, что все эти переменные относятся к одному объекту — студенту. Гораздо удобнее объединить их в одну структуру:
type Student struct {
Name string
Age int
Courses []string
GPA float64
}
Теперь у нас появился собственный тип Student
, который можно использовать в программе как единый объект.
Зачем нужны структуры
- Группировка данных — все, что относится к одному объекту, хранится вместе.
- Улучшение читаемости — названия полей отражают смысл данных.
- Основа для объектного подхода в Go — к структурам можно добавлять методы.
- Удобство работы с внешними данными — например, JSON удобно парсить в структуры.
Пример
Создадим структуру Book
и используем ее в коде:
package main
import "fmt"
type Book struct {
Title string
Author string
Pages int
}
func main() {
book := Book{
Title: "Война и мир",
Author: "Лев Толстой",
Pages: 1225,
}
fmt.Println(book.Title, "—", book.Author, "(", book.Pages, "стр.)")
}
Результат:
Война и мир — Лев Толстой (1225 стр.)
Структуры в Go — это способ объединить разные данные в один логический объект. Они позволяют описывать сущности реального мира (пользователь, книга, заказ, транзакция) и делают программы понятнее.
Как без структур
Представим, что мы описываем систему заказов. Каждый заказ должен содержать идентификатор, имя клиента, список товаров и статус. Если мы не используем структуры, нам приходится хранить данные в отдельных срезах и синхронизировать их по индексам:
func main() {
// данные о заказах разнесены по разным срезам
orderIDs := []int{101, 102}
customers := []string{"Иван", "Мария"}
items := [][]string{
{"Ноутбук", "Мышь"},
{"Телефон"},
}
statuses := []string{"new", "paid"}
// чтобы вывести заказ, приходится помнить про индексы
fmt.Println("Заказ:", orderIDs[0],
"Клиент:", customers[0],
"Товары:", items[0],
"Статус:", statuses[0])
}
Результат:
Заказ: 101 Клиент: Иван Товары: [Ноутбук Мышь] Статус: new
Проблемы такого подхода:
- данные одного заказа разбросаны по разным переменным,
- любая ошибка в индексах приводит к несоответствиям,
- добавление нового свойства (например, сумма) потребует переписывать несколько структур данных.
Как со структурами
Теперь опишем заказ как единый объект:
// Order описывает заказ
type Order struct {
ID int
Customer string
Items []string
Status string
}
func main() {
// создаем объект заказа
order := Order{
ID: 101,
Customer: "Иван",
Items: []string{"Ноутбук", "Мышь"},
Status: "new",
}
// обращаемся к полям напрямую
fmt.Println("Заказ:", order.ID,
"Клиент:", order.Customer,
"Товары:", order.Items,
"Статус:", order.Status)
}
Теперь данные связаны, ошибки с индексами мы избегаем, а при добавлении нового поля (Amount
, CreatedAt
) нам достаточно изменить определение структуры.
Объекты и структуры в Go
В классических объектно-ориентированных языках есть классы и объекты. В Go нет классов и наследования, но структуры позволяют описывать данные и методы для них. Экземпляр структуры можно называть объектом в широком смысле — это значение пользовательского типа, объединяющее поля. Однако формально это просто значение структуры, а не объект ООП.
Синтаксис структур и базовые практики
В Go для объявления структур используется ключевое слово struct
. Оно всегда пишется после имени нового типа и открывает блок с перечнем полей. Этот синтаксис уникален для Go: структуры не объявляются через ключевые слова вроде class или record, как в других языках.
Синтаксис объявления
// общий шаблон
type <ИмяСтруктуры> struct {
Поле1 Тип1
Поле2 Тип2
}
type
создает новый тип.- Имя структуры принято писать с заглавной буквы.
- Внутри фигурных скобок перечисляются поля и их типы.
Создание экземпляра
- Литерал структуры:
u := User{Login: "alex", Password: "pwd"}
- Пустая структура:
var u User
- Через
new
:u := new(User)
(возвращает указатель).
Обращение к полям
func main() {
u := User{Login: "alex"}
fmt.Println(u.Login) // чтение
u.Login = "alexander" // запись
}
Лучше всего придерживаться следующих правил,
- Имя структуры отражает сущность (
User
,Order
,Report
). - Поля называются понятно и однозначно (
CreatedAt
,Status
,Amount
). - Экспортируемые поля (с заглавной буквы) видны за пределами пакета, неэкспортируемые (с маленькой) — только внутри.
- Логически связанные поля лучше группировать во вложенные структуры.
Итоги
Структуры — пользовательские типы данных в Go. Они объединяют разнородные поля в один объект; позволяют описывать сущности предметной области; дают именованный доступ к данным; упрощают изменение модели (добавление/удаление полей происходит в одном месте). На следующих уроках мы разберем поля и их типы, указатели на структуры, методы, конструкторы, композицию, теги/JSON и сравнение структур.
Самостоятельная работа
Структуры позволяют объединять разнородные данные в один логический объект. В этой работе опишите простые сущности и поработайте с их полями — это закрепит базовый синтаксис и логику использования структур.
Фильм
Вспомните любимый фильм и опишите его «паспорт» в виде структуры Movie
со следующими полями:
- Название — строка
- Год выхода — целое число
- Жанры — список строк
- Рейтинг — число с плавающей точкой
Создайте один экземпляр и выведите строку в формате:
Название (год) — жанры: [...] — рейтинг: ...
Показать пример ответа
package main
import (
"fmt"
)
type Movie struct {
Title string
Year int
Genres []string
Rating float64
}
func main() {
m := Movie{
Title: "Интерстеллар",
Year: 2014,
Genres: []string{"Sci‑Fi", "Drama"},
Rating: 8.6,
}
fmt.Printf("%s (%d) — жанры: %v — рейтинг: %.1f\n", m.Title, m.Year, m.Genres, m.Rating)
}
Банковский счёт
Добавим немного практики: опишите структуру Account
со следующими полями:
- Идентификатор — целое число
- Владелец — строка
- Баланс — число с плавающей точкой
Напишите функцию Deposit(acc *Account, amount float64)
, которая аккуратно пополняет счёт на указанную сумму. Для наглядности выведите баланс «до» и «после» вызова функции.
Показать пример ответа
package main
import (
"fmt"
)
type Account struct {
ID int
Owner string
Balance float64
}
// Deposit увеличивает баланс на amount.
func Deposit(acc *Account, amount float64) {
if amount <= 0 {
return // в базовом уроке просто игнорируем невалидные суммы
}
acc.Balance += amount
}
func main() {
a := Account{ID: 1, Owner: "Иван", Balance: 1000}
fmt.Println("До:", a.Balance)
Deposit(&a, 500)
fmt.Println("После:", a.Balance)
}
Анализ
Пара минут на размышления: почему хранить связанные данные в отдельных срезах (например, ids
, owners
, balances
) — плохая идея? Сформулируйте как минимум два аргумента (ошибки из‑за индексов, сложности с добавлением новых полей и т.п.).
Показать пример ответа
- Данные одного объекта расползаются по разным срезам — легко перепутать индексы и получить несоответствие (клиент не к тому заказу).
- Сложно добавлять новые поля — приходится менять несколько срезов и следить за их длинами.
- Код хуже читается и тестируется, чем работа с именованными полями структуры.
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.