Golang中这种实现方式的背后原因是什么?求大神解答
Golang中这种实现方式的背后原因是什么?求大神解答
在构建一个非常简单的缓存系统时,我遇到了一个场景:我有一个切片,在遍历它的过程中,它会被消耗。新传入的数据会追加到切片末尾,而发送操作则通过发送 slice[0](假设 len() > 0 检查通过)并使用 myslice = myslice[1:] 重建切片来处理。我在这里重新实现了一个类似的逻辑:
func main() {
var finval []int = []int{1, 2, 3, 4, 5, 6, 7}
var lenfin = len(finval)
//for i := 0; i < len(finval); i++{
for i := 0; i < lenfin; i++ {
fmt.Println(i, ":", finval[0])
finval = finval[1:]
}
}
我注意到,在摆弄这个简化版本时,被注释掉的那一行在迭代过程中会重新检查 finval 的长度,导致输出为:
0 : 1
1 : 2
2 : 3
3 : 4
而不是预期的、正确的结果:
0 : 1
1 : 2
2 : 3
3 : 4
4 : 5
5 : 6
6 : 7
在我看来,这似乎很明显:每次循环时,它都会重新检查长度。我不确定原因,但我原本期望长度值在开始时被检查一次,之后不再改变。我这样期望是错的吗?这种实现方式是否有技术上的原因?我原以为需要一些涉及指针的“黑魔法”才能让它每次循环都重新检查。
更多关于Golang中这种实现方式的背后原因是什么?求大神解答的实战教程也可以访问 https://www.itying.com/category-94-b0.html
它在每次迭代后重新评估结束条件,并且 finval 的长度在循环内部发生变化。
func main() {
fmt.Println("hello world")
}
更多关于Golang中这种实现方式的背后原因是什么?求大神解答的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Go 语言中没有 while 循环。
func main() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}
有一个类似的讨论在这里,也许能帮到你
实际上,Go语言中的for循环是这样的:
func main() {
var finval []int = []int{1, 2, 3, 4, 5, 6, 7}
var lenfin = len(finval)
var i int = 0
while i < lenfin {
fmt.Println(i, ":", finval[0])
finval = finval[1:]
i++
}
}
如果你在循环中检查长度,会是这样的:
func main() {
var finval []int = []int{1, 2, 3, 4, 5, 6, 7}
var lenfin = len(finval)
var i int = 0
while i < lenfin {
fmt.Println(i, ":", finval[0])
finval = finval[1:]
lenfin = len(finval) // 这里再次检查
i++
}
}
考虑以下代码:
func main() {
shiftingTarget := 100
for i := 0; i < shiftingTarget; i++ {
shiftingTarget--
}
fmt.Println("All done and shifting target is", shiftingTarget)
}
它的行为完全符合你的预期,对吧?如果你把它反转为 for i := 0; shiftingTarget > i; i++ {,你也会期望它能正常工作,对吧?那么编译器如何知道你认为哪些属性是常量呢?来自 Go 语言之旅(强调部分是我加的):
基本的
for循环有三个由分号分隔的组成部分:
- 初始化语句:在第一次迭代前执行
- 条件表达式:在每次迭代前求值
- 后置语句:在每次迭代结束时执行
初始化/条件/后置语句并没有什么特殊之处。如果你愿意,可以写成下面这样:
func main() {
i := 0
for fmt.Println("init"); condition(); fmt.Println("post") {
if i == 10 {
break
}
i++
}
}
func condition() bool {
fmt.Println("condition")
return true
}
话虽如此,我认为你的循环更准确和简单的表示方式可能是这样的:
func main() {
finval := []int{1, 2, 3, 4, 5, 6, 7}
// 处理切片中的所有元素
for len(finval) > 0 {
fmt.Println(finval[0])
finval = finval[1:]
}
}
在Go语言中,切片在循环条件中重新计算长度是预期的行为,这并非“黑魔法”,而是由语言规范和实现机制决定的。
你观察到的现象是因为:
-
循环条件在每次迭代时都会重新求值
在for i := 0; i < len(finval); i++中,len(finval)会在每次循环开始前调用,此时finval可能已经被修改(例如finval = finval[1:]),因此长度会变化。 -
切片是引用类型
切片本身是一个包含指向底层数组指针的结构,当你执行finval = finval[1:]时,你创建了一个新的切片头(slice header),但底层数组可能仍然是同一个。循环条件中的len(finval)会读取这个新切片头的长度字段。 -
你“修复”后的版本
在for i := 0; i < lenfin; i++中,lenfin是一个在循环开始前就确定的整数值,不会随切片变化而改变,因此循环会执行固定的次数。
示例代码说明:
package main
import "fmt"
func main() {
// 原始版本:每次迭代重新计算长度
fmt.Println("原始版本(错误逻辑):")
finval := []int{1, 2, 3, 4, 5, 6, 7}
for i := 0; i < len(finval); i++ {
fmt.Println(i, ":", finval[0])
finval = finval[1:] // 修改了 finval,下次 len(finval) 会变小
}
// 修复版本:提前保存长度
fmt.Println("\n修复版本(正确逻辑):")
finval2 := []int{1, 2, 3, 4, 5, 6, 7}
lenfin := len(finval2)
for i := 0; i < lenfin; i++ {
fmt.Println(i, ":", finval2[0])
finval2 = finval2[1:]
}
// 另一种常见模式:直接遍历切片
fmt.Println("\n直接遍历切片(推荐):")
finval3 := []int{1, 2, 3, 4, 5, 6, 7}
for _, v := range finval3 {
fmt.Println(v)
}
}
输出:
原始版本(错误逻辑):
0 : 1
1 : 2
2 : 3
3 : 4
修复版本(正确逻辑):
0 : 1
1 : 2
2 : 3
3 : 4
4 : 5
5 : 6
6 : 7
直接遍历切片(推荐):
1
2
3
4
5
6
7
技术原因:
Go的 for 循环条件表达式在每次迭代前都会求值,这是语言规范定义的。这样做是为了允许动态条件(例如从通道读取、检查变化的状态等)。如果你希望长度固定,就必须在循环前保存它。
在你的缓存系统场景中,如果需要在遍历时修改切片,通常建议使用 for len(slice) > 0 或提前保存长度,而不是依赖会变化的循环条件。


