Golang教程:數組和切片

數組

數組是類型相同的元素的集合。例如,整數 5, 8, 9, 79, 76 的集合就構成了一個數組。Go不容許在數組中混合使用不一樣類型的元素(好比整數和字符串)。html

聲明

var variable_name [SIZE] variable_type

有不少聲明數組的方式,讓咱們一個一個地介紹。golang

package main

import (  
    "fmt"
)

func main() {  
    var a [3]int //int array with length 3
    fmt.Println(a)
}

var a [3]int 聲明瞭一個長度爲 3 的整型數組。數組中的全部元素都被自動賦值爲元素類型的 0 值。好比這裏 a 是一個整型數組,所以 a 中的全部元素都被賦值爲 0(即整型的 0 值)。運行上面的程序,輸出爲:[0 0 0]數組

數組的索引從 0 開始到 length - 1 結束。下面讓咱們給上面的數組賦一些值。數據結構

package main

import (  
    "fmt"
)


func main() {  
    var a [3]int //int array with length 3
    a[0] = 12 // array index starts at 0
    a[1] = 78
    a[2] = 50
    fmt.Println(a)
}

a[0]表示數組中的第一個元素。程序的輸出爲:[12 78 50]app

譯者注:能夠用下標運算符([])來訪問數組中的元素,下標從 0 開始,例如 a[0] 表示數組 a 的第一個元素,a[1]表示數組 a 的第二元素,以此類推。ide

能夠利用速記聲明(shorthand declaration)的方式來建立一樣的數組:函數

package main 

import (  
    "fmt"
)

func main() {  
    a := [3]int{12, 78, 50} // shorthand declaration to create array
    fmt.Println(a)
}

上面的程序輸出爲:[12 78 50]oop

譯者注:這個例子給出了速記聲明的方式:在數組類型後面加一對大括號({}),在大括號裏面寫元素初始值列表,多個值用逗號分隔。優化

在速記聲明中,沒有必要爲數組中的每個元素指定初始值。ui

package main

import (  
    "fmt"
)

func main() {  
    a := [3]int{12} 
    fmt.Println(a)
}

上面程序的第 8 行:a := [3]int{12} 聲明瞭一個長度爲 3 的數組,可是隻提供了一個初值 12。剩下的兩個元素被自動賦值爲 0。程序 的輸出爲:[12 0 0]

在聲明數組時你能夠忽略數組的長度並用 ... 代替,讓編譯器爲你自動推導數組的長度。好比下面的程序

package main

import (  
    "fmt"
)

func main() {  
    a := [...]int{12, 78, 50} // ... makes the compiler determine the length
    fmt.Println(a)
}

上面已經提到,數組的長度是數組類型的一部分。所以 [5]int 和 [25]int 是兩個不一樣類型的數組。正是由於如此,一個數組不能動態改變長度。不要擔憂這個限制,由於切片(slices)能夠彌補這個不足。

package main

func main() {  
    a := [3]int{5, 78, 8}
    var b [5]int
    b = a //not possible since [3]int and [5]int are distinct types
}

在上面程序的第 6 行,咱們試圖將一個 [3]int 類型的數組賦值給一個 [5]int 類型的數組,這是不容許的。編譯會報錯:main.go:6: cannot use a (type [3]int) as type [5]int in assignment

數組是值類型

在 Go 中數組是值類型而不是引用類型。這意味着當數組變量被賦值時,將會得到原數組(譯者注:也就是等號右面的數組)的拷貝。新數組中元素的改變不會影響原數組中元素的值。

package main

import "fmt"

func main() {  
    a := [...]string{"USA", "China", "India", "Germany", "France"}
    b := a // a copy of a is assigned to b
    b[0] = "Singapore"
    fmt.Println("a is ", a)
    fmt.Println("b is ", b) 
}

上面程序的第 7 行,將數組 a 的拷貝賦值給數組 b。第 8 行,b 的第一個元素被賦值爲 Singapore。這將不會影響到原數組 a程序的輸出爲:

a is [USA China India Germany France]  
b is [Singapore China India Germany France]  

