Golang中数组转切片范围参数的使用困惑

Golang中数组转切片范围参数的使用困惑 我正在学习Go语言,我认为我现在理解了数组到切片的范围参数,但我一直有一个挥之不去的问题。为什么第二个范围参数是源数组的长度?难道把它设为从第一个参数开始的长度不是更合理吗?或者,换一种方式,作为第二个偏移量?就目前这样,在我脑子里,我不得不进行一个 结束 - 1 的计算,这有点烦人。

为了更好地解释这一点,这里有一些示例代码:

package main

import (
	"fmt"
)

func main() {
	darr := [...]int{10, 20, 30, 40, 50, 60, 70}
	dslice := darr[3:5]
	fmt.Println(dslice)
}

结果:

[40 50]

从上面的 dslice 赋值来看,3 是第三个索引(从零开始),而 5 是源数组的长度减一。如果这是一个电子表格,单元格范围会被写成 [3:4]

打个比方。你走进一个有窗户的房间,你想知道那个窗户的长度。Go程序员会说:“窗户的第一个点在距离门3米处,第二个点在距离门5米处”。没错,但你本可以直接说“它有2米长”。或者,电子表格的类比会说“窗户占据了3米和4米处的两个位置”。无论如何,Go的这种做法迫使读者进行一个减法计算。

希望我已经解释清楚了。


更多关于Golang中数组转切片范围参数的使用困惑的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

是的,这种模式也让我感到困扰。 希望 Go 能像你提到的那样,将其改为一种更简单的方式。

更多关于Golang中数组转切片范围参数的使用困惑的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


可以这样理解:起始索引是包含的,而结束索引是不包含的。这是许多其他编程语言中常见的惯例,因此 Go 语言的创建者们也采用了它。这样做的好处是,当你在 Go 和 Python 等语言之间来回切换时,无需转换思维方式。

我想你是对的。我刚刚查了一下 JavaScript 和 Rust,它们确实如你所说,但 PHP 的 slice 函数正如其名,如果你想要两个元素,你就传入“2”,而不是“5 - 3”。

冒号让它看起来像电子表格里的范围,幸好我从来不需要对它进行减一操作。

就目前而言,在我脑海里,我必须进行一个 end - 1 的计算,这有点烦人。

你在这里提到的是编程中最古老、永无止境且悬而未决的讨论之一。就像争论集合的索引应该从 0 还是 1 开始一样。

这不是 Go 语言独有的问题,而是所有编程语言普遍存在的。

顺便提一下,在 Go 语言中重新实现 PHP 的 array_slice 作为一个自定义工具函数应该是一个简单的任务。

在Go语言中,切片范围参数的设计确实需要一些适应,但它的逻辑是清晰且一致的。关键点在于:第二个参数表示的是切片结束位置的索引(不包含该索引本身),而不是长度或偏移量。

这种设计有以下几个优点:

  1. 一致性a[i:j] 创建的切片包含从索引 ij-1 的元素,这样 j-i 就是切片的长度。
  2. 方便连续切片:可以轻松地创建相邻的切片,例如 a[:i]a[i:] 正好分割数组。
  3. 与容量配合:切片的容量计算也基于这种设计,cap(a[i:j]) = len(a) - i

示例代码说明:

package main

import "fmt"

func main() {
    arr := [...]int{10, 20, 30, 40, 50, 60, 70}
    
    // 创建切片 [40, 50]
    slice1 := arr[3:5]
    fmt.Printf("slice1: %v, len: %d\n", slice1, len(slice1)) // [40 50], len: 2
    
    // 连续切片
    slice2 := arr[:3]
    slice3 := arr[3:]
    fmt.Printf("slice2: %v\n", slice2) // [10 20 30]
    fmt.Printf("slice3: %v\n", slice3) // [40 50 60 70]
    
    // 计算长度
    length := 5 - 3 // 直接相减得到长度2
    fmt.Printf("length: %d\n", length)
    
    // 扩展切片(利用容量)
    slice4 := arr[2:5]
    slice5 := slice4[:cap(slice4)]
    fmt.Printf("slice4: %v, cap: %d\n", slice4, cap(slice4)) // [30 40 50], cap: 5
    fmt.Printf("slice5: %v\n", slice5) // [30 40 50 60 70]
}

虽然最初可能需要适应,但这种设计在实际使用中提供了更大的灵活性和一致性。当需要特定长度的切片时,可以使用辅助函数:

func sliceFromLength(arr []int, start, length int) []int {
    return arr[start:start+length]
}

// 使用示例
arr := []int{10, 20, 30, 40, 50, 60, 70}
slice := sliceFromLength(arr, 3, 2) // 从索引3开始,取2个元素
fmt.Println(slice) // [40 50]

Go的这种设计确保了切片操作在数学上是清晰的:a[i:j] 包含的元素索引范围是 i ≤ index < j

回到顶部