09、Golang 教程 - Go 指针

  • Go 中指针不像 C 语言里的那么难理解,使用指针可以更简单的执行一些任务。
  • 变量是一种使用方便的占位符,用于引用计算机内存地址。
  • 取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。
  • 和 & 的区别:

  • & 是取地址符号,即取得某个变量的地址 , 如 &a。

    • 是指针运算符,可以表示一个变量是指针类型,也可以表示一个指针变量所指向的存储单元,也就是这个地址所存储的值。
  • 和 & 的区别:

  • & 是取地址符号,即取得某个变量的地址 , 如 &a。

    • 是指针运算符,可以表示一个变量是指针类型,也可以表示一个指针变量所指向的存储单元,也就是这个地址所存储的值。

& 是取地址符号,即取得某个变量的地址 , 如 &a。

  • 是指针运算符,可以表示一个变量是指针类型,也可以表示一个指针变量所指向的存储单元,也就是这个地址所存储的值。

1. 指针概念

一个指针变量指向了一个值的内存地址。

类似于变量和常量,在使用指针前需要声明指针。指针声明格式如下:

 var varName *varType

varType 是指针类型,varName 为指针变量名,* 号用于指定变量是作为一个指针。

 var ip *int            // 指向整型
var fp *float32     // 指向浮点型

2. 使用指针流程

  • 定义指针变量。
  • 为指针变量赋值。
  • 访问指针变量中指向地址的值。

在指针类型前面加上 * 号来获取指针指向的内容。

示例:

 package main

import "fmt"

func main() {

    var (
        a int = 33 //实际变量
        ip *int    //指针变量
    )

    // a 的地址赋值给 ip 指针
    ip = &a

    fmt.Println("a 的值:",a)
    fmt.Println("a 的地址:",&a)
    fmt.Println("指针 ip 的值:",ip)
    fmt.Println("指针 ip 指向的值:",*ip)
}

// 结果
a 的值: 33
a 的地址: 0xc00000a0a0
指针 ip 的值: 0xc00000a0a0
指针 ip 指向的值: 33

3. 空指针

  • 当一个指针被定义后没有分配任何变量时,它的值为 nil。
  • nil 指针也被称为空指针。
  • nil 在概念上和其它语言的 null、None、nil、NULL 一样,都指代零值或空值。
  • 一个指针变量通常缩写为 ptr。
 package main

import "fmt"

func main() {

   var ptr *int

   fmt.Printf("ptr 的值为 : %x\n", ptr  )
}

// 结果
ptr 的值为 : 0

空指针判断:

 if(ptr != nil)    /* ptr 不是空指针 */
if(ptr == nil)    /* ptr 是空指针 */

4. 指针数组

 package main

import "fmt"

func main() {

   a := []int{

     10,100,200}

   for i := 0; i < len(a); i++ {

      fmt.Printf("a[%d] = %d\n", i, a[i] )
   }
}

// 结果
a[0] = 10
a[1] = 100
a[2] = 200

如果我们需要保存数组,这样我们就需要使用到指针。

声明整型指针数组:

 var ptr [num]*int

ptr为整型指针数组。因此每个元素都指向了一个值。以下实例的三个整数将存储在指针数组中:

 package main

import "fmt"

func main() {

    a := []int{

     10,100,200}
    var ptr [3]*int     //声明指针数组

    for  i := 0; i < 3; i++ {

        ptr[i] = &a[i]      //整数地址赋值给指针数组
    }

    for  i := 0; i < 3; i++ {

        fmt.Printf("a[%d] = %d\n", i,*ptr[i] )
    }
}

// 结果
a[0] = 10
a[1] = 100
a[2] = 200

综合示例

 package main

import "fmt"

func main() {

    var ptr [3]*int //声明指针数组
    a := []int{

     10,100,200}    //实际数组

    for i:=0;i<len(a);i++ {

        //地址赋值给指针
        ptr[i] = &a[i]
        fmt.Printf("第 %d 个元素指针地址为 %d\n",i,&a[i])
    }

    //使用指针变量指向值进行遍历
    for j:=0;j<len(ptr);j++ {

        fmt.Printf("a[%d] = %d\n",j,*ptr[j])
    }

    // 实际数组的遍历
    /*
       for i:=0;i<len(a);i++ {
           fmt.Printf("a[%d] = %d\n",i,a[i])
       }
    */
}

// 结果
第 0 个元素指针地址为 824633795424
第 1 个元素指针地址为 824633795432
第 2 个元素指针地址为 824633795440
a[0] = 10
a[1] = 100
a[2] = 200

5. 指向指针的指针

  • 如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。
  • 当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址:
     
  • 指向指针的指针变量声明格式如下:

 var ptr **int;
