指针(pointer)这个概念在Go语言中拆分为两个核心概念:

  1. 类型指针:允许对这个指针类型的数据进行修改。传递数据使用指针,无需拷贝数据,但是类型指针不能进行偏移和运算。
  2. 切片:指的是起始元素的原始指针,元素数量和容量组成。

要弄明白指针,需要先知道以下几个概念:

  • 指针地址
  • 指针类型
  • 指针取值

指针地址和指针类型

每个变量在运行的时候都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&符放在变量前面对变量进行“取地址”的操作。
取地址的格式:

1
ptr := &v

指针的实际用法:

1
2
3
4
5
6
7
8
9
10
11
12
package main 

import "fmt"

func main() {
var cat int = 1
var str string = "banana"
fmt.Printf("%p %p", &cat, &str)
}

输出结果:
0xc042052088 0xc0420461b0

每次运行的时候,输出变量的地址的值可能是不同的,代表cat和str两个变量在运行时的地址。

总结:变量、指针和地址三者的关系是:每个变量都拥有地址,指针的值就是地址。

从指针获取指针指向的值

在普通变量使用&操作符获取到这个变量的指针之后,可以对指针使用*操作,也就是指针获取值(也可以叫做解指针,解指针反而听起来更接地气)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main() {
house := "Malibu Point 10880, 90265"
ptr := &house

//打印ptr的数据类型
fmt.Printf("ptr type: %T\n", ptr)
//打印ptr的地址
fmt.Printf("address: %p\n", ptr)
//对指针进行取值操作
value := *ptr
//取值后的类型
fmt.Printf("value type:%T\n", value)
//指针取值后就是指向变量的值
fmt.Printf("value:%s\n", value)
}

不难发现取地址符&和取值操作符*是一对互补操作符,&取出地址,*根据地址获取地址指向的值

使用指针修改值

使用指针不仅可以获取值,还可以进行值的修改和数据的交换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func swap(a, b *int) {
t := *a
*a = *b
*b = *a
//这里还有一个更加简单的方法: *a, *b = *b, *a
}

func main(){
x, y := 1, 2
swap(&x, &y)
fmt.Prinln(x, y)
}

实例:使用指针变量获取命令行的输入信息

Go语言的flag包中,定义的指令已指针的类型返回,通过学习flag包可以深入的了解指针变量在设计上的方便之处。
下面的代码实现了定义一些命令行指令和对应的变量,在运行的时候,输入对应的参数的命令行参数后,经过flag包的解析后即可通过定义的变量获取命令行的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
"flag"
)

//定义命令行参数
var mode = flag.String("mode", "", "process mode")

func main() {
//解析命令行参数
flag.Parse()
//输出命令行参数
fmt.Println(*mode)
}

命令行输入:$ go run flagParse.go --mode=fast
输出:fast

代码说明如下:

  • 第 10 行,通过 flag.String,定义一个 mode 变量,这个变量的类型是 *string。后面 3 个参数分别如下:
    • 参数名称:在给应用输入参数时,使用这个名称。
    • 参数值的默认值:与 flag 所使用的函数创建变量类型对应,String 对应字符串、Int 对应整型、Bool 对应布尔型等。
    • 参数说明:使用 -help 时,会出现在说明中。
  • 第 15 行,解析命令行参数,并将结果写入创建的指令变量中,这个例子中就是 mode 变量。
  • 第 18 行,打印 mode 指针所指向的变量。

由于之前使用 flag.String 已经注册了一个 mode 的命令行参数,flag 底层知道怎么解析命令行,并且将值赋给 mode*string 指针。在 Parse 调用完毕后,无须从 flag 获取值,而是通过自己注册的 mode 这个指针,获取到最终的值。代码运行流程如下图所示。

创建指针的另一种方法—-new()函数

Go语言还提供了另外一种方法来创建指针变量,格式如下:

1
new(type)

一般定义格式:

1
2
3
str := new(string)
*str = "newDefine"
fmt.Println(*str)

new()函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向的值为默认值。