Комментарии 18
Тут вещи достаточно понятные. Но для меня после плюсов было странно, что простые вещи типа использования массива переменной длины в качестве ключа в map или модификация поля хранящейся там структуры требуют некоторых приседаний.
Ну тут скорее всего это связанно с реализацией мапы в ГО, и как они решают проблему коллизии ключей.
Гошные слайсы по традиции не являются Comparable типами (для скорости..? Не знаю), а ключ мапы требует именно Comparable
для консистентности в первую очередь
как только вы сделаете ключ мутабельным, то при изменениях поменяется хэшкод и потеряете доступ к тому бакету в котором лежат данные. физически они там лежат, но другой хэш адресует другой бакет и приплыли
Comparable ? Я не очень понимаю что вы имеете ввиду под этим, приведите пожалуйста пример.
Для value type мы можем использовать ключевое слово new или литералы для создания
Поведение разное, если что: new размещает дефолтное значение типа в куче и возвращает указатель.
Для reference type мы должны использовать ключевое слово make для создания или литерал,
new тоже можно использовать, просто в куче по новому адресу будет создан объект с валидным, но не всегда удобным дефолтным значением nil
иначе вы получите panic при попытки что-то сделать с nil
Не во всех случаях: попытка чтения из nil map валидна, как и некоторые операции с nil-слайсами вроде append и т.д.
Однако ссылочные переменные (reference type) ведут себя по другому. Потому, что для их при создании выделяется память и создается дескриптор (специальная структура, которая содержит метаданные и ссылку на выделенную область памяти для мастер данных).
Т.к. это поведение обусловлено копированием вложенных указателей, то и для обычных struct/array оно тоже воспроизводимо, пусть они и записаны в value types. А вот для string нет, т.к. они иммутабельны по стандарту языка. Кстати, сам указатель тоже можно бы отнести к reference types, пусть это в go и не совсем тип.
А вообще ссылочных типов в терминологии го не существует уже 12 лет как ¯\_(ツ)_/¯
new почти никогда не используют для chan, map, slice. Да есть случаи но это оч редко и очень специфически
с нил слайсами можно делать почти все кроме взятии по индексу или записи по индексу, я тут не стал вдаваться в подробности, и надеялся, что это и так понятно читателю.
для array и struct я же привел пример, ели передавать по значению а не ссылку то оригинальный массив или структура не измениться
func makeSomething(s [3]string) {
s[0] = "first"
}
func main() {
ar := [3]string{"f", "s", "t"}
makeSomething(ar)
fmt.Println(ar) // [f s t]
}
Я их называю ссылочными переменными, потому много кто так делает и все понимают о чем я говорю, и все понимают когда я говорю передать по ссылки, это значит я передаю ссылку и ничто другое потому что там указан явно астериск *
Спасибо за коменты
для array и struct я же привел пример, ели передавать по значению а не ссылку то оригинальный массив или структура не измениться
Верно, оригинальный массив или структура не изменятся, но могут измениться данные, на которые они ссылаются, т.к. при копировании в другую переменную или при передаче в функцию по значению deep copy не выполняется: я на это хотел обратить внимание, т.к. тут можно запутаться. Slice внутри, например, является обычной структурой, где вся магия ссылочности -- это вложенный указатель на массив. Поведение легко воспроизводится и очень часто используется:
type A struct {
V *int
}
change := func(a A) {
*a.V = 2
}
a := A{V: new(int)}
*a.V = 1
fmt.Println(*a.V) // 1
change(a) // передаем по значению, но внутри есть поле-указатель
fmt.Println(*a.V) // 2
new почти никогда не используют для chan, map, slice. Да есть случаи но это оч редко и очень специфически
Давайте будем честными и сократим до "new почти никогда не используют. Да есть случаи но это оч редко и очень специфически"
для array и struct я же привел пример, ели передавать по значению а не ссылку то оригинальный массив или структура не измениться
Ну, собственно, в случае со слайсом - тоже не изменится. Я вам больше скажу, слайс в принципе не может измениться, он иммутабельный. Аппенд всегда новый слайс создает.
a := make([]int, 0, 3)
a = append(a, 1)
b = append(a, 2)
fmt.Println(a,b) // внезапно [1] и [1,2]
Я их называю ссылочными переменными
Ну, собственно, зря.
"Ссылочные типы данных в программировании – это типы, которые хранят не само значение, а адрес (ссылку) на место в памяти, где хранится это значение"
Из перечисленных вами slice, map и channel вышеуказанное верно только для map, которая буквально алиас на *hMap. Слайс и канал - иммутабельные структуры, со всеми вытекающими.
Для value type мы можем использовать ключевое слово new или литералы для создания
Для reference type мы должны использовать ключевое слово make для создания или литерал, иначе вы получите panic при попытки что-то сделать с nil
Вот это откровенная неправда.
Возьмем слайс: make - запросто, литералом - да не вопрос, var a []int - вообще по барабану. nil-слайс - валидная структура.
Нил-мапа? Ну, при попытке чтения, например, ничего страшного не случится. А вот при попытке записать - паника будет. Но я вам интереснее скажу: при инициализации литералом попытка записи вызывала панику до одной из последних версий.
Канал... А как вы его литералом инициализировать собираетесь?
Да и вообще, в кучу кони и люди. Литералы ортогональны ссылочности/нессылочности. Они вообще вещь в себе - просто сокращенная запись для "создать переменную, проинициализировать ее начальным значением". Для каких-то типов есть, для каких-то не придуманы.
я тут не стал вдаваться в подробности, и надеялся, что это и так понятно читателю
Ну т.е. вы не стали в принципе вдумываться в то, что написали, и решили, что читателю и так понятно:
а) что фраза "в го есть 3 ссылочных типа: мапа, слайс и канал" означает "в го не используется терминология ссылочных типов"
б) что фраза "слайс должен быть явно инициализирован словом make, иначе вы получите панику" на самом деле означает "слайс можно инициализировать как угодно, паники от него не добьешься"
Ну и примерно вся статья такая....
Для value type мы можем использовать ключевое слово new или литералы для создания
Довольно скользкое утверждение, поскольку new возвращает не значение, а указатель на значение.
иначе вы получите panic при попытки что-то сделать с nil
Слайсы довольно осмысленно ведут себя, если они - nil. У них нулевая длина и к ним можно append.
да со слайсами nil можно делать все кроме взятия значения по индексу и сета нового значения по индексу, все остальное будет работать. Я расчитывал на то, что читатель понимает, что если я пишу манипуляции с nil, то понятно о чем я говорю и не надо разъяснять.
Для new да я не уточнил, что возвращается указатель. Но я говорил образно, что происходит. у ок, тут можно было расписать получше, спасибо
да со слайсами nil можно делать все кроме взятия значения по индексу и сета нового значения по индексу, все остальное будет работать
Ровно как и со слайсами нулевой длинны. и len() от nil-слайса возвращает 0.
Для new да я не уточнил, что возвращается указатель
Все-таки, я бы поспорил с Вашей классификацией на reference/value types. Указатель - определенно reference, создаётся вызовом new (или литералом). Указатель на функцию - определенно reference, создаётся путем описания анонимной функции или упоминанием имени неанонимной функции (или метода объекта). Может прихватывать данные, в случае замыкания.
Замечание: слайсы не то чтоб прямо ссылочный тип. Он скорее как морская свинка, которая не свинка, и не морская.
Для примера могу автору попробовать сделать append к слайсу, переданному как аргумент функции, потом поменять какой-то элемент по индексу и объяснить себе разницу в поведении в зависимости от capacity
если ты значение аппенд сохранишь в ту же переменную или неважно в какую, то оригинальный слайс никак не поменяется.
func makeSomething(s []string) {
s = append(s, "item")
s[1] = "meti"
}
func main() {
ar := []string{"f", "s", "t"}
makeSomething(ar)
fmt.Println(ar) // [f s t]
}
Эти сложные map & slice в GO