почему значения среза могут иногда устаревать, но никогда не отображать значения? - PullRequest
1 голос
/ 04 апреля 2019

Я считаю, что функция карты срезов и канал часто упоминаются вместе как ссылочные типы . Тем не менее, я замечаю, что некоторые срезы не имеют ссылочного поведения, так как они могут устареть:

   var s []int
   //must update slice value
   s = append(s, ...) 

или

   //must use pointer if we want to expose the change
   func foo(s *[]int) error  
   //or change the function signature to return it like _append_
   func foo(s []int) (r slice, err error)

Обычно я понимаю это, имея в виду внутренние компоненты реализации дескриптора слайса: значение слайса можно рассматривать как структуру len, cap и указателя данных.

Но значения карт никогда не должны беспокоиться как

   m := make(map[string]int)
   ...
   // don't know how to express with insertion, but you know what i mean.
   m = delete(m, "well")  

Почему? Является ли значение карты просто указателем на дескриптор карты? Если так, то почему бы не сделать ломтик таким образом?

Ответы [ 2 ]

7 голосов
/ 04 апреля 2019

В Go нет ссылочных типов, как у вас в C ++.В Go все передается по значению.Когда термин «ссылочный тип» используется в Go, это означает тип, который ссылается на данные, которые они должны представлять (через указатели).

Срезы - это небольшие структурные структуры данных, представленные типом reflect.SliceHeader:

type SliceHeader struct {
        Data uintptr
        Len  int
        Cap  int
}

Содержит указатель на первый элемент среза в базовом массиве (поле SliceHeader.Data).Эта структура небольшая и эффективна для передачи в качестве значения, нет необходимости передавать ее адрес (и разыменовывать ее для косвенного доступа к любому из ее полей).Элементы слайса хранятся не в заголовке слайса, а в массиве вне области памяти заголовка.Это означает, что изменение «остроконечного» элемента приведет к изменению элемента исходного среза.

Когда вы добавляете (более 0) элементов к срезу, поле Len в заголовке должно измениться, поэтому новыйСрез, который описывает срез с дополнительными элементами, должен отличаться от того, который был до добавления, поэтому вам необходимо присвоить возвращаемое значение встроенной функции append().(Другие значения также могут измениться, но Len обязательно должен измениться.)

Карты реализованы в виде указателей на структуру runtime.hmap:

type hmap struct {
    // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
    // Make sure this stays in sync with the compiler's definition.
    count     int // # live cells == size of map.  Must be first (used by len() builtin)
    flags     uint8
    B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
    noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
    hash0     uint32 // hash seed

    buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
    oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
    nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

    extra *mapextra // optional fields
}

Как видите, этонамного более сложная структура данных, чем заголовок среза, и намного больше, передача этого значения не будет эффективной.

Добавление / удаление элементов (пар ключ-значение) из карты хранится в контейнерах, которыессылки на поля этой структуры, но поскольку карты обрабатываются как указатели под капотом, вам не нужно присваивать результат таких операций.

Для завершения каналы также реализованы как указатели, указывающиек типу runtime пакета hchan:

type hchan struct {
    qcount   uint           // total data in the queue
    dataqsiz uint           // size of the circular queue
    buf      unsafe.Pointer // points to an array of dataqsiz elements
    elemsize uint16
    closed   uint32
    elemtype *_type // element type
    sendx    uint   // send index
    recvx    uint   // receive index
    recvq    waitq  // list of recv waiters
    sendq    waitq  // list of send waiters

    // lock protects all fields in hchan, as well as several
    // fields in sudogs blocked on this channel.
    //
    // Do not change another G's status while holding this lock
    // (in particular, do not ready a G), as this can deadlock
    // with stack shrinking.
    lock mutex
}

Это опять-таки "толстая" структура, которая обрабатывается как значения карты.

См. связанные вопросы:

срез по сравнению с картой, используемой в параметре

Добавление к срезу с достаточной емкостью с использованием приемника значения

Передаются ли кусочки Голанга по значению?

Что означают "семантика значений" и "семантика указателей" в Go?

4 голосов
/ 04 апреля 2019

Slice - это тонкая бумажная оболочка для непрерывного фрагмента памяти, и часто полезно повторно использовать этот контент, частично или полностью (избегая копирования данных). Карта не имеет ни одной из этих характеристик. Это сложная структура данных со сложным поведением, и вы не можете повторно использовать ее хранилище (как вы делаете с кусочками).

...