48

golang 指针

 5 years ago
source link: https://studygolang.com/articles/15206?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在Go语言中,有几种东西可以代表“指针”。

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

 2.unsafe.Pointer:可以代表任何指向 可寻址的值 的指针,同时它也是前面提到的指针值和uintptr值之间的桥梁。也就是说,通过它,我们可以在这两种值之上进行双向的转换。

问:Go语言中哪些值是不可寻址的?

答:

  • 1.常量的值
const num = 123
//_ = &num // 常量不可寻址。
  • 2.基本类型值的字面量
//_ = &(123) // 基本类型值的字面量不可寻址。
  • 3.算术操作的结果值
//_ = &(123 + 456) // 算术操作的结果值不可寻址。
  • 4.对各种字面量的索引表达式和切片表达式的结果值。例外:对切片字面量的索引结果是可寻址的。
//_ = &([3]int{1, 2, 3}[0]) // 对数组字面量的索引结果值不可寻址。
//_ = &([3]int{1, 2, 3}[0:2]) // 对数组字面量的切片结果值不可寻址。
_ = &([]int{1, 2, 3}[0]) // 对切片字面量的索引结果值却是可寻址的。
//_ = &([]int{1, 2, 3}[0:2]) // 对切片字面量的切片结果值不可寻址。
//_ = &(map[int]string{1: "a"}[0]) // 对字典字面量的索引结果值不可寻址。
  • 5.对字符串变量的索引表达式和切片表达式的结果值。
var str = "abc"
_ = str
//_ = &(str[0]) // 对字符串变量的索引结果值不可寻址。
//_ = &(str[0:2]) // 对字符串变量的切片结果值不可寻址。
str2 := str[0]
_ = &str2 // 但这样的寻址就是合法的。
  • 6.对字典变量的索引表达式的结果值
var map1 = map[int]string{1: "a", 2: "b", 3: "c"}
_ = map1
//_ = &(map1[2]) // 对字典变量的索引结果值不可寻址。
  • 7.函数字面量和方法字面量,以及对它们的调用表达式的结果值。
//_ = &(fmt.Sprintf) // 标识符代表的函数不可寻址。
//_ = &(fmt.Sprintln("abc")) // 对函数的调用结果值不可寻址。

dog := Dog{"little pig"}
_ = dog
//_ = &(dog.Name) // 标识符代表的函数不可寻址。
//_ = &(dog.Name()) // 对方法的调用结果值不可寻址。
  • 8.结构体字面量的字段值,也就是对结构体字面量
//_ = &(Dog{"little pig"}.name) // 结构体字面量的字段不可寻址。
  • 9.类型转换表达式的结果值
//_ = &(interface{}(dog)) // 类型转换表达式的结果值不可寻址。
  • 10.类型断言表达式的结果值
dogI := interface{}(dog)
_ = dogI
//_ = &(dogI.(Named)) // 类型断言表达式的结果值不可寻址。
named := dogI.(Named)
_ = named
//_ = &(named.(Dog)) // 类型断言表达式的结果值不可寻址。
  • 11.接受表达式的结果值
var chan1 = make(chan int, 1)
chan1 <- 1
//_ = &(<-chan1) // 接收表达式的结果值不可寻址。

原因:

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

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

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

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

问:不可寻址的值在使用上有哪些限制?

答:

 无法使用取址操作符&获取它们的指针,会使编译器报错。

demo:

func New(name string) Dog {
      return Dog(name)
}
New("litter pig").SetName("monster")

调用表达式dog.SetName("monster")会被自动地转译成(&dog).SetName("monster")

由于New函数的调用结果是不可寻址的,所以无法进行取址操作。因此,编译器会报告两个错误,1:不能在New("litter pig")的结果值上调用指针方法 ; 2:不能取得New("litter pig")的地址。

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

答:

demo:

dog := Dog{"litter pig"}
dogP := &dog
dogPtr := uintPtr(unsafe.Pointer(dogP))

 先声明了一个Dog类型的变量dog,然后用取址操作符&,取出了它的指针值,并把它赋给了变量dogP.

 最后,使用了两个类型转换,先把dogP转换成一个unsafe.Pointer类型的值,然后紧接着又把后者转换成了一个uintptr的值,并把它赋给了变量dogPtr。这背后隐藏着一些转换规则:

 1.一个指针值(比如*Dog类型的值)可以被转换为一个unsafe.Pointer类型的值,反之亦然。

 2.一个uintptr类型的值也可以被转换为一个unsafe.Pointer类型的值,反之亦然。

 3.一个指针值无法被直接转换成一个uintptr类型的值,反过来也是如此。

namePtr := dogPtr + unsafe.Offestof(dog.name)
nameP := (*string)(unsafe.Pointer(namePtr))

unsafe.Offestof函数用于获取两个值在内存中的起始存储地址之间的偏移量,以字节为单位。

通过偏移量跟结构体在内存中的起始存储地址(dogPtr ),把它们相加我们就可以得到dogP的name字段值的起始存储地址(namePtr );

再通过两次类型转换把namePtr 的值转换成一个*string的类型的值,就得到了指向dogP的name字段的指针值。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK