Go 语言切片遍历地址会发生改变吗?

引言:今天面试的时候,面试官问了一道学 Go 语言的同学都会的简单代码,是关于 Go 语言 for 循环问题的,他询问了一个点,循环中共享变量的地址会发生改变吗?

相信听到这个问题的你,第一反应肯定是不会变啊,怎么会发生改变呢?听到这里面试官选择摇了摇头,那到底是怎么回事呢?接下来我们来研究一下。

题目

Go 语言切片遍历地址会发生改变吗?

推荐解析

正文

首先是下面这段代码,这段代码相信很多在学习 Go 语言的同学都有接触过,那么我们现在来问一个问题,就是在

for 循环中,v 的地址会发生改变吗?

go 复制代码
package main

import (
	"fmt"
	"runtime"
)

func main() {
	slice := make([]int, 3, 5)
	fmt.Println(runtime.Version())
	fmt.Println("---------------------")
	for i, v := range slice {
		fmt.Printf("%p\n", &v)
		fmt.Println(i)
	}
}

在此之前,问过一些学习 Go 的同学,如下图所示:

这位学习 Go 的同学第一直觉就是不会变的,那我们现在来运行一下看看结果:

我们会发现,这个关于 v 地址打印的内容,其是会发生改变的,相信很多人第一反应都是不敢相信的的,我们再来测试一组数据:

这次我们会发现,v 的地址又没有改变了,这是怎么回事呢?我们回到原文,将两次运行结果复制下来对比一下:

go 复制代码
第一次运行结果:
go1.22.1
---------------------
0xc00000a100
0
0xc00000a110
1
0xc00000a120
2

==========================

第二次运行结果:
go1.21.2
---------------------
0xc00000a100
0
0xc00000a100
1
0xc00000a100
2

我们会发现一个很关键的点,就是第一次运行结果和第二次运行结果关于 Go 语言的版本号有所不同,到这里我们就可以得出一个初步结论,go 语言 1.22 之后与 1.22 之前的版本是存在差异的,即在 1.22 之前,for 循环遍历循环变量是不会发生改变的,1.22 以及之后的版本都是会进行改变的,那改变的原因是什么呢,我们继续来探究一下。

探究

谷歌每次发布 Go 语言的新版本都会发布博客,首先我们来看一下谷歌发布的博客

谷歌 Go 1.22 博客地址https://golang.google.cn/blog/go1.22

我们会发现其已经发生了改变了,然后我们将官方 Demo 截取下来:

go 复制代码
func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}

期望输出

这段代码在 go 1.21 版本运行的时候,可能会报一个错误,即 loop variable v captured by func literal

但是其还是会输出的,运行结果如下:

go 复制代码
c
c
c

但如果使用 1.22 版本输出的话,顺序随机,但是每个值都会输出

go 复制代码
a
c
b

共享变量

到这里我们大概就知道了,在 Go 1.22 之前,循环闭包共享的是同一个变量,而 1.22 之后,其不再共享同一个变量,这里我们再打印地址测试一下,代码如下:

go 复制代码
package main

import (
    "fmt"
    "runtime"
)

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    fmt.Println("Go Version:", runtime.Version())
    for _, v := range values {
        go func() {
            fmt.Println(&v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}

运行结果如下:

go 复制代码
Go Version: go1.22.1
0xc0000260a0
0xc000026080
0xc000026090
=======================
Go Version: go1.21.2
0xc000026080
0xc000026080
0xc000026080

总结

到这里,我们就探究完毕了,Go 1.22 之后,for 循环的循环变量不再共享了,因此循环变量的地址也会发生改变。

最后

最后,我们回到一开始的问题,for 循环遍历切片,循环变量的地址会发生改变吗?这个时候我们需要回答到关于版本变更的问题,即在 1.22 之前,循环变量的地址是共享的,不会发生改变,在 1.22 之后,循环变量的地址不再是共享的,是会发生改变的。

其他补充

鱼聪明 AI 的回答:

鱼聪明 AI 地址:https://www.yucongming.com/

在 Go 语言中,切片(slice)是一个引用类型(reference type)。当你遍历切片时,遍历过程中获取的元素地址不会改变,因为切片本身并不会重新分配内存或改变其底层数组的地址。

具体来说:

  • 切片包含了一个指向数组的指针(或称为引用),以及切片的长度和容量。
  • 当你通过索引访问切片的元素时,实际上是访问底层数组中对应索引位置的元素。
  • 切片的底层数组在不需要扩展容量时,其地址是固定的,因此切片的元素地址在遍历过程中是稳定的。

例如,考虑以下示例代码:

go 复制代码
package main

import "fmt"

func main() {
    nums := []int{1, 2, 3, 4, 5}

    for i := range nums {
        fmt.Printf("Element at index %d has address: %p\n", i, &nums[i])
    }
}

在这个例子中,切片 nums 的每个元素地址在整个循环过程中保持不变,因为 nums 持有的是底层数组的引用,而不是元素本身的复制。

总结来说,Go 语言中遍历切片获取元素的地址是安全的,因为切片的底层数组地址不会随着遍历过程发生改变。

欢迎交流

本文主要介绍 Go 语言切片遍历地址是否会发生改变的问题,在文末还有剩余三个思考题,欢迎小伙伴在评论区进行留言!近期面试鸭小程序已全面上线,想要刷题的小伙伴可以积极参与!

1)for range 循环遍历 slice 有什么问题?

2)如果对切片进行 for range 遍历的时候,遍历过程中追加的元素会不会遍历到?

3)对于需要跨版本兼容的 Go 代码,如何处理循环变量在不同版本中的行为差异?

相关推荐
高效匠人9 分钟前
Python10天冲刺-设计模型之策略模式
开发语言·人工智能·python·策略模式
黄雪超21 分钟前
JVM——JVM 是如何执行方法调用的?
java·开发语言·jvm
风暴之零30 分钟前
文本中地理位置提取方法—正则和NLP模型
开发语言·python
Dxy123931021641 分钟前
python合并word中的run
开发语言·python·word
光影少年1 小时前
新手学编程前端好还是后端
前端·后端
why1511 小时前
百度网盘golang实习面经
开发语言·后端·golang
楠奕1 小时前
neo4j vs python
开发语言·python·neo4j
layman05281 小时前
javaScript——正则表达式(四)
开发语言·javascript·正则表达式
jiunian_cn2 小时前
【c++】【STL】queue详解
开发语言·c++·visualstudio
achene_ql2 小时前
C++ 与 Lua 联合编程
开发语言·c++·lua