指针

区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针。

要搞明白Go语言中的指针需要先知道3个概念:指针地址、指针类型和指针取值。

很多人的理解可能以为指针是和spark中的游标一样,尤其是取数组中的值根据下标来取,其实不然,在这里,Go 语言中的指针所表示的是:一个指针变量指向了一个值的内存地址。类似于变量和常量,在使用指针前你需要声明指针。

任何程序数据载入内存后,在内存都有他们的地址,这就是指针。而为了保存一个数据在内存中的地址,我们就需要指针变量。

Go语言中的指针不能进行偏移和运算,因此Go语言中的指针操作非常简单,我们只需要记住两个符号:

&(取地址)和*(根据地址取值)。

指针声明:

var var_name *var-type //var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。 var a *int // 指向整型 var b *float32 // 指向浮点型

指针(pointer)在Go语言中可以被拆分为两个核心概念:

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

指针地址和指针类型

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&字符放在变量前面对变量进行“取地址”操作。 Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如:*int、*int64、*string等。

取变量指针的语法

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

其中:

  • v:代表被取地址的变量,类型为T
  • ptr:用于接收地址的变量,ptr的类型就为*T,称做T的指针类型。*代表指针。

指针使用流程:

  • 定义指针变量。
  • 为指针变量赋值。
  • 访问指针变量中指向地址的值。

在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。

func main() { var a int= 20 /* 声明实际变量 */ var b *int /* 声明指针变量 */ b = &a /* 指针变量的存储地址 */ fmt.Printf("a 变量的地址: %x\n", &a ) /* 指针变量的存储地址 */ fmt.Printf("b 变量储存的指针地址: %x\n", b ) /* 使用指针访问值 */ fmt.Printf("*b 变量的值: %d\n", *b ) }

每个变量都拥有地址,指针的值就是地址。

空指针

当一个指针被定义后没有分配到任何变量时,它的值为 nil。

nil 指针也称为空指针。

nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。

func main(){ var ptr *int fmt.printf("ptr的值为:%x\n",ptr) }

从指针获取指针指向的值

当使用&操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用*操作符,也就是指针取值

解析:

取地址操作符&和取值操作符*是一对互补操作符,&取出地址,根据地址取出地址指向的值。变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:

  • 对变量进行取地址操作使用&操作符,可以获得这个变量的指针变量。
  • 指针变量的值是指针地址。
  • 对指针变量进行取值操作使用*操作符,可以获得指针变量指向的原变量的值。

指针取值和修改值

在对普通变量使用&操作符取地址后会获得这个变量的指针,然后可以对指针使用*操作,也就是指针取值,

总结: 取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值。

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

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

通过指针不仅可以取值,也可以修改值。

package main import "fmt" // 交换函数 func swap(a, b *int) { // 取a指针的值, 赋给临时变量t t := *a // 取b指针的值, 赋给a指针指向的变量 *a = *b // 将a指针的值赋给b指针指向的变量 *b = t } func main() { // 准备两个变量, 赋值1和2 x, y := 1, 2 // 交换变量值 swap(&x, &y) // 输出变量值 fmt.Println(x, y) //结果 2 1 }

解析:

定义一个交换函数,参数为 a、b,类型都为 int 指针类型。取指针 a 的值,并把值赋给变量 t,t 此时是 int 类型。取 b 的指针值,赋给指针 a 指向的变量。注意,此时a的意思不是取 a 指针的值,而是“a 指向的变量”。

将 t 的值赋给指针 b 指向的变量。

准备 x、y 两个变量,分别赋值为 1 和 2,类型为 int。

取出 x 和 y 的地址作为参数传给 swap() 函数进行调用。

交换完毕时,输出 x 和 y 的值。

*操作符作为右值时,意义是取指针的值,作为左值时,也就是放在赋值操作符的左边时,表示 a 指针指向的变量。其实归纳起来,*操作符的根本意义就是操作指针指向的变量。

当操作在右值时,就是取指向变量的值,当操作在左值时,就是将值设置给指向的变量。

new和make

new:

new是一个内置的函数

func new(Type) *Type

其中,

  • Type表示类型,new函数只接受一个参数,这个参数是一个类型
  • *Type表示类型指针,new函数返回一个指向该类型内存地址的指针。

new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。

str := new(string) *str = "hello" fmt.Println(*str)

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

make:

make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了

func make(t Type, size ...IntegerType) Type

make函数是无可替代的,我们在使用slice、map以及channel的时候,都需要使用make进行初始化,然后才可以对它们进行操作。这个我们在上一章中都有说明,关于channel我们会在后续的章节详细说明。

比如:var b map[string]int只是声明变量b是一个map类型的变量,需要像下面的示例代码一样使用make函数进行初始化操作之后,才能对其进行键值对赋值

make和new的区别

  1. 二者都是用来做内存分配的。
  2. make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
  3. 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。