Go基础-指针

2022/12/6 go

# 什么是指针

每一个变量都会分配一块内存,数据保存在内存中,内存有一个地址,就像门牌号,通过这个地址就可以找到里面存储的数据。

指针就是保存这个内存地址的变量。

# Go 语言指针

在 Go 语言中, 指针包括两个核心概念:

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

受益于这样的约束和拆分,Go语言的指针类型变量即拥有指针高效访问的特点,又不会发生指针偏移,从而避免了非法修改关键性数据的问题。同时,垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。

切片比原始指针具备更强大的特性,而且更为安全。切片在发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃。

# C/C++中的指针

说到 C/C++ 中的指针,会让许多人“谈虎色变”,尤其是对指针的偏移、运算和转换。 其实,指针是 C/C++ 语言拥有极高性能的根本所在,在操作大块数据和做偏移时即方便又便捷。因此,操作系统依然使用C语言及指针的特性进行编写。

# 指针地址和指针类型

每个变量在程序运行时都有一个地址,这个地址代表变量在内存中的位置。

Go 语言中,通过 & 操作符对变量进行 "取地址" 操作,格式如下:

p := &v // v 的类型为 T
1

上面代码中,v 表示被取地址的变量,取到的地址用变量 p 进行接收, p 的类型为 "*T", 称为 T 的指针类型。*代表指针。

package main

import "fmt"

func main() {
	// 声明一个整型的 man 变量
	var man int = 1
	// 声明一个字符串类型 domain 变量
	var str string = "指针学习"

	// 通过 %p 输出 man 和 str 变量取地址后的指针值,指针值带有 0x 的十六进制前缀
	fmt.Printf("%p %p", &man, &str)
}
1
2
3
4
5
6
7
8
9
10
11
12
13

代码输出:

0xc0000180a8 0xc000050250
1

注意:代码每次运行的结果是不同的,表示 mandomain 两个变量在运行时的地址。

在 32 位平台上,运行结果是 32 位地址;在 64 位平台上,运行结果是 64 位地址。

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

# 获取指针指向的值

指针指向的值获取通过*符号来获取:

package main

import "fmt"

func main() {
	// 声明一个字符串类型 domain 变量
	var str string = "指针学习"
	var s = &str
	// 通过 %p 输出str 变量取地址后的指针值,指针值带有 0x 的十六进制前缀
	fmt.Printf("type is %T ,address is %p \n", s, s)
	var val = *s
    //输出val的类型和值
	fmt.Printf("type is %T ,value is %s \n", val, val)
}

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

代码输出:

type is *string ,address is 0xc000050250
type is string ,value is 指针学习
1
2

变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:

  • 对变量进行取地址 (&) 操作,可以获取这个变量的指针变量;
  • 指针变量的值是指针地址;
  • 对指针变量进行取值(*)操作,可以获取指针变量指向的实际值。

# 使用指针修改值

指针不但可以取值,同时还可以修改值,跟变量赋值差不多,只是多了一步取值操作:

package main

import "fmt"

func main() {
	// 声明一个字符串类型 domain 变量
	var str string = "指针学习"
	var s = &str
	var val = *s
	fmt.Printf("原始值:%s , 地址:%p \n", val, s)
	val = "指针修改"
	fmt.Printf("修改值:%s , 地址:%p \n", val, s)
}

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

代码输出:

原始值:指针学习 , 地址:0xc000088220
修改值:指针修改 , 地址:0xc000088220
1
2

从上面可以发现,内存地址没有变化,只是值变了,那么如果两个指针交换地址的话,值会不会变呢?

package main

import "fmt"

func main() {
	// 声明一个字符串类型 domain 变量
	var str1, str2 = 1, 2
	var p1, p2 = &str1, &str2
	fmt.Printf("p1原始值:%d , 地址:%p \n", str1, p1)
	fmt.Printf("p2原始值:%d , 地址:%p \n", str2, p2)
	//使用多重赋值交换他们的地址
	p1, p2 = p2, p1
	fmt.Printf("p1互换地址后的值:%d , 地址:%p \n", str1, p1)
	fmt.Printf("p2互换地址后的值:%d , 地址:%p \n", str2, p2)
	//使用多重赋值交换他们的值
	str1, str2 = str2, str1
	fmt.Printf("p1互换值后的值:%d , 地址:%p \n", str1, p1)
	fmt.Printf("p2互换值后的值:%d , 地址:%p \n", str2, p2)
}

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

代码输出:

p1原始值:1 , 地址:0xc0000180a8
p2原始值:2 , 地址:0xc0000180c0
p1互换地址后的值:1 , 地址:0xc0000180c0
p2互换地址后的值:2 , 地址:0xc0000180a8
p1互换值后的值:2 , 地址:0xc0000180c0
p2互换值后的值:1 , 地址:0xc0000180a8
1
2
3
4
5
6

可以看到,值交换失败了。 a 和 b 的地址,它们实际指向的值并没有发生改变。这就好比放在桌子上的两个钱包,将位置交换后,里面存放的钱并没有发现改变一样,但是交换值的话,是会成功的,但地址不会变。

# 创建指针

Go 语言中还提供了 new() 函数来创建指针,格式如下:

new (类型)
1

代码如下:

package main

import "fmt"

func main() {
	// 声明一个字符串类型 str 变量
	str := new(string)
	*str = "指针"
	fmt.Println(*str)
}
1
2
3
4
5
6
7
8
9
10

代码输出:

指针
1

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