*[N]T 是指向定长数组的指针,非数组首元素指针,长度是类型的一部分,不同长度不可互转;解引用得原数组而非切片,传参省空间但灵活性不如切片。

Go 里 *[N]T 不是数组指针,而是指向数组的指针
很多人被 *[3]int 这种写法误导,以为它类似 C 的“数组指针”,其实 Go 中它就是一个明确的类型:指向长度为 3 的 int 数组的指针。它的底层不是“指向首元素”,而是真正持有整个数组的地址,且长度信息在类型中固化。这意味着你不能用它来指向任意长度的数组,也不能像切片那样灵活扩容。
常见错误现象:cannot use &arr (type *[5]int) as type *[3]int in assignment —— 即使两个数组元素类型相同,只要长度不同,*[N]T 就是完全不同的不可互转类型。
- 声明时必须写明长度:
var p *[4]string,不能写成*[]string(那是切片指针) - 取地址只能作用于具名数组变量:
arr := [4]string{"a","b","c","d"}; p := &arr,不能对字面量直接取地址(&[4]string{"x","y"}在某些旧版本会报错,1.21+ 允许但需注意逃逸) - 解引用后得到原数组:
*p类型就是[4]string,不是[]string,所以不能直接传给期望切片的函数
传数组指针比传数组值更省内存,但不如切片通用
Go 中数组是值类型,传参默认复制整个底层数组。比如 func f(a [1000]int) 每次调用都拷贝 1000 个 int;而 func f(p *[1000]int) 只传一个指针(通常 8 字节),开销固定。
但要注意:这种优化只在数组较大、且你**确实需要操作原数组内容**时才有意义。如果只是读取,且后续要转成切片处理,不如直接传 []int。
立即学习“go语言免费学习笔记(深入)”;
- 函数接收
*[N]T参数时,内部修改(*p)[i] = x会影响原数组 - 若函数逻辑实际只需要前 M 个元素(M *[N]T 会把无关数据也锁死在类型里,增加维护成本
- 和切片相比,
*[N]T无法做append、无法通过len()/cap()动态判断,也不支持for range直接遍历(得写for i := 0; i )
*[N]T 和 *[]T 完全不同,别混用
*[]T 是“指向切片的指针”,本质是三级结构:指针 → 切片头(含 ptr/len/cap)→ 底层数组。而 *[N]T 是二级结构:指针 → 固定长度数组。二者内存布局、语义、使用方式毫无关系。
常见错误现象:cannot use &slice (type *[]int) as type *[3]int 或反过来,编译器直接拒绝,不会隐式转换。
- 想修改切片头(比如让函数内
append影响调用方的切片变量),才用*[]T;例如:func addOne(s *[]int) { *s = append(*s, 1) } - 想确保函数只能访问某固定长度的连续内存块(如硬件寄存器映射、序列化缓冲区),才用
*[N]T - 把
*[N]T强转成*[]T需要unsafe,且极易出错,不建议在业务代码中出现
用 reflect 或 unsafe 做运行时长度推导很危险
Go 类型系统在编译期就确定了 *[N]T 的 N,没有运行时反射接口能安全获取这个 N(reflect.TypeOf(p).Elem().Len() 能拿到,但仅限于你知道它是数组指针的前提下)。一旦误把其他指针传进去,reflect 会 panic。
更隐蔽的坑是:有人试图用 unsafe.Slice 把 *[N]T 当作切片用,比如 unsafe.Slice(*p, 5) —— 如果 N 小于 5,这会越界读内存,UB(未定义行为),静态分析和运行时都不会报错。
- 不要依赖
unsafe绕过长度检查,除非你在写 runtime 或 FFI 绑定,且已充分测试边界场景 - 如果真需要动态长度 + 原地修改,优先考虑传
[]T加额外长度参数,或封装成自定义结构体 - 单元测试里对
*[N]T参数做越界访问检查(比如故意传N-1索引)非常必要,因为编译器不拦,运行时也不一定崩










