📖 前言
在使用 Go 开发中,我们经常会在函数参数或数据结构中遇到 []*T 和 *[]T 这两种写法。虽然它们看起来只差一个星号(*),但其实含义大相径庭。如果不理解清楚,很容易写出 bug 或出现意料之外的副作用。
这篇文章将带你彻底搞清这两种用法的本质区别、适用场景和常见误区。
🧩 一眼看上去的区别
| 写法 | 含义 |
|---|---|
[]*T | 切片,元素是 *T 类型指针 |
*[]T | 指向切片的指针,元素是 T 值类型 |
🎯 []*T:指针切片
go
复制编辑
type User struct {
Name string
}
func main() {
u1 := &User{Name: "Alice"}
u2 := &User{Name: "Bob"}
users := []*User{u1, u2}
users[0].Name = "Charlie"
fmt.Println(users[0].Name) // 输出:Charlie
}
- ✅ 结构体对象通过指针存储,可以共享引用。
- ✅ 修改
users[i].Name会影响原始对象。 - 🔁 使用场景:需要共享对象、避免结构体复制。
🎯 *[]T:切片指针
go
复制编辑
type User struct {
Name string
}
func modifySlice(users *[]User) {
*users = append(*users, User{Name: "Eve"})
}
func main() {
users := []User{{Name: "Alice"}, {Name: "Bob"}}
modifySlice(&users)
fmt.Println(len(users)) // 输出:3
}
- ✅
*[]T可以修改外部传入的切片本身(例如扩容、替换)。 - ❗ 若传值而非指针,则
append不会生效。 - 🔁 使用场景:函数内希望影响调用者传入的切片内容。
🆚 核心对比总结
| 特性 | []*T(指针元素) | *[]T(指针指向切片) |
|---|---|---|
| 是否为指针 | 否(但元素是指针) | 是 |
| 是否共享元素数据 | 是(指针指向同一个对象) | 否(值拷贝) |
| 是否可修改切片本身 | 否(只读或局部改值) | 是(可以 append/替换) |
| 典型应用场景 | 修改元素内部字段 | 修改切片本身结构 |
🪤 常见误区
- ❌
[]*T≠*[]T,不要以为加个*只是语法风格。 - ❌ 传
[]T到函数中append()不能修改原切片长度,除非用*[]T。 - ✅ 若你的结构体较大,
[]*T可以避免频繁拷贝,性能更优。
🛠 最佳实践建议
- 函数内只读或遍历切片 ➜ 使用
[]T或[]*T。 - 结构体较大,希望共享引用对象 ➜ 使用
[]*T。 - 函数内需要改变切片本身(添加/删除元素) ➜ 使用
*[]T。 - 若两者都需要(引用元素又要修改切片结构) ➜ 使用
*[]*T(高阶情况)。
✅ 结语
掌握 []*T 和 *[]T 的区别是写好 Go 代码的基础,特别是在构建复杂的数据处理逻辑或设计 API 接口时尤为重要。通过理解其背后的语义模型,我们可以避免很多隐蔽的 bug,让代码更健壮、更高效。