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

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 бесполезных конструкций, хранящихся в виде указателей. Если я объявляю кусок структурных значений 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)
}

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

Что такое go-idiomatic способ предварительного выделения и заполнения срезов?

Ответ 1

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

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

Проблема, с которой вы сталкиваетесь в обоих примерах, заключается в том, что вы добавляете к фрагменту уже правильную длину. Если вы устанавливаете mySlice := make([]*UselessStruct, 5), вы запрашиваете фрагмент из nil указателей длины 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]

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

Ответ 2

Есть два способа сделать это. Один из них заключается в предварительном распределении слотов, как вы это делали. Но вместо использования 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) инициализирует срез длиной 0, но он все еще предварительно выделяет достаточно места для 5 записей.

Ответ 3

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

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

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

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

myStruct := &mySlice[0]

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

Ответ 4

Просто для завершения: добавьте работы с nil-срезом, поэтому вам не нужно создавать срез с 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{}
}

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