指针是一个变量,是一个保存了内存地址的变量。
任何一个变量都有对应的内存地址(但是并不是任何一个值都有一个内存地址),通过指针,我们可以访问和更新对应变量的值。
可以通过以下几种常见的方式来声明一个指针型变量p
1. x := 1 // 或者 var x int,此时x会被初始化零值
p := &x // 此时p为指针变量,p指向int类型变量x,保存了x的地址
2. var p *int // 此时p也为一个指针变量,但是此时p为<nil>,因为p未指向任何变量
3. p := new(int) // new会返回一个int*类型的变量,因此此时p为一个int*的变量,
// 指向匿名的int变量,并且*p会被初始化零值
指针变量之间也是可以进行相互比较,判断地址是否相等
1. var p, q *int
fmt.Println(p == q) // 输出为true,因为p和q都是<nil>值
2. var x, y int
p := &x
q := &y
fmt.Println(p == q) // 输出为false,因为x和y都被初始化零值,p和q指向了两个不同的变量
3. p := new(int)
q := new(int)
fmt.Println(p == q) // 输出为false,同样p和q指向了两个不同的匿名变量
通过指针可以方便的访问和更新变量
x := 1
p := &x // p为int*类型的指针
*p = 2 // 通过*来访问和更新p指向的变量x,此时x的值为2
一个普通或者聚合类型的变量可以通过&
进行取地址操作,任何一个指针的零值都是nil
,如果指针指向的是一个有效变量,那么该指针p != nil
var x int64
p := &x // 可以通过&对x进行取地址操作
// 聚合类型
type User struct {
Id string
}
p := &User{ // p为指向一个匿名的User类型的指针
Id : "test",
}
q := &p.Id // q指向了User这个类型中Id这个string类型变量
指针可以作为变量出现在函数的参数和返回值当中
func f(u *Type) *Type {
}
值拷贝
的方式进行传递的,这就意味着在函数内部对参数进行更新无法改变原变量的值,而引用类型(slice、指针、map、chan和函数)则可以在函数内部修改原变量p := &User{
Id : "test",
}
p = changeId(p)
fmt.Println(*p) // 输出*p为{test1}
func changeId(u *User) *User {
u.Id = "test1"
return u
}
c := sha256.Sum256([]byte("x"))
fmt.Println(c) // 输出为[45 113 22 66 183 38 176 68 1 98 124...]
zero(&c)
fmt.Println(c) // 输出为[0 0 0 0 0...]
func zero(ptr *[32]byte) {
for i := range ptr {
ptr[i] = 0
}
}
在Java中有显示定义的类Class
,每一个Class
下都有该类拥有的方法,对象可以调用这些方法实现实现具体的功能;在Go中定义了struct
结构体,每一个结构体可以作为接收器,在接收器中可以定义一系列方法,如我们可以定义一个User的getUserId方法如下:
func (u User) getUserId() string {
return u.Id
}
如果User这个结构体太大的话,在参数传递过程中效率会降低,因此可以用指针来进行代替:
func (u *User) getUserId() string {
return u.Id
}
在现实的程序里,一般会约定如果User
这个类有一个指针作为接收器的方法,那么所有User
的方法都必须有一个指针接收器,即使是那些并不需要这个指针接收器的函数。
此外,为了避免歧义,在声明方法时,如果一个类型名本身是一个指针的话,是不允许其出现在接收器中的,比如下面这个例子:
type P *User
func (p *P) f() {} // 编译器报错,因为P本身已经是一个指针类型
值得一提的是,在Go语言中,如果变量u
是一个User
类型的变量,而方法getUserId
需要一个*User
类型的接收器,那么我们也可以直接使用u.getUserId()
来进行调用,编译器会隐式地帮我们用&u
去调用getUserId
这个方法。但是这种方式仅限于u
是变量的情况下,如下:
u.getUserId() // 编译通过,因为u是变量
User{Id : "test"}.getUserId() // 编译器报错,因为编译器无法直接帮助我们找到常量的地址
(&User{Id : "test"}).getUserId() // 编译通过,手动获取常量的地址