编程笔记

lifelong learning & practice makes perfect

go| []Item类型的传参,是会拷贝一份到函数,还是用的指针,同一份数据

在 Go 语言中,当传递 []Item 类型的参数给函数时,会拷贝 slice 的头部信息,但不会拷贝底层数组的数据。

具体来说:

  • Slice (切片) 的头部信息: Go 的 slice 类型实际上是一个结构体,包含三个字段:

    • pointer: 指向底层数组的指针。
    • length: 切片的长度。
    • capacity: 切片的容量。
  • 传递过程: 当将 []Item 类型的变量传递给函数时,会拷贝这个包含 pointer, length, 和 capacity 的结构体。 也就是说,函数接收到的是一个新的 slice 结构体,但这个新的 slice 结构体中的 pointer 仍然指向同一个底层数组。

这意味着:

  1. 函数内部对 slice 的修改可能影响外部:

    • 修改 slice 中的元素: 由于 slice 指向的是同一个底层数组,如果函数修改了 slice 中的 Item 元素,那么原始 slice 也会受到影响,因为它们共享底层数组。

    • 修改 slice 的长度: 如果函数通过 append 操作改变了 slice 的长度,可能会导致函数内部的 slice 使用一个新的底层数组。在这种情况下,原始 slice 不会受到影响。 只有当 append 操作在函数内部没有超出原始 slicecapacity 时,对长度的修改才会影响到原始 slice

  2. 函数内部对 slice 头部信息的修改不影响外部:

    • 修改 slice 的长度或容量(但未超出 capacity): 如果函数修改了传入 slice 的长度,但未超出原始 slicecapacity,则原始 slice 会受到长度变化的影响。
    • 修改 slice 的长度或容量 (超出 capacity): 如果函数修改了传入 slice 的长度,且超过了原始 slicecapacity,Go 会分配一个新的底层数组,并且将数据复制到新的数组中。 在这种情况下,函数内部的 slice 就不再和原始 slice 共享同一个底层数组,因此原始 slice 不会受到影响。

总结:

当传递 []Item 时,传递的是 slice 结构体的副本,指向同一个底层数组。 因此:

  • 修改 slice 元素的会影响原始 slice
  • 如果 append 操作没有超出原始 slicecapacity,修改 slice 的长度会影响原始 slice
  • 如果 append 操作超出了原始 slicecapacity,修改 slice 的长度不会影响原始 slice

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import "fmt"

type Item struct {
Name string
Price float64
}

func modifySlice(items []Item) {
// 修改元素
items[0].Name = "Updated Item"

// Append, but within capacity
items = append(items, Item{Name: "New Item 1", Price: 10.0})
fmt.Println("Within capacity, Inside function:", items, "len:", len(items), "cap:", cap(items))

// Append, exceeding capacity
items = append(items, Item{Name: "New Item 2", Price: 20.0})
fmt.Println("Exceeding capacity, Inside function:", items, "len:", len(items), "cap:", cap(items))
}

func main() {
originalSlice := []Item{
{Name: "Item 1", Price: 5.0},
{Name: "Item 2", Price: 10.0},
}
fmt.Println("Original slice (before):", originalSlice, "len:", len(originalSlice), "cap:", cap(originalSlice))

modifySlice(originalSlice)

fmt.Println("Original slice (after):", originalSlice, "len:", len(originalSlice), "cap:", cap(originalSlice))
}

输出结果 (可能会因为 Go 版本而略有不同,但核心概念一致):

1
2
3
4
Original slice (before): [{Item 1 5} {Item 2 10}] len: 2 cap: 2
Within capacity, Inside function: [{Updated Item 5} {Item 2 10} {New Item 1 10}] len: 3 cap: 4
Exceeding capacity, Inside function: [{Updated Item 5} {Item 2 10} {New Item 1 10} {New Item 2 20}] len: 4 cap: 4
Original slice (after): [{Updated Item 5} {Item 2 10}] len: 2 cap: 2

解释:

  • items[0].Name = "Updated Item" 修改了原始 slice 的第一个元素。
  • 第一次 append 因为没有超过原始 capacity(2), 这时候,长度改变会影响到原始 slice.
  • 第二次 append 因为超过了原始 capacity(2),会分配一个新的底层数组,所以原始 slice 最终没有受到第二次append带来的影响

因此,在 Go 中传递 slice 时要特别注意,理解它是按值传递(但底层数组是指针)的性质,避免产生意料之外的副作用。 如果你希望完全避免修改原始 slice,可以在函数内部创建一个 slice 的副本,然后操作副本。 例如: copiedSlice := make([]Item, len(items)); copy(copiedSlice, items)。 然后函数操作copiedSlice

欢迎关注我的其它发布渠道

Related Issues not found

Please contact @yiGmMk to initialize the comment