# 指针



本节我们介绍下Golang中的指针，并了解下Golang中的指针跟其他编程语言比如C、C++之间的区别。

#### 什么是指针

指针是一个存储了其他变量的内存地址的变量。这句话有点绕，怎么理解呢？其实很简单。指针其实也是一个变量，只要是变量就会存储某个值，指针存储的值比较特殊，是另外一个变量的内存地址。

&#x20;![0ab351c10442374f8e6509af21c1fa7e.png](https://1527815414-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-L_XyF-PcxCv1bX8ii73%2F-LdU7j9TG5zCT2Ww2jKi%2F-LdU7wWr0vicB2ylsC6W%2Fpointer-explained.png?alt=media\&token=3dfc90ad-1e9a-4aa6-81c5-60c5f21e27e6)&#x20;

看一下前面这张示例图，变量`b`存储的值为`156`，其在内存中的地址为`0x1040a124`。变量`a`存储的值是变量`b`的地址。因此这里变量`a`指向变量`b`，变量`a`是变量`b`的指针。

#### 声明一个指针变量

一个指向`T`类型数据的指针变量声明格式为`*T`。 我们写个函数来声明一个指针变量。

```go
package main

import "fmt"

func main() {
    b := 255
    var a *int = &b
    fmt.Printf("Type of a is %T\n", a)
    fmt.Println("Address of b is", a)
}
```

