数组类型包含长度而切片不包含,导致类型系统、赋值、传参和内存行为根本不同:数组是值类型,复制整个数据;切片是引用类型,仅复制header并共享底层数组。

数组类型包含长度,切片类型不包含长度
这是最根本的语法差异,直接影响变量赋值、函数传参和类型兼容性。[3]int 和 [4]int 是完全不同的类型,不能互相赋值;而 []int 就是唯一类型,不管底层数组多长,所有整型切片都属于同一类型。
常见错误现象:cannot use arr (type [3]int) as type [4]int in assignment —— 编译器直接报错,不是运行时问题,说明长度已固化进类型系统。
- 声明数组必须写明长度:
var a [5]string,[...]int{1,2,3}中的...是编译器推导,仍会生成具体长度类型(如[3]int) - 切片声明可省略长度:
var s []string,s := []int{1,2,3}生成的是[]int类型,与长度无关 - 函数参数若接收
[5]int,传入[4]int或[]int都会编译失败;但接收[]int时,任何长度的切片甚至nil都能传入
数组是值类型,切片是引用类型
这不是“是否指针”的简单说法,而是看赋值或传参时是否共享底层数据。数组复制等于拷贝全部元素;切片复制只拷贝 header(array、len、cap),指向同一底层数组。
容易踩的坑:误以为切片赋值后修改不影响原变量,结果发现两个切片改一个,另一个也变了。
- 对数组元素修改不会影响原始数组:
func f(a [3]int) { a[0] = 99 }调用后原数组不变 - 对切片元素修改会影响所有共享同一底层数组的切片:
s1 := []int{1,2,3}; s2 := s1; s2[0] = 99→s1[0]也变成 99 - 注意
append可能触发扩容:当cap不足时,append会分配新底层数组,此时s1和s2不再共享内存
切片有 len 和 cap,数组只有 len
len 表示当前有效元素个数;cap 是切片能“免费”扩展的最大长度,由底层数组剩余空间决定。这个二元设计直接支撑了 append 的高效实现。
性能影响明显:频繁 append 且未预估容量,会导致多次底层数组重分配,产生额外内存拷贝。
-
make([]int, 3, 5)创建 len=3、cap=5 的切片,后续最多追加 2 个元素而不扩容 -
s := []int{1,2,3}的 cap 等于 len(即 3),第一次append就可能扩容 - 从数组截取切片时,cap 保留到底层数组末尾:
a := [5]int{0,1,2,3,4}; s := a[1:3]→len(s)=2,cap(s)=4(因为从索引 1 到数组末尾共 4 个元素)
切片本质是“视图”,数组本质是“数据块”
数组变量直接持有数据;切片变量只持有三个字段的 header,真正数据在别处(可能是栈上小数组,也可能是堆上大数组)。这解释了为什么切片能动态增长——它不管理内存,只管理“怎么看”。
容易被忽略的地方:同一个底层数组可能被多个切片同时引用,任意一个切片的写操作都可能影响其他切片,除非你明确知道它们各自独立(比如通过 copy 或新 make 分配)。
- 创建切片不等于分配新内存:
s := a[:]和s := a[0:len(a)]都复用原数组内存 - 想彻底隔离数据,必须显式复制:
newS := make([]int, len(s)); copy(newS, s) - 调试时打印地址:
&s[0]可以看出是否共享底层数组,但要注意扩容后地址会变
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