一樣的,若是將數組做爲參數傳遞給函數,仍然是值傳遞,在函數中對(做爲參數傳入的)數組的修改不會形成原數組的改變。

package main

import "fmt"

func changeLocal(num [5]int) {  
    num[0] = 55
    fmt.Println("inside function ", num)

}
func main() {  
    num := [...]int{5, 6, 7, 8, 8}
    fmt.Println("before passing to function ", num)
    changeLocal(num) //num is passed by value
    fmt.Println("after passing to function ", num)
}

上面程序的第 13 行,數組 num 是經過值傳遞的方式傳遞給函數 changeLocal 的,所以該函數執行過程當中不會形成 num的改變。程序輸出以下:

before passing to function  [5 6 7 8 8]  
inside function  [55 6 7 8 8]  
after passing to function  [5 6 7 8 8]  

數組的長度

內置函數 len 用於獲取數組的長度:

package main

import "fmt"

func main() {  
    a := [...]float64{67.7, 89.8, 21, 78}
    fmt.Println("length of a is",len(a))

}

上面程序的輸出爲:length of a is 4

使用 range 遍歷數組

for 循環能夠用來遍歷數組中的元素:

package main

import "fmt"

func main() {  
    a := [...]float64{67.7, 89.8, 21, 78}
    for i := 0; i < len(a); i++ { //looping from 0 to the length of the array
        fmt.Printf("%d th element of a is %.2f\n", i, a[i])
    }
}
package main

import "fmt"

func main() {  
    a := [...]float64{67.7, 89.8, 21, 78}
    for i := 0; i < len(a); i++ { //looping from 0 to the length of the array
        fmt.Printf("%d th element of a is %.2f\n", i, a[i])
    }
}

上面的程序使用 for 循環遍歷數組中的元素(索引從 0 到 len(a) - 1)。上面的程序輸出以下:

0 th element of a is 67.70  
1 th element of a is 89.80  
2 th element of a is 21.00  
3 th element of a is 78.00  

Go 提供了一個更簡單,更簡潔的遍歷數組的方法:使用 range for。range 返回數組的索引和索引對應的值。讓咱們用 range for 重寫上面的程序(除此以外咱們還計算了數組元素的總和)。

package main

import "fmt"

func main() {  
    a := [...]float64{67.7, 89.8, 21, 78}
    sum := float64(0)
    for i, v := range a {//range returns both the index and value
        fmt.Printf("%d the element of a is %.2f\n", i, v)
        sum += v
    }
    fmt.Println("\nsum of all elements of a",sum)
}

上面的程序中,第 8 行 for i, v := range a 是 range 形式的 for 循環。range 將返回數組的索引和相對應的元素。咱們打印這些值並計算數組 a 中全部元素的總和。程序的輸出以下:

0 the element of a is 67.70  
1 the element of a is 89.80  
2 the element of a is 21.00  
3 the element of a is 78.00

sum of all elements of a 256.5 

若是你只想訪問數組元素而不須要訪問數組索引,則能夠經過空標識符來代替索引變量:

for _, v := range a { //ignores index  
}

上面的代碼忽略了索引。一樣的,也能夠忽略值。

多維數組

目前爲止咱們建立的數組都是一維的。也能夠建立多維數組。

package main

import (  
    "fmt"
)

func printarray(a [3][2]string) {  
    for _, v1 := range a {
        for _, v2 := range v1 {
            fmt.Printf("%s ", v2)
        }
        fmt.Printf("\n")
    }
}

func main() {  
    a := [3][2]string{
        {"lion", "tiger"},
        {"cat", "dog"},
        {"pigeon", "peacock"}, //this comma is necessary. The compiler will complain if you omit this comma
    }
    printarray(a)
    var b [3][2]string
    b[0][0] = "apple"
    b[0][1] = "samsung"
    b[1][0] = "microsoft"
    b[1][1] = "google"
    b[2][0] = "AT&T"
    b[2][1] = "T-Mobile"
    fmt.Printf("\n")
    printarray(b)
}

