我刚开始经历了getour,并遇到了关于切片默认章节的问题.
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
}
Run Code Online (Sandbox Code Playgroud)
我可以通过选择大于或等于前一个切片长度的索引来扩展切片的长度.例如s[:4],我可以进入11.但是当我使用s[-1:]左侧扩展并进入条目时2,编译器会给我错误invalid slice index -1 (index must be non-negative).是否可以2在s=s[1:4]执行后延长左边的切片长度以达到输入?
当你从底层数组中的第二个索引创建一个新切片时,就像你一样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]
}
Run Code Online (Sandbox Code Playgroud)
首先,要回答您的问题,否,除非保留原始切片的副本,否则无法使用负索引或访问该数据。
但这是一个有趣的问题,因为您已经指出这里存在不一致之处,这可能是您要问的更多问题。如果你开始用切片的位置:
a := []int{2, 3, 5, 7, 11, 13}
fmt.Println(a,len(a),cap(a))
Run Code Online (Sandbox Code Playgroud)
[2 3 5 7 11 13] 6 6
并取一小部分
b := a[1:2]
fmt.Println(b,len(b),cap(b))
Run Code Online (Sandbox Code Playgroud)
[3] 1 5
正如您所期望的那样,不允许您访问该切片len之后的原始数据中的索引:
fmt.Println(b[3])
Run Code Online (Sandbox Code Playgroud)
紧急:运行时错误:索引超出范围
您不允许从起始索引之前重新切片以再次包含该数据:
d := b[-1:]
Run Code Online (Sandbox Code Playgroud)
无效的分片索引-1(索引必须为非负数)
但你是允许对重新分区包括数据的len个达帽后再次,这是一个有点古怪:
// This is beyond the length of b, you can't index those,
// but you can access them by reslicing
c := b[:5]
Run Code Online (Sandbox Code Playgroud)
[3 5 7 11 13] 5 5
这有点不一致,因为对切片数据执行的其他大多数操作都受数据和len中的偏移量(而不是上限和原始数据长度)的限制。但是,这在规范中已明确说明,并且可能只是切片如何将视图表示到原始阵列上的人工产物,而不是有意提供访问该数据的设计决定。如果您可以返回原始存储,则可能会很好,因为它仍在内存中,但是看来您只能看到阵列的末端达到上限,而无法看到切片后的开始。根据有关切片索引限制的规范:
对于数组或字符串,如果0 <=低<=高<= len(a),则索引在范围内,否则它们超出范围。对于切片,索引的上限是切片容量cap(a)而不是length。常数索引必须是非负数,并且可以由int类型的值表示;对于数组或常量字符串,常量索引也必须在范围内。如果两个索引都恒定,则它们必须满足低<=高。如果索引在运行时超出范围,则会发生运行时恐慌。
为了安全起见,使用原始切片在数据上创建不同的视图可能会更好,而不是依赖于此行为。
让我们从 Go 源代码中查看切片头的类型定义。
type slice struct {
array unsafe.Pointer
len int
cap int
}
Run Code Online (Sandbox Code Playgroud)
“数组”字段的名称有点误导,因为它不是像您从语句中获得的对数组类型的引用a := [10]int{}; p := &a,而是指向数组中特定元素的指针(该元素是数组的第一个元素)片)。
现在,如果您查看unsafe 包文档,您可能会注意到将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]))
}
}
Run Code Online (Sandbox Code Playgroud)
https://play.golang.org/p/4vF-7z45PP5
那么,这对于扩展切片意味着什么?假设您s像这样重新切片:
t = s[a:b]
Run Code Online (Sandbox Code Playgroud)
然后计算新的切片标头 fort是微不足道的,给定标头 for s:
t.array = unsafe.Pointer(uintptr(s.array) + a * unsafe.Sizeof(s[0]))
t.len = b - a
t.cap = s.cap - a
Run Code Online (Sandbox Code Playgroud)
值得注意的是,这会为的负的和非负值的工作a和b,假定背衬阵列具有的能力。
更重要t的是,鉴于上述情况,验证不会超出其分配的容量也是微不足道的:
if t.len < 0 || t.len > t.cap {
panic("invalid slice!")
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,上述假设仅在a和b非负时成立。如果允许负切片索引,则无法检查重新切片是否已扩展到其支持数组的分配之外,鉴于上面切片标头的类型定义。因此,在当前切片实现下,负索引是不安全的,也是不允许的。
话虽如此,通过向切片标头添加另一个字段来跟踪指针前面的可用容量也非常简单int,然后您可以非常轻松地检查以确保切片在其分配的负索引范围内,但是作为现在语言作者认为没有必要。