Go中没有“指针数组”类型,日常应使用[]T切片;正确取地址需用&data[i]或循环内声明新变量;需修改切片头时才传[]*T。
Go语言不提供像C那样的 [N]*T(固定长度的指针数组)作为独立类型。你声明 var arr [3]*int 是合法的,但它只是个普通数组,每个元素是 *int;它不能动态增长,也不具备切片的语义。真正日常用、符合Go习惯的,是 []*T——即“元素为指针的切片”。它才是管理动态指针集合的标准方式。
[]*User,不是 [100]*User
*[N]T(指向固定数组的指针),但这是少数场景,比如CGO或unsafe操作[]*T 的底层是动态分配的堆内存,GC能正确追踪每个指针所指向的对象,只要切片还活着,对象就不会被回收[]*int:循环取地址的致命陷阱最常见错误:在 for range 中直接对循环变量取地址,结果所有指针都指向同一个栈地址,值被反复覆盖。
data := []int{10, 20, 30}
var ptrs []*int
for i := range data {
ptrs = append(ptrs, &i) // ❌ 错!i 是同一个变量,地址不变
}
// ptrs 全部指向 i 的最终值(2),解引用全是 2
正确做法只有两种:
data 是可寻址的变量(不能是字面量 []int{1,2,3} 直接传参),然后用索引:&data[i]
data := []int{10, 20, 30}
var ptrs []*int
for i := range data {
v := data[i] // ✅ 每次创建新变量 v
ptrs = append(ptrs, &v)
}
// 或更简洁:
for i := range data {
ptrs = append(ptrs, &data[i]) // ✅ data 是变量,&data[i] 安全
}
*[]*T?只有一种情况传 []*T 就够用了——你能修改每个指针所指向的值,也能遍历、排序、过滤。但如果你的函数要改变切片本身的头信息(比如 append 后容量变了、底层数组换了地址),而希望调用方看到这个变化,就必须传指针:*[]*T。
append 返回新切片,原变量不变;传了就得手动解引用赋值:*s = append(*s, newPtr)
func add(s []*T, x *T) []*T),语义清晰,无副作用func appendUser(users *[]*User, u *User) {
*users = append(*users, u) // ✅ 必须解引用再赋值
}
func main() {
var users []*User
u := &User{Name: "Alice"}
appendUser(&users, u) // 传地址
fmt.Println(len(users)) // 1
}
*ptrs[0] 会影响原数据因为 []*T 本质是两层间接:切片头 → 指针数组 → 实际对象。只要指针没变,解引用就始终访问同一块内存。
[]*T?它们的指针元素地址相
*ptrs[i] 修改的是同一个对象sub := ptrs[1:3],里面的指针仍指向原对象,不是副本若需隔离,别试图复制指针切片本身(copy(dst, src) 只复制指针值),而是确保指针指向的对象是独立的——或者干脆不用指针,用值拷贝。
[]*T 把所有权交给你,也把责任一起交了过来:谁分配、谁释放、谁修改、谁同步,都得你自己画清楚边界。