2013-11-25 3 views

ответ

20

Типичные append использование является

a = append(a, x) 

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

a := []int{1,2,3} 
a = append(a, 4) 
fmt.Println(a) 
append(a[:3], 5) 
fmt.Println(a) 

может печатать

[1 2 3 4] 
[1 2 3 5] 
+0

Спасибо, larsmans, я изменил код. «сделать» даст достаточную мощность. Результат тот же, и я смущен. –

+1

Пробовал в игровой площадке Go. Коды не работали. – Gizak

28

В вашем примере slice аргумент функции Test получает копию переменной a в области видимости вызывающего абонента.

Поскольку переменная срез имеет «срез дескриптора» который просто ссылки основная массив, в вашей Test функции вы изменить дескриптор ломтика содержавшийся в slice переменной несколько раз подряд, но это не влияет на абонента и его переменной a.

Внутри функции Test, первый append перераспределяет массив подложки под переменной slice, копирует его первоначальное содержание более, добавляет 100 к нему, и это то, что вы наблюдаете. При выходе из Test переменная slice выходит за пределы области видимости, а также (новый) базовый массив, который ссылается на срез.

Если вы хотите сделать Test ведут себя как append, вы должны вернуть новый кусок от него — так же, как append делает — и требуют звонящих из Test использовать его таким же образом, они используют бы append:

func Test(slice []int) []int { 
    slice = append(slice, 100) 

    fmt.Println(slice) 

    return slice 
} 

a = Test(a) 

this article Пожалуйста, прочитайте внимательно, как это в основном показывает, как реализовать append вручную, после объяснения, как ломтики работают внутри. Затем прочитайте this.

+2

Я действительно думаю, что это описание неверно тонким образом. Ответ @ doun ниже на самом деле является более правильным представлением о том, что происходит внутри: 'append' в' Test' не перераспределяет какую-либо память, потому что исходное выделение среза массива 'a' может по-прежнему соответствовать одному дополнительному элементу. Другими словами, по мере написания этой программы возвращаемое значение 'Test (a)' и 'a' представляет собой разные заголовки срезов с разной длиной, но они указывают на тот же самый базовый массив. Печать 'fmt.Println (a [: cap (a)]' в качестве последней строки функции 'main' делает это ясно. –

3

ВНИМАНИЕ, что присоединять генерирует новый срез, если колпачок не является достаточным. Ответ @ kostix правильный, или вы можете передать фрагмент аргумент указателем!

+1

Вы правы в указателях, но я решительно не упоминал их, потому что кусочки были изобретены в основном для освободить программистов от обращения к указателям на массивы. В ссылочной реализации (от Go) переменная среза содержит указатель и два целых числа, поэтому копирование является дешевым, и именно поэтому 'slice = append (slice, a, b, c) 'является идиоматичным, не передавая переменную среза указателем и изменяя его« на месте », чтобы вызывающий вызывал изменение. – kostix

+1

@kostix Вы правы, цель кодов должна быть явной. Но я думаю, что вся история просто передавая значение, сохраняющее указатель и передающее указатель, указывающий на указатель.Если мы изменим ссылку, то оба могут работать, но если мы заменим ссылку, первая теряет эффекты. Программист должен знать, что он делает. – Gizak

3

Попробуйте это, что, я думаю, дает понять.основной массив изменяется, но наш кусочек не так, print просто печатает len() символов, другой ломтик к cap(), вы можете увидеть измененный массив:

