MidТеория6 min

Массивы и слайсы

Массивы фиксированного размера, слайсы, внутреннее устройство, append, copy, subslicing и производительность

Массивы (Arrays)

Массив в Go — это фиксированная по размеру последовательность элементов одного типа. Размер массива является частью его типа.

package main

import "fmt"

func main() {
    // Declare with explicit size
    var a [5]int // [0 0 0 0 0]

    // Array literal
    b := [3]string{"Go", "Rust", "Python"}

    // Size inferred from elements
    c := [...]int{10, 20, 30, 40, 50} // [...]  = compiler counts

    // Indexed initialization
    d := [5]int{0: 100, 4: 500} // [100 0 0 0 500]

    // Access and modify
    a[0] = 1
    a[4] = 5
    fmt.Println(a)    // [1 0 0 0 5]
    fmt.Println(b)    // [Go Rust Python]
    fmt.Println(c)    // [10 20 30 40 50]
    fmt.Println(d)    // [100 0 0 0 500]
    fmt.Println(len(a)) // 5

    // Arrays are VALUE types — assignment creates a COPY
    original := [3]int{1, 2, 3}
    copy := original
    copy[0] = 999
    fmt.Println(original) // [1 2 3]   — not affected!
    fmt.Println(copy)     // [999 2 3]

    // Arrays can be compared with ==
    x := [3]int{1, 2, 3}
    y := [3]int{1, 2, 3}
    z := [3]int{1, 2, 4}
    fmt.Println(x == y) // true
    fmt.Println(x == z) // false
    // [3]int and [4]int are DIFFERENT types — cannot compare
}

Ключевые свойства массивов

Свойство Описание
Размер фиксирован [5]int и [10]int — разные типы
Value type Присваивание и передача в функцию создают копию
Сравнимы Можно сравнивать == и !=
Длина через len() Определена в compile-time
Передача в функцию Копируется полностью (неэффективно для больших массивов)

На практике: Массивы в Go используются редко. Почти всегда используют слайсы. Массивы полезны, когда размер известен на этапе компиляции и не меняется (например, SHA-256 хэш [32]byte).

Слайсы (Slices)

Слайс — это динамическая обёртка над массивом. Это самая используемая коллекция в Go.

Внутреннее устройство слайса

Слайс состоит из трёх полей (slice header):

type slice struct {
    ptr *array   // Pointer to underlying array
    len int      // Number of elements in the slice
    cap int      // Capacity (size of underlying array from ptr)
}
Слайс: [1, 2, 3] (len=3, cap=5)

     ptr ──────► ┌───┬───┬───┬───┬───┐
                  │ 1 │ 2 │ 3 │ 0 │ 0 │  ← underlying array
                  └───┴───┴───┴───┴───┘
                  ◄── len=3 ──►
                  ◄──── cap=5 ─────────►

Создание слайсов

package main

import "fmt"

func main() {
    // 1. Slice literal
    s1 := []int{1, 2, 3, 4, 5}

    // 2. make(type, length, capacity)
    s2 := make([]int, 5)      // len=5, cap=5, [0 0 0 0 0]
    s3 := make([]int, 0, 10)  // len=0, cap=10, []

    // 3. Slicing an array or another slice
    arr := [5]int{10, 20, 30, 40, 50}
    s4 := arr[1:4]  // [20, 30, 40] — elements at index 1, 2, 3

    // 4. nil slice (var declaration without initialization)
    var s5 []int    // nil slice: s5 == nil, len=0, cap=0

    // 5. Empty non-nil slice
    s6 := []int{}   // NOT nil, len=0, cap=0
    s7 := make([]int, 0) // NOT nil, len=0, cap=0

    fmt.Println(s1, len(s1), cap(s1)) // [1 2 3 4 5] 5 5
    fmt.Println(s2, len(s2), cap(s2)) // [0 0 0 0 0] 5 5
    fmt.Println(s3, len(s3), cap(s3)) // [] 0 10
    fmt.Println(s4, len(s4), cap(s4)) // [20 30 40] 3 4
    fmt.Println(s5, s5 == nil)         // [] true
    fmt.Println(s6, s6 == nil)         // [] false
    fmt.Println(s7, s7 == nil)         // [] false
}

nil slice vs empty slice

Характеристика nil slice empty slice
Объявление var s []int s := []int{} или make([]int, 0)
== nil true false
len(s) 0 0
cap(s) 0 0
append() Работает Работает
JSON marshal null []
range Не выполняется Не выполняется

Совет: Для JSON API используйте []int{} или make([]int, 0), чтобы получить [], а не null.

Слайсинг (Subslicing)

package main

import "fmt"

