在学习结构体的时候,无意发现一段代码的区别,最终才有了这篇文章,我们先看看这段代码:
样例代码
package main
import "fmt"
func main() {
fmt.Println(person{}.foo()) //这是值类型
fmt.Println(person{}.fooPoint()) //cannot call pointer method on person literal
fmt.Println((&person{}).foo()) //这是值类型
fmt.Println((&person{}).fooPoint()) //这是指针类型
}
type person struct {
}
func (p person) foo() string {
return "这是值类型"
}
func (p *person) fooPoint() string {
return "这是指针类型"
}
代码很简单,定义一个person结构体,又定义了结构体的两个方法,一个是值接收者类型方法foo();一个是指针接收者类型方法fooPoint()。但是在main方法里面的四句打印语句,发现fmt.Println(person{}.fooPoint())这句是编译报错的:cannot call pointer method on person literal,指针方法和值方法都可以在指针或非指针上被调用,在golang内部是会自动转换的,golang会自动帮忙取地址操作的,之前就一直有这么一句话的,为什么这里这句就会报错呢?
原因就是person{}是不可寻址的
我们先看改进的代码:
package main
import "fmt"
func main() {
p := person{}
//fmt.Println(p.foo()) //这是值类型
fmt.Println(p.fooPoint()) //这是指针类型
//po := &person{}
//fmt.Println(po.foo()) //这是值类型
//fmt.Println(po.fooPoint()) //这是指针类型
}
type person struct {
}
func (p person) foo() string {
return "这是值类型"
}
func (p *person) fooPoint() string {
return "这是指针类型"
}
我们就是定义一个p,然后通过p.fooPoint()来调用,就可以了。说明p这个变量是可以寻址的。
什么叫可寻址
可直接使用 & 操作符取地址的对象,就是可寻址的。比如下面这个例子
name := "yif"
fmt.Println(&name) //0xc00003c1f0 【说明 name 这个变量是可寻址的】
但不能说 "yif" 这个字符串是可寻址的。"yif" 是字符串,字符串都是不可变的,是不可寻址的。
哪些是可以寻址的
变量:&x
name := "yif"
fmt.Println(&name) //0xc00003c1f0
指针:&*x
package main
import (
"fmt"
"unsafe"
)
func main() {
fmt.Println(unsafe.Pointer(&person{"yif"})) //0xc00003c1f0
}
type person struct {
name string
}
数组元素索引: &a[0]
s := [3]int{1,2,3}
fmt.Printf("%p\n",&s[0]) //0xc00000e3c0
切片
fmt.Println([]int{1, 2, 3}[1:]) //[2 3]
切片元素索引:&s[0]
s := []int{1, 2, 3}
fmt.Println(&s[0]) //0xc00000e3c0
组合字面量: &struct{X type}{value}
所有的组合字面量都是不可寻址的,就像下面这样子
func main() {
fmt.Println(&getP()) // cannot take the address of getP()
}
type person struct {
name string
}
func getP() person {
return person{name: "yif"}
}
注意上面写法与这个写法的区别,下面这个写法代表不同意思,其中的 & 并不是取地址的操作,而代表实例化一个结构体的指针
func main() {
fmt.Println(&person{name: "yif"}) // &{yif}
}
type person struct {
name string
}
func getP() person {
return person{name: "yif"}
}
虽然组合字面量是不可寻址的,但却可以对组合字面量的字段属性进行寻址(直接访问)
func main() {
fmt.Println(getP().name) // yif
}
type person struct {
name string
}
func getP() person {
return person{name: "yif"}
}
哪些是不可以寻址
常量
const PI = 3.14
func main() {
fmt.Println(&PI) // cannot take the address of PI
}
字符串
func main() {
fmt.Println(&getStr()) // cannot take the address of getStr()
//fmt.Printf("%p\n",&getStr()) // cannot take the address of getStr()
}
func getStr() string {
return "yif"
}
函数或方法
func main() {
fmt.Println(&getStr) // cannot take the address of getStr
//fmt.Printf("%p\n",&getStr) // cannot take the address of getStr
}
func getStr() string {
return "yif"
}
基本类型字面量
字面量分:基本类型字面量 和 复合型字面量。
基本类型字面量,是一个值的文本表示,都是不应该也是不可以被寻址的。
func getInt() int {
return 1024
}
func main() {
fmt.Println(&getInt()) // cannot take the address of getInt()
}
map 中的元素
字典比较特殊,可以从两个角度来反向推导,假设字典的元素是可寻址的,会出现 什么问题?
-
如果字典的元素不存在,则返回零值,而零值是不可变对象,如果能寻址问题就大了。
-
而如果字典的元素存在,考虑到 Go 中 map 实现中元素的地址是变化的,这意味着寻址的结果也是无意义的。
基于这两点,Map 中的元素不可寻址,符合常理。
func main() {
m := map[string]int{
"yif": 20,
}
//fmt.Println(&m["yif"]) // cannot take the address of m["yif"]
fmt.Printf("%p\n",&m["yif"]) // cannot take the address of m["yif"]
}
搞懂了这点,你应该能够理解下面这段代码为什么会报错啦,map里面不能修改结构体的属性。
func main() {
m := map[int]person{
1: {name: "yif"},
2: {name: "tom"},
}
m[1].name = "James" //cannot assign to struct field m[1].name in map
}
type person struct {
name string
}
如果需要改结构体里面的属性,可以改为如下:
func main() {
m := map[int]*person{ //结构体是指针
1: &person{name: "yif"},
2: &person{name: "tom"},
}
fmt.Println(m[1].name) //yif
m[1].name = "James"
fmt.Println(m[1].name) //James
}
type person struct {
name string
}
数组字面量
数组字面量是不可寻址的,当你对数组字面量进行切片操作,其实就是寻找内部元素的地址,下面这段代码是会报错的。
func main() {
fmt.Println([3]int{1, 2, 3}[1:]) //invalid operation [3]int literal[1:] (slice of unaddressable value)
}
其实基本上要达到寻址,就通过使用一个变量承接一下,比如:
func main() {
arr := [3]int{1, 2, 3}
fmt.Println(arr[1:]) //[2,3]
}
还有之前数组那个例子(第二种写法就不对):
s := [3]int{1,2,3}
fmt.Printf("%p\n",&s[0]) //0xc00000e3c0
fmt.Printf("%p\n",&[3]int{1,2,3}[0]) //cannot take the address of [3]int literal[0]
至于字面量的内容,去看这篇Golang的字面量
参考文章: