Go中声明初始化指向数组元素的指针需用循环逐个取地址存入[]T切片,如ptrs[i] = &arr[i];直接修改ptrs[i]可更新原数组元素,但须确保指针生命周期内原数据未销毁。

怎么声明和初始化指向数组元素的指针
Go 里没有“指针数组”这种类型(即 []*int 是指针切片,不是 C 风格的指针数组),但你可以获取数组中每个元素的地址并存入一个指针切片。关键点是:数组本身是值类型,&arr[i] 得到的是第 i 个元素的地址,类型为 *int。
常见错误是试图对整个数组取地址后做偏移(如 &arr + i),Go 不支持指针算术,也不允许对数组变量直接做算术运算。
- 正确方式:用循环逐个取地址,存入
[]*T切片 - 注意:如果原数组是局部变量,确保指针不逃逸到函数外后访问已销毁的栈内存(编译器通常能判断,但大数组或复杂闭包下需留意)
- 数组长度固定,所以
len(arr)可安全用于循环边界
arr := [3]int{10, 20, 30}
ptrs := make([]*int, len(arr))
for i := range arr {
ptrs[i] = &arr[i]
}
// ptrs[0] 现在指向 arr[0],修改 *ptrs[0] 会改变 arr[0]通过指针修改原数组元素是否生效
是的,只要指针指向的是原数组的元素地址,解引用并赋值就直接修改原位置。这是 Go 指针最基础也最可靠的用途——绕过值拷贝,实现就地更新。
容易踩的坑在于混淆“指针副本”和“被指向对象”。比如把 ptrs[i] 赋给另一个变量 p := ptrs[i],p 是新指针变量,但它仍指向同一地址,*p = 999 依然改的是原数组元素。
- 修改操作:直接写
*ptrs[i] = newValue - 不要写
ptrs[i] = &newValue——这会让该指针指向一个新局部变量,原数组不受影响 - 若原数组是函数参数传入,且你传的是数组值(如
func f(a [3]int)),那&a[i]指向的是副本,修改无效;必须传指针*[3]int或用切片
用切片代替数组时指针行为有啥不同
切片底层有底层数组,slice[i] 的地址仍是底层数组对应元素的地址,所以 &slice[i] 依然可用、可修改原数据。但要注意切片头可能被重新切(slice = slice[1:]),此时旧指针仍指向原底层数组位置,不一定再属于当前切片范围——Go 不检查越界解引用,运行时可能读到脏数据或引发 panic(取决于 GC 和内存状态)。
- 安全前提:确保指针生命周期内,底层数组未被回收,且索引仍在有效范围内
- 切片扩容(如 append 导致新底层数组)会使原有元素指针失效——旧指针仍指向老内存,但新切片已不包含它
- 推荐:如需长期持有元素地址,优先用固定大小数组或显式管理底层数组(如
make([]int, n)后不再 append)
data := make([]int, 3)
for i := range data {
data[i] = i * 10
}
ptrs := make([]*int, len(data))
for i := range data {
ptrs[i] = &data[i] // 这里 &data[i] 有效,指向底层数组
}
*ptrs[1] = 999 // data[1] 变成 999
为什么不能用 **int 来批量操作
有人想定义 pp := &ptrs(即 **int),再通过 **pp 改值,这是误解。Go 中 **int 是“指向 int 指针的指针”,它只能间接修改一个指针变量本身的值(比如让那个指针指向别处),不能用来遍历或批量解引用。
真正需要批量修改时,还是得遍历 []*int,逐个 *p = x。没有语法糖能跳过这一步。
-
pp := &ptrs[0]是**int,*pp是*int,**pp是int—— 它只关联单个元素 - 数组/切片的指针集合本质是独立指针的集合,不是连续内存块,无法用双重解引用统一处理
- 性能上,遍历
[]*T并解引用是标准做法,现代 CPU 对 cache 友好,无需过度优化
实际用的时候,最常漏掉的是生命周期检查:传进来的数组或切片是不是临时的?指针存下来之后,原数据还在不在?这点比语法更关键。









