Go的指针
admin
2024-04-30 16:26:34
0

GO 的指针

文章目录

  • GO 的指针
    • 一、GO中代表指标的几种东西
      • (2)uintptr 类型
      • (3)Go语言标准库中的unsafe包:`unsafe#Pointer`
    • 二、GO语言中哪些值是不可以寻址的
      • (1)常量的值
      • (2)基本类型的字面量
      • (3)第一个关键词:不可变的
      • (4)第二个关键词:临时结果
      • (5)第三个关键词:不安全的
      • (6)函数和方法都是不可寻址的
      • (7)管道接收表达式的结果值不可寻址
    • 三、不可寻址值在使用上的限制
    • 四、通过`unsafe.Pointer` 操作可寻址的值

一、GO中代表指标的几种东西

###(1)基本值的指针值

从传统意义上说,指针是一个指向某个确切的内存地址的值,这个内存地址可以是任何数据或代码的起始地址,

type Dog struct {name string
}func (dog *Dog) SetName(name string) {dog.name = name
}

对于基本类型Dog来说,*Dog就是它的指针类型。对于一个Dog类型,值不为nil的变量dog,取值表达式&dog的结果就是该变量的值(也就是基本值的)指针值。

(2)uintptr 类型

uintptr类型是一个数值类型,也是GO语言内建的数据类型之一。根据当前计算机的计算架构不同,它可以存储32位或64位的无符号整数,可以代表任意指针的位(bit)模式,也就是原始的内存地址。

(3)Go语言标准库中的unsafe包:unsafe#Pointer

unsafe.Pointer可以表示任何指向可寻址的值的指针,同时它也是前面提到的指针值和uintpter值之间的桥梁。

也就是说,通过它,可以在这两个值之上进行双向的转换。

二、GO语言中哪些值是不可以寻址的

  • 常量的值
  • 基本类型值的字面量
  • 算术操作的结果值
  • 对各种字面量的索引表达式和切片表达式的结果值。不过有一个例外,对切片字面量的索引结果值却是可寻址的。
  • 对于字符串变量的索引表达式和切片表达式的结果值。
  • 对字典变量索引表达式的结果值;
  • 函数字面量和方法字面量,以及对它们的调用表达式的结果值;
  • 结构体字面量的字段值,也就是结构体字面量的选择表达式的结果值;
  • 类型转换表达式的结果值;
  • 类型断言表达式的结果值;
  • 接收表达式的结果值;

(1)常量的值

常量的值总是会被存储到一个确切的内存区域中,并且这种值肯定是不可变的。

	// 常量值总是会被存储到一个确切的内存区域中,并且这种值肯定是不可变的。const num = 123// _ = &num  // 寻址不可取

(2)基本类型的字面量

基本类型的字面量也是一样的,其实它们本就可以被视为常量,只不过没有任何标识符可以代表它们罢了。

	// _ = &(123) // 基本类型的字面量不可寻址

(3)第一个关键词:不可变的

由于GO语言中的字符串值也是不可变的,所以对一个字符串类型的变量来说,基于它的索引或切片的结果值也都是不可寻址的,因为即使拿到这种内存地址也改变不了什么。

	var str = "abc"_ = str// _ = &(str[0]) // 对字符串变量的索引结果值不可寻址// _ = &(str[0:1]) // 对字符串变量的切片结果值不可寻址str02 := str[2]_ = &str02 // 但这样的寻址就是合法的

(4)第二个关键词:临时结果

算术操作的结果是一种临时结果。在我们把结果赋给任何变量或常量之前,即使拿到它的内存地址也是没有任何意义的。

	const num = 123// _ = &(123 + 456) // 算术操作的结果值不可寻址num2 := 456_ = num2// _ = &(num + num2) // 算术操作的结果值不可寻址

我们把各种对值字面量施加的表达式的求值结果都看做是临时结果。