func main() {
    s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

    // s[low:high] — from low (inclusive) to high (exclusive)
    fmt.Println(s[2:5])   // [2 3 4]
    fmt.Println(s[:3])    // [0 1 2]      — from start
    fmt.Println(s[7:])    // [7 8 9]      — to end
    fmt.Println(s[:])     // [0 1 2 ... 9] — full copy of header

    // Three-index slice s[low:high:max] — controls capacity
    sub := s[2:5:7]
    fmt.Println(sub)           // [2 3 4]
    fmt.Println(len(sub))     // 3
    fmt.Println(cap(sub))     // 5 (7 - 2)

    // WARNING: subslice shares the underlying array!
    sub[0] = 999
    fmt.Println(s[2])  // 999 — original is modified!
}

Опасность разделяемого массива

package main

import "fmt"

func main() {
    // Subslice shares the underlying array
    original := []int{1, 2, 3, 4, 5}
    sub := original[1:3] // [2, 3], shares array with original

    // Appending within capacity modifies original!
    sub = append(sub, 999)
    fmt.Println(original) // [1 2 3 999 5] — element 4 was overwritten!

    // SAFE way: use three-index slice to limit capacity
    original2 := []int{1, 2, 3, 4, 5}
    sub2 := original2[1:3:3] // len=2, cap=2 (no room for append)
    sub2 = append(sub2, 999) // Forces new allocation
    fmt.Println(original2)   // [1 2 3 4 5] — not affected!

    // Or use copy()
    original3 := []int{1, 2, 3, 4, 5}
    sub3 := make([]int, 2)
    copy(sub3, original3[1:3])
    sub3 = append(sub3, 999) // Independent
    fmt.Println(original3)   // [1 2 3 4 5] — not affected
}

append и copy

append

package main

import "fmt"

func main() {
    var s []int

    // Append single elements
    s = append(s, 1)
    s = append(s, 2, 3)

    // Append another slice
    more := []int{4, 5, 6}
    s = append(s, more...)

    fmt.Println(s) // [1 2 3 4 5 6]

    // IMPORTANT: append may return a NEW slice!
    // Always assign the result back
    s = append(s, 7) // s = ... is REQUIRED

    // Growth strategy (simplified):
    // cap < 256:  double capacity
    // cap >= 256: grow by ~25% + some padding
    s2 := make([]int, 0)
    for i := 0; i < 10; i++ {
        s2 = append(s2, i)
        fmt.Printf("len=%d cap=%d\n", len(s2), cap(s2))
    }
    // len=1 cap=1
    // len=2 cap=2
    // len=3 cap=4
    // len=4 cap=4
    // len=5 cap=8
    // ...
}

copy

package main

import "fmt"

func main() {
    src := []int{1, 2, 3, 4, 5}

    // copy(dst, src) — returns number of elements copied
    dst := make([]int, 3)
    n := copy(dst, src)
    fmt.Println(dst, n) // [1 2 3] 3

    // Copy to a larger slice
    dst2 := make([]int, 10)
    n = copy(dst2, src)
    fmt.Println(dst2, n) // [1 2 3 4 5 0 0 0 0 0] 5

    // Copy within the same slice (overlapping is safe)
    s := []int{0, 1, 2, 3, 4}
    copy(s[1:], s[0:]) // Shift right
    fmt.Println(s)      // [0 0 1 2 3]

    // Deep clone a slice
    original := []int{10, 20, 30}
    clone := make([]int, len(original))
    copy(clone, original)
    // Or using append:
    clone2 := append([]int(nil), original...)
    // Or using slices.Clone (Go 1.21+):
    // clone3 := slices.Clone(original)

    clone[0] = 999
    fmt.Println(original) // [10 20 30] — unaffected
    fmt.Println(clone)    // [999 20 30]
    fmt.Println(clone2)   // [10 20 30]
}

Slice Tricks

Стандартные приёмы работы со слайсами:

Удаление элемента

package main

import "fmt"

func main() {
    s := []int{1, 2, 3, 4, 5}

    // Delete element at index i (preserving order)
    i := 2 // Remove element '3'
    s = append(s[:i], s[i+1:]...)
    fmt.Println(s) // [1 2 4 5]

    // Delete element (NOT preserving order) — faster
    s2 := []int{1, 2, 3, 4, 5}
    j := 2
    s2[j] = s2[len(s2)-1] // Replace with last element
    s2 = s2[:len(s2)-1]    // Shrink
    fmt.Println(s2) // [1 2 5 4]
}

Вставка элемента

func insert(s []int, index int, value int) []int {
    // Grow by one
    s = append(s, 0)
    // Shift elements right
    copy(s[index+1:], s[index:])
    // Insert value
    s[index] = value
    return s
}

func main() {
    s := []int{1, 2, 4, 5}
    s = insert(s, 2, 3)
    fmt.Println(s) // [1 2 3 4 5]
}

Фильтрация (без аллокации)

