Go 面试题:SliceHeader 隐藏陷阱

大家好,我是煎鱼。

最近也是面试季+毕业季了,很多同学正在积极准备面试。尤其是很多同学,已经通过官网资料熟悉了 Go 基本语法,但没有太大把握。希望对一些常见的棘手面试问题做一些预习。

今天结合与此分享给大家,一起学习和进步!

面试问题

请先在脑子里思考一下具体的运行结果,再查看答案。

如下代码:

go 复制代码
func main() {
    s := make([]int, 0, 2)
    doSomething(s)
    fmt.Println(s)
}

func doSomething(a []int) {
    a = append(a, 1)
}
// 煎鱼干点什么...

面试问题:fmt.Println 的输出结果是什么?

问题解析

运行程序,查看输出结果:

css 复制代码
[]

fmt.Println 最终打印的是一个长度为 0 的切片。

答案是:空切片。(你答对了吗?)

在 Go 中,函数参数是按值传递的,这意味着上述代码在参数传递时,创建了参数值的副本并传递给函数。

而切片实际上是一个包含长度(len)、容量(cap)和指向底层数组指针(data)的结构体。

当我们将切片作为函数参数传递时,实质上复制的是切片的 SliceHeader,对应的底层数组是保持不变的。

结合代码来讲,就是因为在 doSomething 函数中,创建了 SliceHeader 的新副本。然后 append 函数会在超过容量时重新分配新切片,并返回更新后的切片。

深入验证

我们可以使用 unsafe 包去打印 SliceHeader(切片头),进行进一步的验证和分析。

如下代码:

go 复制代码
type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

func main() {
    s := make([]int, 0, 2)
    sh := (*SliceHeader)(unsafe.Pointer(&s))
    fmt.Println(sh)
    doSomething(s)
}

func doSomething(a []int) {
    a = append(a, 1)
    sh := (*SliceHeader)(unsafe.Pointer(&a))
    fmt.Println(sh)
}

输出结果:

arduino 复制代码
&{1374389592336 0 2} // main
&{1374389592336 1 2} // doSomething

两个切片的 Data 指针地址指向的是同一个底层数组。但由于长度不同,它们在应用的表现上是两个不同的切片。

这也印证了前面问题的结果是输出了空切片,切片长度为 0 的内部原理。

变通方法

这种情况下,建议是修改写法,提高代码易读性。否则后续维护也比较麻烦,不熟悉的同学咋一眼一看很有可能发现不了问题。

但如果你还是希望输出你想要的切片值,可以采取以下变通方法。

改动后的代码:

go 复制代码
func main() {
    s := make([]int, 0, 2)
    doSomething(s)
    fmt.Println(s[:1]) // 进行新的切片操作
}

func doSomething(a []int) {
    a = append(a, 1)
}

输出结果:

csharp 复制代码
[1]

原因是在进行 s[:1] 切片操作时,本质上是创建了一个新的 SliceHeader,所以可以正常打印和获取预期的元素。

当然,还有一种常见的写法就是切片 append 等变更后一定做一遍再赋值,这样可以规避掉不少使用上的细节坑。

总结

今天这篇文章讨论了一个很常见的 Go 面试问题,内容涉及切片作为函数参数的传递和修改。

重点在于切片作为参数是按值传递的,因此函数内部的修改不会影响外部变量。

如果仍然希望获取可以通过切片操作,重新切分一下新的切片结果集就可以了。

推荐阅读

相关推荐
牛奔2 小时前
Go + Vue 接入行为验证码完整指南
go
人间打气筒(Ada)15 小时前
go:如何实现接口限流和降级?
开发语言·中间件·go·限流·etcd·配置中心·降级
我叫黑大帅1 天前
Go 中最强大的权限控制库(Casbin)
后端·面试·go
古城小栈2 天前
Jenkins+K8s实现Go后端服务自动化部署
go·k8s·jenkins
不会写DN2 天前
Gin 实战入门:从环境搭建到企业级常用特性全解析
go·gin
下次一定x2 天前
深度解析 Kratos 客户端服务发现与负载均衡:从 Dial 入口到 gRPC 全链路落地(下篇)
后端·go
乐茵lin3 天前
大厂都在问:如何解决map的并发安全问题?三种方法让你对答如流
开发语言·go·编程·map·并发安全·底层源码·sync.map
不会写DN3 天前
GORM 实战入门:从环境搭建到企业级常用特性全解析
sql·mysql·go·gin
F1FJJ3 天前
Shield CLI 的 PostgreSQL 插件 v0.5.0 发布:数据库导出 + 协作增强,ER 图全新体验
网络·数据库·docker·postgresql·go
liangbm35 天前
AI-ViewNote:把网课和会议视频自动卷成结构化笔记
ai·typescript·go·软件构建·开源软件·react·桌面软件