[Go Playground在线运行](https://play.golang.org/p/txf9d2IAYOh) 这里的`&`运算符用来获取一个变量的地址。我们将变量`b`的地址赋值给指针变量`a`，其数据类型为`*int`。这时指针`a`指向变量`b`。当我们打印指针变量`a`的值时，其实打印的是变量`b`的地址。该程序输出如下：

```
Type of a is *int
Address of b is 0x414020
```

变量`b`可能存在于内存中的任何地址，因此你的打印结果可能跟我的不同。

#### 指针变量的零值

指针变量的零值为`nil`。

```go
package main

import "fmt"

func main() {
    a := 25
    var b *int
    if b == nil {
        fmt.Println("b is", b)
        b = &a
        fmt.Println("b after initialization is", b)
    }
}
```

[Go Playground在线运行](https://play.golang.org/p/2-jC9R5_Mxm) 这里指针变量`b`的初始值为`nil`，然后我们给其赋值变量`a`的地址。执行结果如下：

```
b is <nil>
b after initialization is 0x414020
```

#### 通过`new`函数来创建指针

Golang提供了一个便捷的`new`函数用来创建指针。`new`函数的入参为类型`T`，返回值为一个指向该类型零值变量的指针。有点绕，我们举个例子看下。

```go
package main

import "fmt"

func main() {
    size := new(int)
    fmt.Printf("Size value is %d, type is %T, address is %v\n", *size, size, size)
    *size = 85
    fmt.Println("New size value is", *size)
}
```

[Go Playground在线运行](https://play.golang.org/p/D0YxvPh06tp) 这里我们通过`new`函数创建了一个指向一个临时的`int`类型数据的指针，该临时数据分配的值为对应类型的零值，比如这里即为`int`类型的零值`0`。该程序执行结果如下：

```
Size value is 0, type is *int, address is 0x414020
New size value is 85
```

#### 指针解除引用

所谓的**指针解引用**指的是访问指针指向的变量的值。指针`a`解引用的语法为`*a`。

下面通过一个例子来了解下：

```go
package main

import "fmt"

func main() {
    b := 255
    a := &b
    fmt.Println("address of b is", a)
    fmt.Println("value of b is", *a)
}
```

[Go Playground在线运行](https://play.golang.org/p/uY5n637-kGZ) 这里我们通过指针`a`解引用来访问其指向的变量`b`的值。执行结果如下：

```
address of b is 0x414020
value of b is 255
```

下面修改下前面的程序，我们通过指针来修改变量`b`的值：

```go
package main

import "fmt"

func main() {
    b := 255
    a := &b
    fmt.Println("address of b is", a)
    fmt.Println("value of b is", *a)
    *a++
    fmt.Println("new value of b is", b)
}
```

[Go Playground在线运行](https://play.golang.org/p/67ht6aoDOiA) 这里我们通过`*a++`将指针`a`指向的变量加1，由于`a`指向的变量为`b`，因此`b`变量的值变为`256`。该程序输出如下：

```
address of b is 0x414020
value of b is 255
new value of b is 256
```

#### 指针作为函数入参

```go
package main

import "fmt"

func change(p *int) {
    *p = 55
}

func main() {
    a := 58
    fmt.Println("value of a before function call is", a)
    b := &a
    change(b)
    fmt.Println("value of a after function call is", a)
}
```

[Go Playground在线运行](https://play.golang.org/p/2kZjVjTg9nR) 这里我们将指向变量`a`的指针变量`b`作为入参传递给函数`change`，`change`函数内部通过指针解引用改变了变量`a`的值。输出结果为：

```
value of a before function call is 58
value of a after function call is 55
```

#### 指针作为函数返回值

Golang中允许返回指向局部变量的指针，如果一个指向局部变量的指针作为函数返回值，那么Golang编译器会在**堆内存**上分配该局部变量。

```go
package main

import "fmt"

func hello() *int {
    i := 5
    return &i
}

func main() {
    d := hello()
    fmt.Println("value of d", *d)
}
```

[Go Playground在线运行](https://play.golang.org/p/9v_mkFC8NS3) 这里函数`hello`将局部变量`i`的指针作为函数返回值。在其他诸如C和C++之类的编程语言中这种写法是错误的，因为变量`i`是局部变量，一旦函数返回，局部变量就访问不到了。但是Golang会在局部变量初始化时检查下是否会在函数外部被引用，比如这里的局部变量`i`，其指针作为函数返回值返回出去，因此会在函数外部引用，这是Golang就将该局部变量分配在堆内存中。该程序执行结果为：

```
value of d 5
```

#### 尽量避免数组指针，多用切片

如果我们把一个指向数组的指针作为函数入参，那么可以在函数内部通过该指针来修改数组内部值，并且该修改在函数外也是可见的。

```go
package main

import "fmt"

func modify(arrP *[3]int) {
    (*arrP)[0] = 90
}

func main() {
    a := [3]int{89, 90, 91}
    modify(&a)
    fmt.Println(a)
}
```

[Go Playground在线运行](https://play.golang.org/p/QlNIrFgQufM) 这里将数组`a`的指针作为函数`modify`的入参，并通过该指针来将其指向的数组的第一个元素的值改为`90`。输出为：`[90 90 91]`。

`(*a)[x]`**可以简写为**`a[x]`**，因此前面程序中的**`(*arrP)[0]`**可以简写为**`arrP[0]`**。**

```go
package main

import "fmt"

func modify(arrP *[3]int) {
    arrP[0] = 90
}

func main() {
    a := [3]int{89, 90, 91}
    modify(&a)
    fmt.Println(a)
}
```

[Go Playground在线运行](https://play.golang.org/p/XXWS_9UTlZu) 执行结果不变，输出：`[90 90 91]`。

尽管这种通过数组指针来在函数内部修改数组是行得通的，但是Golang中一般不这么用，Golang中比较惯用的用法一般是使用切片(slice)。

```go
package main

import "fmt"

func modify(sls []int) {
    sls[0] = 90
}

func main() {
    a := [3]int{89, 90, 91}
    modify(a[:])
    fmt.Println(a)
}
```

[Go Playground在线运行](https://play.golang.org/p/VkQU5tajHOH) 这里我们将一个切片`a[:]`作为函数`modify`的入参，然后在函数内部将该切片的第一个元素修改为`90`。该程序输出：`[90 90 91]`。

因此，**我们在Golang中，尽量避免数组指针，而应该多用切片**。

#### Golang不支持指针运算

Golang跟C和C++不同，不支持指针运算，这其实在一定程度上降低了Golang这门编程语言的复杂度。

```go
package main

func main() {
    b := [...]int{109, 110, 111}
    p := b
    p++
}
```

[Go Playground在线运行](https://play.golang.org/p/CvRaLuxRJHy)

该程序执行会报错：

```
invalid operation: p++ (non-numeric type [3]int)
```
