惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

M
MIT News - Artificial intelligence
雷峰网
雷峰网
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Last Week in AI
Last Week in AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
阮一峰的网络日志
阮一峰的网络日志
月光博客
月光博客
博客园 - Franky
腾讯CDC
T
Tailwind CSS Blog
Recent Announcements
Recent Announcements
V
V2EX
N
Netflix TechBlog - Medium
量子位
Jina AI
Jina AI
Y
Y Combinator Blog
The GitHub Blog
The GitHub Blog
G
Google Developers Blog
爱范儿
爱范儿
博客园 - 叶小钗
D
Docker
MongoDB | Blog
MongoDB | Blog
D
DataBreaches.Net
T
The Blog of Author Tim Ferriss

极客兔兔

Go sync.Cond | Go 语言高性能编程 Go 死码消除与调试(debug)模式 | Go 语言高性能编程 Go sync.Once | Go 语言高性能编程 Go 逃逸分析 | Go 语言高性能编程 2020 年终总结 | 极客兔兔 Go struct 内存对齐 | Go 语言高性能编程 Go 空结构体 struct{} 的使用 | Go 语言高性能编程 控制协程(goroutine)的并发数量 | Go 语言高性能编程 | 极客兔兔 如何退出协程 goroutine (其他场景) | Go 语言高性能编程 如何退出协程 goroutine (超时场景) | Go 语言高性能编程 减小 Go 代码编译后的二进制体积 | Go 语言高性能编程 Go Reflect 提高反射性能 | Go 语言高性能编程 读写锁和互斥锁的性能比较 | Go 语言高性能编程 | 极客兔兔 for 和 range 的性能比较 | Go 语言高性能编程 切片(slice)性能及陷阱 | Go 语言高性能编程 | 极客兔兔 字符串拼接性能及原理 | Go 语言高性能编程 | 极客兔兔 pprof 性能分析 | Go 语言高性能编程 benchmark 基准测试 | Go 语言高性能编程 Go 语言高性能编程 | 极客兔兔 Go 接口型函数的使用场景 | 极客兔兔 Python 简明教程 | 快速入门 | 极客兔兔 Go 语言笔试面试题(代码输出) | 极客面试 | 极客兔兔 动手写RPC框架 - GeeRPC第七天 服务发现与注册中心(registry) | 极客兔兔 动手写RPC框架 - GeeRPC第六天 负载均衡(load balance) 动手写RPC框架 - GeeRPC第五天 支持HTTP协议 | 极客兔兔 动手写RPC框架 - GeeRPC第四天 超时处理(timeout) | 极客兔兔 动手写RPC框架 - GeeRPC第三天 服务注册(service register) 动手写RPC框架 - GeeRPC第二天 支持并发与异步的客户端 | 极客兔兔 动手写RPC框架 - GeeRPC第一天 服务端与消息编码 | 极客兔兔 7天用Go从零实现RPC框架GeeRPC | 极客兔兔
Go 语言陷阱 - 数组和切片 | Go 语言高性能编程
2020-12-07 · via 极客兔兔

源代码/数据集已上传到 Github - high-performance-go

golang gotchas

1 第一个陷阱

1.1 下面程序的输出是

1
2
3
4
5
6
7
8
9
func foo(a [2]int) {
a[0] = 200
}

func main() {
a := [2]int{1, 2}
foo(a)
fmt.Println(a)
}

1.2 答案

正确的输出是 [1 2],数组 a 没有发生改变。

  • 在 Go 语言中,数组是一种值类型,而且不同长度的数组属于不同的类型。例如 [2]int[20]int 属于不同的类型。
  • 当值类型作为参数传递时,参数是该值的一个拷贝,因此更改拷贝的值并不会影响原值。

我们在 切片(slice)性能及陷阱 这篇文章中也提到了,为了避免数组的拷贝,提高性能,建议传递数组的指针作为参数,或者使用切片代替数组。

1.3 更多

如果将上述程序替换为:

1
2
3
4
5
6
7
8
9
func foo(a *[2]int) {
(*a)[0] = 200
}

func main() {
a := [2]int{1, 2}
foo(&a)
fmt.Println(a)
}

1
2
3
4
5
6
7
8
9
func foo(a []int) {
a[0] = 200
}

func main() {
a := []int{1, 2}
foo(a)
fmt.Println(a)
}

输出将会变成 [200 2]

切片(slice)性能及陷阱 这篇文章中,我们也提到了切片由三个值构成:

  • *ptr 指向底层数组的指针
  • len 长度
  • cap 容量

因此,将切片作为参数时,拷贝了一个新切片,即拷贝了构成切片的三个值,包括底层数组的指针。对切片中某个元素的修改,实际上是修改了底层数组中的值,因此原切片也发生了改变。

2 第二个陷阱

2.1 下面程序的输出是

1
2
3
4
5
6
7
8
9
10
func foo(a []int) {
a = append(a, 1, 2, 3, 4, 5, 6, 7, 8)
a[0] = 200
}

func main() {
a := []int{1, 2}
foo(a)
fmt.Println(a)
}

2.2 答案

输出仍是 [1 2],切片 a 没有发生改变。

传参时拷贝了新的切片,因此当新切片的长度发生改变时,原切片并不会发生改变。而且在函数 foo 中,新切片 a 增加了 8 个元素,原切片对应的底层数组不够放置这 8 个元素,因此申请了新的空间来放置扩充后的底层数组。这个时候新切片和原切片指向的底层数组就不是同一个了。因此,对新切片第 0 个元素的修改,并不会影响原切片的第 0 个元素。

如果如果希望 foo 函数的操作能够影响原切片呢?

两种方式:

  • 设置返回值,将新切片返回并赋值给 main 函数中的变量 a
  • 切片也使用指针方式传参。
1
2
3
4
5
6
7
8
9
10
11
func foo(a []int) []int {
a = append(a, 1, 2, 3, 4, 5, 6, 7, 8)
a[0] = 200
return a
}

func main() {
a := []int{1, 2}
a = foo(a)
fmt.Println(a)
}

1
2
3
4
5
6
7
8
9
10
func foo(a *[]int) {
*a = append(*a, 1, 2, 3, 4, 5, 6, 7, 8)
(*a)[0] = 200
}

func main() {
a := []int{1, 2}
foo(&a)
fmt.Println(a)
}

上述两个程序的输出均为:

1
[200 2 1 2 3 4 5 6 7 8]

从可读性上来说,更推荐第一种方式。

附 推荐与参考


edit this page last updated at 2026-02-23