Go的表达式有很多种,常用的包括以下几种:

  • 用于获取某个元素的索引表达式;
  • 用于获取某个切片(片段)的切片表达式;
  • 用于访问某个字段的选择表达式;
  • 用于调用某个函数或方法的调用表达式;
  • 用于转换值的类型的类型转换表达式;
  • 用于判断值的类型的类型断言表达式;
  • 向通道发送元素值或从通道那里接收元素值的接收表达式;

我们把上述表达式施加在一个字面量上一般都会得到一个临时结果。

	// _ = &([3]int{1, 2, 3}[1]) // 对数组字面量的索引结果值不可寻址// _ = &([3]int{1, 2, 3}[0:1]) // 对数组字面的切片结果值不可寻址_ = &([]int{1, 2, 3}[0]) // 对切片的字面量的索引结果却是可寻址的// _= &([]int{1, 2,3}[0:2]) // 对切字面量的切片结果值不可寻址// _ = &(map[int]string{0:"aa"}[0]) // 对字面字面量的索引结果值不可寻址

特别注意:对切片字面量的索引结果值是可寻址的。 因为不论怎样,每个切片值都会持有一个底层数组,而这个底层数组中的每个元素值都是有一个确切的内存地址。

但是对切片字面量的切片结果值却是不可寻址的,这是因为切片表达式总会返回一个新的切片值,而这个新的切片值在给赋给变量之前属于临时结果。

如果针对的不是数组值、切片值或字典值的字面量的表达式,而是针对的数组类型或切片类型的变量,那么索引或切片的结果值就都不属于临时结果了,是可寻址的。这主要是因为变量本身不是“临时的”。

(5)第三个关键词:不安全的

对字典变量的索引结果值不可寻址

	var map01 = map[int]string{1: "a", 2: "b", 3: "c"}_ = map01// _ = &(map01[1]) // 对字典变量的索引结果值不可寻址

字典每个“‘键-元素”对的存储位置都可能会变化,而且这种变化外界是无法感知的。

字典中总会有若干个哈希桶用于均匀的存储键-元素对。当满足一定条件时,字典可能会改变哈希桶的数量,并适时地其中的键-元素对搬运到对应的新的哈希桶中。

这种情况下,获取字典中的任何元素值的指针都是无意义的,也是不安全的。

(6)函数和方法都是不可寻址的

一个原因是函数就是代码,是不可变的。另一个原因是,拿到一段代码的指针是不安全的。

此外,对函数或方法的调用结果值也是不可寻址的,这是因为它们都属于临时结果。

	// _ = &(func(x, y int) int {// 	return x + y// })   // 字面量代表的函数不可寻址// _ = &(fmt.Sprint) // 标识符代表的函数不可寻址// _ = &(fmt.Sprintln("aa")) // 对函数调用的结果值不可寻址
type Named interface {Name() string
}type Dog struct {name string
}func (dog *Dog) SetName(name string) {dog.name = name
}func (dog Dog) Name() string {return dog.name
}
	dog := Dog{"little pig"}_ = dog// _ = &(dog.Name) // 标识符代表的函数不可寻址// _ = &(dog.Name())  // 对方法的调用结果值不可寻址// _ = &(Dog{"little pig"}.name) // 对结构体字面量的字段不可寻址

类型转换和断言:

	// _ = &(interface{}(dog)) // 类型转换表达式的结果值不可寻址dogI := interface{}(dog)_ = dogI// _ = &(dogI.(Named))  // 类型断言表达式的结果值不可寻址named := dogI.(Named)_ = named// _ = &(named.(Dog)) // 类型断言表达式的结果值不可寻址

(7)管道接收表达式的结果值不可寻址

	var ch chan int = make(chan int, 1)ch <- 1// _ = &(<- ch) //  接收表达式的结果值不可寻址

三、不可寻址值在使用上的限制

  • 无法使用取值符号&获取它们的指针;
  • 不能调用不可寻址值的指针方法;
