"指针的艺术:如何在 Go 语言中正确使用指针"

认识指针

Go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量。传递数据使用指针,而无须拷贝数据。类型指针不能进行偏移和运算。Go语言中的指针操作非常简单,只需要记住两个符号:&(取地址)和*(根据地址取值)。

&–>取地址,*–>去地址中取值

&–>取地址,*–>去地址中取值

&–>取地址,*–>去地址中取值

————–联想到下单快递,上门取件

& –>快递小哥得到你家地址, *–>小哥根据地址上门取件

当你确定好快递员的取件时间后,需要将你的地址告诉他。这就是取地址操作,使用 & 符号可以获取你的地址。例如:

// 你要寄的东西
var apple = "一箱苹果"
// 你家地址
var addr = &apple

然后,当快递员到达你的地址时,需要知道包裹存放的位置,并将其取走。这就是去地址中取值操作,使用 * 符号可以获取地址中存储的值。例如:

// 你的包裹
var yourPackage = *addr


fmt.Printf("快递小哥将在地址为 >%p< 来上门取走你的包裹-->%#v \n", addr, yourPackage)
//快递小哥将在地址为 >0xc00005e260< 来上门取走你的包裹-->"一箱苹果" 

在上面的代码中,使用 *addr 可以获取存储在 addr 指向的地址中的值

值得注意的是*号的用法,把*加在数据类型前面(如:*string)表示一个指针类型,把*加在指针变量前面(如*addr),则用于访问该指针指向的变量。

Go语言中的值类型

  1. 布尔类型(bool):存储 true 或 false。
  2. 整数类型(int、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint64、uintptr):存储整数。
  3. 浮点数类型(float32、float64):存储浮点数。
  4. 复数类型(complex64、complex128):存储复数。
  5. 字符串类型(string):存储字符串。
  6. 数组类型(array):存储固定长度的同类型元素的集合。
  7. 结构体类型(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< 来上门取你的包裹 ---> 一箱苹果 

到这,有些人可能懂了,(噢!原来是这样),有些人还是懵(别急,我给你放图来理解)

image.png

练习

然后现在,你们懂了吗,真的懂了吗??
来几个测试:把下面结果相同的序号挑出来组成一组,并判断该组的类型(string,*string, **string, ***string)答案我放在评论区

  addr1 >>>>>>>>>>>1>>>>>>>>
 &addr1 >>>>>>>>>>>2>>>>>>>>
 *addr1 >>>>>>>>>>>3>>>>>>>>
**addr1 >>>>>>>>>>>4>>>>>>>>
*&addr1 >>>>>>>>>>>5>>>>>>>>
&*addr1 >>>>>>>>>>>6>>>>>>>>
  addr2 >>>>>>>>>>>7>>>>>>>>
 &addr2 >>>>>>>>>>>8>>>>>>>>
 *addr2 >>>>>>>>>>>9>>>>>>>>

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYQzxMXB' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片