// 以上指向指针的指针变量为整型
  • 访问指向指针的指针变量值需要使用两个 * 号

示例一

 package main

import "fmt"

func main() {

    var (
        a int           //变量
        ptr *int        //指针
        pptr **int      //指针的指针
    )
    //变量赋值
    a = 3000

    //ptr 赋值 a 的地址
    ptr = &a

    //pptr 赋值 ptr 的地址
    pptr = &ptr

    fmt.Println("a 的值:",a)
    fmt.Println("a 的地址:",ptr)
    fmt.Println("指针 ptr 指向的值:",*ptr)
    fmt.Println("指针 pptr 指向的值:",*pptr)
    fmt.Println("指针 pptr 指向指针 ptr 指向的值:",**pptr)
    fmt.Println("指针 pptr 的地址:",&pptr)
}

// 结果
a 的值: 3000
a 的地址: 0xc00000a0a0
指针 ptr 指向的值: 3000
指针 pptr 指向的值: 0xc00000a0a0
指针 pptr 指向指针 ptr 指向的值: 3000
指针 pptr 的地址: 0xc000006030

 package main

import "fmt"

func main() {

    var a int = 5
    var ptr *int = &a   //一级指针,指向 a 的地址,可获取 a 的值
    var pts *int = ptr  //一级指针,指向 ptr 指向的地方,可获得 a 的地址,以及 a 的值
    var pto **int = &ptr    //二级指针,指向 ptr 地址,ptr 指向 &a 的地址,所以 pto 可以获得 ptr 地址,可获得 a 的地址,可获得 a 的值
    var pt3 ***int = &pto   //三级指针,指向 pto 地址,pto 指向 ptr,ptr 指向 a,可获得 pto 地址,获得 ptr 地址,可获得 a 地址,获得 a 的值

    fmt.Println("a 的地址为:",&a)
    fmt.Println("a 的值为:",a)

    fmt.Println("ptr 的地址为:",&ptr)
    fmt.Println("ptr 指向的地址为:",ptr)

    fmt.Println("pts 的地址为:",&pts)
    fmt.Println("pts 指向的地址为:",pts)
    fmt.Println("pts 指向的地址对应的值为:",*pts)

    fmt.Println("pto 的地址为:",&pto)
    fmt.Println("pto 指向的指针(ptr)的存储地址为:",pto)
    fmt.Println("pto 指向的指针(ptr)所指向的地址为:",*pto)
    fmt.Println("pto 最终指向的地址对应的值(a)为:",**pto)

    fmt.Println("pt3 的地址为:",&pt3)
    fmt.Println("pt3 指向的指针(pto)的存储地址为:",pt3)
    fmt.Println("pt3 指向的指针(pt0)所指向的地址为:",*pt3)
    fmt.Println("pt3 最终指向的地址对应的值(a)为:",***pt3)
}

// 结果
a 的地址为: 0xc0000a2058
a 的值为: 5
ptr 的地址为: 0xc0000cc018
ptr 指向的地址为: 0xc0000a2058
pts 的地址为: 0xc0000cc020
pts 指向的地址为: 0xc0000a2058
pts 指向的地址对应的值为: 5
pto 的地址为: 0xc0000cc028
pto 指向的指针(ptr)的存储地址为: 0xc0000cc018
pto 指向的指针(ptr)所指向的地址为: 0xc0000a2058
pto 最终指向的地址对应的值(a)为: 5
pt3 的地址为: 0xc0000cc030
pt3 指向的指针(pto)的存储地址为: 0xc0000cc028
pt3 指向的指针(pt0)所指向的地址为: 0xc0000cc018
pt3 最终指向的地址对应的值(a)为: 5

6. 指针作为函数参数

Go 语言允许向函数传递指针,只需要在函数定义的参数上设置为指针类型即可。

 // 向函数传递指针,并在函数调用后修改函数内的值
package main

import (
    "fmt"
)

func main() {

    // 定义局部变量
    var a int = 100
    var b int = 200
    fmt.Println("原 a 值:", a)
    fmt.Println("原 b 值:", b)

    /*
        调用 swap 函数用于交换值
        &a 指向 a 变量的地址
        &b 指向 b 变量的地址
    */
    swap(&a, &b)
    fmt.Println("交换后 a 值:", a)
    fmt.Println("交换后 b 值:", b)
}

// 定义交换函数
func swap(x *int, y *int) {

    var temp int
    temp = *x
    *x = *y
    *y = temp
}

/* 交换函数也可以这样写,仅限于 go 语言
func swap(x *int, y *int) {
    *x, *y = *y, *x
}
*/

// 结果
原 a 值: 100
原 b 值: 200
交换后 a 值: 200
交换后 b 值: 100