上面的程序中,第 17 行利用速記聲明建立了一個二維數組 a。第 20 行的逗號是必須的,這是由於詞法分析器會根據一些簡單的規則自動插入分號。若是你想了解更多,請閱讀:https://golang.org/doc/effective_go.html#semicolons 。

在第 23 行聲明瞭另外一個二維數組 b,並經過索引的方式給數組 b 中的每個元素賦值。這是初始化二維數組的另外一種方式。

第 7 行聲明的函數 printarray 經過兩個嵌套的 range for 打印二維數組的內容。上面程序的輸出爲:

lion tiger  
cat dog  
pigeon peacock 

apple samsung  
microsoft google  
AT&T T-Mobile  

以上就是對數組的介紹。儘管數組看起來足夠靈活,可是數組的長度是固定的,沒辦法動態增長數組的長度。而切片卻沒有這個限制,實際上在 Go 中,切片比數組更爲常見。

切片

切片(slice)是創建在數組之上的更方便,更靈活,更強大的數據結構。切片並不存儲任何元素而只是對現有數組的引用。

建立切片

元素類型爲 T 的切片表示爲: []T

package main

import (  
    "fmt"
)

func main() {  
    a := [5]int{76, 77, 78, 79, 80}
    var b []int = a[1:4] //creates a slice from a[1] to a[3]
    fmt.Println(b)
}

經過 a[start:end] 這樣的語法建立了一個從 a[start] 到 a[end -1] 的切片。在上面的程序中,第 9 行 a[1:4] 建立了一個從 a[1] 到 a[3] 的切片。所以 b 的值爲:[77 78 79]

下面是建立切片的另外一種方式:

package main

import (  
    "fmt"
)

func main() {  
    c := []int{6, 7, 8} //creates and array and returns a slice reference
    fmt.Println(c)
}

在上面的程序中,第 9 行 c := []int{6, 7, 8} 建立了一個長度爲 3 的 int 數組,並返回一個切片給 c。

修改切片

切片自己不包含任何數據。它僅僅是底層數組的一個上層表示。對切片進行的任何修改都將反映在底層數組中。

package main

import (  
    "fmt"
)

func main() {  
    darr := [...]int{57, 89, 90, 82, 100, 78, 67, 69, 59}
    dslice := darr[2:5]
    fmt.Println("array before",darr)
    for i := range dslice {
        dslice[i]++
    }
    fmt.Println("array after",darr) 
}

上面程序的第 9 行,咱們建立了一個從 darr[2] 到 darr[5] 的切片 dslicefor 循環將這些元素值加 1。執行完 for 語句以後打印原數組的值,咱們能夠看到原數組的值被改變了。程序輸出以下:

array before [57 89 90 82 100 78 67 69 59]  
array after [57 89 91 83 101 78 67 69 59]

當若干個切片共享同一個底層數組時,對每個切片的修改都會反映在底層數組中。

package main

import (  
    "fmt"
)

func main() {  
    numa := [3]int{78, 79 ,80}
    nums1 := numa[:] //creates a slice which contains all elements of the array
    nums2 := numa[:]
    fmt.Println("array before change 1",numa)
    nums1[0] = 100
    fmt.Println("array after modification to slice nums1", numa)
    nums2[1] = 101
    fmt.Println("array after modification to slice nums2", numa)
}

能夠看到,在第 9 行, numa[:] 中缺乏了開始和結束的索引值,這種狀況下開始和結束的索引值默認爲 0 和len(numa)。這裏 nums1 和 nums2 共享了同一個數組。程序的輸出爲:

array before change 1 [78 79 80]  
array after modification to slice nums1 [100 79 80]  
array after modification to slice nums2 [100 101 80]  

從輸出結果能夠看出,當多個切片共享同一個數組時,對每個切片的修改都將會反映到這個數組中。

切片的長度和容量

切片的長度是指切片中元素的個數。切片的容量是指從切片的起始元素開始到其底層數組中的最後一個元素的個數。

譯者注:使用內置函數 cap 返回切片的容量。

讓咱們寫一些代碼來更好地理解這一點。

