См. ответ Руаха . Однако я хочу указать на некоторые детали внутренней реализации. Вы не должны использовать их в производственном коде, но они помогают осветить то, что действительно происходит за кулисами, во время выполнения Go.
По сути, срез представлен тремя значениями. Пакет reflect
экспортирует тип, SliceHeader
:
SliceHeader - представление слайса во время выполнения. Он не может использоваться безопасно или переносимо, и его представление может измениться в более позднем выпуске. Более того, поле данных недостаточно для гарантии того, что данные, на которые оно ссылается, не будут собираться мусором, поэтому программы должны хранить отдельный, правильно типизированный указатель на базовые данные.
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
Если мы используем это для проверки переменной типа []T
(для любого типа T
), мы можем увидеть три части: указатель на базовый массив, длину и емкость. Внутри значение среза v
всегда имеет все три эти части. Есть общее условие, которое, на мой взгляд, должно удерживать , и если вы не используете unsafe
, чтобы его сломать, то, судя по проверке, оно будет удерживаться (в любом случае на основании ограниченного тестирования ):
- либо поле
Data
не равно нулю (в этом случае Len
и Cap
может, но не обязательно должно быть ненулевым), либо - *
Data
поле равно нулю (в этом случае Len
и Cap
должны быть равны нулю).
Это значение среза v
равно nil
, если поле Data
равно нулю.
Используя пакет unsafe
, мы можем сознательно сломать его (а затем положить все обратно - и, надеюсь, ничего не случится, пока мы его сломаем) и, таким образом, осмотреть куски. Когда этот код на Go Playground выполняется (также есть копия ниже), он печатает:
via &literal: base of array is 0x1e52bc; len is 0; cap is 0.
Go calls this non-nil.
via new: base of array is 0x0; len is 0; cap is 0.
Go calls this nil even though we clobbered len() and cap()
Making it non-nil by unsafe hackery, we get [42] (with cap=1).
after setting *p1=nil: base of array is 0x0; len is 0; cap is 0.
Go calls this nil even though we clobbered len() and cap()
Making it non-nil by unsafe hackery, we get [42] (with cap=1).
Сам код немного длинный, поэтому я оставил его до конца (или используйте вышеуказанную ссылку на игровую площадку). Но это показывает, что фактический p == nil
тест в исходном коде компилируется только с проверкой поля Data
.
Когда вы делаете:
p2 := new([]int)
функция new
на самом деле выделяет только срез заголовок . Он устанавливает все три части на ноль и возвращает указатель на результирующий заголовок. Таким образом, *p2
содержит три нулевых поля, что делает его правильным nil
значением.
С другой стороны, когда вы делаете:
p1 := &[]int{}
компилятор Go создает пустой массив (размером ноль, с нулевыми значениями), а затем создает заголовок слайса: часть указателя указывает на пустой массив, а длина и емкость устанавливаются на ноль. Затем p1
указывает на этот заголовок с полем, отличным от nil Data
. Более позднее назначение, *p1 = nil
, записывает нули во все три поля.
Позвольте мне повторить это жирным шрифтом: это не обещано спецификацией языка, это просто фактическая реализация в действии.
Карты работают очень похоже. Переменная карты фактически является указателем на заголовок карты . Детали заголовков карты еще менее доступны, чем заголовки слайсов: для них нет типа reflect
. Реальная реализация доступна для просмотра здесь в type hmap
(обратите внимание, что она не экспортируется).
Это означает, что m2 := new(map[T1]T2)
действительно выделяет только один указатель и установите этот указатель на ноль. Там нет реальной карты! Функция new
возвращает нулевой указатель, а m2
равен nil
. Аналогично var m1 map[T1]T2
просто устанавливает значение простого указателя в m1
на nil
. Но var m3 map[T1]T2{}
выделяет фактическую структуру hmap
, заполняет ее и указывает m3
на нее. Мы можем еще раз заглянуть за занавес на Go Playground , с кодом, который не гарантированно будет работать завтра, чтобы увидеть это в действии.
Как кто-то пишет Go программы Вам не нужно , нужно , чтобы знать что-либо из этого. Но если вы работали с языками более низкого уровня (например, ассемблером и C), это многое объясняет. В частности, они объясняют , почему нельзя вставить в nil-карту: сама переменная map содержит значение указателя, и до тех пор, пока сама переменная map не имеет -nil указатель на (возможно, пустой) заголовок карты, нет способа сделать вставку. Вставка может выделить новую карту и вставить данные, но переменная карты не будет указывать на правильный объект заголовка hmap
.
(Авторы языка могли бы выполнить эту работу используя второй уровень косвенности: переменная карты может быть указателем, указывающим на переменную, которая указывает на заголовок карты, или они могли бы сделать так, чтобы переменные карты всегда указывали на заголовок, и new
фактически выделял заголовок, как это делает make
, тогда никогда бы не было нулевой карты. Но они не сделали ни одного из них, и мы получаем то, что получаем, и это хорошо: вам просто нужно знать, чтобы инициализировать карту.)
Вот инспектор срезов. (Используйте ссылку игровая площадка для просмотра инспектора карты: учитывая, что мне пришлось скопировать определение hmap
из среды выполнения, я ожидаю, что оно будет особенно fr agile и не стоит показывать. Структура заголовка среза кажется намного меньше может меняться со временем.)
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
p1 := &[]int{}
p2 := new([]int)
show("via &literal", *p1)
show("\nvia new", *p2)
*p1 = nil
show("\nafter setting *p1=nil", *p1)
}
// This demonstrates that given a slice (p), the test
// if p == nil
// is really a test on p.Data. If it's zero (nil),
// the slice as a whole is nil. If it's nonzero, the
// slice as a whole is non-nil.
func show(what string, p []int) {
pp := unsafe.Pointer(&p)
sh := (*reflect.SliceHeader)(pp)
fmt.Printf("%s: base of array is %#x; len is %d; cap is %d.\n",
what, sh.Data, sh.Len, sh.Cap)
olen, ocap := len(p), cap(p)
sh.Len, sh.Cap = 1, 1 // evil
if p == nil {
fmt.Println(" Go calls this nil even though we clobbered len() and cap()")
answer := 42
sh.Data = uintptr(unsafe.Pointer(&answer))
fmt.Printf(" Making it non-nil by unsafe hackery, we get %v (with cap=%d).\n",
p, cap(p))
sh.Data = 0 // restore nil-ness
} else {
fmt.Println("Go calls this non-nil.")
}
sh.Len, sh.Cap = olen, ocap // undo evil
}