type Dog struct {name string
}func (dog *Dog) SetName(name string) {dog.name = name
}func (dog Dog) Name() string {return dog.name
}func New(name string) Dog {return Dog{name}
}
	p := New("Big pig")p.SetName("mon")// New("Big pig").SetName("mon") // 不可寻址,链式调用出错
  • 对于++-- 有一个重要限制,表达式的结果必须是可寻址的,

  • 这里有一个例外,虽然字典字面量和字典索引表达式的结果值是不可寻址的,但是这样的表达式却可以被用在自增语句和自减语句中

    	map[string]int{"a": 2}["a"]++var m map[string]int  = map[string]int{"b":2}m["b"]++
    
  • 对于赋值语句,赋值操作符左边的表达式的结果值必须是可寻址的,但是对字典索引结果值也是可以的

    	var m01 map[string]int = map[string]int{"a": 2}m01["a"] = 3
    
  • 同样,对于带有range的for语句,rang左边的表达式的结果值必须是可寻址的。不过对字典的索引结果同样可以用在这里

    	var m01 map[string]int = map[string]int{"a": 2}for mk := range m01 {fmt.Println(m01[mk])}
    

四、通过unsafe.Pointer 操作可寻址的值

首先说明,这是一项黑科技,它可以绕过GO语言的编译器和其他工具的重新检查,并达到潜入内存修改数据1目的。这并不是一种正常的编程手段,使用它会很危险,很有可能造成安全隐患。

我们总是应该优先使用常规代码包中提供的API去编写程序,当然也可以把reflect以及go/ast这样的代码包作为备选项。作为上层应用的开发者,请谨慎使用unsafe 包中的任何程序实体。

type Dog struct {name string
}func main() {dog := Dog{"little pig"}dogP := &dogdogPtr := uintptr(unsafe.Pointer(dogP))fmt.Println(dogPtr)
}

转换规则:

  • 一个指针值(比如*Dog类型的值)可以被转换为一个unsafe.Pointer类型的值,反之依然;
  • 一个uintptr 类型的值也可以被转换为一个unsafe.Pointer 类型的值,反之依然;
  • 一个指针值无法直接转换为uintptr 类型的值,反之依然;

通过dogPtr可以获取字段的指针值:

	namePtr := dogPtr + unsafe.Offsetof(dog.name)nameP := (*string)(unsafe.Pointer(namePtr))fmt.Println(nameP)

相关内容

热门资讯

做ppt用什么软件 值得推荐的... 做PPT这件事,说起来好像人人都会,但真要做出一份拿得出手的演示文稿,很多人坐在电脑前盯着空白页面发...
汽车报价大全app官网免费下载... 买车这件事,说麻烦也麻烦,说不麻烦也就那样。现在汽车报价大全app官网免费下载的渠道多了,手机上就能...
听曲子识别歌名的软件有哪些 听... 街上偶尔会飘来一段旋律,就是叫不出名字,那种感觉说不上来,有点抓心。现在手机上有不少能听曲子识别歌名...
高人气的免费影视app没有广告... 追剧这件事,说起来门槛不高,但真正找到一款免费影视app没有广告的,其实没那么顺。打开某些平台,片子...
什么软件能把视频变清晰合集 高... 视频拍完之后,画质不理想是很多人都遇到过的问题,模糊、噪点、色彩偏差,看着就不舒服。什么软件能把视频...
免费小说app有什么 热门好用... 平常我们经常会遇到想看的书需要付费、可舍不得钱包、想追更却舍不得充VIP的尴尬时刻,生活中总是会有想...
高人气的买客运汽车票的软件有什... 出行路上要操心的事情不少,订票、订酒店、叫车,每一步都得打开不同的软件来回切换,费力又费时。不少人都...
好用的拜年视频制作app合集 ... 春节将至,给亲友送上一段用心制作的拜年视频,远比发条消息更有温度。拜年视频制作app这两年冒出来不少...
高人气的建模软件3d合集 下载... 手机里装着建模软件3d的人,其实比想象中多得多。设计师出差途中想改个方案,学生课间想把脑子里的结构画...
日常开销记账app哪个好用 热... 做生意想要提高效率,必须要用到一些管理软件。它们能够系统化的制定出预算,以避免浪费。有人问日常开销记...