Срезы Голанга проходят по значению?
В Голанге я пытаюсь создать функцию ломаного лома для моей задачи коммивояжера. Делая это, я заметил, что когда я начал редактировать срез, я назначил функцию скремблирования каждый раз, когда передавал ее.
После некоторой отладки я узнал, что это из-за того, что я редактировал срез внутри функции. Но так как Голанг должен быть языком "передачи по значению", как это возможно?
https://play.golang.org/p/mMivoH0TuV
Я предоставил ссылку на игровую площадку, чтобы показать, что я имею в виду. Удалив строку 27, вы получите другой вывод, чем оставив его, это не должно иметь значения, так как функция должна создавать свою собственную копию среза, когда передается в качестве аргумента.
Может кто-нибудь объяснить это явление?
5 ответов
Да, все в Go передается по значению. Ломтики тоже. Но значение среза является заголовком, описывающим непрерывный раздел резервного массива, а значение среза содержит только указатель на массив, где элементы фактически сохранены. Значение среза не включает его элементы (в отличие от массивов).
Поэтому, когда вы передаете фрагмент функции, из этого заголовка будет сделана копия, включая указатель, который будет указывать на тот же массив резервных копий. Модификация элементов среза подразумевает изменение элементов резервного массива, и поэтому все срезы, которые совместно используют один и тот же резервный массив, будут "наблюдать" за изменением.
Чтобы увидеть, что находится в заголовке среза, проверьте reflect.SliceHeader
тип:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
См. Связанный / возможно повторяющийся вопрос: передаются ли функции-функции Голанга как копии при записи?
Прочитайте сообщение в блоге: Go Slices: использование и внутреннее оборудование
Вы можете найти пример ниже. Кратко срезы также передаются по значению, но исходный и скопированный срез связаны с одним и тем же базовым массивом. Если один из этих фрагментов изменяется, то изменяется базовый массив, затем изменяется другой фрагмент.
package main
import "fmt"
func main() {
x := []int{1, 10, 100, 1000}
double(x)
fmt.Println(x) // ----> 3 will print [2, 20, 200, 2000] (original slice changed)
}
func double(y []int) {
fmt.Println(y) // ----> 1 will print [1, 10, 100, 1000]
for i := 0; i < len(y); i++ {
y[i] *= 2
}
fmt.Println(y) // ----> 2 will print [2, 20, 200, 2000] (copy slice + under array changed)
}
Срез будет работать с передачей функции по значению, но мы не должны использовать добавление для добавления значений к срезу в функции, вместо этого мы должны использовать присваивание напрямую. Причина в том, что добавление создаст новую память и скопирует в нее значения. Вот пример.
// Go program to illustrate how to
// pass a slice to the function
package main
import "fmt"
// Function in which slice
// is passed by value
func myfun(element []string) {
// Here we only modify the slice
// Using append function
// Here, this function only modifies
// the copy of the slice present in
// the function not the original slice
element = append(element, "blackhole")
fmt.Println("Modified slice: ", element)
}
func main() {
// Creating a slice
slc := []string{"rocket", "galaxy", "stars", "milkyway"}
fmt.Println("Initial slice: ", slc)
//slice pass by value
myfun(slc)
fmt.Println("Final slice: ", slc)
}
Output-
Initial slice: [rocket galaxy stars milkyway]
Modified slice: [rocket galaxy stars milkyway blackhole]
Final slice: [rocket galaxy stars milkyway]
// Go program to illustrate how to
// pass a slice to the function
package main
import "fmt"
// Function in which slice
// is passed by value
func myfun(element []string) {
// Here we only modify the slice
// Using append function
// Here, this function only modifies
// the copy of the slice present in
// the function not the original slice
element[0] = "Spaceship"
element[4] = "blackhole"
element[5] = "cosmos"
fmt.Println("Modified slice: ", element)
}
func main() {
// Creating a slice
slc := []string{"rocket", "galaxy", "stars", "milkyway", "", ""}
fmt.Println("Initial slice: ", slc)
//slice pass by value
myfun(slc)
fmt.Println("Final slice: ", slc)
}
Output-
Initial slice: [rocket galaxy stars milkyway ]
Modified slice: [Spaceship galaxy stars milkyway blackhole cosmos]
Final slice: [Spaceship galaxy stars milkyway blackhole cosmos]
При передаче фрагменты передаются вместе с указателем на базовый массив, поэтому фрагмент представляет собой небольшую структуру, указывающую на базовый массив. Небольшая структура копируется, но по-прежнему указывает на тот же базовый массив. блок памяти, содержащий элементы среза, передается по "ссылке". Триплет информации о срезах, содержащий емкость, количество элементов и указатель на элементы, передается по значению.
Лучший способ обрабатывать срезы, передаваемые в функцию (если элементы среза управляются в функцию, и мы не хотим, чтобы это отражалось в блоке памяти элементов, это скопировать их с помощью copy(s, *c)
в качестве:
package main
import "fmt"
type Team []Person
type Person struct {
Name string
Age int
}
func main() {
team := Team{
Person{"Hasan", 34}, Person{"Karam", 32},
}
fmt.Printf("original before clonning: %v\n", team)
team_cloned := team.Clone()
fmt.Printf("original after clonning: %v\n", team)
fmt.Printf("clones slice: %v\n", team_cloned)
}
func (c *Team) Clone() Team {
var s = make(Team, len(*c))
copy(s, *c)
for index, _ := range s {
s[index].Name = "change name"
}
return s
}
Но будьте осторожны, если этот фрагмент содержит sub slice
требуется дальнейшее копирование, так как у нас по-прежнему будут общие элементы вспомогательного фрагмента, указывающие на одни и те же элементы блока памяти, например:
type Inventories []Inventory
type Inventory struct { //instead of: map[string]map[string]Pairs
Warehouse string
Item string
Batches Lots
}
type Lots []Lot
type Lot struct {
Date time.Time
Key string
Value float64
}
func main() {
ins := Inventory{
Warehouse: "DMM",
Item: "Gloves",
Batches: Lots{
Lot{mustTime(time.Parse(custom, "1/7/2020")), "Jan", 50},
Lot{mustTime(time.Parse(custom, "2/1/2020")), "Feb", 70},
},
}
inv2 := CloneFrom(c Inventories)
}
func (i *Inventories) CloneFrom(c Inventories) {
inv := new(Inventories)
for _, v := range c {
batches := Lots{}
for _, b := range v.Batches {
batches = append(batches, Lot{
Date: b.Date,
Key: b.Key,
Value: b.Value,
})
}
*inv = append(*inv, Inventory{
Warehouse: v.Warehouse,
Item: v.Item,
Batches: batches,
})
}
(*i).ReplaceBy(inv)
}
func (i *Inventories) ReplaceBy(x *Inventories) {
*i = *x
}
В дополнение к этому сообщению, вот пример передачи по ссылке для Golang PlayGround, которым вы поделились:
type point struct {
x int
y int
}
func main() {
data := []point{{1, 2}, {3, 4}, {5, 6}, {7, 8}}
makeRandomDatas(&data)
}
func makeRandomDatas(dataPoints *[]point) {
for i := 0; i < 10; i++ {
if len(*dataPoints) > 0 {
fmt.Println(makeRandomData(dataPoints))
} else {
fmt.Println("no more elements")
}
}
}
func makeRandomData(cities *[]point) []point {
solution := []point{(*cities)[0]} //create a new slice with the first item from the old slice
*cities = append((*cities)[:0], (*cities)[1:]...) //remove the first item from the old slice
return solution
}