英文:
In a Go slice, why does s[lo:hi] end at element hi-1?
问题
根据Go之旅的说明,在Go语言的切片中,表达式s[lo:hi]
会计算出从lo
到hi-1
(包括hi-1
)的元素组成的切片。
在上面的代码示例中,p[0:3]
直观上看起来应该是“从位置0到位置3的切片”,即[0, 10, 20, 30]。但实际上,它等于[0, 10, 20]。
所以我的问题是:为什么上限值要计算为hi-1
而不是简单地使用hi
?这看起来不太直观,但肯定有一些我没有理解到的原因,我很好奇这是什么原因。
提前感谢。
英文:
According to the Tour of Go, in a Go slice s
, the expression s[lo:hi]
evaluates to a slice of the elements from lo
through hi-1
, inclusive:
package main
import "fmt"
func main() {
p := []int{0, // slice position 0
10, // slice position 1
20, // slice position 2
30, // slice position 3
40, // slice position 4
50} // slice position 5
fmt.Println(p[0:3]) // => [0 10 20]
}
In my code example above, "p[0:3]" would seem to intuitively "read" as: "the slice from position 0 to position 3", equating to [0, 10, 20, 30]. But of course, it actually equates to [0 10 20].
So my question is: what is the design rationale for the upper value evaluating to hi-1
rather than simply hi
? It feels unintuitive, but there must be some reason for it that I'm missing, and I'm curious what that might be.
Thanks in advance.
答案1
得分: 19
这完全是一种约定的问题,当然还有其他的方法(例如,Matlab使用的是从1开始的数组索引)。选择实际上取决于你想要的属性。事实证明,使用从0开始的数组索引,并且切片是包含起始位置但不包含结束位置(即,从a到b的切片包含元素a但不包含元素b),具有一些非常好的特性,因此这是一个非常常见的选择。以下是一些优点。
0索引数组和包含起始位置但不包含结束位置的切片的优点
(请注意,我使用的是非Go术语,所以我将按照C或Java的方式来讨论数组。数组是Go所称的切片,切片是子数组(即,“从索引1到索引4的切片”))
-
指针算术运算有效。如果你使用的是C这样的语言,数组实际上只是指向数组第一个元素的指针。因此,如果你使用从0开始的数组索引,那么你可以说索引为i的元素就是数组指针指向的元素加上i。例如,如果我们有数组[3 2 1],数组的地址为10(假设每个值占用一个字节的内存),那么第一个元素的地址为10 + 0 = 10,第二个元素的地址为10 + 1 = 11,依此类推。简而言之,这使得数学计算变得简单。
-
切片的长度也是切片的位置。也就是说,对于一个数组
arr
,arr[0:len(arr)]
就等于arr
本身。这在实践中非常方便。例如,如果我调用n, _ := r.Read(arr)
(其中n
是读入arr
的字节数),那么我只需要使用arr[:n]
就可以得到实际写入arr
的数据对应的切片。 -
索引不重叠。这意味着如果我有
arr[0:i]
、arr[i:j]
、arr[j:k]
、arr[k:len(arr)]
,这些切片完全覆盖了arr
本身。你可能不经常将一个数组分割成这样的子切片,但它有许多相关的优点。例如,考虑以下根据非连续整数拆分数组的代码:func consecutiveSlices(ints []int) [][]int { ret := make([][]int, 0) i, j := 0, 1 for j < len(ints) { if ints[j] != ints[j-1] + 1 { ret = append(ret, ints[i:j]) i = j } } ret = append(ret, ints[i:j]) }
(显然,这段代码不能很好地处理一些边界情况,但你可以理解其中的思想)
如果我们尝试使用包含起始位置和结束位置的切片来编写等效的函数,它将变得更加复杂。
如果有人能想到更多的优点,请随时编辑这个答案并添加它们。
英文:
This is completely a matter of convention, and there are certainly other ways to do it (for example, Matlab uses arrays whose first index is 1). The choice really comes down to what properties you want. As it turns out, using 0-indexed arrays where slicing is inclusive-exclusive (that is, a slice from a to b includes element a and excludes element b) has some really nice properties, and thus it's a very common choice. Here are a few advantages.
Advantages of 0-indexed arrays and inclusive-exclusive slicing
(note that I'm using non-Go terminology, so I'll talk about arrays in the way that C or Java would talk about them. Arrays are what Go calls slices, and slices are sub-arrays (ie, "the slice from index 1 to index 4"))
-
Pointer arithmetic works. If you're in a language like C, arrays are really just pointers to the first element in the array. Thus, if you use 0-indexed arrays, then you can say that the element at index i is just the element pointed at by the array pointer plus i. For example, if we have the array [3 2 1] with the address of the array being 10 (and assuming that each value takes up one byte of memory), then the address of the first element is 10 + 0 = 10, the address of the second is 10 + 1 = 11, and so on. In short, it makes the math simple.
-
The length of a slice is also the place to slice it. That is, for an array
arr
,arr[0:len(arr)]
is justarr
itself. This comes in handy a lot in practice. For example, if I calln, _ := r.Read(arr)
(wheren
is the number of bytes read intoarr
), then I can just doarr[:n]
to get the slice ofarr
corresponding to the data that was actually written intoarr
. -
Indices don't overlap. This means that if I have
arr[0:i]
,arr[i:j]
,arr[j:k]
,arr[k:len(arr)]
, these slices fully coverarr
itself. You may not often find yourself partitioning an array into sub-slices like this, but it has a number of related advantages. For example, consider the following code to split an array based on non-consecutive integers:func consecutiveSlices(ints []int) [][]int { ret := make([][]int, 0) i, j := 0, 1 for j < len(ints) { if ints[j] != ints[j-1] + 1 { ret = append(ret, ints[i:j]) i = j } } ret = append(ret, ints[i:j]) }
(this code obviously doesn't handle some edge cases well, but you get the idea)
If we were to try to write the equivalent function using inclusive-inclusive slicing, it would be significantly more complicated.
If anyone can think of any more, please feel free to edit this answer and add them.
答案2
得分: 2
《Go编程语言规范》
切片类型
切片表达式
对于字符串、数组、数组指针或切片a,主表达式
a[low : high]
构造一个子字符串或切片。索引low和high选择操作数a中出现在结果中的元素。结果的索引从0开始,长度等于high - low。
为了方便起见,任何索引都可以省略。缺少的低索引默认为零;缺少的高索引默认为切片操作数的长度。
对于数组或字符串,如果0 <= low <= high <= len(a),则索引在范围内,否则索引超出范围。对于切片,上限索引边界是切片容量cap(a),而不是长度。常量索引必须是非负的,并且可以由int类型的值表示;对于数组或常量字符串,常量索引还必须在范围内。如果两个索引都是常量,则它们必须满足low <= high。如果索引在运行时超出范围,则会发生运行时恐慌。
对于q := p[m:n]
,q
是从索引m
开始长度为n-m
的切片。
英文:
> The Go Programming Language Specification
>
> Slice types
>
> Slice expressions
>
> For a string, array, pointer to array, or slice a, the primary
> expression
>
> a[low : high]
>
> constructs a substring or slice. The indices low and high select which
> elements of operand a appear in the result. The result has indices
> starting at 0
and length equal to high - low
.
>
> For convenience, any of the indices may be omitted. A missing low
> index defaults to zero; a missing high index defaults to the length of
> the sliced operand
>
> For arrays or strings, the indices are in range if 0 <= low <= high <=
> len(a), otherwise they are out of range. For slices, the upper index
> bound is the slice capacity cap(a) rather than the length. A constant
> index must be non-negative and representable by a value of type int;
> for arrays or constant strings, constant indices must also be in
> range. If both indices are constant, they must satisfy low <= high. If
> the indices are out of range at run time, a run-time panic occurs.
For q := p[m:n]
, q
is a slice of p
starting at index m
for a length of n-m
elements.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论