Go语言核心36讲(Go语言进阶技术九)--学习笔记 (3)

1、不可变的值不可寻址。常量、基本类型的值字面量、字符串变量的值、函数以及方法的字面量都是如此。其实这样规定也有安全性方面的考虑。

2、绝大多数被视为临时结果的值都是不可寻址的。算术操作的结果值属于临时结果,针对值字面量的表达式结果值也属于临时结果。但有一个例外,对切片字面量的索引结果值虽然也属于临时结果,但却是可寻址的。

3、若拿到某值的指针可能会破坏程序的一致性,那么就是不安全的,该值就不可寻址。由于字典的内部机制,对字典的索引结果值的取址操作都是不安全的。另外,获取由字面量或标识符代表的函数或方法的地址显然也是不安全的。

最后说一句,如果我们把临时结果赋给一个变量,那么它就是可寻址的了。如此一来,取得的指针指向的就是这个变量持有的那个值了。

知识扩展 问题 1:不可寻址的值在使用上有哪些限制?

首当其冲的当然是无法使用取址操作符&获取它们的指针了。不过,对不可寻址的值施加取址操作都会使编译器报错,所以倒是不用太担心,你只要记住我在前面讲述的那几条规律,并在编码的时候提前注意一下就好了。

我们来看下面这个小问题。我们依然以那个结构体类型Dog为例。

func New(name string) Dog { return Dog{name} }

我们再为它编写一个函数New。这个函数会接受一个名为name的string类型的参数,并会用这个参数初始化一个Dog类型的值,最后返回该值。我现在要问的是:如果我调用该函数,并直接以链式的手法调用其结果值的指针方法SetName,那么可以达到预期的效果吗?

New("little pig").SetName("monster")

如果你还记得我在前面讲述的内容,那么肯定会知道调用New函数所得到的结果值属于临时结果,是不可寻址的。

可是,那又怎样呢?别忘了,我在讲结构体类型及其方法的时候还说过,我们可以在一个基本类型的值上调用它的指针方法,这是因为 Go 语言会自动地帮我们转译。

更具体地说,对于一个Dog类型的变量dog来说,调用表达式dog.SetName("monster")会被自动地转译为(&dog).SetName("monster"),即:先取dog的指针值,再在该指针值上调用SetName方法。

发现问题了吗?由于New函数的调用结果值是不可寻址的,所以无法对它进行取址操作。因此,上边这行链式调用会让编译器报告两个错误,一个是果,即:不能在New("little pig")的结果值上调用指针方法。一个是因,即:不能取得New("little pig")的地址。

除此之外,我们都知道,Go 语言中的++和--并不属于操作符,而分别是自增语句和自减语句的重要组成部分。

虽然 Go 语言规范中的语法定义是,只要在++或--的左边添加一个表达式,就可以组成一个自增语句或自减语句,但是,它还明确了一个很重要的限制,那就是这个表达式的结果值必须是可寻址的。这就使得针对值字面量的表达式几乎都无法被用在这里。

package main type Dog struct { name string } func New(name string) Dog { return Dog{name} } func (dog *Dog) SetName(name string) { dog.name = name } func (dog Dog) Name() string { return dog.name } func main() { // 示例1。 //New("little pig").SetName("monster") // 不能调用不可寻址的值的指针方法。 // 示例2。 map[string]int{"the": 0, "word": 0, "counter": 0}["word"]++ map1 := map[string]int{"the": 0, "word": 0, "counter": 0} map1["word"]++ }

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

与之类似的规则还有两个。一个是,在赋值语句中,赋值操作符左边的表达式的结果值必须可寻址的,但是对字典的索引结果值也是可以的。

另一个是,在带有range子句的for语句中,在range关键字左边的表达式的结果值也都必须是可寻址的,不过对字典的索引结果值同样可以被用在这里。以上这三条规则我们合并起来记忆就可以了。

与这些定死的规则相比,我刚刚讲到的那个与指针方法有关的问题,你需要好好理解一下,它涉及了两个知识点的联合运用。起码在我面试的时候,它是一个可选择的考点。

问题 2:怎样通过unsafe.Pointer操纵可寻址的值?

前边的基础知识很重要。不过现在让我们再次关注指针的用法。我说过,unsafe.Pointer是像*Dog类型的值这样的指针值和uintptr值之间的桥梁,那么我们怎样利用unsafe.Pointer的中转和uintptr的底层操作来操纵像dog这样的值呢?

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

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgyxyy.html