[]*T vs *[]T:彻底搞懂 Go 切片和指针的微妙差异

111 阅读2分钟

📖 前言

在使用 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,让代码更健壮、更高效。