阅读 72

Golang基础 - 指针的使用

指针是一个变量,是一个保存了内存地址的变量。

任何一个变量都有对应的内存地址(但是并不是任何一个值都有一个内存地址),通过指针,我们可以访问和更新对应变量的值。

指针声明的方式

可以通过以下几种常见的方式来声明一个指针型变量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 {
}
复制代码
  • 在Go语言中,所有值类型的函数参数都是通过值拷贝的方式进行传递的,这就意味着在函数内部对参数进行更新无法改变原变量的值,而引用类型(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()    // 编译通过,手动获取常量的地址
复制代码
文章分类
后端
文章标签