golang 传参使用切换和数组有什么区别?
·
一、快速记忆(先记核心)
| 方面 | 数组 | 切片 |
|---|---|---|
| 本质 | 固定长度的值 | 指向底层数组的视图 |
| 传参时 | 整个复制 | 只复制描述信息(24字节) |
| 函数内改元素 | 不影响外面 | 影响外面 |
| 函数内改长度 | 不允许(长度固定) | 外面看不到(需返回) |
二、详细对比(4个方面)
1️⃣ 内存和性能
数组传参:复制全部数据
func consumeArray(arr [1000000]int) { // 100万个int,约8MB
// 函数调用时会完整复制这8MB数据
}
func main() {
huge := [1000000]int{}
consumeArray(huge) // 慢!复制8MB
}
切片传参:只复制小纸条
func consumeSlice(slice []int) { // 只复制24字节(指针+长度+容量)
// 不管底层数组多大,都只复制24字节
}
func main() {
huge := make([]int, 1000000) // 底层8MB数组
consumeSlice(huge) // 快!只复制24字节
}
结论:大数组用切片传参,性能好得多。
2️⃣ 修改内部元素的影响
package main
import "fmt"
// 数组:改副本,不影响外面
func modifyArray(arr [3]int) {
arr[0] = 999
}
// 切片:改原数组,影响外面
func modifySlice(slice []int) {
slice[0] = 999
}
func main() {
// 数组测试
arr := [3]int{1, 2, 3}
modifyArray(arr)
fmt.Println("数组传参后:", arr) // [1 2 3] 没变!
// 切片测试
slice := []int{1, 2, 3}
modifySlice(slice)
fmt.Println("切片传参后:", slice) // [999 2 3] 变了!
}
输出:
数组传参后: [1 2 3]
切片传参后: [999 2 3]
3️⃣ 修改长度(增删元素)
这是最坑的地方,细看:
package main
import "fmt"
// 尝试在函数内增加元素
func addElement(s []int) {
s = append(s, 100) // 增加一个元素
fmt.Println("函数内部:", s) // [1 2 3 100]
}
func main() {
slice := []int{1, 2, 3}
addElement(slice)
fmt.Println("函数外部:", slice) // [1 2 3] 没变!
}
为什么?
因为切片有3个信息:
-
ptr(指针):指向底层数组
-
len(长度):当前有多少元素
-
cap(容量):最多能装多少
传参时复制了这3个信息,所以:
-
函数内的
len是3,append后变成4(但改的是副本) -
外面的
len仍然是3
解决方案:返回新切片
func addElement(s []int) []int {
s = append(s, 100)
return s // 返回新的切片
}
func main() {
slice := []int{1, 2, 3}
slice = addElement(slice) // 接收返回值
fmt.Println(slice) // [1 2 3 100] 成功!
}
4️⃣ 函数签名(类型系统)
// 数组:长度是类型的一部分!
func process(arr1 [3]int) {} // 只能接收长度3的数组
func process(arr2 [5]int) {} // 这是不同的类型
// 切片:长度不是类型的一部分
func process(slice []int) {} // 可以接收任何长度的int切片
func main() {
arr3 := [3]int{1, 2, 3}
arr5 := [5]int{1, 2, 3, 4, 5}
process(arr3) // ✅ 可以
process(arr5) // ❌ 编译错误!类型不匹配
slice := []int{1, 2, 3}
process(slice) // ✅ 可以
slice2 := []int{1, 2, 3, 4, 5}
process(slice2) // ✅ 也可以
}
结论:切片更灵活,数组太死板。
三、特殊情况:切片扩容
当append时容量不够,会重新分配底层数组:
func modifyWithAppend(s []int) {
s = append(s, 4) // 如果容量够,继续用原数组
s[0] = 100
}
func main() {
// 情况1:容量够
slice1 := make([]int, 3, 10) // 长度3,容量10
slice1[0], slice1[1], slice1[2] = 1, 2, 3
modifyWithAppend(slice1)
fmt.Println("容量够:", slice1) // [100 2 3] 元素变了,但长度还是3
// 情况2:容量不够
slice2 := []int{1, 2, 3} // 长度3,容量3(不够了)
modifyWithAppend(slice2)
fmt.Println("容量不够:", slice2) // [1 2 3] 完全没变!
// 因为append时重新分配了新数组,跟外面的数组没关系了
}
四、如何选择?(实际开发建议)
| 你的需求 | 选择 | 理由 |
|---|---|---|
| 日常开发,数据会变动 | 切片 | 99%的情况都用切片 |
| 数组大小固定不变(如坐标点) | 数组或切片均可 | 但切片更灵活 |
| 函数需要修改长度 | 切片 + 返回新切片 | 这是标准做法 |
| 想保护数据不被函数修改 | 数组 或 复制切片 | 用 copy() 复制切片 |
| 性能极端敏感,要避免扩容 | 切片 + make预分配容量 | make([]int, 0, 1000) |
| 函数间传递超大数据 | 切片(必须是切片) | 避免复制开销 |
五、实用技巧
技巧1:想保护切片不被修改
func protectData(original []int) {
// 复制一份
copy := make([]int, len(original))
copyData := append([]int{}, original...) // 更简洁的复制
// 让其他函数用副本
dangerousFunction(copyData)
}
技巧2:明确需要修改切片
func modifySlice(s []int) []int {
// 修改...
s = append(s, 999)
return s // 始终返回
}
// 调用时
mySlice = modifySlice(mySlice)
技巧3:面试常问的坑
func test(s []int) {
s = append(s, 4)
s[0] = 100
}
func main() {
s := []int{1, 2, 3}
test(s)
fmt.Println(s) // 请问输出什么?
}
// 答案:[1 2 3]
// 因为append后s变了,但外面的s没变
总结口诀
数组笨重传全部,改了外面不关注。
切片轻巧传地址,增删改要懂规矩。
改元素时内外通,要变长度得返回送。
最后记住:实际写代码,99%都用切片,数组只有在极特殊场景(比如需要固定长度类型的key、极小数组)才用。
更多推荐



所有评论(0)