我刚开始经历了我的问题,并遇到了关于切片默认章节的问题。
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
s = s[1:4]
fmt.Println(s) // [3 5 7]
fmt.Println(len(s)) // 3
s = s[:4]
fmt.Println(s) // [3 5 7 11]
fmt.Println(len(s)) // 4
}
我可以通过选择大于或等于前一个切片长度的索引来扩展切片的长度。例如s[:4]
所以我可以进入11
条目。但是当我使用s[-1:]
在左侧扩展并到达条目2
时,编译器会给我错误invalid slice index -1 (index must be non-negative)
。
在2
执行后,是否可以将左边的切片长度扩展到条目s=s[1:4]
?
答案 0 :(得分:7)
当您从基础数组中的第二个索引创建一个新切片时,就像使用s = s[1:4]
一样,那么您已经丢失了对底层数组中第一个元素的引用。
由于切片运算符s[i:j]
需要i >= 0
,因此您无法再通过进一步切片现有切片来访问原始数组中的第一个元素。
然而,您可以保留原始数组:
func main() {
a := [...]int{2, 3, 5, 7, 11, 13}
s := a[1:4]
fmt.Println(s) // [3 5 7]
fmt.Println(len(s)) // 3
s = s[:4]
fmt.Println(s) // [3 5 7 11]
fmt.Println(len(s)) // 4
s = a[:]
fmt.Println(s) // [2 3 5 7 11 13]
}
答案 1 :(得分:3)
首先,要回答您的问题,除了保留原始切片的副本之外,不能使用负数索引或访问该数据。
然而,这是一个有趣的问题,因为你已经指出这里有一个不一致的地方,这可能是你所要求的更多。如果您从切片开始为here:
a := []int{2, 3, 5, 7, 11, 13}
fmt.Println(a,len(a),cap(a))
[2 3 5 7 11 13] 6 6
然后选择中间的一片
b := a[1:2]
fmt.Println(b,len(b),cap(b))
[3] 1 5
您不能像正常期望那样访问切片len之后的原始数据中的索引:
fmt.Println(b[3])
恐慌:运行时错误:索引超出范围
不允许您在起始索引之前重新包含该数据:
d := b[-1:]
无效的切片索引-1(索引必须是非负的)
但是 允许重新包含在len之后再次包含该数据,这有点奇怪:
// This is beyond the length of b, you can't index those,
// but you can access them by reslicing
c := b[:5]
[3 5 7 11 13] 5 5
这有点不一致,因为切片数据上的大多数其他操作受到数据和len的偏移量的限制,而不是上限和原始数据长度。但是,这在规范中明确说明,可能只是切片如何表示原始数组的视图,而不是有意设计决定提供对该数据的访问。如果你可以回到原始存储器可能会很好,因为它仍然在内存中,但是看起来你只能看到数组的末端达到上限,而不是你切片一次后的开始。从spec关于切片索引的限制:
对于数组或字符串,如果0 <=低&lt; = high&lt; =,则索引在范围内 len(a),否则他们超出范围。 对于切片,上部索引 bound是切片容量上限(a)而不是长度。一个常数 index必须是非负的,并且可以通过int类型的值表示; 对于数组或常量字符串,常量索引也必须在 范围。如果两个指数都是常数,则它们必须满足低<=高。如果 指数在运行时超出范围,发生运行时恐慌。
安全并使用原始切片创建数据的不同视图可能会更好,而不是依赖于此行为。
答案 2 :(得分:0)
让我们看一下切片头from the Go source code的类型定义。
type slice struct {
array unsafe.Pointer
len int
cap int
}
“数组”字段的名称有些误导,因为它不是对语句类型a := [10]int{}; p := &a
中的数组类型的引用,而是指向数组中特定元素的指针(元素,它是切片的第一个元素)。
现在,如果您查看unsafe
package documentation,可能会注意到将unsafe.Pointer
递增到未分配的内存中是无效操作:
以这种方式从指针添加和减去偏移量都是有效的。通常使用&^舍入指针(通常用于对齐)也是有效的。在所有情况下,结果都必须继续指向原始分配的对象。
与C语言不同,将指针提前到其原始分配的末尾是无效的...
尽管如此,并不能保证在运行时会对其进行实际检查,所以最终以unsafe.Pointer
结尾指向未分配的内存通常是很可能的并且很简单,如以下示例所示:>
package main
import (
"fmt"
"unsafe"
)
func main() {
a := [10]int{}
for i := range a {
a[i] = i + 1
}
fmt.Printf("a: %v\n", a)
s := a[2:5]
p := unsafe.Pointer(&s[0])
fmt.Printf("p initially points at the 3rd element of a...\n")
// Let's break the rules!
for i := 0; i < 15; i++ {
if i == len(s) {
fmt.Printf("We've hit the end of slice s, but there is still memory allocated past this in the backing array a...\n")
}
if i == cap(s) {
fmt.Printf("We're in invalid/undefined territory now...\n")
}
fmt.Printf("addr: %v val: %v\n", uintptr(p), *(*int)(p))
p = unsafe.Pointer(uintptr(p) + unsafe.Sizeof(s[0]))
}
}
https://play.golang.org/p/4vF-7z45PP5
那么,这对扩展切片意味着什么?假设您像这样重新切片s
:
t = s[a:b]
在给定t
的标头的情况下,计算s
的新切片标头很简单:
t.array = unsafe.Pointer(uintptr(s.array) + a * unsafe.Sizeof(s[0]))
t.len = b - a
t.cap = s.cap - a
值得注意的是,这将对a
和b
的负值和非负值都起作用,假设支持数组具有容量。
更重要的是,鉴于上述情况,验证t
不会超出其分配的容量也很重要:
if t.len < 0 || t.len > t.cap {
panic("invalid slice!")
}
不幸的是,上述假设仅在a
和b
为非负数的情况下成立。 鉴于上面的切片标头的类型定义,如果允许使用负片索引,则无法检查重新切片是否已扩展到其后备数组之外。因此,负索引在当前slice实施下是不安全的,并且不允许使用。
话虽如此,通过在切片头中添加另一个int
字段来跟踪指针的前端中的可用容量将非常简单,然后您可以非常轻松地请检查以确保该片段在其为负索引分配的范围内,但是到目前为止,语言作者还认为这没有必要。