package main

import (  
    "fmt"
)

func main() {  
    fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
    fruitslice := fruitarray[1:3]
    fmt.Printf("length of slice %d capacity %d", len(fruitslice), cap(fruitslice)) //length of is 2 and capacity is 6
}

在上面的程序中,建立了一個以 fruitarray 爲底層數組,索引從 1 到 3 的切片 fruitslice。所以 fruitslice 長度爲2

fruitarray 的長度是 7。fruiteslice 是從 fruitarray 的索引 1 開始的。所以 fruiteslice 的容量是從 fruitarray 的第 1 個元素開始算起的數組中的元素個數,這個值是 6。所以 fruitslice 的容量是 6程序的輸出爲:length of slice 2 capacity 6。

切片的長度能夠動態的改變(最大爲其容量)。任何超出最大容量的操做都會發生運行時錯誤。

package main

import (  
    "fmt"
)

func main() {  
    fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
    fruitslice := fruitarray[1:3]
    fmt.Printf("length of slice %d capacity %d\n", len(fruitslice), cap(fruitslice)) //length of is 2 and capacity is 6
    fruitslice = fruitslice[:cap(fruitslice)] //re-slicing furitslice till its capacity
    fmt.Println("After re-slicing length is",len(fruitslice), "and capacity is",cap(fruitslice))
}

在上面的程序中, 第 11 行修改 fruitslice 的長度爲它的容量。上面的程序輸出以下:

length of slice 2 capacity 6  
After re-slicing length is 6 and capacity is 6 

用 make 建立切片

內置函數 func make([]T, len, cap) []T 能夠用來建立切片,該函數接受長度和容量做爲參數,返回切片。容量是可選的,默認與長度相同。使用 make 函數將會建立一個數組並返回它的切片。

package main

import (  
    "fmt"
)

func main() {  
    i := make([]int, 5, 5)
    fmt.Println(i)
}

用 make 建立的切片的元素值默認爲 0 值。上面的程序輸出爲:[0 0 0 0 0]

追加元素到切片

咱們已經知道數組是固定長度的,它們的長度不能動態增長。而切片是動態的,可使用內置函數 append 添加元素到切片。append 的函數原型爲:append(s []T, x ...T) []T

x …T 表示 append 函數能夠接受的參數個數是可變的。這種函數叫作變參函數

你可能會問一個問題:若是切片是創建在數組之上的,而數組自己不能改變長度,那麼切片是如何動態改變長度的呢?實際發生的狀況是,當新元素經過調用 append 函數追加到切片末尾時,若是超出了容量,append 內部會建立一個新的數組。並將原有數組的元素被拷貝給這個新的數組,最後返回創建在這個新數組上的切片。這個新切片的容量是舊切片的二倍(譯者注:當超出切片的容量時,append 將會在其內部建立新的數組,該數組的大小是原切片容量的 2 倍。最後 append 返回這個數組的全切片,即從 0 到 length - 1 的切片)。下面的程序使事情變得明朗:

package main

import (  
    "fmt"
)

func main() {  
    cars := []string{"Ferrari", "Honda", "Ford"}
    fmt.Println("cars:", cars, "has old length", len(cars), "and capacity", cap(cars)) //capacity of cars is 3
    cars = append(cars, "Toyota")
    fmt.Println("cars:", cars, "has new length", len(cars), "and capacity", cap(cars)) //capacity of cars is doubled to 6
}

在上面的程序中,cars 的容量開始時爲 3。在第 10 行咱們追加了一個新的元素給 cars,並將 append(cars, "Toyota")的返回值從新複製給 cars。如今 cars 的容量翻倍,變爲 6。上面的程序輸出爲:

cars: [Ferrari Honda Ford] has old length 3 and capacity 3  
cars: [Ferrari Honda Ford Toyota] has new length 4 and capacity 6  

切片的 0 值爲 nil。一個 nil 切片的長度和容量都爲 0。能夠利用 append 函數給一個 nil 切片追加值。

package main

import (  
    "fmt"
)

