З’єднайте дві скибочки в Go


477

Я намагаюся поєднати шматочок [1, 2]і шматочок [3, 4]. Як я можу це зробити в Go?

Я намагався:

append([]int{1,2}, []int{3,4})

але отримав:

cannot use []int literal (type []int) as type int in append

Однак, здається, документація вказує, що це можливо, чого я пропускаю?

slice = append(slice, anotherSlice...)

Відповіді:


877

Додайте крапки після другого фрагмента:

//---------------------------vvv
append([]int{1,2}, []int{3,4}...)

Це подібно до будь-якої іншої варіативної функції.

func foo(is ...int) {
    for i := 0; i < len(is); i++ {
        fmt.Println(is[i])
    }
}

func main() {
    foo([]int{9,8,7,6,5}...)
}

37
append()варіатична функція, і ...дозволяє передати декілька аргументів на різноманітну функцію з фрагмента.

11
Це взагалі виконавець, коли шматочки досить великі? Або компілятор насправді не передає всі елементи як параметри?
Жаба

15
@Toad: Це насправді їх не поширює. У foo()наведеному вище прикладі isпараметр містить копію оригінального фрагмента, тобто, він має копію посилання на малу вагу на той самий базовий масив, len та cap. Якщо fooфункція змінює член, зміна буде помічена в оригіналі. Ось демонстрація . Тож єдиним реальним накладним покриттям буде те, що він створює новий фрагмент, якщо у вас його ще не було, наприклад: foo(1, 2, 3, 4, 5)який створить новий фрагмент, який isбуде утримуватися.

2
Ага. Якщо я правильно розумію, варіативна функція реально реалізована як масив параметрів (замість кожного параметра в стеку)? А оскільки ви проходите по шматочку, він насправді відображає один на один?
Жаба

