Какие ссылочные типы данных существуют в Golang? Приведите примеры.

Ответ

В Go ссылочные типы — это типы, которые хранят ссылку (адрес) на данные в памяти, а не сами данные напрямую. Это означает, что при передаче таких типов в функции или присваивании одной переменной другой, копируется ссылка, а не весь объем данных. Изменения, сделанные через одну ссылку, будут видны через другую.

К ним относятся:

  1. Указатели (*T) — хранят адрес переменной в памяти. Позволяют напрямую изменять значение по адресу.

    x := 10
    p := &x // p теперь указатель на x
    *p = 20 // Изменяем значение по адресу, на который указывает p
    fmt.Println(x) // Вывод: 20
  2. Срезы ([]T) — это дескрипторы, которые ссылаются на базовый массив. Они содержат указатель на первый элемент, длину и емкость.

    a := []int{1, 2, 3}
    b := a // b теперь ссылается на тот же базовый массив, что и a
    b[0] = 99
    fmt.Println(a) // Вывод: [99 2 3] (изменение в b повлияло на a)
  3. Мапы (map[K]V) — это хэш-таблицы, которые управляются через указатель на внутреннюю структуру данных. При передаче мапы, передается ссылка на эту структуру.

    m := make(map[string]int)
    m["one"] = 1
    n := m // n ссылается на ту же мапу, что и m
    n["two"] = 2
    fmt.Println(m) // Вывод: map[one:1 two:2]
  4. Каналы (chan T) — используются для безопасного обмена данными между горутинами. Каналы являются ссылочными типами, и при их передаче передается ссылка на структуру канала.

    ch := make(chan int)
    go func() {
    ch <- 42
    }()
    val := <-ch
    fmt.Println(val) // Вывод: 42
  5. Функции — в Go функции являются первоклассными гражданами и могут быть присвоены переменным, переданы как аргументы и возвращены из других функций. Когда вы передаете функцию, вы передаете ссылку на ее исполняемый код.

    add := func(a, b int) int { return a + b }
    operate := func(f func(int, int) int, x, y int) int {
    return f(x, y)
    }
    result := operate(add, 5, 3)
    fmt.Println(result) // Вывод: 8
  6. Интерфейсы — значение интерфейса состоит из двух компонентов: указателя на тип и указателя на значение. Когда вы присваиваете конкретное значение интерфейсу, оно хранится по ссылке.

    
    type Greeter interface {
    Greet() string
    }

type Person struct { Name string } func (p Person) Greet() string { return "Hello, " + p.Name }

var g Greeter p := Person{"Alice"} g = p // g теперь содержит ссылку на p и его тип fmt.Println(g.Greet()) // Вывод: Hello, Alice

Ответ 18+ 🔞

Хули ты такой серьёзный, а? Сейчас я тебе про ссылочные типы в Go так расскажу, что ты обосрёшься от ясности. Это ж не какая-то там хуйня с манямирками, а реально важная вещь, если не хочешь потом дебажить три дня, почему у тебя данные нахуй пропадают или плодятся как тараканы.

Смотри, есть в Go такие типы, которые не таскают за собой всю свою поебень, как какой-нибудь здоровенный массив. Они, хитрожопые, хранят только адресок, где эта поебень лежит. Как записка «спрятано под третьим кирпичом». И когда ты эту записку кому-то передаёшь — копируется только бумажка, а не весь схрон. И если кто-то по этому адресу что-то поменяет, то все, у кого такая же записка, увидят изменения. Пиздец, да? Вот они, эти штуки:

  1. *Указатели (`T`)** — это прям классика, пацанский привет из Си. Хранит адрес переменной в памяти. Хочешь поменять оригинал на расстоянии? Держи указатель.

    x := 10
    p := &x // p — это бумажка с адресом, где лежит x
    *p = 20 // Иду по адресу и пишу на двери "20"
    fmt.Println(x) // Вывод: 20. Ну да, я же там поменял, ёпта!
  2. Срезы ([]T) — вот тут многие обжигаются, блядь. Срез — это не массив! Это такой хитрожопый дескриптор: у него внутри есть указатель на кусок массива, длина и ёмкость. Когда ты присваиваешь один срез другому — копируется этот дескриптор, а не данные! Поэтому:

    a := []int{1, 2, 3}
    b := a // Скопировал бумажку с адресом склада
    b[0] = 99 // Пошёл по адресу и на складе всё переставил
    fmt.Println(a) // Вывод: [99 2 3]. Ага, сука, сюрприз! Оба среза показывают на одно и то же.
  3. Мапы (map[K]V) — с ними та же песня, только хэш-таблица. Внутри там сложная структура, но тебе передаётся только ссылка на неё. Два названия, одна мапа — как близнецы-братья, которые одну девушку на двоих водят.

    m := make(map[string]int)
    m["one"] = 1
    n := m // n — вторая пулька от того же пистолета
    n["two"] = 2
    fmt.Println(m) // Вывод: map[one:1 two:2]. Всё логично, мы же в одну бочку писали.
  4. Каналы (chan T) — инструмент для общения между горутинами. Тоже ссылочный. Создал канал — получил ссылку на трубу. Кидаешь эту ссылку в десять горутин — все они могут в одну трубу писать или из неё читать. Главное, не устроить давку, а то будет пиздец.

    ch := make(chan int)
    go func() { ch <- 42 }() // Одна горутина пишет в трубу
    val := <-ch // А эта — читает из той же трубы
    fmt.Println(val) // Вывод: 42
  5. Функции — да-да, функции в Go тоже ссылочные. Переменная с функцией — это не сам код, а указатель на него. Можно швыряться функциями как мячиками.

    add := func(a, b int) int { return a + b }
    // Передал ссылку на функцию `add` в другую функцию
    result := func(f func(int, int) int, x, y int) int { return f(x, y) }(add, 5, 3)
    fmt.Println(result) // Вывод: 8
  6. Интерфейсы — вот это, блядь, магия! Значение интерфейса — это две ссылки в одном флаконе: одна на тип, другая на само значение. Когда ты засовываешь в интерфейсную переменную свою структуру — она туда попадает по ссылке (если, конечно, это не примитив типа int, но это уже другой разговор).

    type Greeter interface { Greet() string }
    type Person struct { Name string }
    func (p Person) Greet() string { return "Hello, " + p.Name }
    
    var g Greeter
    p := Person{"Alice"}
    g = p // В g теперь лежит ссылка на p и информация о его типе
    fmt.Println(g.Greet()) // Вывод: Hello, Alice

Вот и вся магия, ёпта. Запомни эту шестёрку, и когда будешь что-то куда-то передавать — думай: «А это случайно не ссылочный тип? Не получится ли так, что я изменю его здесь, а оно еб*нется в десяти других местах?». А то потом волосы на жопе рвать будешь.