Go 语言中的指针是一个强大但也容易让人困惑的概念。本文将带你深入理解 Go 的指针与引用机制,包括内存管理、传参、最佳实践等。
一、指针基础
1. 什么是指针
package main
import "fmt"
func main() {
x := 10
fmt.Println("x 的值:", x)
fmt.Println("x 的地址:", &x)
var p *int = &x
fmt.Println("p 的值(x 的地址):", p)
fmt.Println("p 指向的值:", *p)
*p = 20
fmt.Println("修改后 x 的值:", x)
}
2. 指针的零值
package main
import "fmt"
func main() {
var p *int
fmt.Println("p 的零值:", p)
if p == nil {
fmt.Println("p 是 nil")
}
}
3. new 函数
package main
import "fmt"
func main() {
p := new(int)
fmt.Println("p 的值:", p)
fmt.Println("p 指向的值:", *p)
*p = 42
fmt.Println("修改后 p 指向的值:", *p)
}
二、指针与结构体
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Alice", Age: 25}
fmt.Println(p)
pp := &p
fmt.Println(pp.Name)
fmt.Println((*pp).Age)
pp.Age = 26
fmt.Println(p)
p2 := &Person{Name: "Bob", Age: 30}
fmt.Println(p2)
}
三、函数传参
1. 值传递
package main
import "fmt"
func modifyValue(x int) {
x = 100
fmt.Println("函数内 x:", x)
}
func main() {
x := 10
fmt.Println("调用前 x:", x)
modifyValue(x)
fmt.Println("调用后 x:", x)
}
2. 指针传递
package main
import "fmt"
func modifyPointer(x *int) {
*x = 100
fmt.Println("函数内 *x:", *x)
}
func main() {
x := 10
fmt.Println("调用前 x:", x)
modifyPointer(&x)
fmt.Println("调用后 x:", x)
}
3. 结构体传递
package main
import "fmt"
type Person struct {
Name string
Age int
}
func modifyPerson(p Person) {
p.Age = 100
fmt.Println("函数内 p:", p)
}
func modifyPersonPointer(p *Person) {
p.Age = 100
fmt.Println("函数内 p:", p)
}
func main() {
p1 := Person{Name: "Alice", Age: 25}
fmt.Println("调用前 p1:", p1)
modifyPerson(p1)
fmt.Println("调用后 p1:", p1)
p2 := Person{Name: "Bob", Age: 30}
fmt.Println("调用前 p2:", p2)
modifyPersonPointer(&p2)
fmt.Println("调用后 p2:", p2)
}
四、切片与映射
1. 切片
package main
import "fmt"
func modifySlice(s []int) {
s[0] = 100
fmt.Println("函数内 s:", s)
}
func appendSlice(s []int) {
s = append(s, 4)
fmt.Println("函数内 append 后 s:", s)
}
func main() {
s := []int{1, 2, 3}
fmt.Println("调用前 s:", s)
modifySlice(s)
fmt.Println("调用后 s:", s)
s2 := []int{1, 2, 3}
fmt.Println("调用前 s2:", s2)
appendSlice(s2)
fmt.Println("调用后 s2:", s2)
}
2. 映射
package main
import "fmt"
func modifyMap(m map[string]int) {
m["a"] = 100
m["d"] = 4
fmt.Println("函数内 m:", m)
}
func main() {
m := map[string]int{"a": 1, "b": 2, "c": 3}
fmt.Println("调用前 m:", m)
modifyMap(m)
fmt.Println("调用后 m:", m)
}
五、指针接收者
package main
import "fmt"
type Counter struct {
value int
}
func (c Counter) Value() int {
return c.value
}
func (c *Counter) Increment() {
c.value++
}
func (c *Counter) Reset() {
c.value = 0
}
func main() {
c := Counter{}
fmt.Println(c.Value())
c.Increment()
fmt.Println(c.Value())
c.Increment()
fmt.Println(c.Value())
c.Reset()
fmt.Println(c.Value())
}
六、最佳实践
1. 何时使用指针
package main
import "fmt"
type LargeStruct struct {
data [1000]int
}
func processByValue(s LargeStruct) {
fmt.Println("处理大结构体(值传递)")
}
func processByPointer(s *LargeStruct) {
fmt.Println("处理大结构体(指针传递)")
}
func main() {
s := LargeStruct{}
processByValue(s)
processByPointer(&s)
}
2. 避免 nil 指针
package main
import "fmt"
func safeAccess(p *int) {
if p == nil {
fmt.Println("指针是 nil,无法访问")
return
}
fmt.Println("指针指向的值:", *p)
}
func main() {
var p *int
safeAccess(p)
x := 42
p = &x
safeAccess(p)
}
七、总结
Go 的指针是一个强大的工具,理解它对于编写高效的 Go 代码至关重要。记住:
- 值传递会拷贝数据,指针传递共享数据
- 切片和映射本身就是引用类型
- 大结构体用指针传递更高效
- 方法的接收者根据需求选择值或指针
掌握指针,让你的 Go 代码更加高效和优雅!