认识指针
Go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量。传递数据使用指针,而无须拷贝数据。类型指针不能进行偏移和运算。Go语言中的指针操作非常简单,只需要记住两个符号:&
(取地址)和*
(根据地址取值)。
&
–>取地址,*
–>去地址中取值
&
–>取地址,*
–>去地址中取值
&
–>取地址,*
–>去地址中取值
————–联想到下单快递,上门取件
&
–>快递小哥得到你家地址, *
–>小哥根据地址上门取件
当你确定好快递员的取件时间后,需要将你的地址告诉他。这就是取地址操作,使用 &
符号可以获取你的地址。例如:
// 你要寄的东西
var apple = "一箱苹果"
// 你家地址
var addr = &apple
然后,当快递员到达你的地址时,需要知道包裹存放的位置,并将其取走。这就是去地址中取值操作,使用 *
符号可以获取地址中存储的值。例如:
// 你的包裹
var yourPackage = *addr
fmt.Printf("快递小哥将在地址为 >%p< 来上门取走你的包裹-->%#v \n", addr, yourPackage)
//快递小哥将在地址为 >0xc00005e260< 来上门取走你的包裹-->"一箱苹果"
在上面的代码中,使用 *addr
可以获取存储在 addr
指向的地址中的值
值得注意的是
*
号的用法,把*
加在数据类型前面(如:*string
)表示一个指针类型,把*
加在指针变量前面(如*addr
),则用于访问该指针指向的变量。Go语言中的值类型
- 布尔类型(bool):存储 true 或 false。
- 整数类型(int、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint64、uintptr):存储整数。
- 浮点数类型(float32、float64):存储浮点数。
- 复数类型(complex64、complex128):存储复数。
- 字符串类型(string):存储字符串。
- 数组类型(array):存储固定长度的同类型元素的集合。
- 结构体类型(struct):存储不同类型的字段的集合。
都有对应的指针类型,如:
*int、*int64、*string
……
初始化指针
空指针
- 当一个指针被定义后没有分配到任何变量时,它的值为 nil
- 空指针的判断
var i *int
fmt.Println(i) //nil
var s *string
fmt.Println(s) //nil
var user *User
fmt.Println(user)//nil
分配空间
先来个例子
func main() {
var i *int
*i = 666
fmt.Println(*i) // 报错
}
执行上面的代码会引发panic,为什么呢? 在Go语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储。而对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间。要分配内存,就引出来今天的new
。 Go语言中new
是内建的函数,主要用来分配内存
new
new
是一个内置的函数,它的函数签名如下:
func new(Type) *Type
1.Type表示类型,new函数只接受一个参数,这个参数是一个类型
2.*Type表示类型指针,new函数返回一个指向该类型内存地址的指针。
new
函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。举个例子:
func main() {
a := new(int)
b := new(bool)
fmt.Printf("%T\n", a) // *int
fmt.Printf("%T\n", b) // *bool
fmt.Println(*a) // 0
fmt.Println(*b) // false
}
开始的示例代码中var i *int
只是声明了一个指针变量a但是没有初始化,指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。应该按照如下方式使用内置的new函数对i进行初始化之后就可以正常对其赋值了:
func main() {
var i *int
i = new(int)
*i = 10
fmt.Println(*i) //10
}
好,上点强度
指针类型的指针
前面我们有提到过,把*
加在数据类型前面(如:*string
)表示一个指针类型,ok,那也就是说*T
它也是一种类型,那么,是不是可以套娃了?
比如:
var m **int
在 Go 语言中,var m **int
声明了一个名为 m
的变量,其类型为指向指针的指针,即 **int
类型。
这意味着 m
可以存储一个指向指针的指针的值,指向的指针可以指向一个 int
类型的变量。例如,可以将 m
赋值为指向一个 int
类型变量的指针的指针:
var m **int
var n int = 42
var p *int = &n
m = &p
在上面的代码中,变量 n
存储了整数值 42,变量 p
存储了指向 n
变量的指针。然后,将 p
的地址赋值给变量 m
,即指向指针的指针 m
存储了指向 p
的指针的地址。
需要注意的是,由于 m
是一个指向指针的指针,因此在使用 m
之前,必须先为其分配内存。例如,可以使用 new
函数为 m
分配内存:
var m **int = new(*int)
在上面的代码中,new(*int)
创建了一个新的指向指针的指针,并将其地址赋值给变量 m
。这样就可以安全地对 m
进行赋值和解引用操作了。
上面的套娃,部分人可能有点蒙了,那把这个映射到我们开始的上门取件的例子上,把类型也给你打上
var addr1 **string = new(*string)
// apple 你要寄的东西,就是普通的 string类型
var apple string = "一箱苹果"
// addr2 你家门牌号,苹果在几楼几号啊,就是普通的 指针类型
var addr2 *string = &apple
// 你的包裹
var yourPackage string = *addr2
// addr1 你家在哪个小区啊
addr1 = &addr2
fmt.Printf("快递小哥在 >%p< 小区的 >%p< 来上门取你的包裹 ---> %v \n", addr1, addr2, yourPackage)
//快递小哥在 >0xc00000a030< 小区的 >0xc00005e260< 来上门取你的包裹 ---> 一箱苹果
到这,有些人可能懂了,(噢!原来是这样),有些人还是懵(别急,我给你放图来理解)
练习
然后现在,你们懂了吗,真的懂了吗??
来几个测试:把下面结果相同的序号挑出来组成一组,并判断该组的类型(string
,*string
, **string
, ***string
)答案我放在评论区
addr1 >>>>>>>>>>>1>>>>>>>>
&addr1 >>>>>>>>>>>2>>>>>>>>
*addr1 >>>>>>>>>>>3>>>>>>>>
**addr1 >>>>>>>>>>>4>>>>>>>>
*&addr1 >>>>>>>>>>>5>>>>>>>>
&*addr1 >>>>>>>>>>>6>>>>>>>>
addr2 >>>>>>>>>>>7>>>>>>>>
&addr2 >>>>>>>>>>>8>>>>>>>>
*addr2 >>>>>>>>>>>9>>>>>>>>