《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 函也就是會每次增數        當傳遞映射給一個函數,並對這個映射作了修改時,    全部對這個映射的引用都會察覺到這個修改    這個特性和切片相似,保證能夠用很小的成原本複製映射
相關文章
相關標籤/搜索