Go语言中的指针是很多开发者入门和进阶时都会遇到的核心知识点,其中*和&两个符号的作用区分,以及方法接收器中值接收器和指针接收器的选择逻辑,是实际开发中经常需要面对的问题。理解这些内容能够帮助开发者写出更合理、性能更优的Go代码。

*和&的基本概念区分
在Go语言中,&符号的作用是取地址,*符号的作用是指针解引用,两者是配套使用的核心操作符。
&符号的作用
&放在变量前面时,会返回该变量在内存中的地址,得到的是一个指针类型的值。比如定义一个整型变量,使用&获取它的地址:
package main
import "fmt"
func main() {
var num int = 10
// 使用&获取num的内存地址,ptr的类型是*int
ptr := &num
fmt.Printf("num的值是:%d,num的地址是:%p,ptr存储的地址是:%pn", num, &num, ptr)
}
*符号的作用
*符号有两种使用场景,一种是指针类型定义时的标识,另一种是指针解引用时的操作。
在类型定义前加*,表示这是一个指向该类型的指针类型,比如*int表示指向整型的指针。在指针变量前加*,会获取该指针指向的内存地址中存储的实际值,也就是解引用操作:
package main
import "fmt"
func main() {
var num int = 10
ptr := &num
// 对指针解引用,获取指向的值
fmt.Printf("ptr指向的值是:%dn", *ptr)
// 通过指针修改指向的变量的值
*ptr = 20
fmt.Printf("修改后num的值是:%dn", num)
}
方法接收器的两种类型
在Go语言中定义方法时,接收器可以是值类型,也可以是指针类型,两者的行为有明显差异。
值接收器
值接收器会在方法调用时,对接收器的值进行一次拷贝,方法内部操作的是拷贝后的副本,不会影响到原来的实例。适合接收器是小的不可变类型,或者不需要修改接收器状态的场景。
package main
import "fmt"
type User struct {
Name string
Age int
}
// 值接收器方法,不会修改原实例的属性
func (u User) UpdateAge(newAge int) {
u.Age = newAge
fmt.Printf("方法内部Age:%dn", u.Age)
}
func main() {
user := User{Name: "张三", Age: 18}
user.UpdateAge(20)
fmt.Printf("原实例Age:%dn", user.Age)
}
指针接收器
指针接收器传递的是实例的内存地址,方法内部操作的是原实例本身,修改属性会直接反映到原实例上。适合需要修改接收器状态、接收器是大的结构体避免拷贝开销的场景。
package main
import "fmt"
type User struct {
Name string
Age int
}
// 指针接收器方法,会修改原实例的属性
func (u *User) UpdateAge(newAge int) {
u.Age = newAge
fmt.Printf("方法内部Age:%dn", u.Age)
}
func main() {
user := User{Name: "张三", Age: 18}
user.UpdateAge(20)
fmt.Printf("原实例Age:%dn", user.Age)
}
接收器选择的核心判断标准
实际开发中可以按照以下规则选择接收器类型:
- 如果方法需要修改接收器的状态,必须使用指针接收器
- 如果接收器是大的结构体,为了避免值拷贝带来的性能开销,优先使用指针接收器
- 如果接收器是小的不可变类型,比如基本类型、小的结构体,且不需要修改状态,可以使用值接收器
- 同一个类型的方法集,接收器类型要保持一致,不要混合使用值接收器和指针接收器,避免逻辑混乱
常见误区说明
很多开发者会疑惑为什么值类型的实例也可以调用指针接收器的方法,这是因为Go编译器会自动做取地址的操作,比如上面的user.UpdateAge(20),编译器会自动转换成(&user).UpdateAge(20)。同样指针类型的实例也可以调用值接收器的方法,编译器会自动解引用。这种语法糖是为了开发方便,但是理解底层逻辑还是很有必要的。