官术网_书友最值得收藏!

1.3.1 數組

數組是一個由固定長度的特定類型元素組成的序列,一個數組可以由零個或多個元素組成。數組的長度是數組類型的一部分,所以不同長度或不同類型的元素組成的數組都是不同類型的數組。因此,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類型的初始化語法類似。數組的長度以出現的最大的索引為準,沒有明確初始化的元素依然用零值初始?化。

第四種方式是混合了第二種和第三種的初始化方式,前面兩個元素采用順序初始化,第三個和第四個元素采用零值初始化,第五個元素通過索引初始化,最后一個元素跟在前面的第五個元素之后采用順序初始?化。

數組的內存結構比較簡單。例如,圖1-6給出的是數組[4]int{2,3,5,7}的內存結?構。

圖1-6 數組[4]int{2,3,5,7}的內存結構

Go語言中數組是值語義。一個數組變量即表示整個數組,它并不是隱式地指向第一個元素的指針(C語言的數組變量是指針),而是一個完整的值。當一個數組變量被賦值或者被傳遞的時候,實際上會復制整個數組。如果數組較大的話,數組的賦值也會有較大的開銷。為了避免復制數組帶來的開銷,可以傳遞一個指向數組的指針,但是數組指針并不是數?組。

var a = [...]int{1, 2, 3} // a是一個數組
var b = &a                // b是指向數組的指針
fmt.Println(a[0], a[1])   // 打印數組的前兩個元素
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")
}

其中,times對應一個[5][0]int類型的數組,雖然第一維數組有長度,但是數組的元素[0]int大小是0,因此整個數組占用的內存大小依然是0。不用付出額外的內存代價,我們就通過for range循環實現了times次快速迭代(Go 1.22已經支持基于一個整數的for range用法)。

數組不僅可以定義數值數組,還可以定義字符串數組、結構體數組、函數數組、接口數組、通道數組等:

// 字符串數組
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語言中,數組類型是切片和字符串等結構的基礎。以上對于數組的很多操作都可以直接用于字符串或切片?中。

主站蜘蛛池模板: 顺平县| 龙里县| 鄂州市| 营山县| 抚顺市| 东方市| 清水河县| 罗江县| 平度市| 德保县| 万山特区| 深水埗区| 宜城市| 大邑县| 塔城市| 崇仁县| 张北县| 鸡东县| 孟州市| 延川县| 巨野县| 佛冈县| 西充县| 鹤山市| 龙门县| 新邵县| 德阳市| 古丈县| 改则县| 贵港市| 巫溪县| 五峰| 竹北市| 贡觉县| 马关县| 桂阳县| 耒阳市| 星子县| 牙克石市| 本溪市| 香港|