В чем основная разница между t = & T {} и t = new (T) - PullRequest
0 голосов
/ 19 января 2020

Кажется, что оба способа создания нового указателя объекта со всеми значениями элемента "0", оба возвращают указатель:

type T struct{}
...
t1:=&T{}
t2:=new(T)

Так в чем же разница между t1 и t2, или есть все, что «новое» может сделать, в то время как & T {} не может, или наоборот?

Ответы [ 3 ]

4 голосов
/ 19 января 2020

[…] может ли что-то, что «новое» может сделать, в то время как & T {} не может, или наоборот?

Я могу представить три различия:

  • Синтаксис «составного литерала» (часть T{} в &T{}) работает только для «структур, массивов, фрагментов и карт» [ link ], тогда как функция new работает для любого типа [ link ].
  • Для типа структуры или массива функция new всегда генерирует нулевые значения для своих элементов, в то время как составной литерал синтаксиса позволяет инициализировать некоторые из элементы с ненулевыми значениями, если хотите.
  • Для среза или типа карты функция new всегда возвращает указатель на nil, тогда как синтаксис составного литерала всегда возвращает инициализированный срез или карту. (Для карт это очень важно, потому что вы не можете добавлять элементы к nil.) Кроме того, составной литеральный синтаксис может даже создавать не -пустой срез или карту.

(Вторая и третья маркированные точки на самом деле являются двумя аспектами одного и того же: функция new всегда создает нулевые значения, но я перечисляю их отдельно, потому что значения немного различаются для разных типов.)

0 голосов
/ 19 января 2020

См. ответ Руаха . Однако я хочу указать на некоторые детали внутренней реализации. Вы не должны использовать их в производственном коде, но они помогают осветить то, что действительно происходит за кулисами, во время выполнения 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
}
0 голосов
/ 19 января 2020

Для структур и других композитов оба одинаковы.

t1:=&T{}
t2:=new(T)
//Both are same

Нельзя вернуть адрес безымянной переменной, инициализированной нулевым значением других базовых c типов, таких как int, без использования new. Вам нужно будет создать именованную переменную и затем взять ее адрес.

func newInt() *int {            
    return new(int)                 
}               

func newInt() *int {
    // return &int{} --> invalid
    var dummy int
    return &dummy
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...