4. 複合數據類型
4.1 數組
沒有手動分配值初始化,編譯器會給數組進行零初始化
如果在數組的長度位置出現的是“…”省略號,則表示數組的長度是根據初始化值的個數來計算。
q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int"
數組的長度是數組類型的一個組成部分,因此[3]int和[4]int是兩種不同的數組類型。所以長度不同但底層類型相同的數組不能進行比較(==, !=等都算非法)
a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Println(a == b, a == c, b == c) // "true false false"
d := [3]int{1, 2}
fmt.Println(a == d) // compile error: cannot compare [2]int == [3]int
可以在初始化的時候指定索引(位置)進行初始化,生成的數組大小爲最後一個索引的值+1。在最後一個索引之前的索引的位置沒有手動初始化賦值,那麼會進行零初始化。因此除非必要數組依然很少用作函數參數;相反,我們一般使用slice來替代數組。
a := [...]int{2:10, 4:20}
fmt.Println(a) // "[0, 0, 10, 0, 20]"
fmt.Printf("%T", a) // "[5]int"
由於數組長度只要不一樣,就屬於不同的類型了,在一些情況處理起來會很麻煩。比如一個函數聲明的時候有個參數是16大小的數組,那麼在傳參的時候就不能使用大小爲8或者32的數組了。
4.2 切片
一個slice由三個部分構成:指針、長度和容量。
有點類型於:
type IntSlice struct {
ptr *int
len, cap int
}
多個slice之間可以共享底層的數據,並且引用的數組部分區間可能重疊。由於共享底層數據,所以一個切片的改動會影響另外一個切片的數據:
func main() {
arr := [...]int{1:10, 5:7, 9:10}
s1 := arr[:7]
s2 := arr[5:]
fmt.Println(arr, s1, s2)
//[0 10 0 0 0 7 0 0 0 10] [0 10 0 0 0 7 0] [7 0 0 0 10]
s1[6] = 66
fmt.Println(arr, s1, s2)
//[0 10 0 0 0 7 66 0 0 10] [0 10 0 0 0 7 66] [7 66 0 0 10]
}
和數組不同的是,slice之間不能比較,哪怕長度一樣。slice唯一合法的比較操作是和nil比較。可以bytes.Equal函數來判斷兩個字節型slice是否相等。其他的類型還是要自己動手寫一個出來。
func equal(x, y []string) bool {
if len(x) != len(y) {
return false
}
for i := range x {
if x[i] != y[i] {
return false
}
}
return true
}
如果你需要測試一個slice是否是空的,使用len(s) == 0來判斷,而不應該用s == nil來判斷。因爲有不爲nil的切片其長度也爲空的。
可以利用append函數在切片中插入新的元素。append對於底層數組內存擴展采取的是翻倍,一旦容量不夠,就擴充原理一倍的容量。但是通常我們並不知道append調用是否導致了內存的重新分配,因此我們也不能確認新的slice和原始的slice是否引用的是相同的底層數組空間。比如以下代碼
func main() {
s1 := make([]int, 0, 5)
s1 = append(s1, 1, 2, 3, 4, 5)
s2 := append(s1, 6, 7, 8, 9, 10, 11)
s1[0] = 20
fmt.Println(s1, s2)
}
結果如下,可以看到s1和s2指向的底層數據不一樣了
[20 2 3 4 5] [1 2 3 4 5 6 7 8 9 10 11]
4.3 map
一個map就是一個哈希表的引用,map類型可以寫爲map[K]V,其中K和V分別對應key和value。
但是map中的元素並不是一個變量,因此我們不能對map的元素進行取址操作:
_ = &ages["bob"] // compile error: cannot take address of map element
禁止對map元素取址的原因是map可能隨着元素數量的增長而重新分配更大的內存空間,從而可能導致之前的地址無效。
Map的迭代順序是不確定的。這是故意的,每次都使用隨機的遍歷順序可以強制要求程序不會依賴具體的哈希函數實現。如果要按順序遍歷key/value對,我們必須顯式地對key進行排序,可以使用sort包的Strings函數對字符串slice進行排序。
map上的大部分操作,包括查找、刪除、len和range循環都可以安全工作在nil值的map上,它們的行爲和一個空的map類似。但是向一個nil值的map存入元素將導致一個panic異常:
ages["carol"] = 21 // panic: assignment to entry in nil map
4.4 結構體
和map不同,結構體可以對成員取地址,然後通過指針訪問。點操作符也可以和指向結構體的指針一起工作:
var employeeOfTheMonth *Employee = &dilbert
employeeOfTheMonth.Position += " (proactive team player)"
// 相當於
(*employeeOfTheMonth).Position += " (proactive team player)"
如果結構體成員名字是以大寫字母開頭的,那麼該成員就是導出的:
package test
type T struct{ a, b int } // a and b are not exported
type G struct{ A, B int } // a and b are not exported
package main
import (
"studygo/day12/test"
)
func main() {
t := test.T{a:10, b:20} //compile error
t := test.T{10, 20} //compile error
g := test.G{A:10, B:20}
}
如果結構體沒有任何成員的話就是空結構體,寫作struct{}。它的大小爲0。空結構題會有一些特殊作用:Go語言–空結構體struct{}解析
如果結構體的全部成員都是可以比較的,那麼結構體也是可以比較的。
可比較的結構體類型和其他可比較的類型一樣,可以用於map的key類型。注意,一定要是可比較的結構體
type address struct {
hostname string
port int
}
hits := make(map[address]int)
hits[address{"golang.org", 443}]++
嵌套結構體以及初始化
type Point struct {
X int
Y int
}
type Circle struct {
P Point
Radius int
}
type Wheel struct {
Circle // 匿名嵌套
Spokes int
}
func main() {
var w1 Wheel
fmt.Printf("%v\n", w1) // {{{0 0} 0} 0} 自動進行零值初始化
//w2 := Wheel{10, 20, 30, 40, 50 } // 這種聲明初試化的方式不行
// 比較簡潔的初試化方式
w2 := Wheel{Circle{Point{1, 2}, 3}, 4}
fmt.Printf("%v\n", w2) ////{{{1 2} 3} 4}
// 比較詳細的初始化方式
w3 := Wheel{
Circle: Circle{ //匿名嵌套使用的是該類型的名字
P:Point{X:1, Y:2}, // 非匿名的用聲明的那個名
Radius:3,
},
Spokes: 4,
}
fmt.Printf("%v\n", w3) //{{{1 2} 3} 4}
}