ToB企服应用市场:ToB评测及商务社交产业平台

标题: go数据类型-空结构体、空接口、nil [打印本页]

作者: 钜形不锈钢水箱    时间: 2024-1-18 07:24
标题: go数据类型-空结构体、空接口、nil
空结构体
  1.   func main() {
  2.           a := struct{}{}
  3.           fmt.Println(unsafe.Sizeof(a))
  4.           fmt.Printf("%p\n", &a)
  5.   }
  6.   打印
  7.   0
  8.   0x117f4e0
复制代码
有经验的开发人员都知道,所有的空结构体是指向一个 zerobase的地址,而且大小为0
一般用来作结合map作为set 或者 在channel中 传递信号。
  1. type void struct{}
  2. type void1 struct {
  3.         a void
  4. }
  5. type void2 struct {
  6.         a void
  7.         b int
  8. }
  9. func main() {
  10.         a0 := void{}
  11.         a1 := void1{}
  12.         a2 := void2{}
  13.         fmt.Println(unsafe.Sizeof(a0))
  14.         fmt.Println(unsafe.Sizeof(a1))
  15.         fmt.Println(unsafe.Sizeof(a2))
  16.         fmt.Printf("void: %p\n", &a0)
  17.         fmt.Printf("void1:%p\n", &a1)
  18.         fmt.Printf("void2: %p\n", &a2)
  19. }
  20. 打印:
  21. 0
  22. 0
  23. 8
  24. void: 0x11804e0 zerobase的地址,不是固定,每次运行都会有偏移量
  25. void1:0x11804e0
  26. void2: 0xc00010c008
复制代码
能看到当一个空结构体中,包含了其他类型的变量,就不指向 zerobase。
  1. runtime的malloc.go中
  2. // base address for all 0-byte allocations
  3. var zerobase uintptr
复制代码
接口

go中的接口都是隐式的,增加的封装的灵活性,也为阅读源码增加了一些难度。
  1. 正常使用: 情况1
  2. type Person interface {
  3.         eat()
  4. }
  5. type Man struct {
  6.         name string
  7. }
  8. func (m Man) eat() {
  9.         fmt.Println(" man eat")
  10. }
  11. func main() {
  12.         var p Person = Man{}
  13.         p.eat()
  14. }
复制代码
情况2:
  1. func main() {
  2.     // 变成指针 也是正常的
  3.         var p Person = &Man{}
  4.         p.eat()
  5. }
复制代码
情况3:
  1.   // 指针实现
  2.     func (m *Man) eat() {
  3.             fmt.Println(" man eat")
  4.     }
  5.     func main() {
  6.             var p Person = &Man{}
  7.             p.eat()
  8.     }
  9. 也正常
复制代码
情况4:
  1. // 指针
  2. func (m *Man) eat() {
  3.         fmt.Println(" man eat")
  4. }
  5. func main() {
  6.     // 未加指针
  7.         var p Person = Man{}
  8.         p.eat()
  9. }
复制代码
报错: cannot use Man{} (value of type Man) as Person value in variable declaration: Man does not implement Person (method eat has pointer receiver) (typecheck)
Man结构未实现,person的方法 eat这个。
网上很多人有讲过这个,这里换个角度归纳下:
只有一种情况下是失败的:当实现接口方法时候,采用指针,用的使用 未采用指针。
原理:在使用 func (m Man) eat() 实现接口时候,编译器会自动加上 带指针的实现 func (m  *Man) eat(),反之,不会。所以才会导致情况4失败。
接口的定义
  1. 这个数据结构,就是上面例子中变量 p 底层结构。
  2. 接口的内部实现:
  3.   type iface struct {
  4.           tab  *itab
  5.           data unsafe.Pointer // 具体实现接口的对象, 就是例子中的 Man结构体的实例
  6.   }
  7.   type itab struct {
  8.           inter *interfacetype    // 接口自身定义的类型信息,用于定位到具体interface类型
  9.           _type *_type
  10.           hash  uint32 // _type.hash的拷贝,用于快速查询和判断目标类型和接口中类型是一致
  11.           _     [4]byte
  12.           fun   [1]uintptr // 实现那些接口方法
  13. }
复制代码
整理下:接口值的底层表示
接口数据使用 runtime.iface 表示
iface记录了数据的地址
iface 中记录了接口类型信息和实现的方法 , 在接口断言时候,用到这些信息。
空接口
  1. 空接口的底层实现:
  2.   type eface struct {
  3.           _type *_type // 只记录数据类型,因为没有方法,所以不用像iface一样,记录接口方法信息
  4.           data  unsafe.Pointer // 指向数据本身
  5.   }
复制代码
空接口常用来作为 任意类型的形参 使用。
  1.   例如:
  2.   type any = interface{}
  3.    func Println(a ...any) (n int, err error) {
  4.           return Fprintln(os.Stdout, a...)
  5.   }
复制代码
为什么 空接口可以作为任意类型使用?
基于它的底层实现定义:任意的类型,都可以表示为 数据类型 和 数据本身,例如 int 5 ,类型int,数据5
例如在我们使用 fmt.Println(5) 时候,会先将 5 进行组装:
  1.     伪代码:
  2.     a := eface{type : int ,data : 5}
  3.     fmt.Println(a)
复制代码
整理:空接口的用途
  1. 空接口的最大用途是作为任意类型的函数入参
  2. 函数调用时,会新生成一个空接口,再传参
复制代码
nil

定义:
  1. var nil Type // Type must be a pointer, channel, func, interface, map, or slice type
复制代码
也就是说nil只能表示 指针、channel、func、interface、map 、slice 这六种类型的空值。
注意这里没有 struct
  1. 空结构体是zerobase的空值,不是nil
  2. var a *int
  3. var b map[string]string
  4. var c struct{}
  5. fmt.Println(a == nil) // true
  6. fmt.Println(b == nil) // true
  7. fmt.Println(c == nil) // mismatched types struct{} and untyped nil
  8. fmt.Println(a == b) // mismatched types *int and map[string]string
复制代码
都是nil,a和b的值也不同。
有了上面空接口的基础就好理解了, 一个数据,包含了数据类型和数据本身的值。这里a和b都是nil,只是值为nil,但是它们的类型并不一样,所以不等
小结:
  1. nil 是空,并不一定是“空指针”
  2. nil是6种类型的 “零值〞
  3. 每种类型的nil是不同的,无法比较
复制代码
再一个例子:
  1. var a *int
  2. var b interface{}
  3. fmt.Println(a == nil) // true
  4. fmt.Println(b == nil) // true
  5. b = a
  6. fmt.Println(b == nil) // false
复制代码
回忆下上面 nil的定义,可以表示 interface的空值,但是,通过上面的了解,interface底层实际上是一个结构体eface。
nil能作为eface的值,有严格的要求,要求type 和 data 都为空
当把 b = a时候,这时候 type 已经有值,data还为空,但是这个时候 eface 已经是一个结构体了。nil 不能表示 结构体的值,而且这个结构体中成员还不为空。
总结:
3.空接口零值是nil,-旦有了类型信息就不是nil

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4