我去,我假设切片是通过引用传递的,但这似乎适用于值,但不适用于数组本身。例如,如果我有这个结构:
l := Line{
Points: []Point{
Point{3, 4},
},
}
Run Code Online (Sandbox Code Playgroud)
我可以定义一个变量,该变量传递对结构切片的引用
slice := l.Points
Run Code Online (Sandbox Code Playgroud)
然后如果我修改它,变量引用的原始结构将反映这些修改。
slice[0].X = 1000
fmt.Printf(
"This value %d is the same as this %d",
slice[0].X,
l.Points[0].X,
)
Run Code Online (Sandbox Code Playgroud)
这与我认为按值传递的数组的行为不同。因此,例如,如果我使用数组定义了前面的代码:
l := Line{
Points: [1]Point{
Point{3, 4},
},
}
arr := l.Points
arr[0].X = 1000
fmt.Println(arr.[0].X != s.Points[0].X) // equals true, original struct is untouched
Run Code Online (Sandbox Code Playgroud)
然后,该l结构不会被修改。
现在,如果我想修改切片本身,我显然不能这样做:
slice = append(slice, Point{99, 100})
Run Code Online (Sandbox Code Playgroud)
因为那只会重新定义切片变量,丢失原始引用。我知道我可以简单地做到这一点:
l.Points = append(l.Points, Point{99, 100})
Run Code Online (Sandbox Code Playgroud)
但是,在某些情况下,使用另一个变量而不是输入整个变量会更方便。
我试过这个:
*slice = append(*slice, Point{99, 100})
Run Code Online (Sandbox Code Playgroud)
但它不起作用,因为我试图取消引用显然不是指针的东西。
我终于尝试了这个:
slice := &l.Points
*slice = append(l.Points, Point{99, 100})
Run Code Online (Sandbox Code Playgroud)
它有效,但我不确定发生了什么。为什么 slice 的值没有被覆盖?append这里的工作如何?
让我们首先免除术语问题。在围棋语言规范不使用这个词引用您正在使用它的方式。然而,Go 确实有指针,而指针是一种引用形式。此外,切片和映射有点特殊,因为有一些底层数据——切片下面的数组,或者映射的存储——可能已经存在,也可能不存在,或者通过声明或定义一个类型为或for的变量来创建某种类型T或类型对T1和T2。1slice of Tmap[T1]T2
我们可以把你的这个词的用法参考意味着明确的指针在谈论,例如,当:
func f1(p *int) {
// code ...
}
Run Code Online (Sandbox Code Playgroud)
以及谈论时的隐含指针:
func f2(m map[T1]T2) { ... }
func f3(s []T) { ... }
Run Code Online (Sandbox Code Playgroud)
在f1,p真的是一个指针:因此它指的是一些实际的int,或者是nil。在 中f2,m指的是一些底层映射,或者是nil。在f3,s指的是一些底层数组,或者是nil。
但是如果你写:
Run Code Online (Sandbox Code Playgroud)l := Line{ Points: []Point{ Point{3, 4}, }, }
那么你一定写过:
type Line struct {
// ... maybe some fields here ...
Points []Point
// ... maybe more fields here ...
}
Run Code Online (Sandbox Code Playgroud)
这Line是一个结构类型。它不是切片类型;它不是地图类型。它包含一个切片类型,但它本身不是一个类型。
您现在谈论传递这些切片。如果你通过l,你是通过struct价值传递整个。区分它并传递l.Points. 接收这些参数之一的函数必须用正确的类型声明它。
那么,在大多数情况下,谈论参考文献只是一种疏忽大意——分散对真实情况的注意力。我们需要知道的是:您使用什么源代码为什么变量分配什么值?
抛开所有这些,让我们谈谈您的实际代码示例:
Run Code Online (Sandbox Code Playgroud)l.Points = append(l.Points, Point{99, 100})
这正是它所说的:
传递l.Points到append,这是一个内置的,因为它有点神奇的类型灵活(与 Go 的其余部分相比,类型非常严格)。它采用任何类型的值(T 的切片,对于任何有效类型T)加上一个或多个 type 值,并生成相同类型的新值。[]TT[]T
将结果分配给l.Points。
当append它工作时,它可能:
nil(给定类型的):在这种情况下,它创建底层数组,或因此,在所有情况下,底层数组实际上可能只是被创建或替换了。因此,适当更新相同底层数组的任何其他用途非常重要。将结果分配回更新引用底层数组的(大概是唯一的)切片变量。l.Points
然而,我们可以打破这些假设:
s2 := l.Points
Run Code Online (Sandbox Code Playgroud)
现在l.Points和s2两者都指的是(单个)底层数组。修改底层数组的操作至少可能会影响s2 和 l.Points。
你的第二个例子本身没问题:
Run Code Online (Sandbox Code Playgroud)*slice = append(*slice, Point{99, 100})
但是您还没有展示它 slice自己是如何声明和/或分配给的。
你的第三个例子也很好:
Run Code Online (Sandbox Code Playgroud)slice := &l.Points *slice = append(l.Points, Point{99, 100})
这些行中的第一行声明并初始化slice为指向l.Points。slice因此该变量具有类型*[]Point。它的值—— in 的值slice,即,而不是 in 的值*slice——是 的地址l.Points,它的类型为[]Point。
中的值*slice是 中的值l.Points。所以你可以写:
*slice = append(*slice, Point{99, 100})
Run Code Online (Sandbox Code Playgroud)
这里。由于*slice只是 的另一个名称l.Points,您也可以这样写:
l.Points = append(*slice, Point{99, 100})
Run Code Online (Sandbox Code Playgroud)
您只需要在*slice某些原因l.Points不可用时使用,3但如果更方便,您可以使用*slice。阅读*slice阅读l.Points和更新*slice更新l.Points。
1要了解我所说的可能会或可能不会在这里创建的含义,请考虑:
var s []int
Run Code Online (Sandbox Code Playgroud)
对比:
var s = []int{42}
Run Code Online (Sandbox Code Playgroud)
第一个离开,s == nil而第二个创建一个底层数组,该数组能够保存一个int值42,保存一个int值 42,这样s != nil.
2我不清楚是否有承诺永远不会写入容量大于其当前长度但不足以保存最终结果的现有切片数组。也就是说,可以append先将10个对象追加到现有的底层数组中,然后发现需要更大的数组,扩展底层数组吗?如果存在引用现有底层数组的其他切片值,则可以观察到差异。
3在这里,如果您有理由传递l.Points或传递&l.Points到某些现有(预先编写的)函数,则会出现一个经典示例:
如果您需要将l.Points切片值传递给某个现有函数,则该现有函数无法更改切片值,但可以更改底层数组。这可能是一个糟糕的计划,所以如果它确实这样做了,请确保这没问题!如果它只读取切片和底层数组,那就更安全了。
如果您需要将&l.Points一个指向切片值的值传递给某个现有函数,该现有函数可以更改切片和底层数组。
如果您正在编写一个新函数,则由您决定以最合适的方式编写它。如果您只想读取切片和底层数组,则可以采用 type 值[]Point。如果您打算就地更新切片,则应采用类型*[]Point为 - 指向切片的指针的值Point。
| 归档时间: |
|
| 查看次数: |
534 次 |
| 最近记录: |