Ответ
В Go ссылочные типы — это типы, которые хранят ссылку (адрес) на данные в памяти, а не сами данные напрямую. Это означает, что при передаче таких типов в функции или присваивании одной переменной другой, копируется ссылка, а не весь объем данных. Изменения, сделанные через одну ссылку, будут видны через другую.
К ним относятся:
-
Указатели (
*T) — хранят адрес переменной в памяти. Позволяют напрямую изменять значение по адресу.x := 10 p := &x // p теперь указатель на x *p = 20 // Изменяем значение по адресу, на который указывает p fmt.Println(x) // Вывод: 20 -
Срезы (
[]T) — это дескрипторы, которые ссылаются на базовый массив. Они содержат указатель на первый элемент, длину и емкость.a := []int{1, 2, 3} b := a // b теперь ссылается на тот же базовый массив, что и a b[0] = 99 fmt.Println(a) // Вывод: [99 2 3] (изменение в b повлияло на a) -
Мапы (
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] -
Каналы (
chan T) — используются для безопасного обмена данными между горутинами. Каналы являются ссылочными типами, и при их передаче передается ссылка на структуру канала.ch := make(chan int) go func() { ch <- 42 }() val := <-ch fmt.Println(val) // Вывод: 42 -
Функции — в 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 -
Интерфейсы — значение интерфейса состоит из двух компонентов: указателя на тип и указателя на значение. Когда вы присваиваете конкретное значение интерфейсу, оно хранится по ссылке.
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 такие типы, которые не таскают за собой всю свою поебень, как какой-нибудь здоровенный массив. Они, хитрожопые, хранят только адресок, где эта поебень лежит. Как записка «спрятано под третьим кирпичом». И когда ты эту записку кому-то передаёшь — копируется только бумажка, а не весь схрон. И если кто-то по этому адресу что-то поменяет, то все, у кого такая же записка, увидят изменения. Пиздец, да? Вот они, эти штуки:
-
*Указатели (`T`)** — это прям классика, пацанский привет из Си. Хранит адрес переменной в памяти. Хочешь поменять оригинал на расстоянии? Держи указатель.
x := 10 p := &x // p — это бумажка с адресом, где лежит x *p = 20 // Иду по адресу и пишу на двери "20" fmt.Println(x) // Вывод: 20. Ну да, я же там поменял, ёпта! -
Срезы (
[]T) — вот тут многие обжигаются, блядь. Срез — это не массив! Это такой хитрожопый дескриптор: у него внутри есть указатель на кусок массива, длина и ёмкость. Когда ты присваиваешь один срез другому — копируется этот дескриптор, а не данные! Поэтому:a := []int{1, 2, 3} b := a // Скопировал бумажку с адресом склада b[0] = 99 // Пошёл по адресу и на складе всё переставил fmt.Println(a) // Вывод: [99 2 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]. Всё логично, мы же в одну бочку писали. -
Каналы (
chan T) — инструмент для общения между горутинами. Тоже ссылочный. Создал канал — получил ссылку на трубу. Кидаешь эту ссылку в десять горутин — все они могут в одну трубу писать или из неё читать. Главное, не устроить давку, а то будет пиздец.ch := make(chan int) go func() { ch <- 42 }() // Одна горутина пишет в трубу val := <-ch // А эта — читает из той же трубы fmt.Println(val) // Вывод: 42 -
Функции — да-да, функции в 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 -
Интерфейсы — вот это, блядь, магия! Значение интерфейса — это две ссылки в одном флаконе: одна на тип, другая на само значение. Когда ты засовываешь в интерфейсную переменную свою структуру — она туда попадает по ссылке (если, конечно, это не примитив типа
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
Вот и вся магия, ёпта. Запомни эту шестёрку, и когда будешь что-то куда-то передавать — думай: «А это случайно не ссылочный тип? Не получится ли так, что я изменю его здесь, а оно еб*нется в десяти других местах?». А то потом волосы на жопе рвать будешь.