1. 數組:是同一種數據類型的固定長度的序列。 2. 數組定義:var a [len]int,比如:var a [5]int,數組長度必須是常量,且是類型的組成部分。一旦定義,長度不能變。 3. 長度是數組類型的一部分,因此,var a[5] int和var a[10]int是不同的類型。 4. 數組可以通過下標進行訪問,下標是從0開始,最后一個元素下標是:len-1 for i := 0; i < len(a); i++ { } for index, v := range a { } 5. 訪問越界,如果下標在數組合法范圍之外,則觸發訪問越界,會panic 6. 數組是值類型,賦值和傳參會復制整個數組,而不是指針。因此改變副本的值,不會改變本身的值。 7.支持 "=="、"!=" 操作符,因為內存總是被初始化過的。 8.指針數組 [n]*T,數組指針 *[n]T。
數組介紹
數組是一個由固定長度的特定類型元素組成的序列,一個數組可以由零個或多個元素組成。數組的長度是數組類型的組成部分。
因為數組的長度是數組類型的一個部分,不同長度或不同類型的數據組成的數組都是不同的類型,因此在Go語言中很少直接使用數組(不同長度的數組因為類型不同無法直接賦值)。
和數組對應的類型是切片,切片是可以動態增長和收縮的序列,切片的功能也更加靈活,但是要理解切片的工作原理還是要先理解數組。
數組的內存結構:
一維數組初始化
var a [3]int // 定義長度為3的int型數組, 元素全部為0
var b = [...]int{1, 2, 3} // 定義長度為3的int型數組, 元素為 1, 2, 3
var c = [...]int{2: 3, 1: 2} // 定義長度為3的int型數組, 元素為 0, 2, 3
var d = [...]int{1, 2, 4: 5, 6} // 定義長度為6的int型數組, 元素為 1, 2, 0, 0, 5, 6
第一種方式是定義一個數組變量的最基本的方式,數組的長度明確指定,數組中的每個元素都以零值初始化。
第二種方式定義數組,可以在定義的時候順序指定全部元素的初始化值,數組的長度根據初始化元素的數目自動計算。
第三種方式是以索引的方式來初始化數組的元素,因此元素的初始化值出現順序比較隨意。這種初始化方式和map[int]Type類型的初始化語法類似。數組的長度以出現的最大的索引為准,沒有明確初始化的元素依然用0值初始化。
第四種方式是混合了第二種和第三種的初始化方式,前面兩個元素采用順序初始化,第三第四個元素零值初始化,第五個元素通過索引初始化,最后一個元素跟在前面的第五個元素之后采用順序初始化。
二維數組初始化
全局 var arr0 [5][3]int var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}} 局部: a := [2][3]int{{1, 2, 3}, {4, 5, 6}} b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 緯度不能用 "..."。
代碼:
package main import ( "fmt" ) var arr0 [5][3]int var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}} func main() { a := [2][3]int{{1, 2, 3}, {4, 5, 6}} b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 緯度不能用 "..."。 fmt.Println(arr0, arr1) fmt.Println(a, b) }
輸出結果:
[[0 0 0] [0 0 0] [0 0 0] [0 0 0] [0 0 0]] [[1 2 3] [7 8 9]]
[[1 2 3] [4 5 6]] [[1 1] [2 2] [3 3]]
內置函數 len 和 cap 都返回數組長度 (元素數量)
package main func main() { a := [2]int{} println(len(a), cap(a)) }
輸出結果:
2 2
數組布局
Go語言中數組是值語義(所謂值語義是一個對象被系統標准的復制方式復制后,與被復制的對象之間毫無關系,可以彼此獨立改變互不影響)。一個數組變量即表示整個數組,它並不是隱式的指向第一個元素的指針(比如C語言的數組),而是一個完整的值。當一個數組變量被賦值或者被傳遞的時候,實際上會復制整個數組。如果數組較大的話,數組的賦值也會有較大的開銷。為了避免復制數組帶來的開銷,可以傳遞一個指向數組的指針,但是數組指針並不是數組。
var a = [...]int{1, 2, 3} // a 是一個數組 var b = &a // b 是指向數組的指針 fmt.Println(a[0], a[1]) // 打印數組的前2個元素 fmt.Println(b[0], b[1]) // 通過數組指針訪問數組元素的方式和數組類似 for i, v := range b { // 通過數組指針迭代數組的元素 fmt.Println(i, v) }
其中b是指向a數組的指針,但是通過b訪問數組中元素的寫法和a類似的。還可以通過for range來迭代數組指針指向的數組元素。其實數組指針類型除了類型和數組不同之外,通過數組指針操作數組的方式和通過數組本身的操作類似,而且數組指針賦值時只會拷貝一個指針。但是數組指針類型依然不夠靈活,因為數組的長度是數組類型的組成部分,指向不同長度數組的數組指針類型也是完全不同的。
可以將數組看作一個特殊的結構體,結構的字段名對應數組的索引,同時結構體成員的數目是固定的。內置函數len可以用於計算數組的長度,cap函數可以用於計算數組的容量。不過對於數組類型來說,len和cap函數返回的結果始終是一樣的,都是對應數組類型的長度。
遍歷數組
我們可以用for循環來迭代數組。下面常見的幾種方式都可以用來遍歷數組:
for i := range a { fmt.Printf("a[%d]: %d\n", i, a[i]) } for i, v := range b { fmt.Printf("b[%d]: %d\n", i, v) } for i := 0; i < len(c); i++ { fmt.Printf("c[%d]: %d\n", i, c[i]) }
用for range方式迭代的性能可能會更好一些,因為這種迭代可以保證不會出現數組越界的情形,每輪迭代對數組元素的訪問時可以省去對下標越界的判斷。
用for range方式迭代,還可以忽略迭代時的下標:
var times [5][0]int for range times { fmt.Println("hello") }
多維數組遍歷
package main import ( "fmt" ) func main() { var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}} for k1, v1 := range f { for k2, v2 := range v1 { fmt.Printf("(%d,%d)=%d ", k1, k2, v2) } fmt.Println() }
}
輸出結果:
(0,0)=1 (0,1)=2 (0,2)=3
(1,0)=7 (1,1)=8 (1,2)=9
不同數據類型的數組
數組不僅僅可以用於數值類型,還可以定義字符串數組、結構體數組、函數數組、接口數組、管道數組等等:
// 字符串數組 var s1 = [2]string{"hello", "world"} var s2 = [...]string{"你好", "世界"} var s3 = [...]string{1: "世界", 0: "你好", } // 結構體數組 var line1 [2]image.Point var line2 = [...]image.Point{image.Point{X: 0, Y: 0}, image.Point{X: 1, Y: 1}} var line3 = [...]image.Point{{0, 0}, {1, 1}} // 圖像解碼器數組 var decoder1 [2]func(io.Reader) (image.Image, error) var decoder2 = [...]func(io.Reader) (image.Image, error){ png.Decode, jpeg.Decode, } // 接口數組 var unknown1 [2]interface{} var unknown2 = [...]interface{}{123, "你好"} // 管道數組 var chanList = [2]chan int{}
我們還可以定義一個空的數組:
var d [0]int // 定義一個長度為0的數組 var e = [0]int{} // 定義一個長度為0的數組 var f = [...]int{} // 定義一個長度為0的數組
長度為0的數組在內存中並不占用空間。空數組雖然很少直接使用,但是可以用於強調某種特有類型的操作時避免分配額外的內存空間,比如用於管道的同步操作:
c1 := make(chan [0]int) go func() { fmt.Println("c1") c1 <- [0]int{} }() <-c1
在這里,我們並不關心管道中傳輸數據的真實類型,其中管道接收和發送操作只是用於消息的同步。對於這種場景,我們用空數組來作為管道類型可以減少管道元素賦值時的開銷。當然一般更傾向於用無類型的匿名結構體代替:
c2 := make(chan struct{}) go func() { fmt.Println("c2") c2 <- struct{}{} // struct{}部分是類型, {}表示對應的結構體值 }() <-c2
我們可以用fmt.Printf
函數提供的%T
或%#v
謂詞語法來打印數組的類型和詳細信息:
fmt.Printf("b: %T\n", b) // b: [3]int fmt.Printf("b: %#v\n", b) // b: [3]int{1, 2, 3}
在Go語言中,數組類型是切片和字符串等結構的基礎。以上數組的很多操作都可以直接用於字符串或切片中。
https://chai2010.cn/advanced-go-programming-book/ch1-basic/ch1-03-array-string-and-slice.html
https://www.kancloud.cn/liupengjie/go/574250