Структуры данных — это способы организации данных в программе. В Go встроены мощные и простые структуры: массивы, срезы (slices), карты (maps) и структуры (structs). Они позволяют хранить и обрабатывать данные, от списков чисел до сложных объектов, таких как профили пользователей.
💬 Зачем это нужно?
Представь, что ты хранишь информацию о книгах: название, автор, количество страниц. Вместо множества переменных лучше использовать структуры данных, чтобы всё было организовано и удобно.
🗃 Массивы
Массивы — это наборы элементов одного типа с фиксированным размером, заданным при создании.
Объявление массива
package main
import "fmt"
func main() {
var numbers [4]int // Массив из 4 целых чисел
numbers[0] = 10
numbers[1] = 20
fmt.Println(numbers) // [10 20 0 0]
}
Короткий синтаксис
numbers := [4]int{10, 20, 30, 40}
fmt.Println(numbers) // [10 20 30 40]
Особенности
- Размер массива — часть его типа (
[4]int
≠[5]int
). - Элементы, не инициализированные явно, будут иметь стандартное значение (например,
0
дляint
,""
дляstring
).
⚠️ Массивы редко используются напрямую из-за фиксированного размера. Чаще применяются срезы.
🔪 Срезы (Slices)
Срезы — это динамические массивы, которые могут расти или уменьшаться. Они создаются на основе массива, но Go управляет их размером автоматически.
Создание среза
package main
import "fmt"
func main() {
slice := []int{1, 2, 3} // Срез без фиксированного размера
fmt.Println(slice) // [1 2 3]
}
Добавление элементов
Функция append
добавляет элементы в срез:
slice = append(slice, 4, 5)
fmt.Println(slice) // [1 2 3 4 5]
Длина и ёмкость
len(slice)
— возвращает текущую длину (число элементов).cap(slice)
— возвращает ёмкость (сколько элементов срез может вместить без перевыделения памяти).
fmt.Println(len(slice), cap(slice)) // 5 6
Как увеличивается ёмкость?
Когда срез заполняет свою ёмкость, Go выделяет новый массив с большей ёмкостью. Обычно ёмкость удваивается до определённого порога (примерно 1024 элемента), а затем растёт линейно (примерно на 25%). Это снижает количество перевыделений памяти.
Пример:
package main
import "fmt"
func main() {
slice := make([]int, 0, 2) // Длина 0, ёмкость 2
fmt.Printf("Начало: len=%d, cap=%d, %v\n", len(slice), cap(slice), slice)
slice = append(slice, 1) // len=1, cap=2
fmt.Printf("После 1: len=%d, cap=%d, %v\n", len(slice), cap(slice), slice)
slice = append(slice, 2, 3) // len=3, cap=4 (ёмкость удвоилась)
fmt.Printf("После 2,3: len=%d, cap=%d, %v\n", len(slice), cap(slice), slice)
}
Вывод:
Начало: len=0, cap=2, []
После 1: len=1, cap=2, [1]
После 2,3: len=3, cap=4, [1 2 3]
💡 Используйте
make
с начальной ёмкостью (make([]int, 0, n)
), чтобы минимизировать перевыделение памяти.
🗺 Карты (Maps)
Карты — это словари, хранящие пары “ключ-значение”. Ключи уникальны, а типы ключей и значений задаются при создании.
Создание и работа с картой
package main
import "fmt"
func main() {
scores := map[string]int{
"Алиса": 90,
"Боб": 85,
}
scores["Катя"] = 95 // Добавление
delete(scores, "Боб") // Удаление
fmt.Println(scores) // map[Алиса:90 Катя:95]
}
Проверка ключа
value, exists := scores["Алиса"]
if exists {
fmt.Println("Оценка Алисы:", value) // Оценка Алисы: 90
} else {
fmt.Println("Ключ не найден")
}
Итерация по карте
Используйте for ... range
для перебора ключей и значений:
for name, score := range scores {
fmt.Printf("%s: %d\n", name, score)
}
Вывод:
Алиса: 90
Катя: 95
⚠️ Порядок итерации по карте не гарантирован — Go специально рандомизирует его. Карта должна быть инициализирована (
make
или{}
), иначе возникнет ошибкаnil map
.
Пример: Подсчёт частоты
Карты отлично подходят для подсчёта данных, например, частоты символов в строке:
package main
import "fmt"
func main() {
text := "hello"
charCount := make(map[rune]int)
for _, char := range text {
charCount[char]++
}
fmt.Println(charCount) // map[e:1 h:1 l:2 o:1]
}
🏗 Структуры (Structs)
Структуры — это пользовательские типы, объединяющие поля для моделирования сложных объектов.
Объявление и использование
package main
import "fmt"
type Person struct {
Name string
Age int
Address struct { // Вложенная структура
City string
Zip string
}
}
func main() {
p := Person{
Name: "Алиса",
Age: 25,
Address: struct {
City string
Zip string
}{City: "Москва", Zip: "101000"},
}
fmt.Println(p.Address.City) // Москва
}
Методы для структур
Структуры могут иметь методы — функции, привязанные к типу:
package main
import "fmt"
type Person struct {
Name string
Age int
}
func (p Person) Greet() string {
return "Привет, я " + p.Name + "!"
}
func main() {
p := Person{Name: "Боб", Age: 30}
fmt.Println(p.Greet()) // Привет, я Боб!
}
Анонимные структуры
Для временных данных можно использовать анонимные структуры:
package main
import "fmt"
func main() {
point := struct {
X, Y int
}{X: 10, Y: 20}
fmt.Println(point) // {10 20}
}
💡 Структуры подходят для моделирования сложных сущностей (например, пользователей, заказов). Методы добавляют поведение, делая структуры похожими на объекты.
📚 Полезные советы
- Массивы: Используйте для фиксированных данных (например, координаты).
- Срезы: Задавайте начальную ёмкость через
make
для больших данных. - Карты: Проверяйте наличие ключа и используйте
for ... range
для итерации. Инициализируйте карту перед использованием. - Структуры: Добавляйте методы для поведения и используйте вложенные структуры для сложных данных, но избегайте избыточной вложенности.
- Оптимизация: Следите за
cap
срезов и очищайте карты с помощьюdelete
для экономии памяти.
🧠 Правильный выбор структуры данных упрощает код и повышает производительность.
🧪 Пример программы
package main
import "fmt"
func main() {
// Массив
arr := [3]int{10, 20, 30}
fmt.Println("Массив:", arr)
// Срез
slice := make([]int, 0, 2)
slice = append(slice, 1, 2, 3)
fmt.Printf("Срез: %v, len=%d, cap=%d\n", slice, len(slice), cap(slice))
// Карта
grades := map[string]int{"Математика": 5, "Физика": 4}
for subject, grade := range grades {
fmt.Printf("Предмет: %s, оценка: %d\n", subject, grade)
}
// Структура
type Student struct {
Name string
Grades map[string]int
}
s := Student{Name: "Алиса", Grades: grades}
fmt.Printf("Студент: %s, оценки: %v\n", s.Name, s.Grades)
}
🔍 Вопросы для самопроверки
- Чем отличается массив от среза в Go?
- Как работает увеличение ёмкости среза при использовании
append
? - Как перебрать все элементы карты?
- Как добавить метод к структуре?
- Что выведет
cap([]int{1, 2, 3})
? - Как создать анонимную структуру и зачем она нужна?
- Как удалить ключ из карты?
📌 Главное из главы
- Массивы — фиксированные наборы элементов одного типа.
- Срезы — динамические массивы, ёмкость которых удваивается при переполнении (до ~1024 элементов).
- Карты — словари для пар “ключ-значение”, поддерживающие итерацию и удаление ключей.
- Структуры — пользовательские типы с полями и методами, включая вложенные и анонимные структуры.
- Правильный выбор структуры данных улучшает читаемость и производительность.
🛠 Упражнение
Напишите программу, которая:
- Создаёт срез чисел
{1, 2, 3}
с начальной ёмкостью 2 и добавляет4
, выводяlen
иcap
. - Создаёт карту для подсчёта частоты букв в строке
"hello"
. - Создаёт структуру
Book
с полямиTitle
(строка),Pages
(число) и методомInfo
, возвращающим строку вида"Книга: <Title>, страниц: <Pages>"
. - Выводит результаты.