有過Python
、JavaScript
編程經驗的人都知道其數組是動態的,可以隨需求自動增大數組長度,而Go
里面的數組長度卻是固定的,無法擴大或者縮小
但Go
中也有類似的動態"數組",稱為切片slice
Go
中的數組是slice
和map
兩種數據類型的基礎,這兩種數據類型的底層都是通過數組實現的
1、存儲方式
當在Go
中聲明一個數組之后,會在內存中開辟一段固定長度的、連續的空間存放數組中的各個元素,這些元素的數據類型完全相同,可以是內置的簡單數據類型(int
、string等),也可以是自定義的
struct`結構體類型
- 固定長度:這意味着數組不可增長、不可縮減。想要擴展數組,只能創建新數組,將原數組的元素復制到新數組
- 連續空間:這意味可以在緩存中保留的時間更長,搜索速度更快,是一種非常高效的數據結構,同時還意味着可以通過數值
index
的方式訪問數組中的某個元素 - 數據類型:意味着限制了每個
block
中可以存放什么樣的數據,以及每個block
可以存放多少字節的數據
例如,使用下面的語句聲明一個長度為4
的int
類型的數組,那么這個數組最多只能存放4
個元素,且所有元素都只能是int
類型。同時,還為這個數組做了初始化
array := [4]int{3, 1, 4,1 }
2、聲明與初始化
2.1 聲明語法
Go
語言數組聲明需要指定元素類型及元素個數,語法格式如下
var variable_name [SIZE] variable_type
比如聲明一個長度為5
, 類型是float64
的數組
var arrayf [5]float64
2.2 數組類型
雖然稱呼數組為int
類型的數組,但數組的數據類型是兩部分組成的[n]TYPE
,這個整體才是數組的數據類型。所以,[4]int
和[5]int
是兩種不同的數組類型
var (
a1 [4]int
a2 [5]int
)
fmt.Println(reflect.TypeOf(a1))
fmt.Println(reflect.TypeOf(a2))
2.3 數組默認值
當一個變量被聲明之后, 都會立即賦予一個默認值, 數組的默認值和數組的數據類型有關
var a1 [5]int
fmt.Println(a1) // [0 0 0 0 0]
var a2 [4]string
fmt.Println(a2) // [ ]
2.4 聲明並初始化
如果不想填充默認值, 可以聲明時就賦值
a1 := [3]int{1, 2, 3}
fmt.Println(a1)
// 如果將元素個數指定為特殊符號...,則表示通過初始化時的給定的值個數來推斷數組長度
a2 := [...]int{1, 2, 3, 4}
fmt.Println(a2)
a3 := [...]int{1, 1, 1}
fmt.Println(a3)
// 如果聲明數組時,只想給其中某幾個元素初始化賦值,則使用索引號
a4 := [4]int{0: 1, 3: 5}
fmt.Println(a4)
3、訪問與修改
可以通過數組的索引訪問數組的值
a := [4]int{0: 1, 2: 5}
fmt.Println(a[0])
fmt.Println(a[2])
同理可通過數組的索引修改數組的值
a[0] = 10
a[3] = 20
fmt.Println(a[0])
fmt.Println(a[3])
4、指針數組
可以聲明一個指針類型的數組,這樣數組中就可以存放指針。注意,指針的默認初始化值為nil
例如,創建int
類型指針的數組
a := [4]*int{0: new(int), 3: new(int)}
fmt.Println(a)
// [0xc00001c2a8 <nil> <nil> 0xc00001c2b0]
// 如果指針地址為空, 是會報空指針錯誤的, 比如
// *a[1] = 3
// panic: runtime error: invalid memory address or nil pointer dereference
*a[0] = 10
*a[3] = 20
fmt.Println(a)
fmt.Println(*a[0], *a[3])
// 為1賦值
a[1] = new(int)
*a[1] = 30
fmt.Println(a, *a[1])
5、數組拷貝
在Go
中,由於數組算是一個值類型,所以可以將它賦值給其它數組
因為數組類型的完整定義為[n]TYPE
,所以數組賦值給其它數組的時候,n
和TYPE
必須相同
修改a2數組元素的值,不會影響a1數組
例如:
a1 := [4]string{"a", "b", "c", "m"}
a2 := [4]string{"x", "y", "z", "n"}
a1 = a2
fmt.Println(a1, a2)
數組賦值給其它數組時,實際上是完整地拷貝一個數組。所以,如果數組是一個指針型的數組,那么拷貝的將是指針數組,而不會拷貝指針所指向的對象
a1 := [4]*string{new(string), new(string), new(string), new(string)}
a2 := a1
fmt.Println(a1, a2)
*a1[0] = "A"
*a1[1] = "B"
*a1[2] = "C"
*a1[3] = "D"
fmt.Println(*a1[0], *a2[0])
// A A
6、數組遍歷
range
關鍵字可以對array
進行迭代,每次返回一個index
和對應的元素值。可以將range
的迭代結合for
循環對array
進行遍歷
a := [4]int{1, 2, 3, 4}
for i, v := range a {
fmt.Println(i, v)
}
/*
0 1
1 2
2 3
3 4
*/
7、多維數組
可以通過組合兩個一維數組的方式構成二維數組, 二維數據還是比較常用,
比如定義坐標, 表示4
個坐標(1,1) (2,2) (3,3) (4,4)
// pos := [4][2]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}}
fmt.Println(pos)
// [[1 1] [2 2] [3 3] [4 4]]
// 修改第一點的坐標
pos[0] = [2]int{10, 10}
fmt.Println(pos)
// [[10 10] [2 2] [3 3] [4 4]]
8、數組作為函數參數
Go
中的傳值方式是按值傳遞,這意味着給變量賦值、給函數傳參時,都是直接拷貝一個副本然后將副本賦值給對方的。這樣的拷貝方式意味着:
- 如果數據結構體積龐大,則要完整拷貝一個數據結構副本時效率會很低
- 函數內部修改數據結構時,只能在函數內部生效,函數一退出就失效了,因為它修改的是副本對象
示例
func TestMain1(t *testing.T) {
payload := [4]int{1}
fmt.Printf("%p\n", &payload) // 0xc00014a040
change(payload) // 0xc00014a060
}
func change(payload [4]int) {
fmt.Printf("%p\n", &payload)
payload[0] = 10
}