《Go语言实战》笔记之第四章 ----数组、切片、映射

原文地址:算法

http://www.niu12.com/article/11数组

####数组    数组是一个长度固定的数据类型,用于存储一段具备相同的类型的元素的连续块。    数组存储的类型能够是内置类型,如整型或者字符串,也能够是某种结构类型,    其占用的内存是连续分配的.    因为内存连续,CPU能把正在使用的数据缓存更久的时间。     并且内存连续很容易计算索引, 能够快速迭代数组里的全部元素。        声明:    两个要素:长度和类型        声明数组时须要指定内部存储的数据的类型, 以及须要存储的元素的数量,     这个数量也称为数组的长度        // 声明一个包含 5 个元素的整型数组     var array [5]int        一旦声明,数组里存储的数据类型和数组长度就都不能改变,元素为零值    若是须要存储更多的元素,    就须要先建立一个更长的数组,再把原来数组里的值复制到新数组里        // 声明一个包含 5 个元素的整型数组并初始化    array := [5]int{10, 20, 30, 40, 50}             ...可替代长度,Go 语言会根据初始化时数组元素的数量来肯定该数组的长度    // 容量由初始化值的数量决定     array := [...]int{10, 20, 30, 40, 50}         // 声明一个有 5 个元素的数组     // 用具体值初始化索引为 1 和 2 的元素     // 其他元素保持零值     array := [5]int{1: 10, 2: 20}    array[3] = 30    array值为: [0, 10, 20, 30 0]            指针数组:全部元素都是指针的数组    (指向整型的指针叫整型指针)    (指向字符串的指针叫字符串指针)    (......)    声明包含 5 个元素的指向整数的数组     // 用整型指针初始化索引为 0 和 1 的数组元素     array := [5]*int{0: new(int), 1: new(int)}     // 为索引为 0 和 1 的元素赋值     *array[0] = 10    *array[1] = 20    array值为: [0xc0420080a8 0xc0420080c0 <nil> <nil> <nil>]    <code>        array := [5]*int{0: new(int), 1: new(int)}        // 为索引为 0 和 1 的元素赋值        *array[0] = 10        *array[1] = 20        for _, p := range array {            if p != nil {                fmt.Println(*p)            } else {                fmt.Println(p)            }        }        // 输出 10 20 nil nil nil    </code>        数组的比较:    数组变量的类型包括 数组长度 和每一个元素的 类型 。     只有这两部分都相同的数组, 才是类型相同的数组,才能互相赋值        var array1 [5]string     array2 := [5]string{"Red", "Blue", "Green", "Yellow", "Pink"}     array1 = array2 // ok        var array3 [5]*string    array3 = array2 // error        在函数间传递数组:        根据内存和性能来看,在函数间传递数组是一个开销很大的操做。    在函数之间传递变量时,老是以值的方式传递的。    若是这个变量是一个数组,意味整个数组,无论有多长,都会完整复制,并传递给函数        最佳实践:传递数组的指针,这个操做会更有效地利用内存,性能也更好。    要意识到,由于如今传递的是指针,因此若是改变指针指向的值,会改变共享的内存    ####切片slice       切片是动态数组,能够按需自动增加和缩小。    切片的动态增加是经过内置函数 append 来实现的。    这个函数能够快速且高效地增加切片。     还能够经过对切片再次切片来缩小一个切片的大小。     由于切片的底层内存也是在连续块中分配的,    因此切片还能得到索引、迭代以及为垃圾回收优化的好处。        声明:    两个必选要素: 类型与长度    一个可选要素: 容量        // 建立一个字符串切片     // 其长度和容量都是 5 个元素     slice := make([]string, 5)         分别指定长度和容量时,建立的切片,底层数组的长度是指定的容量,    可是初始化后并不能访问全部的数组元素            // 建立一个整型切片     // 其长度为 3 个元素,容量为 5 个元素     slice := make([]int, 3, 5)    for k := range slice {        fmt.Println(k) // 0 1 2    }    能够访问 3 个元素,而底层数组拥有 5 个元素。    剩余的 2 个元素能够在后期操做中合并到切片,能够经过切片访问这些元素    若是基于这个切片建立新的切片,新切片会和原有切片共享底层数组        len(array) <= cap(array)        使用切片字面量建立切片,同数组,只是不须要规定长度:    初始的长度和容量会基于初始化时提供的元素的个数肯定        // 建立字符串切片     // 其长度和容量都是 5 个元素     slice := []string{"Red", "Blue", "Green", "Yellow", "Pink"}     // 建立一个整型切片     // 其长度和容量都是 3 个元素     slice := []int{10, 20, 30}         // 设置初始长度和容量    // 建立字符串切片     // 使用空字符串初始化第 100 个元素     slice := []string{99: ""}         切片与数组的区别:    // 建立有 3 个元素的整型数组     array := [3]int{10, 20, 30}     // 建立长度和容量都是 3 的整型切片     slice := []int{10, 20, 30}         // 建立 nil 整型切片    var slice []int    // true    fmt.Println(slice == nil)    // 使用 make 建立空的整型切片    slice2 := make([]int, 0)    // false    fmt.Println(slice2 == nil)    // 使用切片字面量建立空的整型切片    slice3 := []int{}    // false    fmt.Println(slice3 == nil)        切片赋值:    // 建立一个整型切片     // 其容量和长度都是 5 个元素     slice := []int{10, 20, 30, 40, 50}     // 改变索引为 1 的元素的值     slice[1] = 25         使用切片建立切片:        // 建立一个整型切片     // 其长度和容量都是 5 个元素     slice := []int{10, 20, 30, 40, 50}     // 建立一个新切片     // 其长度为 2 个元素,容量为 4 个元素  [i:j]包i不包j    newSlice := slice[1:3]    fmt.println(newSlice) // [20, 30]        第一个切片 slice 可以看到底层数组所有 5 个元素的容量,    不过以后的 newSlice 就看不到。    对于 newSlice ,底层数组的容量只有 4 个元素。    newSlice 没法访问到它所指向的底层数组的第一个元素以前的部分。    因此,对 newSlice 来讲,以前的那些元素就是不存在的。            如今两个切片共享同一个底层数组。    若是一个切片修改了该底层数组的共享部分,另外一个切片也能感知到        newSlice[0] = 1    fmt.Println(slice, newSlice) // [10 1 30 40 50] [1 30]        对底层数组容量是 k 的切片 slice[i:j]来讲     长度: j - i     容量: k - i     对于 slice[i:j:k] 或 [2:3:4]     长度: j – i 或 3 - 2 = 1     容量: k – i 或 4 - 2 = 2    若是k - i大于可用容量,error: slice bounds out of range        切片只能访问到其长度内的元素。    试图访问超出其长度的元素将会致使语言运行时异常。    与切片的容量相关联的元素只能用于增加切片        切片增加:    用 append,须要一个被操做的切片和一个要追加的值    当    append 调用返回时,会返回一个包含修改结果的新切片。    函数 append 老是会增长新切片的长度,而容量有可能会改变,    也可能不会改变,这取决于被操做的切片的可用容量            // 建立一个整型切片     // 其长度和容量都是 5 个元素     slice := []int{10, 20, 30, 40, 50}     // 建立一个新切片     // 其长度为 2 个元素,容量为 4 个元素     newSlice := slice[1:3]     // 使用原有的容量来分配一个新元素     // 将新元素赋值为 60     newSlice = append(newSlice, 60)     // [10 20 30 60 50] [20 30 60]    fmt.Println(slice, newSlice)    由于 newSlice 在底层数组里还有额外的容量可用,    append 操做将可用的元素合并到切片的长度,    并对其进行赋值。因为和原始的 slice 共享同一个底层数组,    slice 中索引为 3 的元素的值也被改动了。        newSlice = append(newSlice, 60)    newSlice = append(newSlice, 60)    // 4    fmt.Println(cap(newSlice))    newSlice = append(newSlice, 60)    // 8    fmt.Println(cap(newSlice))    // [10 20 30 60 60] [20 30 60 60 60]    fmt.Println(slice, newSlice)    若是切片的底层数组没有足够的可用容量,    append 函数会建立一个新的底层数组,    将被引用的现有的值复制到新数组里,再追加新的值        函数 append 会智能地处理底层数组的容量增加。在切片的容量小于 1000 个元素时,老是    会成倍地增长容量。一旦元素个数超过 1000,容量的增加因子会设为 1.25,也就是会每次增    加 25%的容量。随着语言的演化,这种增加算法可能会有所改变。            内置函数 append 会首先使用可用容量。一旦没有可用容量,会分配一个    新的底层数组。这致使很容易忘记切片间正在共享同一个底层数组。    一旦发生这种状况,对切片进行修改,极可能会致使随机且奇怪的问题。    对切片内容的修改会影响多个切片,却很难找到问题的缘由。     若是在建立切片时设置切片的容量和长度同样,    就能够强制让新切片的第一个 append 操做建立新的底层数组,    与原有的底层数组分离。    新切片与原有的底层数组分离后,能够安全地进行后续修改        内置函数 append 也是一个可变参数的函数。    这意味着能够在一次调用传递多个追加的值。    若是使用...运算符,能够将一个切片的全部元素追加到另外一个切片里    // 建立两个切片,并分别用两个整数进行初始化    s1 := []int{1, 2}    s2 := []int{3, 4}    // 将两个切片追加在一块儿,并显示结果 [1 2 3 4]    fmt.Printf("%v\n", append(s1, s2...))        关键字 range配合关键字 for 来迭代切片里的元素    当迭代切片时,关键字 range 会返回两个值。    第一个值是当前迭代到的索引位置,    第二个值是该位置对应元素值的一份副本而不是直接返回对该元素的引用    可使用空白标识符来忽略值        有两个特殊的内置函数 len 和 cap,能够用于处理数组、切片和通道        函数传递切片:    在函数间传递切片就是要在函数间以值的方式传递切片。    因为切片的尺寸很小,在函数间复制和传递切片成本也很低    在 64 位架构的机器上,一个切片须要 24 字节的内存:    指针字段须要 8 字节,长度和容量字段分别须要 8 字节    因为与切片关联的数据包含在底层数组里,不属于切片自己,    因此将切片复制到任意函数的时候,对底层数组大小都不会有影响。    复制时只会复制切片自己,不会涉及底层数组    ####映射    映射是一种数据结构,用于存储一系列无序的键值对。     映射里基于键来存储值,映射功能强大的地方是,可以基于键快速检索数据。    映射的实现使用了散列表,因此映射是无序的集合        映射的散列表包含一组桶。在存储、删除或者查找键值对的时候,    全部操做都要先选择一个桶。把操做映射时指定的键传给映射的散列函数,    就能选中对应的桶。这个散列函数的目的是生成一个索引,    这个索引最终将键值对分布到全部可用的桶里。    随着映射存储的增长,索引分布越均匀,访问键值对的速度就越快    映射经过合理数量的桶来平衡键值对的分布。        / 建立一个映射,键的类型是 string,值的类型是 int     dict := make(map[string]int)     // 建立一个映射,键和值的类型都是 string     // 使用两个键值对初始化映射     dict := map[string]string{"Red": "#da1337", "Orange": "#e95a22"}        映射的键能够是任何值。这个值的类型能够是内置的类型,也能够是结构类型,    只要这个值可使用==运算符作比较        映射赋值:    // 建立一个空映射,用来存储颜色以及颜色对应的十六进制代码     colors := map[string]string{}     // 将 Red 的代码加入到映射     colors["Red"] = "#da1337"         从映射获取值并判断键是否存在     // 获取键 Blue 对应的值     value, exists := colors["Blue"]     经过键来索引映射时,即使这个键不存在也总会返回一个值。    在这种状况下,返回的是该值对应的类型的零值。        迭代映射里的全部值和迭代数组或切片同样,使用关键字 range        若是想把一个键值对从映射里删除,就使用内置的 delete 函也就是会每次增数        当传递映射给一个函数,并对这个映射作了修改时,    全部对这个映射的引用都会察觉到这个修改    这个特性和切片相似,保证能够用很小的成原本复制映射
相关文章
相关标签/搜索