Go基础之指针

Go 语言的指针

Go 语言中获取内存地址

Go语言使用&符号来取地址

1
2
3
4
5
6
7
8
9
10

package main

import "fmt"

func main() {
var lv int = 10

fmt.Println(&lv)
}

执行结果为:

1
0xc420080030

Go语言中的指针

一个指针变量指向了一个值的内存地址, 指针也是一种数据类型, 在使用之前, 你需要先声明指针

1
2
var age *int  // 指向整型的指针
var name *string // 指向字符串的指针

*变量: * 变量是一种占位符, 用于引用计算机内存的地址, 可以理解为内存地址的标签

*指针: * 指向其他变量内存地址的值

*&: * 取变量的内存地址

*\: ** 取指针指向的内存地址的值

比喻: 现屋里有一个2*2的箱子, 左下角箱子的编号是1-1标签贴了苹果, 右下角箱子的编号是1-2标签贴了玉米, 左上角箱子的编号是2-1标签贴了桃子, 右上角箱子的编号是2-2标签贴了土豆, 房间里还立着一个大指示牌. 🔜 如下图所示

1
2
3
4
5
6
7
8
9
10

-----------------
|桃子🍑 | 土豆🥔 |
|2-1 | 2-2 |
----------------|
|苹果🍎 | 玉米🌽 |
|1-1 |1-2 |
-----------------

🔜 指示牌
  • 变量: 变量就是箱子上的标签, 桃子就是一个变量名, 而箱子里的桃子🍑则是值
  • 值: 实际保存内容的地方, 也就是实际放在盒子里的东西
  • 指针: 指针就理解为摆在房间里的指示牌, 指示牌可以指向任意一个箱子的地址, 也就是可以指向1-1 1-2 2-12-2
  • &: 获取变量的地址, 用在变量名前面, 理解为获取标签的地址, 比如&玉米, 意思就是获取玉米所在箱子的地址, 也就是1-2
  • *: 获取指针指向内存地址的值, 假如该指针指向了玉米的地址, 则使用*获取该指针的值即为箱子里所有的玉米🌽

*注意: * 指针是有数据类型的, 比如你声明了一个水果类型的指针, 那么这个指针(这个指示牌)就只能指向1-12-1 这两个地址, 因为只有这两个地址的值类型为水果; 同理, 如果你声明了一个蔬菜类型的指针, 也只能指向 1-22-2 这两块内存地址, 下面介绍指针的使用时, 仍将使用到该比喻

指针的使用

  • 定义指针变量
  • 为指针赋值
  • 访问指针变量中指向地址的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

func main() {

// 声明一个水果变量, 假设int类型为水果类型
// 假设59为苹果🍎, 59即为盒子实际存放的东西
var fruit int = 59

// 声明一个int类型(水果类型)的指针变量
var indicator *int

// 将fruit变量的地址赋值给指针
// (将苹果盒子的地址1-1赋值给指示牌, 指示牌上写了/指向了1-1)
indicator = &fruit

// 访问指针指向的值
// 打开指示牌指向的盒子, 打开1-1盒子发现了真正存放的苹果🍎
fmt.Println(*indicator)
}

执行结果为:

1
59

空指针

当一个指针被声明后, 却没有被赋予任何变量的内存地址, 此时, 该指针的值为nil, nil指针也被称作空指针

nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值, 一个指针变量通常缩写为 ptr

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

import "fmt"

func main() {
var ptr *int
fmt.Println(ptr)

if ptr == nil {
fmt.Println("ptr为空指针")
} else {
fmt.Println("ptr为非空指针")
}
}

执行结果为:

1
2
<nil>
ptr为空指针

Go指针引用之向函数传递指针参数

值传递? 引用传递?

在Python中, 除了像int, str这样基本数据类型是值传递外, 其余绝大部分都是引用传递.
而在Go语言中, 只有值传递一种方式, Go语言中是通过传递指针来实现引用传递的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package main

import "fmt"

//交换值的函数
func swap1(a, b int) {
b, a = a, b
}

// *int 指向整型的指针
func swap2(a, b *int) {
// b指向的值赋值给a, a指向的值赋值给b
// *b, *a = *a, *b
// *a 取出 a 指针指向的值赋值给 c 变量(中间变量)
c := *a
// *b 取出 b 指针的指向的值赋值给 a指针的值
// 注意, 不能把值直接赋值给指针
// 指针只是保存值的地址
// 所以这里将 b 指针指向的值赋值给 a 指针指向的值, 即用 b 值覆盖 a 值
*a = *b
// 最后用 c 变量的值, 覆盖 b 值
*b = c
}

func swap3(a, b *int) {
*a, *b = *b, *a
}

func swap4(a, b int) (int, int) {
// 使用值传递的方式, 利用返回值来实现值的交换
return b, a
}

func main() {
a, b := 5, 9

// 值传递, 交换只在函数内生效, 不能改变外面的值
swap1(a, b)
fmt.Println(a, b) // 结果为: 5 9
// 从结果中可以看出, a, b两个变量进入到函数是值拷贝, 在函数内的操作并没有改变函数外a, b实际的值

// 交换地址(引用传递)
swap2(&a, &b)
fmt.Println(a, b)

// 再次交换地址(引用传递)
swap3(&a, &b)
fmt.Println(a, b)

// 值传递, 利用返回值交换值
// 注意返回值的顺序, 返回值里交换了, 接收返回值的顺序就不要变
// 如果return是 a,b 那么接收的时候就要变成 b, a 去接收
a, b = swap4(a, b)
fmt.Println(a, b)
}

以上交换的场景中, swap4为最佳方案, 简单有效! 指针在实际使用中也要充分考虑使用场景, 是否非用指针不可

Go指针引用之指向指针的指针

待续…