4 回答
TA贡献1946条经验 获得超4个赞
让我们首先解决术语问题。Go语言规范并不像您所使用的那样使用引用一词。然而 Go 确实有指针,而指针是引用的一种形式。此外,切片和映射有点特殊,因为存在一些底层数据(切片下的数组或映射的存储),这些数据可能已经存在,也可能不存在,或者通过声明或定义类型为 或 的变量来创建某些类型T或类型对T1和T2。1slice of T
map[T1]T2
在谈论时,我们可以将引用一词的用法理解为显式指针,例如:
func f1(p *int) {
// code ...
}
以及谈论时隐含的指针:
func f2(m map[T1]T2) { ... }
func f3(s []T) { ... }
在 中f1,preally 是一个指针:因此它指的是某个实际的int或 is nil。在 中f2,m指的是某些底层地图,或者是nil。在 中f3,s指的是某个底层数组,或者是nil。
但如果你写:
l := Line{
Points: []Point{
Point{3, 4},
},
}
那么你一定写过:
type Line struct {
// ... maybe some fields here ...
Points []Point
// ... maybe more fields here ...
}
这Line是一个结构类型。它不是切片类型;它不是地图类型。它包含一种切片类型,但它本身不是一种切片类型。
您现在谈论传递这些切片。如果您传递,则您将按值l传递整个内容。struct区分它和传递 的值非常重要l.Points。接收这些参数之一的函数必须使用正确的类型来声明它。
因此,在大多数情况下,谈论参考文献只是转移注意力——分散人们对实际情况的注意力。我们需要知道的是:您使用什么源代码为哪些变量分配什么值?
完成所有这些后,让我们讨论一下实际的代码示例:
l.Points = append(l.Points, Point{99, 100})
这正是它所说的:
传递
l.Points
toappend
,它是一个内置函数,因为它具有神奇的类型灵活性(与 Go 的其余部分相比,类型相当严格)。它采用任何类型的值( T的切片,对于任何有效类型T)加上一个或多个类型的值,并生成相同类型的新值。[]T
T
[]T
将结果赋给
l.Points
.
它什么时候append
起作用,它可能:
接收
nil
(给定类型):在这种情况下,它创建底层数组,或者接收非零切片:在这种情况下,它会写入底层数组或根据需要丢弃该数组,转而使用新的更大容量的数组。2
因此,在所有情况下,底层数组实际上可能刚刚被创建或替换。因此,对同一底层数组的任何其他使用进行适当更新非常重要。将结果分配回更新(可能是唯一的)引用底层数组的切片变量。l.Points
然而,我们可以打破这些假设:
s2 := l.Points
现在l.Points
和s2
都引用(单个)底层数组。修改底层数组的操作至少可能会影响s2
和 l.Points
。
你的第二个例子本身就可以:
*slice = append(*slice, Point{99, 100})
但您尚未显示其自身是如何 slice
声明和/或分配给的。
你的第三个例子也很好:
slice := &l.Points *slice = append(l.Points, Point{99, 100})
这些行中的第一行声明并初始化slice
为指向l.Points
。slice
因此该变量的类型为*[]Point
。它的值(slice
即 中的值,而不是 中的值)*slice
是 的地址l.Points
,其类型为[]Point
。
中的值*slice
就是 中的值l.Points
。所以你可以写:
*slice = append(*slice, Point{99, 100})
这里。由于*slice
是 的另一个名称l.Points
,您还可以编写:
l.Points = append(*slice, Point{99, 100})
仅当由于某种原因无法使用时才需要使用, 3但如果更方便的话也可以使用。读读读,更新更新。*slice
l.Points
*slice
*slice
l.Points
*slice
l.Points
1要了解我所说的可能或可能不会在此处创建的意思,请考虑:
var s []int
对比:
var s = []int{42}
第一个离开,s == nil
而第二个创建一个底层数组,该数组能够保存一个int
值42
,保存一个int
值 42,因此s != nil
。
2我不清楚是否有承诺永远不会在容量大于其当前长度但不足以保存最终结果的现有切片数组上写入。也就是说,可以append
先将10个对象追加到现有的底层数组中,然后发现需要更大的数组并扩展底层数组吗?如果有其他切片值引用现有的底层数组,则可以观察到差异。
3如果您有理由将l.Points
or传递&l.Points
给某些现有(预先编写的)函数,则会出现一个经典示例:
如果您需要将
l.Points
切片值传递给某个现有函数,则该现有函数无法更改切片值,但可以更改底层数组。这可能是一个糟糕的计划,所以如果确实这样做,请确保这是可以的!如果它只读取切片和底层数组,那就安全得多。如果您需要将
&l.Points
指向切片值的值传递给某个现有函数,则该现有函数可以更改切片和底层数组。
如果您正在编写一个新函数,则由您决定以最合适的方式编写它。如果您只想读取切片和底层数组,则可以采用 类型的值[]Point
。如果您打算就地更新切片,则应该采用类型为“*[]Point
指向切片的指针”的值Point
。
TA贡献1821条经验 获得超4个赞
Append 返回一个新切片,该切片可能会修改初始切片的原始支持数组。原始切片仍将指向原始后备数组,而不是新数组(新切片可能位于内存中的同一位置,也可能不位于内存中的同一位置)
例如(操场)
slice := []int{1,2,3}
fmt.Println(len(slice))
// Output: 3
newSlice := append(slice, 4)
fmt.Println(len(newSlice))
// Output: 4
fmt.Println(len(slice))
// Output: 3
虽然切片可以被描述为“指向数组的胖指针”,但它不是指针,因此您无法取消引用它,这就是您收到错误的原因。
通过创建一个指向切片的指针,并append
按照上面的操作使用,您可以将指针指向的切片设置为 . 返回的“新”切片append
。
TA贡献1900条经验 获得超5个赞
我知道这可能是亵渎的,但是对我来说,将切片视为结构很有用。
type Slice struct {
len int
cap int
Array *[n]T // Pointer to array of type T
}
由于在像 C 这样的语言中,该[]运算符也是一个解引用运算符,因此我们可以认为每次访问切片时,我们实际上都在解引用底层数组并为其分配一些值。那是:
var s []int
s[0] = 1
可能被认为等同于(在伪代码中):
var s Slice
*s.Array[0] = 1
这就是为什么我们可以说切片是“指针”。因此,它可以像这样修改其底层数组:
myArray := [3]int{1,1,1}
mySlice := myArray[0:1]
mySlice = append(mySlice, 2, 3) // myArray == mySlice
修改mySlice也会修改myArray,因为切片存储了指向数组的指针,并且在追加时,我们取消引用该指针。
然而,这种行为并不总是这样。如果超出原始数组的容量,则会创建一个新数组,并且原始数组保持不变。
myArray := [3]int{1,1,1}
mySlice := myArray[0:1]
mySlice = append(mySlice, 2, 3, 4, 5) // myArray != mySlice
当我们尝试将切片本身视为实际指针时,就会出现混乱。由于我们可以通过附加到底层数组来修改它,因此我们相信在这种情况下:
sliceCopy := mySlice
sliceCopy = append(sliceCopy, 6)
两个切片slice和sliceCopy是相同的,但它们不是。我们必须显式传递对切片内存地址的引用(使用&运算符)才能修改它。那是:
sliceAddress := &mySlice
*sliceAddress = append(mySlice, 6) // or append(*sliceAddress, 6)
TA贡献1993条经验 获得超5个赞
您的第一次尝试没有成功,因为切片不是指针,它们可以被视为引用类型。如果底层数组有足够的容量,Append 将修改它,否则返回一个新切片。
通过结合这两种尝试,您可以实现您想要的目标。
l := Line{
Points: []Point{
Point{3, 4},
},
}
slice := &l.Points
for i := 0; i < 100; i++ {
*slice = append(*slice, Point{99 + i, 100 + i})
}
fmt.Println(l.Points)
- 4 回答
- 0 关注
- 141 浏览
添加回答
举报