func main() {  
    var names []string //zero value of a slice is nil
    if names == nil {
        fmt.Println("slice is nil going to append")
        names = append(names, "John", "Sebastian", "Vinay")
        fmt.Println("names contents:",names)
    }
}

在上面的程序中 names 爲 nil,而且咱們把 3 個字符串追加給 names程序的輸出爲:

slice is nil going to append  
names contents: [John Sebastian Vinay]

可使用 ... 操做符將一個切片追加到另外一個切片末尾:

package main

import (  
    "fmt"
)

func main() {  
    veggies := []string{"potatoes","tomatoes","brinjal"}
    fruits := []string{"oranges","apples"}
    food := append(veggies, fruits...)
    fmt.Println("food:",food)
}

上面的程序中,在第10行將 fruits 追加到 veggies 並賦值給 food...操做符用來展開切片。程序的輸出爲:food: [potatoes tomatoes brinjal oranges apples]

切片做爲函數參數

能夠認爲切片在內部表示爲以下的結構體:

type slice struct {  
    Length        int
    Capacity      int
    ZerothElement *byte
}

能夠看到切片包含長度、容量、以及一個指向首元素的指針。當將一個切片做爲參數傳遞給一個函數時,雖然是值傳遞,可是指針始終指向同一個數組。所以將切片做爲參數傳給函數時,函數對該切片的修改在函數外部也能夠看到。讓咱們寫一個程序來驗證這一點。

package main

import (  
    "fmt"
)

func subtactOne(numbers []int) {  
    for i := range numbers {
        numbers[i] -= 2
    }

}
func main() {

    nos := []int{8, 7, 6}
    fmt.Println("slice before function call", nos)
    subtactOne(nos)                               //function modifies the slice
    fmt.Println("slice after function call", nos) //modifications are visible outside

}

在上面的程序中,第 17 行將切片中的每一個元素的值減2。在函數調用以後打印切片的的內容,發現切片內容發生了改變。你能夠回想一下,這不一樣於一個數組,對函數內部的數組所作的更改在函數外不可見。上面的程序輸出以下:

array before function call [8 7 6] array after function call [6 5 4] 
  • 1
  • 2

多維切片

同數組同樣,切片也能夠有多個維度。

package main

import (  
    "fmt"
)


func main() {  
     pls := [][]string {
            {"C", "C++"},
            {"JavaScript"},
            {"Go", "Rust"},
            }
    for _, v1 := range pls {
        for _, v2 := range v1 {
            fmt.Printf("%s ", v2)
        }
        fmt.Printf("\n")
    }
}

上面程序的輸出以下:

C C++  
JavaScript  
Go Rust  

內存優化

切片保留對底層數組的引用。只要切片存在於內存中,數組就不能被垃圾回收。這在內存管理方即可能是值得關注的。假設咱們有一個很是大的數組,而咱們只須要處理它的一小部分,爲此咱們建立這個數組的一個切片,並處理這個切片。這裏要注意的事情是,數組仍然存在於內存中,由於切片正在引用它。

解決該問題的一個方法是使用 copy 函數 func copy(dst, src []T) int 來建立該切片的一個拷貝。這樣咱們就可使用這個新的切片,原來的數組能夠被垃圾回收。

package main

import (  
    "fmt"
)

func countries() []string {  
    countries := []string{"USA", "Singapore", "Germany", "India", "Australia"}
    neededCountries := countries[:len(countries)-2]
    countriesCpy := make([]string, len(neededCountries))
    copy(countriesCpy, neededCountries) //copies neededCountries to countriesCpy
    return countriesCpy
}
func main() {  
    countriesNeeded := countries()
    fmt.Println(countriesNeeded)
}

在上面程序中,第 9 行 neededCountries := countries[:len(countries)-2] 建立一個底層數組爲 countries 並排除最後兩個元素的切片。第 11 行將 neededCountries 拷貝到 countriesCpy 並在下一行返回 countriesCpy。如今數組countries 能夠被垃圾回收,由於 neededCountries 再也不被引用。

相關文章
相關標籤/搜索