清水泥沙

golang基础(18.指针的基本概念和使用)

指针概述

在go中我们可以通过变量来定义操作我们的物理存储空间,其本质是一块内存空间的定义。而指针的定义是指指向存储这些变量值的内存地址

package main

import "fmt"

func main() {
	var a = 100
	var ptr *int

	ptr = &a
	fmt.Println(a)   // 变量值
	fmt.Println(ptr) // 变量值存储地址
}
上述代码定义了一个整形变量a值是一百,然后定义了一个整形指针。通过&符号,将变量a的变量值地址赋值给了指针ptr。我们可以通过 *ptr 获取指针指向内存地址存储的变量值(我们通常将这种引用称作「间接引用」),ptr 本身是一个内存地址值(通过 &a 可以获取变量 a 所在的内存地址)<br />go语言引入指针类型,主要基于两点考虑。一个是为程序员提供操作变量对应内存数据结构的能力,一个是为了提供程序性能。(指针可以值直接传递某个变量的内存地址,可以在传递过程当中产生的值拷贝)<br />指针在go中有两个使用场景
  • 类型指针
  • 数组切片

作为类型指针时,允许对这个指针类型的数据直接进行修改指向其他内存地址,传递数据时如果使用指针则无须拷贝数据从而节省内存空间,此外和 C 语言中的指针不同,Go 语言中的类型指针不能进行偏移和运算,因此更为安全。数组切片,由指向起始元素的原始指针、元素数量和容量组成,所以切片与数组不同,是引用类型,而非值类型。

指针的基本使用

指针类型的声明和初始化

指针变量传值时之所以可以节省空间,因为指针指向的内存地址大小是固定的,在32位机器上占4个字节,在64位上占8个字节,与指向内存存储的值无关。

var ptr *int
fmt.Println(ptr)

a := 100
ptr = &a
fmt.Println(ptr)
fmt.Println(*ptr)

当指针被声明后,没有指向任何变量内存地址时,它的零值是 nil,然后我们可以通过在给定变量前加上取地址符 & 获取变量对应的内存地址将其赋值给声明的指针类型,这样,就是对指针的初始化了,然后我们可以通过在指针类型前加上间接引用符 * 获取指针指向内存空间存储的变量值。当然,我们也可以通过 := 对指针进行初始化:

a := 100
ptr := &a
fmt.Printf("%p
", ptr)
fmt.Printf("%d
", *ptr)

底层会自动判断指针的类型,在格式化输出时,可以通过 %p 来标识指针类型。

通过指针传值

我们再来看一个通过指针传值的示例,通过指针传值就类似于 PHP 中通过引用传值,这样做的好处是节省内存空间,此外还可以在调用函数中实现对变量值的修改,因为直接修改的是指针指向内存地址上存储的变量值,而不是值拷贝。

package main

import "fmt"

func swap(a, b int) {
	a, b = b, a
	fmt.Println(a, b)
}

func swapPtr(a, b *int) {
	*a, *b = *b, *a
	fmt.Println(*a, *b)
}

func main() {
	a := 1
	b := 2
	swap(a, b)
	fmt.Println(a, b)
	swapPtr(&a, &b)
	fmt.Println(a, b)
}

最终输出

2 1
1 2
2 1
2 1
可以看到外部传递的变量的值已经被swapPtr方法修改了。