美文网首页
golang--数据类型

golang--数据类型

作者: 风再起时ME | 来源:发表于2018-11-04 22:55 被阅读18次

    A 基本数据类型

    1 布尔类型 bool

    1)Go 对于值之间的比较有非常严格的限制,只有两个类型相同的值才可以进行比较,如果值的类型是接口(interface),它们也必须都实现了相同的接口。如果其中一个值是常量,那么另外一个值的类型必须和该常量类型相兼容的。
    2)对于布尔值的好的命名能够很好地提升代码的可读性,例如以 is 或者 Is 开头的isSorted 、 isFinished 、 isVisivle 。
    3)在格式化输出时,你可以使用 %t 来表示你要输出的值为布尔型。
    4)布尔型的值只可以是常量 true 或者 false。

    2 数字类型

    ##有符号整数
    int8(-128 -> 127)
    int16(-32768 -> 32767)
    int32(-2,147,483,648 -> 2,147,483,647)
    int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
    int( 32 位操作系统上32 位,64 位操作系统64 位)
    
    ##无符号整数
    uint8(0 -> 255)
    uint16(0 -> 65,535)
    uint32(0 -> 4,294,967,295)
    uint64(0 -> 18,446,744,073,709,551,615)
    uint ( 32 位操作系统上32 位,64 位操作系统64 位)
    
    ##浮点型
    float32(+- 1e-45 -> +- 3.4 * 1e38)
    float64(+- 5 1e-324 -> 107 1e308)
    #无float类型
    
    #复数
    Go 拥有以下复数类型:
    complex64 (32 位实数和虚数)
    complex128 (64 位实数和虚数)
    
    复数使用 re+imI 来表示,其中 re 代表实数部分, im 代表虚数部分,I 代表根号负 1
    var c1 complex64 = 5 + 10i
    fmt.Printf("The value is: %v", c1)  // 输出: 5 + 10i
    

    1)整型的零值为 0,浮点型的零值为 0.0。
    2)int 和 uint 在 32 位操作系统上,它们均使用 32 位(4 个字节) ,在 64 位操作系统上,它们均使用 64 位(8 个字节)。
    3)uintptr 的长度被设定为足够存放一个指针即可。
    4)整型的零值为 0,浮点型的零值为 0.0。
    5)int 型是计算最快的一种类型。

    1. 尽可能地使用 float64,因为 math 包中所有有关数学运算的函数都会要求接收这个类型。
    2. Go 是强类型语言,不会进行隐式转换.。所以Go 中不允许不同类型之间的混合使用,但允许常量之间的混合使用。
    package main
    func main() {
      var a int
      var b int32
      a = 15
      b = a + a // 编译错误
      b = b + 5 // 因为 5 是常量,所以可以通过编译
    }
    

    3 字符类型

    字符不是 Go 语言的一个类型,字符只是整数的特殊用例:
    1)byte 类型是 uint8的别名:下面三个表达式等价:

    var ch byte = 'A' 
    var ch byte = 65
    var ch byte = '\x41'  // \x 总是紧跟着长度为 2 的 16 进制数
    

    2) rune 是 int32 的别名。
    Unicode 至少占用 2 个字节,所以GO使用 int16 或者 int 类型来表示。如果需要使用到 4 字节,则会加上 \U 前缀;前缀 \u 则总是紧跟着长度为 4 的 16 进制数,前缀 \U紧跟着长度为 8 的 16 进制数。

    var ch int = '\u0041'
    var ch2 int = '\u03B2'
    var ch3 int = '\U00101234'
    fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer
    fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character
    fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes
    fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point
    

    B 字符串

    // $GOROOT/src/pkg/runtime/runtime.h
    struct String
    {
        byte*   str;
        intgo   len;
    };
    

    1)字符串是 UTF-8 字符的一个序列(当字符为 ASCII 码时则占用 1 个字节,其它字符根据需要占用 2-4 个字节)
    2)字符串在Go语言内存模型中用一个2字长的数据结构表示。它包含一个指向字符串存储数据的指针和一个长度数据。


    string.png

    3)字符串是一种值类型,且值不可变。所以多字符串共享同一个存储数据是安全的。
    4)切分操作 str[i:j] 会得到一个新的2字长结构,一个可能不同的但仍指向同一个字节序列(即上文说的存储数据)的指针和长度数据。字符串切分不涉及内存分配。
    5)string 类型的零值为ptr为nil,len为0的字符串,即空字符串 ""
    6)获取字符串中某个字节的地址的行为是非法的,例如: &str[i]

    C slice

    // $GOROOT/src/pkg/runtime/runtime.h
    struct  Slice
    {               // must not move anything
        byte*   array;      // actual data
        uintgo  len;        // number of elements
        uintgo  cap;        // allocated number of elements
    };
    
    slice.png

    1)一个slice是一个数组某个部分的引用。在内存中,它是一个包含3个域的结构体:指向slice中第一个元素的指针,slice的长度,以及slice的容量。
    2)切片 s 来说该不等式永远成立: 0 <= len(s)<= cap(s)
    3)在对slice进行append等操作时,可能会造成slice的自动扩容:
    首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)
    否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap)
    否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的 1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)

    func growslice(et *_type, old slice, cap int) slice {
        if raceenabled {
            callerpc := getcallerpc(unsafe.Pointer(&et))
            racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
        }
        if msanenabled {
            msanread(old.array, uintptr(old.len*int(et.size)))
        }
    
        if et.size == 0 {
            // 如果新要扩容的容量比原来的容量还要小,这代表要缩容了,那么可以直接报panic了。
            if cap < old.cap {
                panic(errorString("growslice: cap out of range"))
            }
    
            // 如果当前切片的大小为0,还调用了扩容方法,那么就新生成一个新的容量的切片返回。
            return slice{unsafe.Pointer(&zerobase), old.len, cap}
        }
    
        // 这里就是扩容的策略
        newcap := old.cap
        doublecap := newcap + newcap
        if cap > doublecap {
            newcap = cap
        } else {
            if old.len < 1024 {
                newcap = doublecap
            } else {
                // Check 0 < newcap to detect overflow
                // and prevent an infinite loop.
                for 0 < newcap && newcap < cap {
                    newcap += newcap / 4
                }
                // Set newcap to the requested cap when
                // the newcap calculation overflowed.
                if newcap <= 0 {
                    newcap = cap
                }
            }
        }
    
        // 计算新的切片的容量,长度。
        var lenmem, newlenmem, capmem uintptr
        const ptrSize = unsafe.Sizeof((*byte)(nil))
        switch et.size {
        case 1:
            lenmem = uintptr(old.len)
            newlenmem = uintptr(cap)
            capmem = roundupsize(uintptr(newcap))
            newcap = int(capmem)
        case ptrSize:
            lenmem = uintptr(old.len) * ptrSize
            newlenmem = uintptr(cap) * ptrSize
            capmem = roundupsize(uintptr(newcap) * ptrSize)
            newcap = int(capmem / ptrSize)
        default:
            lenmem = uintptr(old.len) * et.size
            newlenmem = uintptr(cap) * et.size
            capmem = roundupsize(uintptr(newcap) * et.size)
            newcap = int(capmem / et.size)
        }
    
        // 判断非法的值,保证容量是在增加,并且容量不超过最大容量
        if cap < old.cap || uintptr(newcap) > maxSliceCap(et.size) {
            panic(errorString("growslice: cap out of range"))
        }
    
        var p unsafe.Pointer
        if et.kind&kindNoPointers != 0 {
            // 在老的切片后面继续扩充容量
            p = mallocgc(capmem, nil, false)
            // 将 lenmem 这个多个 bytes 从 old.array地址 拷贝到 p 的地址处
            memmove(p, old.array, lenmem)
            // 先将 P 地址加上新的容量得到新切片容量的地址,然后将新切片容量地址后面的 capmem-newlenmem 个 bytes 这块内存初始化。为之后继续 append() 操作腾出空间。
            memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
        } else {
            // 重新申请新的数组给新切片
            // 重新申请 capmen 这个大的内存地址,并且初始化为0值
            p = mallocgc(capmem, et, true)
            if !writeBarrier.enabled {
                // 如果还不能打开写锁,那么只能把 lenmem 大小的 bytes 字节从 old.array 拷贝到 p 的地址处
                memmove(p, old.array, lenmem)
            } else {
                // 循环拷贝老的切片的值
                for i := uintptr(0); i < lenmem; i += et.size {
                    typedmemmove(et, add(p, i), add(old.array, i))
                }
            }
        }
        // 返回最终新切片,容量更新为最新扩容之后的容量
        return slice{p, old.len, newcap}
    }
    

    make和new

    Go有两个数据结构创建函数:new和make。
    new返回一个指向已清零内存的指针,而make返回一个复杂的结构。
    基本的区别是 new(T) 返回一个 *T ,返回的这个指针可以被隐式地消除引用(图中的黑色箭头) 。而 make(T, args) 返回一个普通的T.


    new_make.png

    注意:空切片和 nil 切片是不同的,空切片指向的地址不是nil,指向的是一个内存地址,但是它没有分配任何内存空间,即底层元素包含0个元素。
    不管是使用 nil 切片还是空切片,对其调用内置函数 append,len 和 cap 的效果都是一样的。

    D map

    map在底层是用哈希表实现的。

    struct Hmap
    {
    uint8 B; // 可以容纳2^B个项
    uint16 bucketsize; // 每个桶的大小
    byte *buckets; // 2^B个Buckets的数组
    byte *oldbuckets; // 前一个buckets,只有当正在扩容时才不为空
    };
    
    struct Bucket
    {
    uint8 tophash[BUCKETSIZE]; // hash值的高8位....低位从bucket的array定位到bucket
    Bucket *overflow; // 溢出桶链表,如果有
    byte data[1]; // BUCKETSIZE keys followed by BUCKETSIZE values
    };
    // BUCKETSIZE是用宏定义的8,每个bucket中存放最多8个key/value对, 如果多于8个,那么会申请一个新的bucket,overflow指向它
    

    1)Bucket中key/value的放置顺序,是将keys放在一起,values放在一起。
    2)扩容使用的是增量扩容:扩容会建立一个大小是原来2倍的新的表,将旧的bucket搬到新的表中之后,并不会将旧的bucket从oldbucket中删除,而是加上一个已删除的标记。当hash表扩容之后,需要将那些旧的pair重新哈希到新的table上,这个工作是逐步的完成(在insert和remove时每次搬移1-2个pair)

    查找过程

    1. 根据key计算出hash值。
    2. 如果存在old table, 首先在old table中查找,如果找到的bucket已经evacuated,转到步骤3。 反之,返回其对应的value。
    3. 在new table中查找对应的value。

    插入过程分析

    1. 根据key算出hash值,进而得出对应的bucket。
    2. 如果bucket在old table中,将其重新散列到new table中。
    3. 在bucket中,查找空闲的位置,如果已经存在需要插入的key,更新其对应的value。
    4. 根据table中元素的个数,判断是否grow table。
    5. 如果对应的bucket已经full,重新申请新的bucket作为overbucket。
    6. 将key/value pair插入到bucket中。

    F nil

    Go任何类型在未初始化时都对应一个零值:布尔类型是false,整型是0,字符串是"",而指针,函数,interface,slice,channel和map的零值都是nil。

    相关文章

      网友评论

          本文标题:golang--数据类型

          本文链接:https://www.haomeiwen.com/subject/dxarxqtx.html