Go 语言指针 (图文教程,超级详细)

更新时间 2022-03-31 14:54:01

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 54w+ 字,讲解图 2476+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 1900+ 小伙伴加入学习 ,欢迎点击围观

大部分面向对象语言都很少有用到指针的场景了,但是在Go语言中有大量的指针应用场景,要想学好 Go 语言,指针是必须了解的。

本小节中,就将学习 Go 语言 指针相关知识点。

一、什么是指针

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

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

二、Go 语言指针

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

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

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

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

三、C/C++中的指针

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

要明白指针,需要知道几个概念:指针地址、指针类型和指针取值,下面将展开详细说明。

四、Go 语言指针地址和指针类型

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

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

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

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

下面的代码示例,演示了指针的取地址用法:

package main

import "fmt"

func main()  {
  // 声明一个整型的 man 变量
	var man int = 1
  // 声明一个字符串类型 domain 变量
	var domain string = "犬小哈教程 www.quanxiaoha.com"

  // 通过 %p 输出 man 和 domain 变量取地址后的指针值,指针值带有 0x 的十六进制前缀
	fmt.Printf("%p %p", &man, &domain)
}

代码输出:

0xc000016080 0xc000010200

实际效果图:

go语言指针取地址代码示例go语言指针取地址代码示例

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

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

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

五、获取指针指向的值

我们已经知道了如何通过 & 获取变量的指针,那么要如何通过指针取值呢?代码如下:

package main

import "fmt"

func main()  {
	// 准备一个字符串类型的变量
	var domain string = "犬小哈教程 www.quanxiaoha.com"

	// 获取字符串的指针, p 类型为*string
	p := &domain

	// 打印变量 p 的类型
	fmt.Printf("p type: %T\n", p)

	// 打印变量 p 的指针地址
	fmt.Printf("address: %p\n", p)

	// 通过 * 对指针进行取值操作
	value := *p

	// 取值后的类型
	fmt.Printf("value type: %T\n", value)

	// 指针取值后指向变量的值
	fmt.Printf("value type: %s\n", value)
}

代码输出如下:

p type: *string
address: 0xc000010200
value type: string
value type: 犬小哈教程 www.quanxiaoha.com

图示:

go语言指针取值代码示例go语言指针取值代码示例

  • &, 取地址操作符,功能是取出变量在内存中的地址;
  • *,取值操作符,功能是取出地址指向的实际值。

小结:

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

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

六、使用指针修改值

指针不但可以取值,同时还可以修改值。

在前面 Go 语言变量 小节中说到了多重赋值的方法进行数值交互,使用指针同样可以。代码如下:

package main

import "fmt"

// 数值交换函数
func swap(a, b *int)  {
	// 取 a 指针的值,赋给临时变量 t
	t := *a

	// 取 b 指针的值,赋给 a 指针指向的变量
	*a = *b

	// 将 a 指针的值赋给 b 指针指向的变量
	*b = t
}

func main()  {
	// 声明两个变量 x, y, 值分别为 1,2
	x, y := 1, 2

	// 交换变量值
	swap(&x, &y)

	// 输出交换后的 x, y 值
	fmt.Println(x, y)
}

代码输出如下:

2 1

假设,我们在上面 swap() 函数中交换的是指针值,情况会如何呢?

package main

import "fmt"

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

func main()  {
	// 声明两个变量 x, y, 值分别为 1,2
	x, y := 1, 2

	// 交换变量值
	swap(&x, &y)

	// 输出交换后的 x, y 值
	fmt.Println(x, y)
}

代码输出如下:

1 2

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

七、通过 new() 函数来创建指针

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

new (类型)

代码如下:

str := new(string)
*str = "quanxiaoha"

fmt.Println(*str)

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