Как заранее выделить и заполнить часть указателей идиоматическим способом?

http://play.golang.org/p/j-Y0mQzTdP

package main

import "fmt"

type UselessStruct struct {
    a int
    b int
}

func main() {
    mySlice := make([]*UselessStruct, 5)
    for i := 0; i != 5; i++ {
        mySlice = append(mySlice, &UselessStruct{})
    }

    fmt.Println(mySlice)
}

Выходы: [<nil> <nil> <nil> <nil> <nil> 0xc010035160 0xc010035170 0xc010035180 0xc010035190 0xc0100351a0]

Что я хотел бы сделать, так это предварительно выделить память для 5 UselessStructs, хранящихся в виде указателей. Если я объявлю фрагмент значений структуры eq:

mySlice := make([]UselessStruct, 5)

то это создает 5 пустых структур - добавление не заменяет пустые структуры, а продолжает добавлять к срезу, поэтому конечный результат с этим кодом:

http://play.golang.org/p/zBYqGVO85h

package main

import "fmt"

type UselessStruct struct {
    a int
    b int
}

func main() {
    mySlice := make([]UselessStruct, 5)
    for i := 0; i != 5; i++ {
        mySlice = append(mySlice, UselessStruct{})
    }

    fmt.Println(mySlice)
}

is: [{0 0} {0 0} {0 0} {0 0} {0 0} {0 0} {0 0} {0 0} {0 0} {0 0}]

Какой идиоматический способ предварительного выделения и заполнения фрагментов?


person Dante    schedule 03.06.2013    source источник


Ответы (4)


Для вашего первого примера я бы сделал:

mySlice := make([]*UselessStruct, 5)
for i := range mySlice {
     mySlice[i] = new(UselessStruct)
}

Проблема, с которой вы сталкиваетесь в обоих примерах, заключается в том, что вы добавляете фрагмент, который уже имеет правильную длину. Если вы установите mySlice := make([]*UselessStruct, 5), вы запросите фрагмент нулевых указателей длины 5. Если вы добавите один указатель, он теперь будет иметь длину 6.

Вместо этого вы хотите использовать mySlice := make([]*UselessStruct, 0, 5). Это создает срез длиной 0, но емкостью 5. Каждый раз, когда вы добавляете, он будет добавлять единицу к длине, но не будет перераспределяться, пока вы не превысите емкость среза.

mySlice := make([]*UselessStruct, 0, 5)
for i := 0; i != 5; i++ {
    mySlice = append(mySlice, &UselessStruct{})
}
// mySlice is [0xc010035160 0xc010035170 0xc010035180 0xc010035190 0xc0100351a0]

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

person Stephen Weinberg    schedule 04.06.2013
comment
Не могли бы вы уточнить из соображений чисто стиля? Лично я нахожу решение append более логичным и удобочитаемым, поскольку оно позволяет избежать промежуточных состояний, в которых некоторые элементы имеют нулевые значения. Кроме того, не является ли изначально заполнение среза нулевыми значениями впустую? - person ke.; 04.03.2019
comment
Вся память, выделенная в Go, обнуляется. Так что нет напрасных усилий. - person Stephen Weinberg; 06.03.2019

Есть два способа сделать это. Один из них заключается в предварительном распределении слотов, как вы это сделали. Но вместо использования append вы просто индексируете один из существующих слотов:

mySlice[i] = &UselessStruct{}

Второй — использовать «перегруженную» версию make. Вы указываете нулевую длину, но емкость 5.

package main

type T struct {
    A int
    B int
}

func main() {
    mySlice := make([]*T, 0, 5)
    for i := 0; i < 5; i++ {
        mySlice = append(mySlice, &T{1, 2})
    }
}

mySlice := make([]*T, 0, 5) инициализирует срез с нулевой длиной, но все же предварительно выделяет достаточно места для 5 записей.

person jimt    schedule 03.06.2013

Вы уверены, что вам нужны указатели? Ваша структура имеет нулевое значение, поэтому:

mySlice := make([]UselessStruct, 5) # has memory preallocated for 5 UselessStructs.

А поскольку срезы являются ссылочными типами, у вас фактически есть 5 указателей на эти 5 бесполезных структур.

Если вам нужно получить ссылку на отдельную структуру для передачи, вы можете просто так

myStruct := &mySlice[0]

И теперь у вас есть указатель на UseLessStruct, который вы можете использовать по своему усмотрению. Это намного меньше кода, чем у вас, и использует функцию Go с нулевым значением.

person Jeremy Wall    schedule 03.06.2013

Просто для завершения: append работает с нулевым слайсом, поэтому вам не нужно создавать слайс с помощью make, вы можете просто добавить к нему элемент.

var mySlice []*UselessStruct
for i := 0; i < 5; i++ {
    mySlice = append(mySlice, &UselessStruct{})
}

Это будет делать то же самое, что и в предыдущем примере, без предварительного распределения, но если вы знаете размер, вы скорее используете что-то вроде этого:

mySlice := make([]*UselessStruct, 0, 5)
for i := range mySlice {
    mySlice[i] = &UselessStruct{}
}

Это может избежать некоторого перераспределения.

person Marwan Burelle    schedule 31.03.2016