Go基础:指针地址、指针类型、多重指针、指针运算

简介: Go基础:指针地址、指针类型、多重指针、指针运算

目录

到底什么是指针呢?

指针

指针地址和指针类型

指针取值

指针变量初始化

指针运算符

多重指针

指针运算



到底什么是指针呢?

内存就是一系列有序列号的存储单元,变量就是编译器为内存地址分配的昵称,那么指针是什么呢?

指针就是一个指向另一个内存地址变量的值

指针指向变量的内存地址,指针就像该变量值的内存地址一样

我们来看一个代码片段

func main() {
    a := 200
    b := &a
    *b++
    fmt.Println(a)
}

在 main 函数的第一行,我们定义了一个新的变量 a ,并赋值为 200。接下来我们定义了一个变量 b ,并将变量 a 的地址赋值给 b 。我们并不知道 a 的准确存储地址,但是我们依然可以将 a 的地址存储在变量 b 中。


因为 Go 强类型的特性,第三行代码也许是最具干扰性的了,b 包含 a 变量的地址,但是我们想增加存储在 a 变量中的值。

这样我们必须取消引用 b ,而是跟随指针由 b 引用 a。

然后我们将该值加 1 后,存储回 b 中存储的内存地址上。


最后一行打印了 a 的值,可以看到 a 的值已经增加为了 201


指针

Go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量

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

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


指针地址和指针类型

Go语言中的指针操作非常简单,只需要记住两个符号:&(取地址)和*(根据地址取值)

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&字符放在变量前面对变量进行“取地址”操作。

取变量指针的语法如下:

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

其中:

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

func main() {
    a := 10
    b := &a
    fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
    fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int
    fmt.Println(&b)                    // 0xc00000e018
}


指针取值

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

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

1.对变量进行取地址(&)操作,可以获得这个变量的指针变量。

2.指针变量的值是指针地址。

3.对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。

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


指针变量初始化

func main() {
    var a *int
    *a = 100
    fmt.Println(*a)
    var b map[string]int
    b["测试"] = 100
    fmt.Println(b)
}
// panic: runtime error: invalid memory address or nil pointer dereference
//[signal 0xc0000005 code=0x1 addr=0x0 pc=0x49a7ca]


执行上面的代码会引发panic,为什么呢?

  • 在Go语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储。
  • 而对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间

Go语言中new和make是内建的两个函数,主要用来分配内存

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

1. 二者都是用来做内存分配的。

2. make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身

3. 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。


指针运算符

1.指针运算符为左值时,我们可以更新目标对象的状态;而为右值时则是为了获取目标的状态。

func main() {
    x := 10
    var p *int = &x  //获取地址,保存到指针变量
    *p += 20        //用指针间接引用,并更新对象
    println(p, *p)  //输出指针所存储的地址,以及目标对象
}

输出:

0xc000040780 30

2.指针类型支持相等运算符,但不能做加减运算和类型转换。如果两个指针指向同一地址,或都为nil,那么它们相等。

func main() {
    x := 10
    p := &x
    p++   //编译报错 invalid operation: p++ (non-numeric type *int)
    var p2 *int = p+1  //invalid operation: p + 1 (mismatched types *int and int)
    p2 = &x
    println(p == p2)   //指向同一地址
}

可通过unsafe.Pointer将指针转换为uintptr后进行加减法运算,但可能会造成非法访问。


多重指针

指针可以指向任何类型的变量。所以也可以指向另一个指针。以下示例显示如何创建指向另一个指针的指针:

package main
import "fmt"
func main() {
  var a = 3.141596
  var p = &a
  var pp = &p
  fmt.Println("a = ", a)
  fmt.Println("p = ", p)
  fmt.Println("pp = ", pp)
  fmt.Println("&p = ", &p)
  fmt.Println("&pp = ", &pp)
  fmt.Println("*p = ", *p)
  fmt.Println("*pp = ", *pp)
  fmt.Println("**pp = ", **pp)
  //a =  3.141596
  //p =  0xc00008e060
  //pp =  0xc000090018
  //&p =  0xc000090018
  //&pp =  0xc000090020
  //*p =  3.141596
  //*pp =  0xc00008e060
  //**pp =  3.141596
}


指针运算

在很多 golang 程序中,虽然用到了指针,但是并不会对指针进行加减运算,这和 C 程序是很不一样的。Golang 的官方入门学习工具(go tour) 甚至说 Go 不支持指针算术。虽然实际上并不是这样的,但我在一般的 go 程序中,好像确实没见过指针运算(嗯,我知道你想写不一般的程序)。

  • 但实际上,go 可以通过 unsafe.Pointer 来把指针转换为 uintptr 类型的数字,来实现指针运算。
  • 这里请注意,uintptr 是一种整数类型,而不是指针类型。

比如:

uintptr(unsafe.Pointer(&p)) + 1


就得到了 &p 的下一个字节的位置。然而,根据 《Go Programming Language》 的提示,我们最好直接把这个计算得到的内存地址转换为指针类型:

unsafe.Pointer(uintptr(unsafe.Pointer(&p) + 1))


因为 go 中是有垃圾回收机制的,如果某种 GC 挪动了目标值的内存地址,以整型来存储的指针数值,就成了无效的值。

同时也要注意,go 中对指针的 + 1,真的就只是指向了下一个字节,而 C 中 + 1 或者 ++ 考虑了数据类型的长度,会自动指向当前值结尾后的下一个字节(或者说,有可能就是下一个值的开始)。如果 go 中要想实现同样的效果,可以使用 unsafe.Sizeof 方法:

unsafe.Pointer(uintptr(unsafe.Pointer(&p) + unsafe.Sizeof(p)))


最后,另外一种常用的指针操作是转换指针类型。这也可以利用 unsafe 包来实现:

var a int64 = 1
(*int8)(unsafe.Pointer(&a))


如果你没有遇到过需要转换指针类型的需求,可以看看这个项目(端口扫描工具),其中构建 IP 协议首部的代码,就用到了指针类型转换。



目录
相关文章
|
2月前
|
JSON 前端开发 JavaScript
聊聊 Go 语言中的 JSON 序列化与 js 前端交互类型失真问题
在Web开发中,后端与前端的数据交换常使用JSON格式,但JavaScript的数字类型仅能安全处理-2^53到2^53间的整数,超出此范围会导致精度丢失。本文通过Go语言的`encoding/json`包,介绍如何通过将大整数以字符串形式序列化和反序列化,有效解决这一问题,确保前后端数据交换的准确性。
68 4
|
2月前
|
Go
go语言常量的类型
【10月更文挑战第20天】
32 2
|
3月前
|
C语言 C++
【C语言】指针篇-一篇搞定不同类型指针变量-必读指南(3/5)
【C语言】指针篇-一篇搞定不同类型指针变量-必读指南(3/5)
|
4月前
|
存储 Go
Go to Learn Go之指针
Go to Learn Go之指针
39 0
|
4月前
|
存储 Go
Go: struct 结构体类型和指针【学习笔记记录】
本文是Go语言中struct结构体类型和指针的学习笔记,包括结构体的定义、成员访问、使用匿名字段,以及指针变量的声明使用、指针数组定义使用和函数传参修改值的方法。
|
5月前
|
安全 Go
|
5月前
|
存储 安全 程序员
|
5月前
|
编译器 Go 开发者
Go 在编译时评估隐式类型的优点详解
【8月更文挑战第31天】
41 0
|
5月前
|
存储 编译器 Go
|
5月前
|
存储 安全 Go
深入理解 Go 语言中的指针类型
【8月更文挑战第31天】
61 0