@Toad: Так, коли ви використовуєте ...наявний фрагмент, він просто передає цей фрагмент. Коли ви передаєте окремі аргументи, він збирає їх у новий фрагмент і передає його. Я не з перших рук знання точної механіки, але я припускаю , що це: foo(1, 2, 3, 4, 5)і це: func foo(is ...int) {тільки де-цукор до цього: foo([]int{1, 2, 3, 4, 5})і це: func foo(is []int) {.

77

Додавання та копіювання фрагментів

Функція Variadic appendдодає нулю або більше значень xдо s типу S, який повинен бути типом зрізу, і повертає отриманий фрагмент також типу S. Значення xпередаються параметру типу, ...Tде Tє тип елемента, Sі застосовуються відповідні правила передачі параметрів. Як особливий випадок, додаток також приймає перший аргумент, який можна призначити типу, []byteз другим stringтипом аргументу, за яким слід .... Ця форма додає байти рядка.

append(s S, x ...T) S  // T is the element type of S

s0 := []int{0, 0}
s1 := append(s0, 2)        // append a single element     s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)  // append multiple elements    s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)    // append a slice              s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}

Передача аргументів до ... параметрів

Якщо fє варіативним з кінцевим типом параметра ...T, то в межах функції аргумент еквівалентний параметру типу []T. При кожному виклику fаргументу, переданого кінцевому параметру, є новий фрагмент типу []T, послідовними елементами якого є фактичні аргументи, які всі повинні бути віднесені до типу T. Отже, довжина фрагмента - це кількість аргументів, пов'язаних з кінцевим параметром і може відрізнятися для кожного сайту виклику.

Відповідь на ваше запитання - приклад s3 := append(s2, s0...)у специфікації мови програмування Go . Наприклад,

s := append([]int{1, 2}, []int{3, 4}...)

6
Примітка: загальне використання додатка (slice1, slice2 ...) здається мені досить небезпечним. Якщо slice1 - це фрагмент більшого масиву, значення цього масиву будуть перезаписані slice2. (Це змушує мене нагадати, що це, здається, не є спільним питанням?)
Хьюго,

7
@Hugo Якщо ви "передасте" фрагмент свого масиву, знайте, що "власник" фрагмента зможе побачити / перезаписати частини масиву, що перевищують поточну довжину фрагмента. Якщо ви цього не хочете, ви можете використовувати вираз повного фрагмента (у вигляді a[low : high : max]), який також визначає максимальну ємність . Наприклад, зріз a[0:2:4]матиме ємність, 4і його не можна перенести на додавання елементів поза цим, навіть якщо резервний масив має тисячу елементів після цього.
icza

30

Нічого проти інших відповідей, але я знайшов коротке пояснення в документах легше зрозумілим, ніж приклади в них:

функція додавати

func append(slice []Type, elems ...Type) []TypeВбудована функція додавання додає елементи до кінця фрагмента. Якщо вона має достатню ємність, місце призначення розміщується для розміщення нових елементів. Якщо цього не відбувається, буде виділено новий базовий масив. Додаток повертає оновлений фрагмент. Тому необхідно зберігати результат додавання, часто у змінній, що містить сам зріз:

slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)

Як особливий випадок, законно додавати рядок до байтового фрагменту, як це:

slice = append([]byte("hello "), "world"...)

1
Дякую! Цінне для мене!
Корявін Іван

23

Я думаю, що важливо вказати і знати, що якщо фрагмент призначення (фрагмент, який ви додаєте) має достатню ємність, додавання відбудеться "на місці", зменшивши місце призначення (зменшивши його, щоб збільшити його довжину, щоб бути вміє вмістити придатні елементи).

Це означає, що якщо призначення було створене шляхом нарізання більшого масиву або фрагмента, який містить додаткові елементи, що перевищують довжину отриманого фрагмента, вони можуть бути перезаписані.

Для демонстрації див. Цей приклад:

a := [10]int{1, 2}
fmt.Printf("a: %v\n", a)

x, y := a[:2], []int{3, 4}
fmt.Printf("x: %v, y: %v\n", x, y)
fmt.Printf("cap(x): %v\n", cap(x))

x = append(x, y...)
fmt.Printf("x: %v\n", x)

fmt.Printf("a: %v\n", a)

Результат (спробуйте на майданчику Go ):

a: [1 2 0 0 0 0 0 0 0 0]
x: [1 2], y: [3 4]
cap(x): 10
x: [1 2 3 4]
a: [1 2 3 4 0 0 0 0 0 0]

Ми створили масив "backing" aз довжиною 10. Потім ми створюємо xцільовий зріз, розрізаючи цей aмасив, yфрагмент створюється за допомогою складеного літералу []int{3, 4}. Тепер , коли ми додаємо yдо x, результат є очікуваним [1 2 3 4], але то , що може бути дивного в тому , що масив підтримка aтакож змінився, тому що здатність xIS , 10яка достатня для додавання yдо нього, так xяк resliced , який також буде використовувати той же aмасив підкладковий і append()буде копіювати елементи yтуди.

Якщо ви хочете цього уникнути, ви можете використовувати вираз із повним фрагментом, який має форму

a[low : high : max]

який створює фрагмент, а також контролює ємність отриманого фрагмента, встановлюючи його max - low.

Дивіться модифікований приклад (різниця полягає лише в тому, що ми створюємо xтак x = a[:2:2]::

a := [10]int{1, 2}
fmt.Printf("a: %v\n", a)

x, y := a[:2:2], []int{3, 4}
fmt.Printf("x: %v, y: %v\n", x, y)
fmt.Printf("cap(x): %v\n", cap(x))

x = append(x, y...)
fmt.Printf("x: %v\n", x)

fmt.Printf("a: %v\n", a)

Вихід (спробуйте на майданчику Go )

a: [1 2 0 0 0 0 0 0 0 0]
x: [1 2], y: [3 4]
cap(x): 2
x: [1 2 3 4]
a: [1 2 0 0 0 0 0 0 0 0]

Як бачимо, ми отримуємо той самий xрезультат, але резервний масив aне змінився, оскільки ємність x"лише" 2(завдяки висловленню повного фрагмента a[:2:2]). Щоб зробити додавання, виділяється новий резервний масив, який може зберігати елементи як xі y, від чого відрізняється a.


2
Це дуже допомагає проблемі, з якою я стикаюся. Дякую.
Ейді

9

Я хотів би підкреслити відповідь @icza та трохи спростити її, оскільки це важлива концепція. Я припускаю, що читач знайомий з фрагментами .

c := append(a, b...)

Це правдива відповідь на запитання. АЛЕ якщо вам потрібно використовувати фрагменти 'a' та 'c' пізніше в коді в іншому контексті, це не безпечний спосіб об'єднання фрагментів.

Для пояснення давайте читаємо вираз не з точки зрізів, а з точки зору базових масивів:

"Візьміть (базовий) масив 'a' та додайте до нього елементи масиву 'b'. Якщо масив 'a' має достатню ємність, щоб включити всі елементи з 'b' - базовий масив 'c' не буде новим масивом , насправді це буде масив 'a'. В основному, фрагмент 'a' буде показувати len (a) елементи базового масиву 'a', а для фрагмента 'c' відображатиметься len (c) масиву 'a'. "

append () не обов'язково створює новий масив! Це може призвести до несподіваних результатів. Див. Приклад Перейти на майданчик .

Завжди використовуйте функцію make (), якщо ви хочете переконатися, що новий фрагмент призначений для фрагмента. Наприклад, тут є кілька некрасивих, але досить ефективних варіантів виконання завдання.

la := len(a)
c := make([]int, la, la + len(b))
_ = copy(c, a)
c = append(c, b...)

la := len(a)
c := make([]int, la + len(b))
_ = copy(c, a)
_ = copy(c[la:], b)

Дякуємо, що вказали на ці побічні ефекти. Дивовижно контрастний до цього модифікованого szenario. play.golang.org/p/9FKo5idLBj4 Хоча при наданні зайвої ємності, слід ретельно продумати ці дивовижні побічні ефекти проти правдоподібної інтуїції.
olippuner

5

append () функція та оператор розповсюдження

Два зрізи можна об'єднати за допомогою appendметоду в стандартній бібліотеці голангів. Що схоже на variadicфункцію операції. Тому нам потрібно користуватися...

package main

import (
    "fmt"
)

func main() {
    x := []int{1, 2, 3}
    y := []int{4, 5, 6}
    z := append([]int{}, append(x, y...)...)
    fmt.Println(z)
}

Вихід з вищевказаного коду: [1 2 3 4 5 6]


2

append([]int{1,2}, []int{3,4}...)буду працювати. Передача аргументів до ...параметрів.

Якщо fє варіаційним з кінцевим параметром pтипу...T , то в межах fтипу pє еквівалентом типу []T.

Якщо f виклик не має фактичних аргументів для p, передане значення pє nil.

В іншому випадку передане значення - це новий фрагмент типу []T з новим базовим масивом, послідовними елементами якого є фактичні аргументи, до яких усі мають бути призначені T. Отже, довжина та ємність фрагмента - це кількість аргументів, пов'язаних pі можуть відрізнятися для кожного сайту виклику.

З огляду на функцію та дзвінки

func Greeting(prefix string, who ...string)
Greeting("nobody")
Greeting("hello:", "Joe", "Anna", "Eileen")
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.