func main() { 

    for i := 0; i < 7; i++ { 
     a[i] = i 
    } 

    Test(a) 

    fmt.Println(a) // prints [0..6] 
    fmt.Println(a[:cap(a)] // prints [0..6,100] 
} 
+0

поэтому 'a' и 'a [: cap (a)]' являются diff ломтик? –

+0

Да, если вы запустите код, вы это узнаете. потому что в ходе теста (a) изменяется колпачок (a) – doun

1

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

package main 

import (
    "fmt" 
) 

var a = make([]int, 7, 8) 

func Test(slice *[]int) { 
    *slice = append(*slice, 100) 

    fmt.Println(*slice) 
} 

func main() { 

    for i := 0; i < 7; i++ { 
     a[i] = i 
    } 

    Test(&a) 

    fmt.Println(a) 
} 
1

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

Ref: http://criticalindirection.com/2016/02/17/slice-with-a-pinch-of-salt/

Выход примера из ссылки объясняет поведение срезов в Go.

Создание ломтик.

Slice a len=7 cap=7 [0 0 0 0 0 0 0] 

фрагмент б обозначает 2, 3, 4 индексов в ломтик. Следовательно, емкость равна 5 (= 7-2).

b := a[2:5] 
Slice b len=3 cap=5 [0 0 0] 

Изменение среза б, также модифицирует а, так как они указывают на тот же базовый массив.

b[0] = 9 
Slice a len=7 cap=7 [0 0 9 0 0 0 0] 
Slice b len=3 cap=5 [9 0 0] 

прилагая 1 ломтик б. Перезаписывает a.

Slice a len=7 cap=7 [0 0 9 0 0 1 0] 
Slice b len=4 cap=5 [9 0 0 1] 

прилагая 2 ломтик б. Перезаписывает a.

Slice a len=7 cap=7 [0 0 9 0 0 1 2] 
Slice b len=5 cap=5 [9 0 0 1 2] 

прилагая 3 нарезать б. Здесь создается новая копия с перегрузкой емкости.

Slice a len=7 cap=7 [0 0 9 0 0 1 2] 
Slice b len=6 cap=12 [9 0 0 1 2 3] 

Проверка ломтики а и точки В в различные массивы, лежащих в основе после наращивания перегрузки в предыдущем шаге.

b[1] = 8 
Slice a len=7 cap=7 [0 0 9 0 0 1 2] 
Slice b len=6 cap=12 [9 8 0 1 2 3] 
0

Вот хорошая реализация добавления для ломтиков. Я думаю, что это похоже на то, что происходит под капотом:

package main 

import "fmt" 

func main() { 
    slice1 := []int{0, 1, 2, 3, 4} 
    slice2 := []int{55, 66, 77} 
    fmt.Println(slice1) 
    slice1 = Append(slice1, slice2...) // The '...' is essential! 
    fmt.Println(slice1) 
} 

// Append ... 
func Append(slice []int, items ...int) []int { 
    for _, item := range items { 
     slice = Extend(slice, item) 
    } 
    return slice 
} 

// Extend ... 
func Extend(slice []int, element int) []int { 
    n := len(slice) 
    if n == cap(slice) { 
     // Slice is full; must grow. 
     // We double its size and add 1, so if the size is zero we still grow. 
     newSlice := make([]int, len(slice), 2*len(slice)+1) 
     copy(newSlice, slice) 
     slice = newSlice 
    } 
    slice = slice[0 : n+1] 
    slice[n] = element 
    return slice 
} 
0

Я думаю, что исходный ответ не совсем верный. append() изменил как срезы, так и базовый массив, хотя базовый массив был изменен, но все еще разделялся обоими срезами.

Как указано в Go Doc:

Срез не хранит каких-либо данных, он просто описывает сечение основного массива.(Link)

Ломтики просто обертка вокруг значения массивов, а это означает, что они содержат информацию о том, как они срез лежащий в основе массива, которые они используют для хранения набора данных. Поэтому по умолчанию срез, передаваемый другому методу, фактически передается по значению вместо указателя/указателя, хотя он все равно будет использовать один и тот же базовый массив. Обычно массивы также передаются по значению, поэтому я предполагаю, что точки среза в базовом массиве вместо сохранения в качестве значения. Что касается вашего вопроса, когда вы бежите сдадите кусочек следующей функции:

func Test(slice []int) { 
    slice = append(slice, 100) 
    fmt.Println(slice) 
} 

вы на самом деле прошли копию ломтика вместе с указателем на тот же основные средств array.That, изменения, которые вы сделали в slice не влияет на функцию в функции main. Это сам срез, который хранит информацию о том, какая часть массива она срезает и предоставляет общественности. Поэтому, когда вы запустили append(slice, 1000), при расширении базового массива вы также изменили информацию обрезания slice, которая была сохранена в вашей функции Test().

Однако, если вы изменили свой код следующим образом, она могла бы работать:

func main() { 
    for i := 0; i < 7; i++ { 
     a[i] = i 
    } 

    Test(a) 
    fmt.Println(a[:cap(a)]) 
} 

Причина заключается в том, что вы расширили a говоря a[:cap(a)] над его изменили основной массив, измененный Test() функции. Как указано здесь:

Вы можете удлинить длину среза, перерезав его, при условии, что он имеет достаточную емкость. (Link)

Смежные вопросы