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 代码,如何处理循环变量在不同版本中的行为差异?

相关推荐
只想摆烂@5 分钟前
C# 如何单纯的优化循环
开发语言·c#
阑梦清川30 分钟前
C++多态~~的两个特殊情况
开发语言·c++
m0_5831059031 分钟前
基于flask的闪现、g对象、蓝图
后端·python·flask
白骑士所长36 分钟前
白骑士的C语言教学基础篇 1.3 控制流
c语言·开发语言
EQ-雪梨蛋花汤44 分钟前
使用docfx生成API文档【生成c#帮助文档】
开发语言·c#
三村阿明1 小时前
Java多线程
java·开发语言·多线程·reentrantlock
菜鸟蹦迪1 小时前
java技术:knife4j实现后端swagger文档
java·开发语言
jqrbcts1 小时前
关于发那科机器人系统升级方法
开发语言·人工智能·python·机器人·c#
lly2024061 小时前
CSS 提示工具:增强网站互动性与用户体验
开发语言
martian6651 小时前
如何成为C#编程高手?
开发语言·c#·编程高手