go一些重要知识点

 nil切片和空切片

import (

        "fmt"

        "reflect"

        "unsafe"

    )

func main() {


    var s1 []int   // nil切片

    s2 := make([]int,0)  // 空切片

   s4 := make([]int,0)   // 空切片

   // (*reflect.SliceHeader)(unsafe.Pointer(&s1)) 将s1,s2,s3 都转换为reflect.SliceHeader类型

   fmt.Printf("s1 pointer:%+v, s2 pointer:%+v, s4 pointer:%+v, \n",

              *(*reflect.SliceHeader)(unsafe.Pointer(&s1)),

              *(*reflect.SliceHeader)(unsafe.Pointer(&s2)),

              *(*reflect.SliceHeader)(unsafe.Pointer(&s4)))

   fmt.Printf("%v\n",

              (*(*reflect.SliceHeader)(unsafe.Pointer(&s1))).Data==

              (*(*reflect.SliceHeader)(unsafe.Pointer(&s2))).Data)

   fmt.Printf("%v\n",

              (*(*reflect.SliceHeader)(unsafe.Pointer(&s2))).Data==

              (*(*reflect.SliceHeader)(unsafe.Pointer(&s4))).Data)

}

reflect.SliceHeaderGo语言slice的本质-SliceHeader_飞雪无情的博客-CSDN博客

nil切片和空切片最大的区别在于指向的数组引用地址是不一样的

所有的空切片指向的数组引用地址都是一样的

三种指针类型

*T:普通类型指针类型,用于传递对象地址,不能进行指针运算。

unsafe.poniter:通用指针类型,用于转换不同类型的指针,不能进行指针运算,不能读取内存存储的值(需转换到某一类型的普通指针)

uintptr:用于指针运算,GC不把uintptr当指针,uintptr无法持有对象。uintptr类型的目标会被回收。

uintptr和unsafe.Pointer的区别

unsafe.Pointer 可以和 普通指针 进行相互转换;

unsafe.Pointer 可以和 uintptr 进行相互转换。

import (

    "fmt"

    "unsafe"

)

type W struct{

    b int32

    c int64

}

func main(){

    var w *W = new(W)


    // 这时w的变量打印出来的都是默认值0,0

    fmt.Println(w.b,w.c)


    // 下面走啊网盘,二命通过指针运算给b变量赋值为10

    b := unsafe.Pointer(uintptr(unsafe.Pointer(w)) + unsafe.Offsetof(w,b))*((*int)(b)) == 10

    // 此时结果就变成了10,0

    fmt.Println(w.b,w.c)


}

uintptr(unsafe.Pointer(w))获取了w指针的起始值

unsafe.Offsetof(w.b)获取了b变量的偏移量

两个相加就得到了b的地址值,将通用指针pointer转换成具体指针((*int)(b)),通过 * 符取值,然后赋值 ` * (( *int)(b))相当于把 ( *int)(b) 转换成 int了,最后对变量重新赋值成10,这样指针运算就完成了

Switch type

如果switch type的case后面只有一个类型T1,那么msg对应的类型就是这个类型T1。 如果switch type的case后面有多个类型(T2,T3),那么msg对应的类型就是interface。

type base interface{ F() }

type student struct{ Name string }

func (s *student) F() {}

type class struct{ Name string }

func (c *class) F() {}

type teacher struct{ Name string }

func (t *teacher) F() {}

func isType(v interface{}) {

    switch msg := v.(type) {

    case student, teacher:

        fmt.Println(msg.Name) //这里会报错,因为这个msg是interface类型,没有Name属性

    case class:

        fmt.Println(msg.Name) //这里不会报错,因为这个msg是class类型,有Name属性

    }

}

Map的实现原理

map是一种key-value键值对的存储结构,其中key是不能重复的,其底层实现采用的是hash表。

当我们用dataMap := make(map[int]string)创建一个 map 对象的时候,得到的是一个 hmap 指针结构。

type hmap struct {

       count     int // 当前的元素个数

       flags     uint8

       B         uint8  // 字段 buckets 数组的长度关联参数,即 buckets 长度为 2^B

       noverflow uint16 // overflow buckets 的近似数

       hash0     uint32 // 哈希种子,作 hash 运算用的

       buckets    unsafe.Pointer // 数组对象,存 key-value的

       oldbuckets unsafe.Pointer // 扩容时用到的 buckets,大小是之前 buckets 的一半。

       nevacuate  uintptr        // 扩容进度

       extra *mapextra // optional fields

}

在这里面,有一个非常关键的字段:buckets,它就是用来存储 key-value 的。

当 Go 对 key 进行 hash 运算后,会通过一定的规则映射到buckets的某个位置,然后就会将 key、value 一起存在这个对应位置的桶里。

桶就会指向这个结构体

type bmap struct {

       tophash [bucketCnt]uint8

}

之所以这么简洁,是因为map 是在编译时才知道对应的 key-value 类型的,所以map在编译过后bmap就会变成

type bmap struct {

    tophash  [bucketCnt]uint8

    keys     [bucketCnt]string

    values   [bucketCnt]int

    overflow *bmap

}

在这里,就有一个 keys,values 数组来存储 key-value 了,其中还有个 tophash 数组,可以认为它是用来定位 key-value 在对应数组里的存储位置

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容