钜形不锈钢水箱 发表于 2024-1-18 07:24:40

go数据类型-空结构体、空接口、nil

空结构体

func main() {
        a := struct{}{}
        fmt.Println(unsafe.Sizeof(a))
        fmt.Printf("%p\n", &a)
}
打印
0
0x117f4e0有经验的开发人员都知道,所有的空结构体是指向一个 zerobase的地址,而且大小为0
一般用来作结合map作为set 或者 在channel中 传递信号。
type void struct{}

type void1 struct {
        a void
}

type void2 struct {
        a void
        b int
}

func main() {
        a0 := void{}
        a1 := void1{}
        a2 := void2{}
        fmt.Println(unsafe.Sizeof(a0))
        fmt.Println(unsafe.Sizeof(a1))
        fmt.Println(unsafe.Sizeof(a2))
        fmt.Printf("void: %p\n", &a0)
        fmt.Printf("void1:%p\n", &a1)
        fmt.Printf("void2: %p\n", &a2)
}
打印:
0
0
8
void: 0x11804e0 zerobase的地址,不是固定,每次运行都会有偏移量
void1:0x11804e0
void2: 0xc00010c008能看到当一个空结构体中,包含了其他类型的变量,就不指向 zerobase。
runtime的malloc.go中
// base address for all 0-byte allocations
var zerobase uintptr接口

go中的接口都是隐式的,增加的封装的灵活性,也为阅读源码增加了一些难度。
正常使用: 情况1
type Person interface {
        eat()
}

type Man struct {
        name string
}

func (m Man) eat() {
        fmt.Println(" man eat")
}

func main() {

        var p Person = Man{}
        p.eat()
}情况2:
func main() {
    // 变成指针 也是正常的
        var p Person = &Man{}
        p.eat()
}情况3:
// 指针实现
    func (m *Man) eat() {
            fmt.Println(" man eat")
    }

    func main() {

            var p Person = &Man{}
            p.eat()
    }
也正常情况4:
// 指针
func (m *Man) eat() {
        fmt.Println(" man eat")
}

func main() {
    // 未加指针
        var p Person = Man{}
        p.eat()
}报错: 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失败。
接口的定义

这个数据结构,就是上面例子中变量 p 底层结构。

接口的内部实现:
type iface struct {
        tab*itab
        data unsafe.Pointer // 具体实现接口的对象, 就是例子中的 Man结构体的实例
}

type itab struct {
        inter *interfacetype    // 接口自身定义的类型信息,用于定位到具体interface类型
        _type *_type
        hashuint32 // _type.hash的拷贝,用于快速查询和判断目标类型和接口中类型是一致
        _   byte
        fun   uintptr // 实现那些接口方法
}整理下:接口值的底层表示
接口数据使用 runtime.iface 表示
iface记录了数据的地址
iface 中记录了接口类型信息和实现的方法 , 在接口断言时候,用到这些信息。
空接口

空接口的底层实现:
type eface struct {
        _type *_type // 只记录数据类型,因为没有方法,所以不用像iface一样,记录接口方法信息
        dataunsafe.Pointer // 指向数据本身
}空接口常用来作为 任意类型的形参 使用。
例如:
type any = interface{}
   func Println(a ...any) (n int, err error) {
        return Fprintln(os.Stdout, a...)
}为什么 空接口可以作为任意类型使用?
基于它的底层实现定义:任意的类型,都可以表示为 数据类型 和 数据本身,例如 int 5 ,类型int,数据5
例如在我们使用 fmt.Println(5) 时候,会先将 5 进行组装:
    伪代码:
    a := eface{type : int ,data : 5}
    fmt.Println(a)整理:空接口的用途
空接口的最大用途是作为任意类型的函数入参
函数调用时,会新生成一个空接口,再传参nil

定义:

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

[*]nil是多个类型的零值,或者空值

[*]空结构体的指针和值都不是nil。 指针是zerobase
3.空接口零值是nil,-旦有了类型信息就不是nil

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: go数据类型-空结构体、空接口、nil