func filter(s []int, fn func(int) bool) []int {
    // Reuse the same underlying array
    result := s[:0] // len=0, same underlying array
    for _, v := range s {
        if fn(v) {
            result = append(result, v)
        }
    }
    return result
}

func main() {
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    evens := filter(numbers, func(n int) bool { return n%2 == 0 })
    fmt.Println(evens) // [2 4 6 8 10]
}

Реверс

func reverse(s []int) {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
}

// With slices package (Go 1.21+)
// slices.Reverse(s)

Пакет slices (Go 1.21+)

Go 1.21 добавил пакет slices с обобщёнными (generic) функциями:

package main

import (
    "fmt"
    "slices"
)

func main() {
    s := []int{3, 1, 4, 1, 5, 9, 2, 6}

    // Sort
    slices.Sort(s)
    fmt.Println(s) // [1 1 2 3 4 5 6 9]

    // Binary search (slice must be sorted)
    i, found := slices.BinarySearch(s, 4)
    fmt.Println(i, found) // 4 true

    // Contains
    fmt.Println(slices.Contains(s, 5)) // true

    // Index
    fmt.Println(slices.Index(s, 9)) // 7

    // Min, Max
    fmt.Println(slices.Min(s)) // 1
    fmt.Println(slices.Max(s)) // 9

    // Clone
    clone := slices.Clone(s)
    clone[0] = 999
    fmt.Println(s[0]) // 1 (unaffected)

    // Compact (remove consecutive duplicates)
    dup := []int{1, 1, 2, 2, 3, 3, 3}
    dup = slices.Compact(dup)
    fmt.Println(dup) // [1 2 3]

    // Reverse
    r := []int{1, 2, 3, 4, 5}
    slices.Reverse(r)
    fmt.Println(r) // [5 4 3 2 1]

    // Equal
    a := []int{1, 2, 3}
    b := []int{1, 2, 3}
    fmt.Println(slices.Equal(a, b)) // true

    // Delete (Go 1.21+)
    d := []int{0, 1, 2, 3, 4}
    d = slices.Delete(d, 1, 3) // Delete elements at indices 1, 2
    fmt.Println(d) // [0 3 4]

    // Insert
    ins := []int{1, 2, 5, 6}
    ins = slices.Insert(ins, 2, 3, 4)
    fmt.Println(ins) // [1 2 3 4 5 6]
}

Производительность

Предварительное выделение ёмкости

package main

import "fmt"

func main() {
    // BAD: no pre-allocation, causes multiple reallocations
    var bad []int
    for i := 0; i < 10000; i++ {
        bad = append(bad, i)
    }

    // GOOD: pre-allocate with known size
    good := make([]int, 0, 10000)
    for i := 0; i < 10000; i++ {
        good = append(good, i)
    }

    // BEST: if you know the exact size, fill directly
    best := make([]int, 10000)
    for i := range best {
        best[i] = i
    }

    fmt.Println(len(bad), len(good), len(best))
}

Предотвращение утечек памяти

package main

// Memory leak: subslice keeps the entire original array alive
func getFirstThreeBad(data []int) []int {
    return data[:3] // Holds reference to entire underlying array!
}

// Fixed: copy to a new slice
func getFirstThreeGood(data []int) []int {
    result := make([]int, 3)
    copy(result, data[:3])
    return result // Original array can be garbage collected
}

// Same issue with strings (strings are backed by byte slices)
func getPrefix(s string) string {
    return string([]byte(s[:10])) // Copy to release the original
}

Проверь себя

Какова стратегия роста слайса при append для ёмкости < 256?

Что произойдёт, если сделать подслайс и изменить элемент в нём?

Как безопасно создать полную независимую копию слайса?

Почему нужно всегда присваивать результат `append` обратно в переменную?

Чем `nil` слайс отличается от пустого слайса при JSON-сериализации?

Code Challenges

Сумма двух чисел

Дан массив целых чисел и целевое значение. Верните индексы двух чисел, сумма которых равна целевому значению.

Test Cases

1. Input: [2,7,11,15], target=9→ Expected: [0,1]
2. Input: [3,2,4], target=6→ Expected: [1,2]
3. Input: [3,3], target=6→ Expected: [0,1]

Перестановки строки

Напишите функцию, которая генерирует все перестановки строки. Верните отсортированный слайс.

Test Cases

1. Input: "ab"→ Expected: ["ab","ba"]
2. Input: "abc"→ Expected: ["abc","acb","bac","bca","cab","cba"]
3. Input: "a"→ Expected: ["a"]

Максимальная сумма подмассива

Реализуйте алгоритм Кадане для нахождения максимальной суммы подмассива.

Test Cases

1. Input: [-2,1,-3,4,-1,2,1,-5,4]→ Expected: 6
2. Input: [1]→ Expected: 1
3. Input: [-1,-2,-3]→ Expected: -1
4. Input: [5,4,-1,7,8]→ Expected: 23