Array в JS → слайс ([]T) — динамический массив
Object / Map → map[K]V — строго типизированная хеш-таблица
undefined → нулевые значения (0, "", nil, false)
Нет === vs == — только ==, но для слайсов/мап только с nil
make() для слайсов/мап/каналов, new() — редко
int8..int64, uint, float32/64, complex64/128
make vs new, стек vs кучаmkdir go-types && cd go-types
go mod init go-types
Представь, что у тебя есть коробка на 6 стаканов. Ты поставил в неё 4 стакана.
| Характеристика | len (длина) | cap (ёмкость) |
|---|---|---|
| Определение | Количество элементов, которые РЕАЛЬНО находятся в слайсе прямо сейчас | Максимальное количество элементов, которое слайс МОЖЕТ вместить без выделения новой памяти |
| Доступ к элементам | Можно читать/писать по индексам 0..len-1 | Нельзя обратиться к индексу ≥ len (паника: index out of range) |
| append() | Увеличивается на 1 при добавлении элемента | Если len < cap — не меняется. Если len == cap — увеличивается (обычно ×2) |
| make([]T, len, cap) | Первый числовой аргумент | Второй числовой аргумент |
| Память | Используется прямо сейчас | Выделена, но пока не используется (зарезервирована) |
| Аналогия | Занятые места в кинотеатре | Всего мест в зале |
║ СТАРЫЙ МАССИВ (40 байт) НОВЫЙ МАССИВ (80 байт) ║ ║ ┌───┬───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┬───┐ ║ ║ │ 1 │ 2 │ 3 │ 4 │ 5 │ │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ ║ ║ └───┴───┴───┴───┴───┘ └───┴───┴───┴───┴───┴───┘ ║ ║ ↑ ║ ║ ┌──────────────┐ └── cap=10 (обычно ×2) ║ ║ │ ptr ─────────┼──→ НОВЫЙ УКАЗАТЕЛЬ (старый массив) ║ ║ │ len = 6 │ ║ ║ │ cap = 10 │ ⚠️ Старый массив будет удалён GC ║ ║ └──────────────┘ ║ ╚══════════════════════════════════════════════════════════════╝
make([]int, 5) — создаётся слайс с len=5, cap=5. Все 5 элементов — нули.make([]int, 0, 5) — создаётся слайс с len=0, cap=5. Элементов нет, но память выделена.var s []int — nil-слайс. len=0, cap=0. append работает (выделит память).package main
import "fmt"
func main() {
// ==========================================
// 1. СОЗДАНИЕ С РАЗНЫМИ len И cap
// ==========================================
// len=0, cap=5 — память выделена, но элементов нет
s1 := make([]int, 0, 5)
fmt.Printf("s1: len=%d cap=%d %v\n", len(s1), cap(s1), s1)
// Вывод: s1: len=0 cap=5 []
// s1[0] — ПАНИКА! len=0, нельзя обратиться
// len=3, cap=5 — 3 элемента (нули), 2 свободных слота
s2 := make([]int, 3, 5)
fmt.Printf("s2: len=%d cap=%d %v\n", len(s2), cap(s2), s2)
// Вывод: s2: len=3 cap=5 [0 0 0]
// s2[0]=42 — МОЖНО (0 ≤ индекс < 3)
// s2[3] — ПАНИКА! (индекс 3 ≥ len=3)
// len=5, cap=5 — заполнен полностью
s3 := make([]int, 5)
fmt.Printf("s3: len=%d cap=%d %v\n", len(s3), cap(s3), s3)
// Вывод: s3: len=5 cap=5 [0 0 0 0 0]
// ==========================================
// 2. APPEND И РОСТ cap
// ==========================================
s := make([]int, 0, 4) // len=0, cap=4
fmt.Printf("\n--- Добавление элементов ---\n")
fmt.Printf("Начало: len=%d cap=%d %v\n", len(s), cap(s), s)
for i := 1; i <= 6; i++ {
s = append(s, i)
fmt.Printf("append(%d): len=%d cap=%d %v", i, len(s), cap(s), s)
if i > 4 && cap(s) > 4 {
fmt.Print(" ← НОВАЯ АЛЛОКАЦИЯ! cap увеличился")
}
fmt.Println()
}
// Вывод:
// Начало: len=0 cap=4 []
// append(1): len=1 cap=4 [1]
// append(2): len=2 cap=4 [1 2]
// append(3): len=3 cap=4 [1 2 3]
// append(4): len=4 cap=4 [1 2 3 4]
// append(5): len=5 cap=8 [1 2 3 4 5] ← НОВАЯ АЛЛОКАЦИЯ! cap=8
// append(6): len=6 cap=8 [1 2 3 4 5 6]
// ==========================================
// 3. ПЕРЕДАЧА СЛАЙСА В ФУНКЦИЮ
// ==========================================
fmt.Printf("\n--- Передача в функцию ---\n")
original := []int{10, 20, 30}
fmt.Printf("До: len=%d cap=%d %v\n", len(original), cap(original), original)
modifySlice(original)
fmt.Printf("После: len=%d cap=%d %v\n", len(original), cap(original), original)
// original[0] изменился! (слайс разделяет память)
}
func modifySlice(s []int) {
s[0] = 999 // Меняет ОРИГИНАЛ (общий массив)
// s = append(s, 999) — это создаст НОВЫЙ слайс (если cap не хватит),
// но оригинал НЕ изменится, потому что s — копия структуры слайса
}
| Ситуация | Что использовать | Пример |
|---|---|---|
| Итерация по элементам | len | for i := 0; i < len(s); i++ {} |
| Проверка, пуст ли слайс | len | if len(s) == 0 {} |
| Предвыделение памяти | cap | make([]int, 0, 1000) |
| Проверка, нужна ли реаллокация | cap | if len(s) == cap(s) { /* будет реаллокация */ } |
| Срез (slicing) | len и cap среза | sub := s[2:5] — len=3, cap зависит от cap(s) |
Если ты ЗНАЕШЬ заранее, сколько элементов будет в слайсе — всегда используй make([]T, 0, expectedSize).
Это предотвратит множественные реаллокации и ускорит код в 2-5 раз при больших объёмах данных.
╔══════════════════════════════════════════════════════════════╗ ║ ШАГ 1: make([]int, 0, 5) ║ ║ ║ ║ СЛАЙС (24 байта) ВЫДЕЛЕННЫЙ МАССИВ (40 байт) ║ ║ ┌──────────────┐ ┌───┬───┬───┬───┬───┐ ║ ║ │ ptr ─────────┼───────→ │ ? │ ? │ ? │ ? │ ? │ ║ ║ │ len = 0 │ └───┴───┴───┴───┴───┘ ║ ║ │ cap = 5 │ ↑ ║ ║ └──────────────┘ └── МЕСТО ЕСТЬ, НО ЭЛЕМЕНТОВ 0 ║ ║ ║ ║ Доступ по индексу [0] → ПАНИКА (index out of range [0]) ║ ║ т.к. len=0 — элементов нет, хотя память выделена! ║ ╚══════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════╗ ║ ШАГ 2: append(slice, 1, 2, 3) ║ ║ ║ ║ ┌──────────────┐ ┌───┬───┬───┬───┬───┐ ║ ║ │ ptr ─────────┼───────→ │ 1 │ 2 │ 3 │ ? │ ? │ ║ ║ │ len = 3 │ └───┴───┴───┴───┴───┘ ║ ║ │ cap = 5 │ ↑ ↑ ║ ║ └──────────────┘ └─ len=3 ─┘└─ ещё 2 слота ─→ ║ ║ ║ ║ Доступ: [0]=1 ✅, [1]=2 ✅, [2]=3 ✅, [3] ❌ ПАНИКА ║ ║ append(slice, 4) → len=4, cap=5 (БЕЗ новой аллокации!) ║ ╚══════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════╗ ║ ШАГ 3: append(slice, 4, 5, 6) — len стал бы 6 > cap=5 ║ ║ ║ ║ СТАРЫЙ МАССИВ (40 байт) НОВЫЙ МАССИВ (80 байт) ║ ║ ┌───┬───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┬───┐ ║ ║ │ 1 │ 2 │ 3 │ 4 │ 5 │ │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ ║ ║ └───┴───┴───┴───┴───┘ └───┴───┴───┴───┴───┴───┘ ║ ║ ↑ ║ ║ ┌──────────────┐ └── cap=10 (обычно ×2) ║ ║ │ ptr ─────────┼──→ НОВЫЙ УКАЗАТЕЛЬ (старый массив) ║ ║ │ len = 6 │ ║ ║ │ cap = 10 │ ⚠️ Старый массив будет удалён GC ║ ║ └──────────────┘ ║ ╚══════════════════════════════════════════════════════════════╝
package main
import (
"fmt"
"math/cmplx"
)
func main() {
// Целочисленные типы: int8, int16, int32 (rune), int64
var (
small int8 = 127 // от -128 до 127
medium int16 = 32767 // от -32768 до 32767
normal int = 42 // 32 или 64 бита (зависит от платформы)
big int64 = 9223372036854775807 // от -2^63 до 2^63-1
)
fmt.Printf("int8: %d | int16: %d | int (arch): %d | int64: %d\n",
small, medium, normal, big)
// Беззнаковые: uint8 (byte), uint16, uint32, uint64
var (
b byte = 255 // byte — алиас для uint8
u uint64 = 1<<63 // 2^63
)
fmt.Printf("byte: %d | uint64: %d\n", b, u)
// Числа с плавающей точкой: float32, float64
var (
f32 float32 = 3.1415927
f64 float64 = 3.141592653589793
)
fmt.Printf("float32: %.7f | float64: %.15f\n", f32, f64)
// Комплексные числа (есть из коробки!)
c := complex(3, 4) // 3 + 4i
fmt.Printf("complex: %v | модуль: %.2f\n", c, cmplx.Abs(c))
// Булево значение (по умолчанию false)
var flag bool
fmt.Printf("bool (default): %v\n", flag)
}
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
// Строка в Go — иммутабельный слайс байт в кодировке UTF-8.
// len() возвращает количество БАЙТ, не символов!
s := "Привет, Gopher!"
fmt.Printf("Строка: %q\n", s)
fmt.Printf(" len() байт: %d\n", len(s))
fmt.Printf(" utf8.RuneCountInString() символов: %d\n",
utf8.RuneCountInString(s))
// Итерация по строке: по байтам vs по рунам
fmt.Println(" По байтам (for i := 0):")
for i := 0; i < len(s); i++ {
fmt.Printf(" [%d] = %x (%c)\n", i, s[i], s[i])
}
fmt.Println(" По рунам (for range):")
for i, r := range s {
fmt.Printf(" позиция %d: %c (U+%04X)\n", i, r, r)
}
// rune — это int32, представляет Unicode-кодпоинт
var ru rune = '€'
fmt.Printf("rune '€': код %d, символ %c\n", ru, ru)
}
package main
import "fmt"
func main(){
// МАССИВ: фиксированный размер, часть типа.
// [3]int и [4]int — РАЗНЫЕ типы.
// При передаче в функцию — ПОЛНОЕ КОПИРОВАНИЕ.
arr := [3]int{10, 20, 30}
fmt.Printf("Массив: %v | тип: %T | len=%d\n", arr, arr, len(arr))
// СЛАЙС: динамический массив. Сам слайс — это структура:
// { указатель_на_массив, длина, ёмкость }
// При передаче в функцию копируется структура, но массив общий!
// Создание слайса через make(type, length, capacity)
slice := make([]int, 0, 5) // len=0, cap=5
fmt.Printf("\nСлайс (пустой): len=%d cap=%d %v\n", len(slice), cap(slice), slice)
// append() добавляет элементы. Если cap хватает — O(1).
// Если нет — аллокация нового массива (обычно x2).
slice = append(slice, 1, 2, 3, 4, 5)
fmt.Printf("После append 5: len=%d cap=%d %v\n", len(slice), cap(slice), slice)
// Добавляем сверх cap — происходит реаллокация
slice = append(slice, 6)
fmt.Printf("После append 6 (реаллокация): len=%d cap=%d %v\n",
len(slice), cap(slice), slice)
// СРЕЗЫ (slicing) — делят память с оригиналом!
// sub := slice[2:5] — элементы с индексами 2,3,4
sub := slice[2:5]
fmt.Printf("\nСрез [2:5]: %v\n", sub)
fmt.Println("⚠️ Срез РАЗДЕЛЯЕТ память с оригиналом!")
sub[0] = 999 // Изменение через срез меняет ОРИГИНАЛ
fmt.Printf("После sub[0] = 999:\n slice=%v\n sub=%v\n", slice, sub)
// copy() — глубокое копирование слайса
copied := make([]int, len(slice))
copy(copied, slice)
copied[0] = 777
fmt.Printf("\nПосле copy() и изменения копии:\n slice=%v\n copy=%v\n", slice, copied)
}
package main
import "fmt"
func main(){
// Создание мапы: make() или литерал
scores := map[string]int{
"Alice": 100,
"Bob": 85,
}
scores["Charlie"] = 92 // Добавление
fmt.Printf("Мапа: %v | len=%d\n", scores, len(scores))
// Comma ok идиома: проверка наличия ключа
// В Go НЕТ undefined. Есть НУЛЕВОЕ ЗНАЧЕНИЕ для типа.
if score, ok := scores["Alice"]; ok {
fmt.Printf(" Alice: %d (найдена)\n", score)
}
// Несуществующий ключ возвращает нулевое значение
missing := scores["Zorro"]
fmt.Printf(" scores[\"Zorro\"] = %d (нулевое значение int)\n", missing)
// Удаление ключа
delete(scores, "Bob")
fmt.Printf("После delete(\"Bob\"): %v\n", scores)
// Итерация — порядок НЕ ГАРАНТИРОВАН
fmt.Println(" Итерация (порядок случаен):")
for key, val := range scores {
fmt.Printf(" %s → %d\n", key, val)
}
}
package main
import "fmt"
// Person — структура. В Go нет классов, только struct с полями.
type Person struct {
Name string
Age int
Emails []string // Слайс строк (динамический массив)
Meta map[string]string // Мапа: ключ string → значение string
}
// NewPerson — функция-конструктор. Возвращает УКАЗАТЕЛЬ на Person.
func NewPerson(name string, age int) *Person {
// make() создаёт слайс длиной 0, ёмкостью 2
return &Person{
Name: name,
Age: age,
Emails: make([]string, 0, 2), // len=0, cap=2
Meta: make(map[string]string),
}
}
func main(){
person := NewPerson("Gopher", 13)
person.Emails = append(person.Emails, "gopher@golang.org", "gopher@example.com")
person.Meta["role"] = "mascot"
person.Meta["lang"] = "Go"
// %+v выводит структуру с именами полей (аналог util.inspect с depth)
fmt.Printf("Person (%%+v): %+v\n", person)
fmt.Printf("Emails: %v (len=%d, cap=%d)\n",
person.Emails, len(person.Emails), cap(person.Emails))
// Указатель на структуру: доступ через . (авторазыменование)
p2 := &Person{Name: "Alice", Age: 30} // & — сразу указатель
p2.Emails = []string{"alice@example.com"}
fmt.Printf("p2 (*Person): %+v\n", p2)
// p2.Name работает как (*p2).Name — Go делает авторазыменование
}
package main
func main(){
fmt.Println("\n── 6. НУЛЕВЫЕ ЗНАЧЕНИЯ ──")
fmt.Println("В Go нет undefined. Всё инициализируется нулём:")
fmt.Printf(" int: %d\n", 0)
fmt.Printf(" float64: %f\n", 0.0)
fmt.Printf(" string: %q\n", "")
fmt.Printf(" bool: %v\n", false)
fmt.Printf(" указатель: %v\n", (*int)(nil))
fmt.Printf(" слайс: %v (nil=%v)\n", []int(nil), []int(nil) == nil)
fmt.Printf(" мапа: %v (nil=%v)\n", map[string]int(nil), map[string]int(nil) == nil)
fmt.Printf(" структура: %+v\n", Person{})
}
# Основной запуск
go run main.go
# Сборка и запуск бинарника
go build -o types-demo main.go
./types-demo
# Запуск с GODEBUG для просмотра аллокаций
GODEBUG=allocfreetrace=1 go run main.go 2>&1 | head -20