← Назад к вопросам

Как слайс передается в функцию?

2.2 Middle🔥 234 комментариев
#Основы Go

Комментарии (4)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Передача слайса в функцию в Go

В Go слайс передается в функцию по значению, но это значение содержит указатель на базовый массив, что приводит к особенностям поведения, которые часто вызывают путаницу.

Структура слайса

Сначала важно понимать, что такое слайс изнутри. Слайс - это дескриптор, содержащий три компонента:

  • Указатель (ptr) на первый элемент базового массива
  • Длину (len) - количество элементов в слайсе
  • Емкость (cap) - размер базового массива
package main

import "fmt"

// Структура, аналогичная внутреннему представлению слайса
type sliceHeader struct {
    ptr *int    // указатель на массив
    len int     // длина
    cap int     // емкость
}

Что происходит при передаче в функцию

Когда вы передаете слайс в функцию, происходит копирование дескриптора слайса, а не содержимого базового массива. Это означает:

package main

import "fmt"

func modifySlice(s []int) {
    // Изменяем существующий элемент - это повлияет на оригинал
    if len(s) > 0 {
        s[0] = 100
    }
    
    // Добавляем элемент - поведение зависит от емкости
    s = append(s, 200)
    fmt.Println("Inside function:", s) // [100 2 3 200]
}

func main() {
    original := []int{1, 2, 3}
    
    fmt.Println("Before:", original) // [1 2 3]
    modifySlice(original)
    fmt.Println("After:", original)  // [100 2 3]
}

Ключевые особенности поведения

1. Изменение существующих элементов

Изменения элементов внутри функции видны вызывающей стороне, так как оба слайса (оригинал и копия) указывают на один и тот же базовый массив.

func changeElements(s []int) {
    for i := range s {
        s[i] = s[i] * 2  // Изменения будут видны снаружи
    }
}

2. Изменение длины и емкости (append)

Операция append может создавать новый базовый массив, если не хватает емкости:

func appendToSlice(s []int) []int {
    s = append(s, 999)
    return s  // Нужно возвращать, если был реаллокация
}

func main() {
    slice := make([]int, 2, 3) // len=2, cap=3
    slice[0], slice[1] = 1, 2
    
    slice = appendToSlice(slice)  // Емкости хватает
    fmt.Println(slice)  // [1 2 999]
}

3. Случай реаллокации массива

Если append превышает емкость, создается новый массив, и изменения в новом слайсе не видны в оригинале:

func trickyAppend(s []int) {
    s[0] = 50       // Изменит оригинал
    s = append(s, 4, 5, 6, 7)  // Превысит емкость
    s[0] = 100      // Не изменит оригинал (новый массив!)
}

func main() {
    arr := []int{1, 2, 3}
    trickyAppend(arr)
    fmt.Println(arr)  // [50 2 3]
}

Практические рекомендации

  • Для модификации содержимого слайса можно просто передавать его по значению
  • Для изменения длины (особенно через append) рекомендуется возвращать новый слайс
  • Если нужно гарантированно изменить оригинал, передавайте указатель на слайс:
func modifyWithPointer(s *[]int) {
    *s = append(*s, 100)
    
    // Доступ к элементам через разыменование
    if len(*s) > 0 {
        (*s)[0] = 999
    }
}

func main() {
    data := []int{1, 2, 3}
    modifyWithPointer(&data)
    fmt.Println(data)  // [999 2 3 100]
}

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

Передача слайса по значению эффективна, так как копируется только дескриптор (24 байта на 64-битной системе), а не все элементы массива. Это делает слайсы идиоматичным и эффективным способом работы с коллекциями данных в Go.

Итог

Слайс в Go передается по значению, но содержит указатель на данные, что дает гибридное поведение: изменения элементов видны снаружи, а изменения структуры (длины/емкости) требуют особого внимания при использовании append. Понимание этой механики критически важно для написания корректного кода на Go.